POST multipart/form-data с Objective-C

Итак, этот HTML-код отправляет данные в правильный формат для меня.

<form action="https://www.example.com/register.php" method="post" enctype="multipart/form-data">
    Name: <input type="text" name="userName"><BR />
    Email: <input type="text" name="userEmail"><BR />
    Password: <input type="text" name="userPassword"><BR />
    Avatar: <input type="file" name="avatar"><BR />
    <input type="submit">
</form>

Я просмотрел большое количество статей о том, как сделать POST на основе multipart/form-data на iOS, но никто не объясняет, что делать, если есть обычные параметры, а также загрузка файла.

Не могли бы вы помочь мне с кодом POST в Obj-C?

Спасибо!

Ответ 1

Процесс выглядит следующим образом:

  • Создайте словарь с параметрами userName, userEmail и userPassword.

    NSDictionary *params = @{@"userName"     : @"rob",
                             @"userEmail"    : @"[email protected]",
                             @"userPassword" : @"password"};
    
  • Определите путь для изображения:

    NSString *path = [[NSBundle mainBundle] pathForResource:@"avatar" ofType:@"png"];
    
  • Создайте запрос:

    NSString *boundary = [self generateBoundaryString];
    
    // configure the request
    
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    [request setHTTPMethod:@"POST"];
    
    // set content type
    
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request setValue:contentType forHTTPHeaderField: @"Content-Type"];
    
    // create body
    
    NSData *httpBody = [self createBodyWithBoundary:boundary parameters:params paths:@[path] fieldName:fieldName];
    
  • Это метод, использованный выше для построения тела запроса:

    - (NSData *)createBodyWithBoundary:(NSString *)boundary
                            parameters:(NSDictionary *)parameters
                                 paths:(NSArray *)paths
                             fieldName:(NSString *)fieldName {
        NSMutableData *httpBody = [NSMutableData data];
    
        // add params (all params are strings)
    
        [parameters enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
            [httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", parameterKey] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"%@\r\n", parameterValue] dataUsingEncoding:NSUTF8StringEncoding]];
        }];
    
        // add image data
    
        for (NSString *path in paths) {
            NSString *filename  = [path lastPathComponent];
            NSData   *data      = [NSData dataWithContentsOfFile:path];
            NSString *mimetype  = [self mimeTypeForPath:path];
    
            [httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fieldName, filename] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimetype] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:data];
            [httpBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
        }
    
        [httpBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
        return httpBody;
    }
    
  • В приведенном выше примере используются следующие методы утилиты:

    @import MobileCoreServices;    // only needed in iOS
    
    - (NSString *)mimeTypeForPath:(NSString *)path {
        // get a mime type for an extension using MobileCoreServices.framework
    
        CFStringRef extension = (__bridge CFStringRef)[path pathExtension];
        CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, extension, NULL);
        assert(UTI != NULL);
    
        NSString *mimetype = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType));
        assert(mimetype != NULL);
    
        CFRelease(UTI);
    
        return mimetype;
    }
    
    - (NSString *)generateBoundaryString {
        return [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
    }
    
  • Затем отправьте запрос. Здесь много, много вариантов.

    Например, если вы используете NSURLSession, вы можете создать NSURLSessionUploadTask:

    NSURLSession *session = [NSURLSession sharedSession];  // use sharedSession or create your own
    
    NSURLSessionTask *task = [session uploadTaskWithRequest:request fromData:httpBody completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"error = %@", error);
            return;
        }
    
        NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"result = %@", result);
    }];
    [task resume];
    

    Или вы можете создать NSURLSessionDataTask:

    request.HTTPBody = httpBody;
    
    NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"error = %@", error);
            return;
        }
    
        NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"result = %@", result);
    }];
    [task resume];
    

    Вышеизложенное предполагает, что сервер просто возвращает текстовый ответ. Это лучше, если сервер вернул JSON, и в этом случае вы использовали бы NSJSONSerialization, а не NSString метод initWithData.

    Аналогично, я использую исполнения блока завершения NSURLSession выше, но не стесняйтесь использовать более богатые представления на основе делегатов. Но это выходит за рамки этого вопроса, поэтому я оставлю это вам.

Но, надеюсь, это иллюстрирует эту идею.


