Objective-Cでmultipart/form-data


最近ハマった事をぼちぼち書こうかと。
内容はタイトルの通りです。

会社でsalesforceを導入しており、社内SNSでChatterというものを使ってました。
APIが用意されてたので、それを利用してアプリを作って遊んでたんですが、画像のアップロードにすんごいハマった。
作ってた機能は、**カメラで撮影した写真付きでコメントを投稿する**っていうそれだけ。

ドキュメントを見る限り、リクエストを投げる際に、

- JSON
- 画像データ

この2つをPOSTで送らないといけなかったらしい。
では、Objective-Cからmurtipart/form-dataの形式でリクエスト投げる際にはどう書くのか？
ドキュメントのリクエストサンプルと、自分のソースコードをちょっと比較しながら見てもらえれば。

ちょっと先にポイントだけ。

- ChatterはOAuth認証を使ってるので、headerにaccess_tokenを付与する
- NSMutableURLRequestを使用
- 引数のHTTPBodyにはNSDataへ変換したものを渡す


##リクエストのサンプル（ドキュメントから抜粋）
```
POST /services/data/v25.0/chatter/feeds/user-profile/005x0000001T9PwAAK/feed-items HTTP/1.1
Authorization: Bearer 00Dx00000001hIF!AQ8AQKy6K1Tmko19Au4U29YanMFt53ccip
1969CAg7t.vlXqyFyMd8cEh8axN1u8o86yAKBlcuyr7YOdNSnW52qNDppsduxI
User-Agent: Jakarta Commons-HttpClient/3.0.1
Host: instance_name
Content-Length: 817
Content-Type: multipart/form-data; boundary=a7V4kRcFA8E79pivMuV2tukQ85cmNKeoEgJgq

--a7V4kRcFA8E79pivMuV2tukQ85cmNKeoEgJgq
Content-Disposition: form-data; name="json"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit

{ "body":
   {
      "messageSegments" : [
      {
         "type" : "Text", 
         "text" : "High priority content "
      }, {
         "type" : "Hashtag", 
         "tag" : "important"
      }, {
         "type" : "Text", 
         "text" : "Please review this as soon as possible."
      }
      ]
   }, 
   "attachment": 
   {
      "desc": "Quarterly review",
      "filename": "2012_q1"
   }
}
--a7V4kRcFA8E79pivMuV2tukQ85cmNKeoEgJgq

Content-Disposition: form-data; name="feedItemFileUpload"; filename="2012_q1_review.ppt"
Content-Type: application/octet-stream; charset=ISO-8859-1
Content-Transfer-Encoding: binary

binary stream of file
--a7V4kRcFA8E79pivMuV2tukQ85cmNKeoEgJgq--
```

次はソースコード。
送信しようとしているリクエストとしては、サンプルとほぼ同じ。

##書いたコード
```Objective-C
    NSString *access_token = [[NSUserDefaults standardUserDefaults] objectForKey:@"access_token"];
    NSString *instance_url = [[NSUserDefaults standardUserDefaults] objectForKey:@"instance_url"];
    NSString *header = [NSString stringWithFormat:@"OAuth %@", access_token];
    NSString *urlStr = [NSString stringWithFormat:@"%@/services/data/v25.0/chatter/feeds/news/me/feed-items", instance_url];
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url = [NSURL URLWithString:urlStr];

    NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
    [req setHTTPMethod:@"POST"];
    [req addValue:header forHTTPHeaderField:@"Authorization"];

    NSMutableData *body = [[NSMutableData alloc] init];
    
    NSString *boundary = @"------------------a7V4kRcFA8E79pivMuV2tukQ85cmNKeoEgJgq";
    [req addValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
    
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Disposition: form-data; name=\"json\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Type: application/json; charset=UTF-8\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Transfer-Encoding: 8bit\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    
    // request JSON
    NSString *bodyString = [NSString stringWithFormat:@""
            "{ \"body\":\r\n"
            "   {\r\n"
            "      \"messageSegments\" : [\r\n"
            "      {\r\n"
            "         \"type\" : \"Text\", \r\n"
            "         \"text\" : \"%@\"\r\n"
            "      }\r\n"
            "      ]\r\n"
            "   }, \r\n"
            "   \"attachment\": \r\n"
            "   {\r\n"
            "      \"desc\": \"Quarterly review\",\r\n"
            "      \"filename\": \"filename.png\"\r\n"
            "   }\r\n"
            "}", text];
    
    [body appendData:[[NSString stringWithFormat:@"%@\r\n", bodyString] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
    NSString *filename = @"filename.png";
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"feedItemFileUpload\"; filename=\"%@\"\r\n", filename] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Type: application/octet-stream\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Transfer-Encoding: binary\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    
    NSData *imgData = UIImageJPEGRepresentation(_image, 1.0);
    [body appendData:imgData];
    [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
    [req setHTTPBody:body];
    
    NSError *error = nil;
    NSURLResponse *response = nil;
    
    [NSURLConnection sendSynchronousRequest:req
                          returningResponse:&response
                                      error:&error];
    
    NSHTTPURLResponse *httpurlResponse = (NSHTTPURLResponse *)response;
    NSLog(@"response -> %d", [httpurlResponse statusCode]);
```


見ればわかりますが、基本的にNSStringをNSDataへ変換してるだけです。
JSONの内容に関しては、Chatter独自の内容かと思うので特に気にする必要はないと思います。
JSON送るときこんなんでいいんだー、レベルで留めておいてくれれば。

###ハマったところ

- UIImageJPEGRepresentationを使っているが、UIImagePNGRepresentationを使ったら縦と横の向きがおかしくなった　→　使うのやめた
- NSURLResponse を NSHTTPURLResponseにキャストしたらstatusCodeが見れるって事を知らなかった
- セパレータの後に1行あけてたのが原因でstatusCodeが400しか返ってこなかった事に全然気付かなかった

###個人的に気になるところ

- Webの世界がいまいちわかってなくてごめんなさい
- このコードとても読みにくい

これくらいはぐぐれば結構出てくる情報なので、そこまで重要な情報でもないかなーと思います。
でも他にネタがないのでこれで勘弁して！