iOS 中无法使用 NSURLSession 多部分表单数据上传文件
unable to upload file using NSURLSession multi-part form data in iOS
我正在尝试使用- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
方法使用多部分表单数据上传视频/图像文件。但不知何故,我无法上传文件,并且出现“stream ended unexpectedly
”错误。
要求
- 上传视频/图片文件到服务器
- 应用程序应支持后台上传(即使应用程序进入后台也继续上传过程)
- 服务器希望使用多部分表单数据发送数据。
方法/API用于实现这个
NSURLSession后台会话API(完整代码如下)
2.- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL
面临的挑战/问题
- 每次我使用此 API 进行上传过程时都会遇到“
stream ended unexpectedly
”错误
注意事项
如果我使用 NSURLConnection
而不是 NSURLSession
,则使用相同的代码上传成功。
NSURLSession
后台上传进程需要文件位置 (NSURL
) 作为参数,不接受 NSData。它不允许我们在上传之前将文件转换为NSData
,即我们不能在文件主体中添加NSData。
需要以下方面的帮助
正在形成的多部分表单数据主体是否有任何错误(注意 - 相同的代码正在使用 NSURLConnection)
我的方法哪里出错了?
我们是否需要在服务器级别进行任何更改以支持 NSURLSession backgroundSession
上传? (在数据解析或其他方面?)
这是用于上传文件的代码
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];
您上传的内容与您认为的不同。您的意图是按原样上传正文数据。相反,当您调用 uploadTaskWithRequest:fromFile:
时,该方法有效地消除了请求中的任何 HTTPBody
或 HTTPBodyStream
值,并将它们替换为您通过fromFile:
参数。
因此,除非您将表单编码正文数据块写入该文件 URL 其他地方,否则您上传的是文件本身,而不是多部分表单数据。
您需要调整代码以将表单数据写入文件而不是将其存储在 HTTPBody
中,然后将该文件的 URL 传递给 fromFile:
参数.
以免浪费时间处理。
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()
}
并且..不要忘记根据要求添加 Headers object 喜欢
request.setValue("multipart/form-data; boundary=\(yourboundary)", forHTTPHeaderField: "Content-Type")
我正在尝试使用- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
方法使用多部分表单数据上传视频/图像文件。但不知何故,我无法上传文件,并且出现“stream ended unexpectedly
”错误。
要求
- 上传视频/图片文件到服务器
- 应用程序应支持后台上传(即使应用程序进入后台也继续上传过程)
- 服务器希望使用多部分表单数据发送数据。
方法/API用于实现这个
NSURLSession后台会话API(完整代码如下)
2.
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL
面临的挑战/问题
- 每次我使用此 API 进行上传过程时都会遇到“
stream ended unexpectedly
”错误
注意事项
如果我使用
NSURLConnection
而不是NSURLSession
,则使用相同的代码上传成功。NSURLSession
后台上传进程需要文件位置 (NSURL
) 作为参数,不接受 NSData。它不允许我们在上传之前将文件转换为NSData
,即我们不能在文件主体中添加NSData。
需要以下方面的帮助
正在形成的多部分表单数据主体是否有任何错误(注意 - 相同的代码正在使用 NSURLConnection)
我的方法哪里出错了?
我们是否需要在服务器级别进行任何更改以支持
NSURLSession backgroundSession
上传? (在数据解析或其他方面?)这是用于上传文件的代码
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];
您上传的内容与您认为的不同。您的意图是按原样上传正文数据。相反,当您调用 uploadTaskWithRequest:fromFile:
时,该方法有效地消除了请求中的任何 HTTPBody
或 HTTPBodyStream
值,并将它们替换为您通过fromFile:
参数。
因此,除非您将表单编码正文数据块写入该文件 URL 其他地方,否则您上传的是文件本身,而不是多部分表单数据。
您需要调整代码以将表单数据写入文件而不是将其存储在 HTTPBody
中,然后将该文件的 URL 传递给 fromFile:
参数.
以免浪费时间处理。
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()
}
并且..不要忘记根据要求添加 Headers object 喜欢
request.setValue("multipart/form-data; boundary=\(yourboundary)", forHTTPHeaderField: "Content-Type")