Я бы отказался, если бы не указал, что намного проще, чем выше, вы можете использовать AFNetworking, повторяя шаги 1 и 2 выше, но затем просто вызывая:

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer]; // only needed if the server is not returning JSON; if web service returns JSON, remove this line
NSURLSessionTask *task = [manager POST:urlString parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
    NSError *error;
    if (![formData appendPartWithFileURL:[NSURL fileURLWithPath:path] name:@"avatar" fileName:[path lastPathComponent] mimeType:@"image/png" error:&error]) {
        NSLog(@"error appending part: %@", error);
    }
}  progress:nil success:^(NSURLSessionTask *task, id responseObject) {
    NSLog(@"responseObject = %@", responseObject);
} failure:^(NSURLSessionTask *task, NSError *error) {
    NSLog(@"error = %@", error);
}];

if (!task) {
    NSLog(@"Creation of task failed.");
}

Ответ 2

Несколько POST изображений с использованием multipart или form-data с Objective-C

-(void)multipleimageandstring
{
    NSString *[email protected]"URL NAME";

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init] ;
    [request setURL:[NSURL URLWithString:urlString]];
    [request setHTTPMethod:@"POST"];

    NSMutableData *body = [NSMutableData data];

    NSString *boundary = @"---------------------------14737809831466499882746641449";
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request addValue:contentType forHTTPHeaderField:@"Content-Type"];

    // file
    float low_bound = 0;
    float high_bound =5000;
    float rndValue = (((float)arc4random()/0x100000000)*(high_bound-low_bound)+low_bound);//image1
    int intRndValue = (int)(rndValue + 0.5);
   NSString *str_image1 = [@(intRndValue) stringValue];

    UIImage *chosenImage1=[UIImage imageNamed:@"Purchase_GUI_curves-12 copy.png"];

    NSData *imageData = UIImageJPEGRepresentation(chosenImage1, 90);
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"files\"; filename=\"%@.png\"\r\n",str_image1] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[NSData dataWithData:imageData]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];





    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"name\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Nilesh" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"apipassword\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithString:app.password] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"adminId\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithString:app.adminId] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

    // close form
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    // set request body
    [request setHTTPBody:body];

    //return and test
    NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
    NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];

    NSLog(@"%@", returnString);

}

Ответ 3

Попробуйте использовать это как для видео, так и для изображений с различными типами mime.

NSDictionary *param;

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

// 1. Create `AFHTTPRequestSerializer` which will create your request.
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
NSMutableURLRequest *request;

NSData *fileData;
if ([objDoc.url containsString:@".mp4"]) {
    manager.responseSerializer.acceptableContentTypes = [manager.responseSerializer.acceptableContentTypes setByAddingObject:@"application/json"];
    [serializer setValue:@"video/mp4" forHTTPHeaderField:@"Content-Type"];
    manager.requestSerializer = serializer;
}

// 2. Create an `NSMutableURLRequest`.

NSLog(@"filename =%@",objDoc.url);
request= [serializer multipartFormRequestWithMethod:@"POST" URLString:strUrl parameters:param constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {

    if ([objDoc.url containsString:@".mp4"]) {
        [formData appendPartWithFileData:fileData
                                    name:@"File"
                                fileName:@"video.mp4"
                                mimeType:@"video/mp4"];

    }else{
        [formData appendPartWithFileData:fileData
                                    name:@"File"
                                fileName:@"image.jpeg"
                                mimeType:@"image/jpeg"];
    }

} error:nil];

// 3. Create and use `AFHTTPRequestOperationManager` to create an `AFHTTPRequestOperation` from the `NSMutableURLRequest` that we just created.

self.objeDocument.isUploading = [NSNumber numberWithInt:1];

self.operation = [manager HTTPRequestOperationWithRequest:request
                                                  success:^(AFHTTPRequestOperation *operation, id responseObject) {

                                                      NSLog(@"Success %@", responseObject);
                                                  } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                                                      UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Error!" message:@"The document attached has failed to upload." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
                                                      [alert show];
                                                      [self.operation cancel];
                                                      NSLog(@"Failure %@", error.description);
                                                  }];


// 4. Set the progress block of the operation.
[self.operation setUploadProgressBlock:^(NSUInteger __unused bytesWritten,
                                         long long totalBytesWritten,
                                         long long totalBytesExpectedToWrite) {
    NSLog(@"Wrote %lld/%lld", totalBytesWritten, totalBytesExpectedToWrite);
    float progress = (float)totalBytesWritten/(float)totalBytesExpectedToWrite;


}];

