5

I have an upload form like this:

<form action="http://localhost/upload.php" method="post" enctype="multipart/form-data">
    <input type="file" id="upload" name="upload" />
</form>

and php code to proceed upload form:

isset($_FILES["upload"]) or die("Error");
// Path prepare stuff
if (move_uploaded_file($_FILES["upload"]["tmp_name"], $outputFile)) {
    // Other processing stuffs
}

In xcode, Im constructing the request like this:

NSMutableURLRequest* request = [[AFHTTPRequestSerializer serializer]
                                multipartFormRequestWithMethod:@"POST"
                                URLString:@"http://localhost/upload.php"
                                parameters:nil
                              constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
                                    [formData appendPartWithFormData:data name:@"somefilename.ext"];
                                } error:nil];

But seem like i did it wrong way, right?

UPDATE

Im new to AFNetworking and I want to understand how it constructs multiplart/form-data post like above. It looks like the code lack of the input's name "upload", hence will not be able to pass the first line of php upload script. I read the document from AFNetworking's GitHub, but they say nothing about constructing a form data with NSData which is the case here.

RyanB
  • 1,287
  • 1
  • 9
  • 28
  • do you want to call service using afnetworking...? – Ashish Ramani Jan 22 '16 at 03:47
  • @RamaniAshish Well, actually it isn't a service. Just a simple upload script written in php & I just want to know how to request that php script to upload a NSData object instead of a file by AFNetworking 3.x – RyanB Jan 22 '16 at 07:52

3 Answers3

14

Well, In AFNetworking 3.0 You can do like this way for uploading multiform part data,Check this

AFNetworking 3.0 is the latest major release of AFNetworking,3.0 removes all support for the now deprecated NSURLConnection based APIs. If your project was previously using these APIs, it is recommended that you now upgrade to the NSURLSession based APIs. This guide will step you through that process.

NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://localhost/upload.php" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {

   [formData appendPartWithFileData:data name:@"uploadFile" fileName:@"somefilename.txt" mimeType:@"text/plain"] // you file to upload

} error:nil];

AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];

NSURLSessionUploadTask *uploadTask;
uploadTask = [manager
          uploadTaskWithStreamedRequest:request
          progress:^(NSProgress * _Nonnull uploadProgress) {
              // This is not called back on the main queue.
              // You are responsible for dispatching to the main queue for UI updates
              dispatch_async(dispatch_get_main_queue(), ^{
                  //Update the progress view
                  [progressView setProgress:uploadProgress.fractionCompleted];
              });
          }
          completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
              if (error) {
                  NSLog(@"Error: %@", error);
              } else {
                  NSLog(@"%@ %@", response, responseObject);
              }
          }];

[uploadTask resume];
Mayank Patel
  • 3,868
  • 10
  • 36
  • 59
  • 1
    Im not upload a file, instead I use NSData to construct data form & here is the signature of the function: `[formData appendPartWithFormData:<#(nonnull NSData *)#> name:<#(nonnull NSString *)#>]`. This function doesn't let me pass the name of the input. – RyanB Jan 22 '16 at 07:47
  • in name you should have to pass parameter of it – Mayank Patel Jan 22 '16 at 07:50
  • @MayankPatel can you check it my issue regarding file upload in afnetwoking 3.0 http://stackoverflow.com/questions/39325058/upload-zip-file-in-afnetwoking-3-0 – Rushabh Sep 06 '16 at 05:27
  • How do we do this but with a file stream? If the file is large, you don't want to have to keep the whole file in memory. – TheJeff Mar 14 '17 at 21:56
  • @TheJeff you can store that file str stream m in your device then you can mange it – Mayank Patel Mar 16 '17 at 07:10
  • Friends Using above code I successfully upload video at server but video can not play in browser After downloading video also can not play in player I also passed mimetype and same problem for image also Guide me – Ankur Patel Oct 23 '17 at 05:03
  • 1
    you need to ask the backend guy it's problem of backend guy – Mayank Patel Oct 24 '17 at 16:06
2

AFNetworking doc regarding multi-part, states that you should use:

[[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST"......

and then use the NSURLSessionUploadTask for the resume method (full code in the link).

I couldn't get this to work with the server I was working on, instead I used AFHTTPSessionManager:

AFHTTPSessionManager *manager = [AFHTTPSessionManager alloc]initWithBaseURL: @"someURL..."];
// If you need to add few more headers, now is the time - if not you can skip this
[manager setRequestSerializer:[AFJSONRequestSerializer serializer]];
[[manager requestSerializer] setValue:@"your custom value"
                    forHTTPHeaderField:@"relevant key"];
// Setting basic auth - only if you need it
[[manager requestSerializer] setAuthorizationHeaderFieldWithUsername:@"username"
                                                             password:@"password"];


             [manager POST:@"appendedPath"
                parameters:params
 constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {

                   [formData appendPartWithFileData:yourNSDataFile
                                               name:@"file"
                                           fileName:@"customFileName"
                                           // The server I was working on had no type but you can google for all the existing types
                                           mimeType:@""];

             }

                 progress:nil
                  success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

                                   if (responseObject != nil)
                                   {
                                       NSDictionary *jsonDictionary = responseObject;
                                   }
                               }

                   failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

                                     // Handle your error
                               }];
OhadM
  • 4,687
  • 1
  • 47
  • 57
1

If you have a large file, you'll never want to do it this way, because it requires loading the whole file into memory. Here's a way to stream the file from disk into the HTTP request so that your memory usage remains low. Thanks for the original answer! It works like a charm.

NSInputStream *fileInputStream = [[NSInputStream alloc] initWithFileAtPath:filePath];

if (!fileInputStream) {
    NSLog(Error, @"Could not get a fileInputStream from the file path");
    return;
}

NSError *error;

NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"PUT" URLString:fullUrlStr parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {

    [formData appendPartWithInputStream:fileInputStream name:@"uniqueIdentifier" fileName:@"filename" length:<lengthOfFileLong>];

} error:&error];

if (error) {
    NSLog(Error, @"Error creating multipart form upload request: %@", [error userInfo]);
    completionHandler(nil, error);
}

[request setAllHTTPHeaderFields:headerDictionary];

AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];

NSURLSessionUploadTask *uploadTask;
uploadTask = [manager
              uploadTaskWithStreamedRequest:request
              progress:^(NSProgress * _Nonnull uploadProgress) {
                  // This is not called back on the main queue.
                  // You are responsible for dispatching to the main queue for UI updates

                  NSLog(Debug, @"Cloud Upload Completion: %f", uploadProgress.fractionCompleted);
              }
              completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {

                  if (error) {
                      NSLog(@"Error: %@", [error userInfo]);
                      completionHandler(nil, error);
                  } else {
                      NSLog(@"Success: %@ %@", response, responseObject);
                  }
              }];

[uploadTask resume];
TheJeff
  • 3,665
  • 34
  • 52