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

时间:2016-04-28 11:34:44

标签: file-upload multipartform-data nsurlsession nsurlsessionuploadtask nsurlsessionconfiguration

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

要求

  1. 将视频/图像文件上传到服务器
  2. 应用应该支持后台上传(即使在应用进入后台后继续上传过程)
  3. 服务器期望使用多部分表单数据发送数据。
  4. 方法/ API用于实现此目的

    1. NSURLSession后台会话API(下面列出的完整代码)

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

    2. 面临的挑战/问题

      1. 面对" stream ended unexpectedly"每次我使用此API进行上传过程时出现错误
      2. 需要注意的要点

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

        2. NSURLSession后台上传进程期望文件位置(NSURL)作为参数,不接受NSData。它不允许我们在上传之前将文件转换为NSData,即我们无法将NSData添加到文件正文中。

        3. 需要以下几点的帮助

          1. 正在形成的multipart formdata正文中是否有任何错误(注意 - 相同的代码与NSURLConnection一起使用)

          2. 我的方法在哪里出错?

          3. 我们是否需要在服务器级别进行任何更改以支持NSURLSession backgroundSession上传? (在数据解析或其他什么?)

            以下是用于上传文件的代码

          4. NSString * BoundaryConstant = @" ---------- V2ymHFg03ehbqgZCaKO6jy&#34 ;;

                // 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];
            

2 个答案:

答案 0 :(得分:10)

您没有上传您认为自己的内容。您的目的是按原样上传正文数据。相反,当您调用uploadTaskWithRequest:fromFile:时,该方法会有效地将请求中的任何HTTPBodyHTTPBodyStream值取消,并将其替换为您通过{{1传入的URL的内容参数。

因此,除非您将表格编码的正文数据块写入其他地方的文件URL,否则您将自己上传文件而不是多部分表单数据。

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

答案 1 :(得分:5)

防止浪费时间处理它。

  

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

@foreach ($categories as $category)
    <tr data-id="{{ $category->id }}">
        <td>{{ $category->id }}</td>
        <td>{{ $category->name }}</td>
        <td id="actions">
            <a href="{{ url('admin/category/' . $category->id . '/get') }}">Show</a>
            <a href="{{ url('admin/category/' . $category->id . '/edit') }}">Edit</a>
            <a href="#" data-toggle="modal" data-target="#confirmDeleteModal">Delete</a>
            <confirm-delete-modal item="category" id="{{ $category->id }}" name="{{ $category->name }}"></confirm-delete-modal>
        </td>
    </tr>
@endforeach

并且..不要忘记在请求对象上添加标题,如

  getUsersForConversations(conversations) {
    conversations.forEach((conversation, index) => {

      // Get user out of local storage
      this.storage.get('user').then(user => {

        let userKeys = Object.keys(conversation.users);

        let sub = Observable
          .from(userKeys)
          .filter(key => (key != user.id && key.length > 0))
          .flatMap(key =>
            Observable.zip(
              Observable.of(key),
              this._af.database.object(`/social/conversations_last_deleted/${conversation.$key}/${user.id}`)
                .filter(lastDeleted => (lastDeleted && (lastDeleted.$value !== null)))
                .flatMap(lastDeleted => this._messages.getMessagesForConvo(conversation.$key, lastDeleted)),
              (key, messages) => {
                if (messages.length === 0) {
                  return null;
                }
                return key;
              })
          )
          .filter(key => key != null)
          .flatMap(userKey => this._af.database.object(`/social/users/${userKey}`))
          .subscribe(users => {
            conversations[index].users = users;
            this.conversations = conversations;
          });

          // REMEMBER TO ALWAYS UNSUBSCRIBE!!!! or you will have memory leaks....
          sub.unsubscribe();

      });
    });
  }