読者です 読者をやめる 読者になる 読者になる

Natural Software

KinectなどのDepthセンサーを中心に活動しています

iPhoneアプリ開発記(10):Objective-C で HTTP のファイルアップロードを実装する

iOS

前回、mixi API を使ってmixiボイスに投稿しました。
今度はフォト付きつぶやきを投稿したかったのですが、一筋縄ではいかなくてしばらくハマっておりました。なんとかMac上のアプリとiPhone実機で送ることができたのでまとめておきます。
リクエストの作成には、前回参考にしたPythonのコードと実際のパケットをにらめっこしながらやりましたが、もっとスマートな方法があれば教えてほしいです。

フォト付きつぶやきの送り方

フォト付きつぶやきを送る方法は下記の2種類あるようで、今回は multiple/form-data 形式のリクエストで送ってみました。

  • multiple/form-data形式のリクエストを送信する。
  • image/jpeg, image/png形式のリクエストを送信する。
Voice API << mixi Developer Center (ミクシィ デベロッパーセンター)

multiple/form-data 形式のリクエストにするためにやること

  • "Content-Type"ヘッダフィールドを"multiple/form-data"にする
  • RFCに沿った形でbodyを作成する


参考にしたページも載せておきます

コード例

    // コンテンツの作成
    NSString *boundary = @"_insert_some_boundary_here_";
    NSMutableData* result = [[NSMutableData alloc] init];
    [result appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSASCIIStringEncoding]];
    [result appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"status\"\r\n\r\n"] dataUsingEncoding:NSASCIIStringEncoding]];
    [result appendData:[[NSString stringWithFormat:@"%@",post] dataUsingEncoding:NSASCIIStringEncoding]];
    [result appendData:[[NSString stringWithString:@"\r\n"] dataUsingEncoding:NSASCIIStringEncoding]];
    
    [result appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSASCIIStringEncoding]];
    [result appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"photo\"; filename=\"%@\"\r\n", fileName] dataUsingEncoding:NSASCIIStringEncoding]];
    [result appendData:[[NSString stringWithString:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSASCIIStringEncoding]];
    [result appendData:photo];
    [result appendData:[[NSString stringWithString:@"\r\n"] dataUsingEncoding:NSASCIIStringEncoding]];
    [result appendData:[[NSString stringWithFormat:@"--%@--\r\n\r\n", boundary] dataUsingEncoding:NSASCIIStringEncoding]];
    
    // リクエストの作成
    NSURL *url = [NSURL URLWithString:@"http://api.mixi-platform.com/2/voice/statuses/update"];
    NSString *header = [[NSString alloc] initWithFormat:@"OAuth %@", [consumer getToken]];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setHTTPMethod:@"POST"];
    [request setValue:header forHTTPHeaderField:@"Authorization"];
    [request setHTTPBody:result];
    [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
    
    NSURLResponse *response = nil;
    NSError       *error    = nil;
	
    NSData *xoauth_response = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

簡単な解説

  1. boundary
    • データの境界です。"Content-Type"フィールドに"multipart/form-data; boundary="という形で設定します。実際のbodyでは、データの開始に"--"と設定し、すべてのデータの終端に"----"という形で設定します。
  2. 実際のデータ
    • mixi ボイスとして送信するデータ(status,photo)はboundaryのあとに「Content-Disposition: form-data; name="status"」または「Content-Disposition: form-data; name="photo"; filename="<ファイル名>"」という形で作ります。nameの部分がパラメータ名になります。フォトは一連の設定の後に実際のバイナリデータを展開することで送信できます。
  3. 改行
    • ことろどころに改行が入っていますが、multipart/form-dataの改行コードは CRLF だそうです。改行も一つだったり二つだったりしますが、ここら辺はよくわかってないので適当に

おまけ:画像データの展開方法

今回、テスト用のMacアプリとiPhone実機に載せるアプリの両方で送信を確認しました。
送信データはNSDataとして作りますが、作成元が違うのでそれぞれのNSDataへの展開方法も載せておきます

  • Macはファイル名からNSDataを作成する
  • iPhoneカメラアプリを流用して作ったので、UIImageからNSDataを作成する
ファイル名からNSDataを作成
NSString *fileName = @"sample.jpg";
NSData *data = [NSData dataWithContentsOfFile:fileName];
UIImageからNSDataを作成
UIImage *originalImage = ごにょごにょ;
NSData *data = [[NSData alloc] initWithData:UIImagePNGRepresentation( originalImage )];