iOS中使用NSURLSession多部分表单数据无法上传文件

17

我正在尝试使用- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;方法,使用多部分表单数据上传视频/图像文件。但是,不知何故,我无法上传文件,并收到“ stream ended unexpectedly ”错误。

要求

  1. 将视频/图像文件上传到服务器
  2. 应用程序应支持后台上传(即使应用程序进入后台,也应继续上传过程)
  3. 服务器希望使用多部分表单数据发送数据。

用于实现此目的的方法/ API

  1. NSURLSession后台会话API(完整代码如下所示)

    2. - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL

面临的挑战/问题

  1. 每次使用此API进行上传过程时都会遇到“ stream ended unexpectedly ”错误

需要注意的几点

  1. 如果我使用 NSURLConnection 而不是 NSURLSession ,则相同的代码可以成功上传。

  2. NSURLSession背景上传过程将文件位置( NSURL )作为参数,不接受NSData。它不允许我们在上传之前将文件转换为 NSData ,即我们无法将NSData添加到文件主体。

以下几点需要帮助

  1. 多部分表单数据体中是否有任何错误?(请注意-如果使用NSURLConnection,则相同的代码可以正常工作)

  2. 我的方法有什么问题?

  3. 我们是否需要在服务器级别进行任何更改以支持 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];

你似乎设置了两次“Content-Type”。不确定这样做是否有效。对于其余部分也不确定。我做了类似的事情,但是将上传缓冲区单独创建(在C++函数中),然后只需添加整个缓冲区即可。因此,我没有太多可以贡献的了。 - Aslak Berby
2个回答

15

你正在上传的并不是你所想象的。你的意图是将body数据按原样上传。然而,当你调用uploadTaskWithRequest:fromFile:时,该方法会有效地将请求中的任何HTTPBodyHTTPBodyStream值清空,并使用通过fromFile:参数传递的URL的内容替换它们。

因此,除非你在其他地方将那段表单编码的body数据写入到文件URL中,否则你只会上传文件本身而不是多部分表单数据。

你需要修改代码,将表单数据写入文件而不是存储在HTTPBody中,然后将该文件的URL传递给fromFile:参数。


1
将 fromFile: 更改为 fromData:,并传递 body 的值。 - dgatwood
取消注释 // [body appendData:self.dataToPost]; 并将 self.dataToPost 更改为 [NSData dataWithContentsOfURL:self.videoURL]。 - dgatwood
现在您拥有一个完整的代码片段。您还可以选择删除[request setHTTPBody:body]行,因为它没有任何价值。 - dgatwood
fromData对于后台上传不起作用 - waldgeist
我尝试过了。然而,AWS仍然抱怨缺少值(比如密钥),而这些值实际上应该在请求体中。你知道有没有一些真正有效的代码示例吗? - waldgeist
显示剩余2条评论

6
为了避免浪费时间处理它。

完整代码片段基于@dgatwood的答案

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()
    }

并且...不要忘记在请求对象中添加头信息,例如:
request.setValue("multipart/form-data; boundary=\(yourboundary)", forHTTPHeaderField: "Content-Type")

文件在哪里? - Eli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接