// 5. Begin!
[self.operation start];

Ответ 4

Я боролся с этим некоторое время, если вы ищете для загрузки нескольких изображений или любых других типов файлов, вы можете сделать следующее, используя AFNetworking 3.0

NSDictionary *params = @{key        : value,
                            ..... etc
                         };

 NSString *urlString = @"http://..... your endpoint url";

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer]; // only needed if the server is not returning JSON;  

NSURLSessionTask *task = [manager POST:urlString parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
    for (int x = 0 ; x< contentArray.count; x++) {
        AttachmentsModel *model = contentArray[x];

        if(model.type == ImageAttachmentType){
            [formData appendPartWithFileData:model.data name:model.name fileName:model.fileName mimeType:model.mimeType];

        }else if(model.type == AudioAttachmentType){
            NSURL *urlVideoFile = [NSURL fileURLWithPath:model.path];
            [formData appendPartWithFileURL:urlVideoFile name:model.name fileName:model.fileName mimeType:model.mimeType error:nil];
        }else{
            [formData appendPartWithFileURL:model.url name:model.name fileName:model.fileName mimeType:model.mimeType error:nil];

        }

    }
}  progress:nil success:^(NSURLSessionTask *task, id responseObject) {
    [Utility stopLoading];

     NSString *result = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
    NSLog(@"result = %@", result);

    id json = [NSJSONSerialization JSONObjectWithData:responseObject options:0 error:nil];


    if (block) {

        //your response comes here
    }

} failure:^(NSURLSessionTask *task, NSError *error) {
    NSLog(@"error = %@", error);

}];

if (!task) {
    NSLog(@"Creation of task failed.");
}

А вот как выглядит моя AttachmentsModel:

//  AttachmentsModel.h

typedef enum AttachmnetType{
    ImageAttachmentType,
    AudioAttachmentType,
    VideoAttachmentType
} AttachmnetType;

@interface AttachmentsModel : NSObject

@property (strong, nonatomic) NSString *path;
@property (strong, nonatomic) NSData *data;
@property (strong, nonatomic) NSString *mimeType;
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *fileName;
@property (strong, nonatomic) NSURL *url;

Ответ 5

Попробуй это:

Swift 5

private func buildMultipartDataRequest(_ request: NetworkRequest, baseURL: NetworkEndPoint) -> URLRequest? {
  var returnRequest: URLRequest?

  if let data = request.multipartData,
    let mimeType = request.mimeType,
    let fileName = request.fileName {

    var requestPath = request.endPoint.value
    if let urlParameters = request.urlParameters {
      requestPath += urlParameters.requestString()
    }

    let completePath = String(format: "%@%@", baseURL.value, requestPath)

    if let cleanPath = completePath.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed),
      let url = URL(string: cleanPath) {

      returnRequest = URLRequest(url: url)
      returnRequest?.httpMethod = request.HTTPMethod.rawValue

      var allheaders: [String: String] = [ :]
      if let headers = request.headers {
        allheaders = headers
      }

      returnRequest?.allHTTPHeaderFields = allheaders

      let boundary = boundaryString()
      returnRequest?.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

      let encoding: UInt = String.Encoding.utf8.rawValue

      if let boundaryStartData = "--\(boundary)\r\n".data(using: String.Encoding(rawValue: encoding)),
        let fileNameData = "Content-Disposition:form-data; name=\"picture\"; filename=\"\(fileName)\"\r\n".data(using: String.Encoding(rawValue: encoding)),
        let contentTypeData = "Content-Type: \(mimeType)\r\n\r\n".data(using: String.Encoding(rawValue: encoding)),
        let endLineData = "\r\n".data(using: String.Encoding(rawValue: encoding)),
        let boundaryEndData = "--\(boundary)--\r\n".data(using: String.Encoding(rawValue: encoding)) {

        var body = Data()

        if let bodyParameters = request.bodyParameters as? [String: AnyObject] {
          bodyParameters.forEach { (pair) in
            if let key = "Content-Disposition: form-data; name=\"\(pair.key)\"\r\n\r\n".data(using: String.Encoding(rawValue: encoding)),
              let value = "\(pair.value)\r\n".data(using: String.Encoding(rawValue: encoding)) {
                body.append(boundaryStartData)
                body.append(key)
                body.append(value)
            }
          }
        }

        body.append(boundaryStartData)
        body.append(fileNameData)

        body.append(contentTypeData)
        body.append(data)
        body.append(endLineData)
        body.append(boundaryEndData)

        returnRequest?.httpBody = body as Data
      }
    }
  }

   return returnRequest as URLRequest?
 }

