Skip to content
Browse files

added jsonrpc

  • Loading branch information...
1 parent 31390a7 commit e8d431ab58d6ed5a0381ef302883fad03fb1c1e2 @abhay abhay committed May 29, 2008
Showing with 126 additions and 14 deletions.
  1. +106 −0 src/jsonrpc.erl
  2. +1 −0 src/rpc_server.app
  3. +19 −14 src/rpc_server_web.erl
View
106 src/jsonrpc.erl
@@ -0,0 +1,106 @@
+%% @author Abhay Kumar <abhay@opensynapse.net>
+%% @copyright 2008 Abhay Kumar
+
+-module(jsonrpc).
+-author("Abhay Kumar <abhay@opensynapse.net>").
+
+-export([handler/2]).
+-export([test/0]).
+
+handler(Req, ModFun) ->
+ case acceptable_request(Req) of
+ ok ->
+ handle_request(Req, ModFun);
+ {status, StatusCode, Reason} ->
+ send(Req, StatusCode, encode_error(Reason, null))
+ end.
+
+test() ->
+ test_acceptable_request(),
+ test_decode_request_body(),
+ ok.
+
+acceptable_request(Req) ->
+ case {Req:get(method), Req:get(version)} of
+ {'POST', {1, 0}} -> ok;
+ {'POST', {1, 1}} -> ok;
+ {'POST', HTTPVersion} -> {status, 505, lists:flatten(io_lib:format("HTTP Version ~p is not supported.", [HTTPVersion]))};
+ {Method, {1, 1}} -> {status, 501, lists:flatten(io_lib:format("The ~p method has not been implemented.", [Method]))};
+ _ -> {status, 400, "Bad Request."}
+ end.
+
+handle_request(Req, {Mod, Fun}) ->
+ Body = binary_to_list(Req:recv_body()),
+ case decode_request_body(Body) of
+ {ok, Decoded, ID} ->
+ case Mod:Fun(Req, Decoded) of
+ {'EXIT', Reason} ->
+ send(Req, 500, encode_error(Reason, ID));
+ {error, Reason} ->
+ send(Req, 500, encode_error(Reason, ID));
+ {result, Result} ->
+ send(Req, 200, encode_result(Result, ID))
+ end;
+ {error, Reason} ->
+ send(Req, 500, encode_error(Reason, null))
+ end.
+
+decode_request_body(Body) ->
+ try
+ JsonObj = mochijson:decode(Body),
+ {ok, {call, list_to_atom(fetch(JsonObj, method)), fetch(JsonObj, params)}, fetch(JsonObj, id)}
+ catch
+ _:_ -> {error, "Error decoding request."}
+ end.
+
+encode_error(Reason, ID) -> lists:flatten(mochijson:encode({struct, [{id, ID}, {error, Reason}, {result, null}]})).
+
+encode_result(Result, ID) ->
+ try
+ lists:flatten(mochijson:encode({struct, [{id, ID}, {error, null}, {result, Result}]}))
+ catch
+ _:_ -> encode_error("Error encoding response.", ID)
+ end.
+
+send(Req, StatusCode, JsonStr) ->
+ Req:respond({StatusCode, [{'Content-Type', "application/json"}], JsonStr}).
+
+fetch(JsonObj, Key) when is_atom(Key) ->
+ fetch(JsonObj, atom_to_list(Key));
+fetch({struct, List}, Key) when is_list(List) ->
+ case lists:keysearch(Key, 1, List) of
+ {value, {Key, Value}} -> Value;
+ _ -> []
+ end.
+
+test_acceptable_request() ->
+ test_each_acceptable_request(tuples_for_test_acceptable_request()).
+
+test_each_acceptable_request([]) -> ok;
+test_each_acceptable_request([{ShouldReceive, WillAsk}|Rest]) ->
+ true = ShouldReceive == acceptable_request(mochiweb:new_request(WillAsk)),
+ test_each_acceptable_request(Rest).
+
+tuples_for_test_acceptable_request() ->
+ [
+ {ok, {foo, {'POST', {abs_path, "/"}, {1,0}}, [{'Content-Type', "application/json"}]}},
+ {ok, {foo, {'POST', {abs_path, "/"}, {1,1}}, [{'Content-Type', "application/json"}]}},
+ {{status, 505, "HTTP Version {5,0} is not supported."}, {foo, {'POST', {abs_path, "/"}, {5,0}}, [{'Content-Type', "application/json"}]}},
+ {{status, 501, "The 'PUT' method has not been implemented."}, {foo, {'PUT', {abs_path, "/"}, {1, 1}}, [{'Content-Type', "application/json"}]}},
+ {{status, 400, "Bad Request."}, {foo, {'PUT', {abs_path, "/"}, {5,0}}, [{'Content-Type', "application/json"}]}}
+ ].
+
+test_decode_request_body() ->
+ test_decode_request_good(),
+ test_decode_request_bad(),
+ ok.
+
+test_decode_request_good() ->
+ GoodJsonStr = "{\"id\":\"foobarbaz\",\"method\":\"foo\",\"params\":{\"bar\":{\"baz\":[1,2,3]}}}",
+ {ok, {call, foo, {struct, [{"bar", {struct, [{"baz", {array, [1, 2, 3]}}]}}]}}, "foobarbaz"} = decode_request_body(GoodJsonStr),
+ ok.
+
+test_decode_request_bad() ->
+ BadJsonStr = "{\"id\":\"foobarbaz\"params\":{\"bar\":\"baz\"}}",
+ {error, "Error decoding request."} = decode_request_body(BadJsonStr),
+ ok.
View
1 src/rpc_server.app
@@ -2,6 +2,7 @@
[{description, "rpc_server"},
{vsn, "0.01"},
{modules, [
+ jsonrpc,
rpc_server,
rpc_server_app,
rpc_server_sup,
View
33 src/rpc_server_web.erl
@@ -7,7 +7,7 @@
-author("Abhay Kumar <abhay@opensynapse.net>").
-export([start/1, stop/0, loop/2]).
-
+-export([rpc_handler/2]).
%% External API
start(Options) ->
@@ -22,22 +22,27 @@ stop() ->
loop(Req, DocRoot) ->
"/" ++ Path = Req:get(path),
- case Req:get(method) of
- Method when Method =:= 'GET'; Method =:= 'HEAD' ->
- case Path of
- _ ->
- Req:serve_file(Path, DocRoot)
- end;
- 'POST' ->
- case Path of
- _ ->
- Req:not_found()
- end;
- _ ->
- Req:respond({501, [], []})
+ case {Path, Req:get(method), Req:get_header_value('Content-Type')} of
+ {"rpc", 'POST', "application/json"} ->
+ jsonrpc:handler(Req, {?MODULE, rpc_handler});
+ {_, Method, _} when Method =:= 'GET'; Method =:= 'HEAD' ->
+ Req:serve_file(Path, DocRoot);
+ {_, 'POST', _} ->
+ Req:not_found();
+ {_, _, _} ->
+ Req:respond({501, [], []})
end.
%% Internal API
+rpc_handler(_Req, {call, Method, Params}) ->
+ case Method of
+ echo ->
+ {array, [Message]} = Params,
+ {result, Message};
+ _ ->
+ {error, "method not implemented."}
+ end.
+
get_option(Option, Options) ->
{proplists:get_value(Option, Options), proplists:delete(Option, Options)}.

0 comments on commit e8d431a

Please sign in to comment.
Something went wrong with that request. Please try again.