web-dev-qa-db-ja.com

iOSでNSURLSessionマルチパートフォームデータを使用してファイルをアップロードできません

マルチパートフォームデータを使用して- (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. 形成されているマルチパートformdata本文に誤りはありますか(注-同じコードが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];
_
16
A for Alpha

自分が思っているものをアップロードしていません。あなたの意図は、ボディデータをそのままアップロードすることです。代わりに、uploadTaskWithRequest:fromFile:を呼び出すと、そのメソッドはリクエスト内のHTTPBodyまたはHTTPBodyStream値を効果的に無効にし、それらをfromFile:パラメーターを介して渡したURLのコンテンツに置き換えます。

したがって、フォームでエンコードされた本文データのブロックを別の場所のファイルURLに書き込んでいない限り、マルチパートフォームデータではなく、ファイル自体をアップロードすることになります。

フォームデータをHTTPBodyに保存するのではなく、ファイルに書き出すようにコードを微調整してから、そのファイルのURLをfromFile:パラメーターに渡す必要があります。

14
dgatwood

それに対処する時間を無駄にするのを防ぐため。

@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")
5
Victor Casé