Edited at
iOSDay 6

Objective-Cでmultipart/form-data

More than 5 years have passed since last update.

最近ハマった事をぼちぼち書こうかと。

内容はタイトルの通りです。

会社で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--

次はソースコード。

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


書いたコード

    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の世界がいまいちわかってなくてごめんなさい

  • このコードとても読みにくい

これくらいはぐぐれば結構出てくる情報なので、そこまで重要な情報でもないかなーと思います。

でも他にネタがないのでこれで勘弁して!