+ Start a Discussion
Yasuki SakuraiYasuki Sakurai 

iOSモバイルアプリケーションからのChatterへの投稿について

お世話になっております。

iOSのモバイルアプリケーション内で、入力された文字列をChatterへ投稿する、という動作を実装しようとしています。

HTTP POSTにはJSONを使い、リクエストを送信するコードを記述し、実行したところ、HTTPステータスコードとして400が返ってきました。
どのようなコードを記述すれば、上記のような動作を実現できるのでしょうか?

記述したコードは以下の通りです。

    NSString *header = [NSString stringWithFormat:@"OAuth %@", access_token];
    NSString *chatterURL = @"https://xxx.salesforce.com/services/data/v31.0/chatter/feed-elements";
   
    NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:chatterURL]
                                                    cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
                                                    timeoutInterval:60];

    [req setHTTPMethod:@"POST"];
    [req addValue:header forHTTPHeaderField:@"Authorization"]; //アクセストークンをヘッダに追加

    NSString *boundary = @"------------------a7V4kRcFA8E79pivMuV2tukQ85cmNKeoEgJgq";
    [req addValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
    [req addValue:[NSString stringWithFormat:@"application/json"] forHTTPHeaderField:@"Accept"];
   
    [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]];
   
    // 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"
                            "\"feedElementType\" : \"FeedItem\", \r\n"
                            "\"subjectId\" : \"xxxxxxxx\" \r\n"
                            "}", inputText];
   
    [body appendData:[[NSString stringWithFormat:@"%@\r\n", bodyString] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

    [req setHTTPBody:body];

    NSError *error = nil;
    NSURLResponse *response = nil;
   
    //リクエストの送信
    [NSURLConnection sendSynchronousRequest:req
                                     returningResponse:&response
                                     error:&error];

上記コードを実行すると、NSURLConnectionの引数responseにHTTPステータスコードの400、
errorはnilが返ってきます。
Best Answer chosen by Yasuki Sakurai
Marty C.Marty C.
Sakuraiさん、詳細的に説明してありがとうございます。読む前私は画像のPOST方法を知りませんでした。Sakuraiさんからその知識をもらって感謝します。

Sakuraiさんのコードが正解だと思いますが、以下の提案ご参照してください。
  • Acceptヘッダーを取り除くこと
  • NSURLConnectionを下記のように書き直すこと
  • 新たなアクセストークンを入手して使うこと

NSURLConnection *connection = [[NSURLConnection alloc]
    initWithRequest:postRequest delegate:self];
initWithRequestの使い方はAppleのUsing NSURLConnection記事[1]で説明されます。私はその方法でChatter REST APIに接続できました。雑ですが、私のサンプルコードをGitHub[2]で共有してSakuraiさんの参照ために提供します。

[1]: Using NSURLConnection記事 (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLConnection.html)
[2]: GitHub (https://github.com/martyychang/sf-dev-forums-906F0000000Ae7pIAC)

All Answers

Marty C.Marty C.
Sakuraiさん、こんにちは。常ともうします。

HTTP 400エラーが返られた原因は「\r\n」の存在かも知れません。「\r\n」を削除するとどうなりますか。同じ結果ですかな。
Yasuki SakuraiYasuki Sakurai
>常さん
sakuraiです。回答ありがとうございます。

ご指摘の通り、「\r\n」を削除してみましたが、状況は変わらず、
responseにはHTTPステータスコードの400しか返ってこない状態です。
Marty C.Marty C.
Sakuraiさん、提案を試したことありがとうございます。「\r\n」は問題じゃなかったら、boundaryが疑わしいと思います。

私の成功的な簡単RESTリクエストは三つの成分があります。
  • Authorizationヘッダー。例: Authorization: Bearer 00Dq00000008k9a!ARsAQBfFJ6rpPVuwBauFtRQE9MQmjQJTVN_YZQHpRxe4BoGxjyAM6zw5asQM3FMTroXo4HK8syWmWRJK9mfb5.jIg.58mwtS
  • Content-Typeヘッダー。例: Content-Type: application/json
  • JSONボディー。例:  "{\"body\":{\"messageSegments\":[{\"type\":\"Text\",\"text\":\"Hello, Chatter REST API!\"}]},\"feedElementType\":\"FeedItem\",\"subjectId\":\"005o0000000iBIx\"}"

私の理解不足ですみませんですが、boundaryの用途を少し説明してくれませんか。
Yasuki SakuraiYasuki Sakurai
>常さん
ご回答ありがとうございます。

boundaryについてですが、コードを書くにあたり、以下のサイトを参考にしました。
http://qiita.com/kouchi67/items/153e7009f8bf8c39f7d8
こちらのサイトでは、chatterにテキストと画像を一緒に投稿するサンプルが紹介されています。
バイナリデータを一緒に投稿する際にはmurtipart/form-dataをContent-Typeヘッダーに設定し、boundaryは、body部でデータ項目の区切り(テキストと画像データの区切り)として使用しているようです。

私が最初に記述したコードは、このサイトの例から画像データ部分を取り除いたものなのですが、
今回実現したい動作はコメントのみなので、murtipart/form-data、boundaryの使用は不適切だったのですね。
こちらの勉強不足でした、すいません。

その後コードを書き直し、常さんのコメントを参考に、以下のように修正しました。
    NSString *chatterURL = @"https://xxx.salesforce.com/services/data/v31.0/chatter/feed-elements";
    NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:chatterURL]
                                                       cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
                                                   timeoutInterval:60];
    
    [req setHTTPMethod:@"POST"];
    
//headerの作成
    NSString *access_token = @"00DO00000007rKW!ARcAQMNo1_zLWDIV.6tW0bBaWjms4oQLG1iDmMbFVhnXsw1qOtsFGeuOehvMmQLUhA4R31Gv60Ni5D24IeuJvUxmAbSOHdiD";
    NSString *header = [NSString stringWithFormat:@"Bearer %@", access_token];
    
    [req setValue:header forHTTPHeaderField:@"Authorization"];
    [req setValue:[NSString stringWithFormat:@"application/json"] forHTTPHeaderField:@"Accept"];
    [req setValue:[NSString stringWithFormat:@"application/json"] forHTTPHeaderField:@"Content-Type"];

//bodyの作成
    NSMutableData *body = [[NSMutableData alloc] init];
    NSString *bodyString = [NSString stringWithFormat:@"{\"body\":{\"messageSegments\":[{\"type\":\"Text\",\"text\":\"test!!\"}]},\"feedElementType\":\"FeedItem\",\"subjectId\":\"me\"}"];
    
    [body appendData:[[NSString stringWithFormat:@"%@", bodyString] dataUsingEncoding:NSUTF8StringEncoding]];
    
    [req setHTTPBody:body];

//リクエスト送信
    [NSURLConnection sendSynchronousRequest:req
                          returningResponse:&response
                                      error:&error];

上記のようなコードに修正し、実行したところ、結果は以下の通りでした。
response : 0
error : The operation couldn’t be completed. (NSURLErrorDomain error -1012.)

この"NSURLErrorDomain error -1012."を調べてみると、認証関係のエラーではないか、というところまでは分かりました。(以下参考URL)
http://stackoverflow.com/questions/7800675/error-migrating-existing-oauth-credentials-to-acaccountstore
認証というとheader部のAuthorization部分がおかしい、ということでしょうか。

お手数おかけしますが、よろしくお願い致します。
Marty C.Marty C.
Sakuraiさん、詳細的に説明してありがとうございます。読む前私は画像のPOST方法を知りませんでした。Sakuraiさんからその知識をもらって感謝します。

Sakuraiさんのコードが正解だと思いますが、以下の提案ご参照してください。
  • Acceptヘッダーを取り除くこと
  • NSURLConnectionを下記のように書き直すこと
  • 新たなアクセストークンを入手して使うこと

NSURLConnection *connection = [[NSURLConnection alloc]
    initWithRequest:postRequest delegate:self];
initWithRequestの使い方はAppleのUsing NSURLConnection記事[1]で説明されます。私はその方法でChatter REST APIに接続できました。雑ですが、私のサンプルコードをGitHub[2]で共有してSakuraiさんの参照ために提供します。

[1]: Using NSURLConnection記事 (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLConnection.html)
[2]: GitHub (https://github.com/martyychang/sf-dev-forums-906F0000000Ae7pIAC)
This was selected as the best answer
Yasuki SakuraiYasuki Sakurai
>常さん
ご回答ありがとうございます。

常さんが提案していただいた通りにコードを修正したところ、無事chatterへの投稿に成功しました!
サンプルコードもありがとうございました!とても参考になりました。