From 89dc8b988c9eb3abeeb836180f19efb851067496 Mon Sep 17 00:00:00 2001 From: Erik Timan Date: Fri, 15 Jun 2012 16:54:08 +0200 Subject: [PATCH] Add support for Cowboy. Also updates README with info regarding Cowboy. --- README.md | 8 +++ ebin/rfc4627_jsonrpc.app | 1 + src/rfc4627_jsonrpc_cowboy.erl | 113 +++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 src/rfc4627_jsonrpc_cowboy.erl diff --git a/README.md b/README.md index 55c46f6..9af123b 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,13 @@ See to learn how to delegate incoming Mochiweb HTTP requests to the JSON-RPC service dispatcher. +### Using Cowboy + +See +[rfc4627\_jsonrpc\_cowboy.erl](http://tonyg.github.com/erlang-rfc4627/doc/rfc4627_jsonrpc_cowboy.html) +to learn how to delegate incoming Cowboy HTTP requests to the +JSON-RPC service dispatcher. + ## Running the example test service that comes with the source code Included with the Erlang RFC4627 source code is a small Inets-based @@ -88,6 +95,7 @@ contributions from others: - Eugene Volchek - Simon MacMullen - Andrey Khozov + - Erik Timan ## Copyright and Licence diff --git a/ebin/rfc4627_jsonrpc.app b/ebin/rfc4627_jsonrpc.app index e06b46c..ef9fe44 100644 --- a/ebin/rfc4627_jsonrpc.app +++ b/ebin/rfc4627_jsonrpc.app @@ -9,6 +9,7 @@ rfc4627_jsonrpc_http, rfc4627_jsonrpc_inets, rfc4627_jsonrpc_mochiweb, + rfc4627_jsonrpc_cowboy, rfc4627_jsonrpc_registry ]}, {registered, []}, diff --git a/src/rfc4627_jsonrpc_cowboy.erl b/src/rfc4627_jsonrpc_cowboy.erl new file mode 100644 index 0000000..0bdc2f4 --- /dev/null +++ b/src/rfc4627_jsonrpc_cowboy.erl @@ -0,0 +1,113 @@ +%% JSON-RPC for Cowboy +%%--------------------------------------------------------------------------- +%% @author Erik Timan +%% @author Tony Garnock-Jones +%% @author LShift Ltd. +%% @copyright 2007-2010, 2011, 2012 Tony Garnock-Jones and 2007-2010 LShift Ltd. +%% @license +%% +%% Permission is hereby granted, free of charge, to any person +%% obtaining a copy of this software and associated documentation +%% files (the "Software"), to deal in the Software without +%% restriction, including without limitation the rights to use, copy, +%% modify, merge, publish, distribute, sublicense, and/or sell copies +%% of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be +%% included in all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%--------------------------------------------------------------------------- +%% +%% @reference the Cowboy github page +%% +%% @doc Support for serving JSON-RPC via Cowboy. +%% +%% Familiarity with writing Cowboy applications is assumed. +%% +%% == Basic Usage == +%% +%%
    +%%
  • Register your JSON-RPC services as usual.
  • +%%
  • Decide on your `AliasPrefix' (see {@link rfc4627_jsonrpc_http:invoke_service_method/4}).
  • +%%
  • When a Cowboy request arrives at your application, call {@link handle/2} with your `AliasPrefix' and the request.
  • +%%
+%% +%% It's as simple as that - if the request's URI path matches the +%% `AliasPrefix', it will be decoded and the JSON-RPC service it names +%% will be invoked. + +-module(rfc4627_jsonrpc_cowboy). + +-export([handle/2]). + +normalize(X) when is_atom(X) -> + string:to_lower(atom_to_list(X)); +normalize(X) when is_binary(X) -> + string:to_lower(binary_to_list(X)); +normalize(X) when is_list(X) -> + string:to_lower(X). + +%% @spec (string(), #http_req{}) -> no_match | {ok, #http_req{}} +%% +%% @doc If the request matches `AliasPrefix', the corresponding +%% JSON-RPC service is invoked, and an `{ok, #http_req{}}' is returned; +%% otherwise, `no_match' is returned. +%% +%% Call this function from your Cowboy HTTP handler's `handle' +%% function, as follows: +%% +%% ``` +%% Req2 = case rfc4627_jsonrpc_cowboy:handle("/rpc", Req) of +%% no_match -> +%% handle_non_jsonrpc_request(Req); +%% {ok, Reponse} -> +%% Response +%% end +%% ''' +%% +%% where `handle_non_jsonrpc_request' does the obvious thing for +%% non-JSON-RPC requests. +handle(AliasPrefix, Req) -> + {BinaryPath, _} = cowboy_http_req:raw_path(Req), + Path = binary_to_list(BinaryPath), + {QSVals, _} = cowboy_http_req:qs_vals(Req), + QueryObj = {obj, [{binary_to_list(K), V} || {K,V} <- QSVals]}, + {Hdrs, _} = cowboy_http_req:headers(Req), + HeaderObj = {obj, [{normalize(K), V} || {K,V} <- Hdrs]}, + {PeerAddr, _} = cowboy_http_req:peer_addr(Req), + Peer = list_to_binary(inet_parse:ntoa(PeerAddr)), + {Method, _} = cowboy_http_req:method(Req), + RequestInfo = {obj, [{"http_method", list_to_binary(atom_to_list(Method))}, + {"http_query_parameters", QueryObj}, + {"http_headers", HeaderObj}, + {"remote_peername", Peer}, + {"scheme", <<"http">>}]}, + {ok, Body, _} = cowboy_http_req:body(Req), + + case rfc4627_jsonrpc_http:invoke_service_method(AliasPrefix, + Path, + RequestInfo, + Body) of + no_match -> + no_match; + {ok, ResultEnc, ResponseInfo} -> + {obj, ResponseHeaderFields} = + rfc4627:get_field(ResponseInfo, "http_headers", {obj, []}), + StatusCode = + rfc4627:get_field(ResponseInfo, "http_status_code", 200), + Headers = [{list_to_binary(K), V} || {K,V} <- ResponseHeaderFields], + RespType = [{<<"Content-Type">>, rfc4627:mime_type()}], + cowboy_http_req:reply(StatusCode, + Headers ++ RespType, + ResultEnc, + Req) + end.