AngularJSの $resourceを使って application/x-www-form-urlencoded 形式のリクエストを POSTする

JavaScript

AngularJSでは、HTTPリクエストでサーバに送信するリクエスト本文の形式として JSONが用いられるが、代わりに HTMLフォームの subimitで使われるのと同じ application/x-www-form-urlencodedを用いる方法もある。

何故そのようなことをする必要があるかというと、APIの設計によっては JSONではなくより古典的な URLエンコードされた引数リストを入力として期待する場合があるからだ。

例えばサーバ側には下記のように Spring Web MVCフレームワークを使って Scala言語で書かれた RESTful APIがあるとしよう。urlencodedメソッドは、URLエンコード形式でPOSTされてくるvalueという引数を JSONでそのまま返す。

case class Response(success:Boolean, info:Option[Any]=None)

@RestController
@RequestMapping(Array("api"))
class RequestHandler {
  @RequestMapping(value=Array("urlencoded"), method=Array(RequestMethod.POST))
  // @RequestParamアノテーションのついた引数は application/x-www-form-urlencoded なリクエストから抽出される
  def urlencoded(@RequestParam value:String):Response = {
    Response(true, Some(value))
  }
}

valueという名前のパラメータに"ばりゅー"という文字列を与えてこのリモートAPIを呼び出したい場合、HTTPクライアントからの POSTリクエストは

Content-Type: application/x-www-form-urlencoded
value=%E3%81%B0%E3%82%8A%E3%82%85%E3%83%BC

とならなくてはならないのだが、AngularJSの $resourceサービス$httpサービスでも同様)を使って

var urlencoded_api = $resource("api/urlencoded");
urlencoded_api.save({value:"ばりゅー"});

のように記述されたコードでは POSTリクエストがURLエンコード形式にならず

Content-Type: application/json
{"value":"ばりゅー"}

のようなJSON形式となってしまい、URLエンコードされた形式を期待している API側では引数が読み取れないということでエラーになってしまう。

このミスマッチを解決するには、$resourceサービスが提供するAPIリソースオブジェクトの saveメソッド(POSTに相当する)に対し Content-Typeを明示的に application/x-www-form-urlencoded と指定した上で、さらに saveメソッド呼び出し時には引数リスト(となるJavaScriptオブジェクト)を jQueryの param() APIで URLエンコードしてから渡してやればよい。

<html ng-app="MyApp">
  <head>
    <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://code.jquery.com/jquery-latest.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular-resource.js"></script>
    <script language="javascript">
      angular.module("MyApp", ["ngResource"])
      .run(["$rootScope", "$resource", function($scope, $resource) {
        $scope.result = null;
        $resource("api/urlencoded", {}, {
          save: {
            method: "POST",
            headers : {'Content-Type': 'application/x-www-form-urlencoded'}
          }
        }).save($.param({value:"ばりゅー"}), 
            function(data) { $scope.result = data }, 
            function(data) { $scope.result = "失敗" });
      }]);
    </script>
  </head>
  <body>
     実行結果: {{ result }}
  </body>
</html>

参考: x-www-form-urlencoded and $resource · Issue #1937 · angular/angular.js

そもそもの話、AngularJSの $resourceサービスを使ってサーバ側のRESTful APIにアクセスするのであれば API設計もそれに合うように行う必要がある(レスポンスは単一のJavaScriptオブジェクトか JavaScriptオブジェクトの Arrayでないといけないなど)のでサーバ側を変更するのが筋ではあるので、実際にサーバ側APIの仕様を変えられない場合は上記と似たようなことを $resourceサービスではなく $httpサービスで行うことになるだろう。(参考: http://stackoverflow.com/questions/11442632/how-can-i-make-angular-js-post-data-as-form-data-instead-of-a-request-payload

同じカテゴリの記事

Angular.JSでselect要素にoptionをぶら下げる色々な方法 2014年3月29日
AngularJSで、時間のかかる通信の間にモーダルを使ってプログレスバーを表示する 2014年3月18日
HTML5アプリケーションでの、サーバとクライアントの時差について 2014年3月17日
AngularJSと UI Bootstrapで日付と時刻の入力をする 2014年3月14日

お勧めカテゴリ

英語でアニメ観ようず
なじみ深い日本製アニメの英語版DVDで、字幕と音声から英語を学びましょうという趣旨のシリーズ記事です。
ScalaのようでJavaだけど少しScalaなJSON API
Scalaと Spring Frameworkを使って REST的なJSON APIを実装してみましょう。
ドクジリアン柔術少女 すから☆ぱいそん
代表 嶋田大貴のブログです。写真は神仏に見せ金をはたらく罰当たりの図