где NetworkRequest - простой протокол, который инкапсулирует информацию запроса

protocol NetworkRequest: class {

  var HTTPMethod: NetworkRequestType { get }

  var endPoint: NetworkEndPoint { get }

  var urlParameters: [String: AnyObject]? { get }

  var bodyParameters: AnyObject? { get }

  var headers: [String: String]? { get }

  var multipartData: Data? { get }

  var mimeType: NetworkMimeType? { get }

  var fileName: String? { get }
}

NetworkEndPoint - Значение NetworkEndPoint

struct NetworkEndPoint {

  let value: String
}

Пограничный Жало

private func boundaryString() -> String {
  return "Boundary-\(UUID().uuidString)"
}

NetworkRequestType:

enum NetworkRequestType: String {

  case UNKNOWN

  case POST
  case GET
  case PUT
  case DELETE
  case PATCH
  // ....
}

NetworkMimeType

enum NetworkMimeType: String {

  case html, htm, shtml = "text/html"
  case css = "text/css"
  case xml = "text/xml"
  case gif = "image/gif"
  case jpeg, jpg = "image/jpeg"
  case jScript = "application/javascript"
  case atom = "application/atom+xml"
  case rss = "application/rss+xml"
  case mml = "text/mathml"
  case txt = "text/plain"
  case jad = "text/vnd.sun.j2me.app-descriptor"
  case wml = "text/vnd.wap.wml"
  case htc = "text/x-component"
  case png = "image/png"
  case tiff, tif = "image/tiff"
  case wbmp = "image/vnd.wap.wbmp"
  case ico = "image/x-icon"
  case jng = "image/x-jng"
  case bmp = "image/x-ms-bmp"
  case svg, svgz = "image/svg+xml"
  case webp = "image/webp"
  case woff = "application/font-woff"
  case jar, war, ear = "application/java-archive"
  case json = "application/json"
  case hqx = "application/mac-binhex40"
  case doc = "application/msword"
  case pdf = "application/pdf"
  case postscript, eps, aiPostscript = "application/postscript"
  case rtf = "application/rtf"
  case m3u8 = "application/vnd.apple.mpegurl"
  case xls = "application/vnd.ms-excel"
  case eot = "application/vnd.ms-fontobject"
  case ppt = "application/vnd.ms-powerpoint"
  case wmlc = "application/vnd.wap.wmlc"
  case kml = "application/vnd.google-earth.kml+xml"
  case kmz = "application/vnd.google-earth.kmz"
  case sevenZ = "application/x-7z-compressed"
  case cco = "application/x-cocoa"
  case jardiff = "application/x-java-archive-diff"
  case jnlp = "application/x-java-jnlp-file"
  case run = "application/x-makeself"
  case perl, perlm = "application/x-perl"
  case prc, pdb = "application/x-pilot"
  case rar = "application/x-rar-compressed"
  case rpm = "application/x-redhat-package-manager"
  case sea = "application/x-sea"
  case swf = "application/x-shockwave-flash"
  case sit = "application/x-stuffit"
  case tcl = "application/x-tcl"
  case der, pem, crt = "application/x-x509-ca-cert"
  case xpi = "application/x-xpinstall"
  case xhtml = "application/xhtml+xml"
  case xspf = "application/xspf+xml"
  case zip = "application/zip"
  case bin, exe, dll, deb, dmg, iso, img, msi, msp, msm = "application/octet-stream"
  case docx = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
  case xlsx = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
  case pptx = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
  case mid, midi, kar = "audio/midi"
  case mp3 = "audio/mpeg"
  case ogg = "audio/ogg"
  case m4a = "audio/x-m4a"
  case realAudio = "audio/x-realaudio"
  case threegpp, threegp = "video/3gpp"
  case mpts = "video/mp2t"
  case mp4 = "video/mp4"
  case mpeg, mpg = "video/mpeg"
  case mov = "video/quicktime"
  case webm = "video/webm"
  case flv = "video/x-flv"
  case m4v = "video/x-m4v"
  case mng = "video/x-mng"
  case asx, asf = "video/x-ms-asf"
  case wmv = "video/x-ms-wmv"
  case avi = "video/x-msvideo"
}