webmachineとAJAX
はじめに
2014年5月30日現在、アプリケーションの開発で、バックエンドにwebmachine、フロントエンドにAngularJSを選択しようと考えています。その2つのやりとりでCORSの問題が起きるので、回避方法をメモします。主にやることは、webmachine側のAccess-Control-Allow-OriginのHeaderの追加と、許可するContent-typeなどの設定になります。
webmachineのResourse Functions API
webmachineのresouceに対する処理は基本的にRESTになるように設計がされています。あるresourceに対して許可するメソッドを設定し、各メソッドの処理を記述していきます。resourceの処理を記述した一例を以下に載せます。
.src/appname_ebooks_resource.erl
-module(appname_ebooks_resource). -export([ init/1, allowed_methods/2, content_types_accepted/2, content_types_provided/2, post_is_create/2, create_path/2, to_json/2, create_resource/2, options/2, delete_resource/2 ]). -include_lib("webmachine/include/webmachine.hrl"). -include_lib("eunit/include/eunit.hrl"). init([]) -> {ok, undefined}. %%%================================================== %%% Setting of OPTIONS method (add headers to ReqData) %%%================================================== options(Req, State) -> {[ {"Access-Control-Allow-Origin","*"}, {"Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"} ], Req, State}. %%%================================================== %%% Allow Methods and Accepted Content Types %%%================================================== %% Allowed HTTP methods allowed_methods(Req, State) -> {['GET','POST','PUT','DELETE','OPTIONS'], Req, State}. %% Accepted content type content_types_accepted(Req, State) -> {[ {"application/json", create_resource} ], Req, State}. %% Privoided content type content_types_provided(Req,State) -> {[ {"html/text", to_json}, {"application/json", create_resource} ], Req, State}. %%%================================================== %%% Post settings %%%================================================== %% creating a new resource from post request post_is_create(Req, State) -> {true, Req, State}. %% this function is called when post_is_create/2 returns true. %% return new resouce path( embeded 'Location' header). create_path(Req,State) -> {"apis/ebooks", Req, State}. %%%================================================== %%% Handler functions %%%================================================== %% GET to_json(#wm_reqdata{method = 'GET'} = Req, State) -> % TODO: Get process io:format("Request Data: ~n ~p~n",[wrq:req_qs(Req)]), {<<"{\"json\":\"get test.\"}">>, Req, State}. %% POST create_resource(#wm_reqdata{method = 'POST'} = Req, State) -> % TODO: create process Req2 = wrq:set_resp_headers([ {"Access-Control-Allow-Origin", "*"} ], Req), case mochijson2:decode(wrq:req_body(Req2)) of {struct, Params} -> io:format("json put request(method:~p).~n", [Params]), EbookTitle = proplists:get_value(<<"title">>, Params), EbookNum = proplists:get_value(<<"number">>, Params), EbookDesc = proplists:get_value(<<"desc">>, Params), EbookPath = proplists:get_value(<<"path">>, Params), io:format("PostData: ~n title:~p~n number:~p~n desc:~p~n, book_path:~p~n", [EbookTitle,EbookNum,EbookDesc,EbookPath]), {true, Req2, State}; _Other -> {false, Req2, State} end; %% PUT create_resource(#wm_reqdata{method = 'PUT'} = Req, State) -> % TODO: Update process io:format("json put request(method:~p).~n", [Req#wm_reqdata.method]), {true, Req, State}. %% DELETE delete_resource(Req, State) -> % TODO: Delete process io:format("delete request(method:~p).~n", [Req#wm_reqdata.method]), {true, Req, State}.
ちなみに、上記の内容はErlang製Webツールキットwebmachine 触ってみた - ごろねこ日記を参考にしました。
必要な箇所だけ、順に説明します。
options(Req, State)
optionsは、OPTIONSメソッドのリクエストに対して、追加設定を記述できるようになります。この機能はwebmachineが提供してくれるResource FunctionsのAPIの一つです(詳しくはResource Functions · basho/webmachine Wiki · GitHub)。
%%%================================================== %%% Setting of OPTIONS method (add headers to ReqData) %%%================================================== options(Req, State) -> {[ {"Access-Control-Allow-Origin","*"}, {"Access-Control-Allow-Headers", "Content-Type, Accept"} ], Req, State}.
内容は、Ajax等でXMLHttpRequestを送られたときに、Access-Control-Allow-OriginのHeaderを追加しクロスドメインの制約を緩めます。また、postする際にContent-typeを指定する場合、OPTIONSメソッドに許可するHeaderを明記する必要がある。書き方は、Access-Control-Allow-Headersとして記述する。この2つのHeaderを明記しないとアクセスができません。前者はクロスドメイン制約によるアクセス拒否、後者は受け入れ許可していないHeaderの混入によるアクセス拒否です。
create_resource(Req, State)
AJAX通信で受け取った内容を処理していきます。ここで注意することは、結果を返すときにもresponseとなる内容にクロスドメイン制約に関するHeaderを設定する必要があることです。以下に、ソースを載せます。
%% POST create_resource(#wm_reqdata{method = 'POST'} = Req, State) -> % TODO: create process Req2 = wrq:set_resp_headers([ {"Access-Control-Allow-Origin", "*"} ], Req), case mochijson2:decode(wrq:req_body(Req2)) of {struct, Params} -> io:format("json put request(method:~p).~n", [Params]), EbookTitle = proplists:get_value(<<"title">>, Params), EbookNum = proplists:get_value(<<"number">>, Params), EbookDesc = proplists:get_value(<<"desc">>, Params), EbookPath = proplists:get_value(<<"path">>, Params), io:format("PostData: ~n title:~p~n number:~p~n desc:~p~n, book_path:~p~n", [EbookTitle,EbookNum,EbookDesc,EbookPath]), {true, Req2, State}; _Other -> {false, Req2, State} end;
4行目の、wrq:set_resp_headers/2でAccess-Control-Allow-OriginのHeaderを追加します。wrqは、webmachineのRequest Data APIと呼ばれるもので、詳細はRequest Data API · basho/webmachine Wiki · GitHubに載っています。
Access-Control-Allow-Originを追加したReqを変数Req2に束縛し、return値のReqDataに渡します({true, Req2, State}の部分)。
まとめと今後の課題
今回は、webmachineとajax通信する場合に起きるクロスドメイン制約の問題がありました。そして、このクロスドメイン制約を解消するための手順について説明しました。具体的なやり方は、1)OPTIONSメソッドで通信された時にレスポンス(既存のRequestデータ)にAccess-Control-Allow-OriginのHeaderを追加、2)メソッド処理の内部でも同様にAccess-Control-Allow-OriginのHeaderを追加、3)通信する際に特別Headerを追加する際にはOPTIONSメソッド通信の設定でAccess-Control-Allow-Headersに明記、の3点です。
今後は、resource部分の処理を詳細に書いて、テストを書き終わった後に、フロントエンドとの連携をしていきます。
余談
OPTIONSメソッドに対して新しくHeaderを追加していく方法が全然わからなかったところ、erlang - Enabling CORS for Cowboy REST API - Stack Overflowを見つけて、紐解きました。
参考URL
- Erlang製Webツールキットwebmachine 触ってみた - ごろねこ日記
- Resource Functions · basho/webmachine Wiki · GitHub
- Request Data API · basho/webmachine Wiki · GitHub
- erlang - Enabling CORS for Cowboy REST API - Stack Overflow
- 作者: Joe Armstrong,榊原一矢
- 出版社/メーカー: オーム社
- 発売日: 2008/02/23
- メディア: 単行本(ソフトカバー)
- 購入: 8人 クリック: 284回
- この商品を含むブログ (97件) を見る