I am trying to upload a video / image file using- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
method using multi-part form data. But somehow i am not able to upload the file and i am getting "stream ended unexpectedly
" error.
Requirements
- Upload a video / image file to server
- App should support background uploads (Continue the upload process even after app goes into background)
- Server expects the data to be sent using multi-part form data.
Methods / API's used to achieve this
NSURLSession background session API (Complete code listed below)
2.
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL
Challenges / Problems being faced
- facing "
stream ended unexpectedly
" error every time I am using this API for upload process
Points to be noted
The upload is getting successful with the same code if i am use
NSURLConnection
instead ofNSURLSession
.NSURLSession
background upload process expects the file location (NSURL
) as parameter, does not accept NSData. It does not allow us to convert the file toNSData
before uploading, i.e, we can not add NSData to file body.
Need help on following points
Is there any mistake in the multipart formdata body that is being formed (note - The same code is working with NSURLConnection)
Where am i going wrong in my approach?
Do we need to make any changes at the server level to support
NSURLSession backgroundSession
uploads? (in data parsing or something else?)Here is the code that is being used for uploading a file
NSString *BoundaryConstant = @"----------V2ymHFg03ehbqgZCaKO6jy";
// string constant for the post parameter 'file'. My server uses this name: `file`. Your's may differ
NSString* FileParamConstant = @"file";
// the server url to which the image (or video) is uploaded. Use your server url here
url=[NSURL URLWithString:[NSString stringWithFormat:@"%@%@%d",baseURL,@"posts/post/update/",createPostObject.PostID]];
// create request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
[request setHTTPShouldHandleCookies:NO];
[request setTimeoutInterval:120];
[request setHTTPMethod:@"POST"];
[request addValue:@"multipart/form-data" forHTTPHeaderField:@"Content-Type"];
[request setURL:url];
// set Content-Type in HTTP header
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", BoundaryConstant];
[request setValue:contentType forHTTPHeaderField: @"Content-Type"];
if([[NSUserDefaults standardUserDefaults] objectForKey:@"accessToken"]){
[request setValue:[[NSUserDefaults standardUserDefaults] objectForKey:@"accessToken"] forHTTPHeaderField:AccessTokenKey];
}
// post body
NSMutableData *body = [NSMutableData data];
// add params (all params are strings)
for (NSString *param in self.postParams) {
NSLog(@"param is %@",param);
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", BoundaryConstant] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", param] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"%@\r\n", [self.postParams objectForKey:param]] dataUsingEncoding:NSUTF8StringEncoding]];
}
// add video file name to body
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", BoundaryConstant] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"file.mp4\"\r\n", FileParamConstant] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithString:@"Content-Type: video/mp4\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
// [body appendData:self.dataToPost];
[body appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", BoundaryConstant] dataUsingEncoding:NSUTF8StringEncoding]];
// setting the body of the post to the request
[request setHTTPBody:body];
// set the content-length
NSString *postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[body length]];
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
NSLog(@"Request body %@", [[NSString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding]);
NSURLSessionConfiguration * backgroundConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"backgroundtask1"];
NSURLSession *backgroundSeesion = [NSURLSession sessionWithConfiguration: backgroundConfig delegate:self delegateQueue: [NSOperationQueue mainQueue]];
NSURLSessionUploadTask *uploadTask = [backgroundSeesion uploadTaskWithRequest:request fromFile:self.videoUrl];
[uploadTask resume];
You aren't uploading what you think you are. Your intent is for the body data to be uploaded as-is. Instead, when you call uploadTaskWithRequest:fromFile:
, that method effectively nils out any HTTPBody
or HTTPBodyStream
values in the request and replaces them with the contents of the URL that you passed in via the fromFile:
parameter.
So unless you're writing that block of form-encoded body data to that file URL somewhere else, you're uploading the file by itself instead of the multipart form data.
You need to tweak your code to write the form data out to a file instead of storing it in HTTPBody
, then pass the URL of that file to the fromFile:
parameter.
To prevent wasting time dealing with it.
The complete snippet based on @dgatwood answer
private func http(request: URLRequest){
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: .main)
/*Tweaking*/
let task = session.uploadTask(with: request, from: request.httpBody!)
task.resume()
}
And.. do not forget to add the Headers on request object like
request.setValue("multipart/form-data; boundary=\(yourboundary)", forHTTPHeaderField: "Content-Type")
来源:https://stackoverflow.com/questions/36913436/unable-to-upload-file-using-nsurlsession-multi-part-form-data-in-ios