From eab4a7b8dcc0a20cdc0fa4bb4bff8f4549d4c6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 17 Oct 2011 13:06:52 +0200 Subject: Initial commit This is mostly a proof of concept. The client-side can be greatly improved (and we should probably take from other projects directly). The server-side is pretty much how it should be though. --- src/bullet_handler.erl | 140 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 src/bullet_handler.erl (limited to 'src/bullet_handler.erl') diff --git a/src/bullet_handler.erl b/src/bullet_handler.erl new file mode 100644 index 0000000..eb34da7 --- /dev/null +++ b/src/bullet_handler.erl @@ -0,0 +1,140 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(bullet_handler). + +-behaviour(cowboy_http_handler). +-export([init/3, handle/2, info/3, terminate/2]). + +-behaviour(cowboy_http_websocket_handler). +-export([websocket_init/3, websocket_handle/3, + websocket_info/3, websocket_terminate/3]). + +-record(state, { + handler :: module(), + handler_state :: term() +}). + +-define(TIMEOUT, 60000). %% @todo Configurable. + +%% HTTP. + +init(Transport, Req, Opts) -> + case cowboy_http_req:header('Upgrade', Req) of + {undefined, Req2} -> + {Method, Req3} = cowboy_http_req:method(Req2), + init(Transport, Req3, Opts, Method); + {Bin, Req2} when is_binary(Bin) -> + case cowboy_bstr:to_lower(Bin) of + <<"websocket">> -> + {upgrade, protocol, cowboy_http_websocket}; + _Any -> + {ok, Req3} = cowboy_http_req:reply(501, [], [], Req2), + {shutdown, Req3, undefined} + end + end. + +init(Transport, Req, Opts, 'GET') -> + {handler, Handler} = lists:keyfind(handler, 1, Opts), + State = #state{handler=Handler}, + case Handler:init(Transport, Req, Opts, once) of + {ok, Req2, HandlerState} -> + Req3 = cowboy_http_req:compact(Req2), + {loop, Req3, State#state{handler_state=HandlerState}, + ?TIMEOUT, hibernate}; + {shutdown, Req2, HandlerState} -> + {shutdown, Req2, State#state{handler_state=HandlerState}} + end; +init(Transport, Req, Opts, 'POST') -> + {handler, Handler} = lists:keyfind(handler, 1, Opts), + State = #state{handler=Handler}, + case Handler:init(Transport, Req, Opts, false) of + {ok, Req2, HandlerState} -> + {ok, Req2, State#state{handler_state=HandlerState}}; + {shutdown, Req2, HandlerState} -> + {shutdown, Req2, State#state{handler_state=HandlerState}} + end; +init(_Transport, Req, _Opts, _Method) -> + {ok, Req2} = cowboy_http_req:reply(405, [], [], Req), + {shutdown, Req2, undefined}. + +handle(Req, State) -> + {Method, Req2} = cowboy_http_req:method(Req), + handle(Req2, State, Method). + +handle(_Req, _State, 'GET') -> + exit(badarg); +handle(Req, State=#state{handler=Handler, handler_state=HandlerState}, + 'POST') -> + {ok, Data, Req2} = cowboy_http_req:body(Req), + case Handler:stream(Data, Req2, HandlerState) of + {ok, Req3, HandlerState2} -> + {ok, Req3, State#state{handler_state=HandlerState2}}; + {reply, Reply, Req3, HandlerState2} -> + {ok, Req4} = cowboy_http_req:reply(200, [], Reply, Req3), + {ok, Req4, State#state{handler_state=HandlerState2}} + end. + +info(Message, Req, + State=#state{handler=Handler, handler_state=HandlerState}) -> + case Handler:info(Message, Req, HandlerState) of + {ok, Req2, HandlerState2} -> + {loop, Req2, State#state{handler_state=HandlerState2}, hibernate}; + {reply, Data, Req2, HandlerState2} -> + {ok, Req3} = cowboy_http_req:reply(200, [], Data, Req2), + {ok, Req3, State#state{handler_state=HandlerState2}} + end. + +terminate(Req, #state{handler=Handler, handler_state=HandlerState}) -> + Handler:terminate(Req, HandlerState). + +%% Websocket. + +websocket_init(Transport, Req, Opts) -> + {handler, Handler} = lists:keyfind(handler, 1, Opts), + State = #state{handler=Handler}, + case Handler:init(Transport, Req, Opts, true) of + {ok, Req2, HandlerState} -> + Req3 = cowboy_http_req:compact(Req2), + {ok, Req3, State#state{handler_state=HandlerState}, + ?TIMEOUT, hibernate}; + {shutdown, Req2, _HandlerState} -> + {shutdown, Req2} + end. + +websocket_handle({text, Data}, Req, + State=#state{handler=Handler, handler_state=HandlerState}) -> + case Handler:stream(Data, Req, HandlerState) of + {ok, Req2, HandlerState2} -> + {ok, Req2, State#state{handler_state=HandlerState2}, hibernate}; + {reply, Reply, Req2, HandlerState2} -> + {reply, {text, Reply}, Req2, + State#state{handler_state=HandlerState2}, hibernate} + end; +websocket_handle(_Frame, Req, State) -> + {ok, Req, State, hibernate}. + +websocket_info(Info, Req, State=#state{ + handler=Handler, handler_state=HandlerState}) -> + case Handler:info(Info, Req, HandlerState) of + {ok, Req2, HandlerState2} -> + {ok, Req2, State#state{handler_state=HandlerState2}, hibernate}; + {reply, Reply, Req2, HandlerState2} -> + {reply, {text, Reply}, Req2, + State#state{handler_state=HandlerState2}, hibernate} + end. + +websocket_terminate(_Reason, Req, + #state{handler=Handler, handler_state=HandlerState}) -> + Handler:terminate(Req, HandlerState). -- cgit v1.2.3