summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2011-10-17 13:06:52 +0200
committerLoïc Hoguin <[email protected]>2011-10-17 13:08:13 +0200
commiteab4a7b8dcc0a20cdc0fa4bb4bff8f4549d4c6f0 (patch)
treef40a36f646790933265ab291d78cd3831fdca335 /src
downloadbullet-eab4a7b8dcc0a20cdc0fa4bb4bff8f4549d4c6f0.tar.gz
bullet-eab4a7b8dcc0a20cdc0fa4bb4bff8f4549d4c6f0.tar.bz2
bullet-eab4a7b8dcc0a20cdc0fa4bb4bff8f4549d4c6f0.zip
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.
Diffstat (limited to 'src')
-rw-r--r--src/bullet.app.src26
-rw-r--r--src/bullet_handler.erl140
2 files changed, 166 insertions, 0 deletions
diff --git a/src/bullet.app.src b/src/bullet.app.src
new file mode 100644
index 0000000..bb72c7b
--- /dev/null
+++ b/src/bullet.app.src
@@ -0,0 +1,26 @@
+%% Copyright (c) 2011, Loïc Hoguin <[email protected]>
+%%
+%% 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.
+
+{application, bullet, [
+ {description,
+ "Simple, reliable, efficient streaming between JS and Cowboy."},
+ {vsn, "0.1.0"},
+ {modules, []},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib,
+ cowboy
+ ]}
+]}.
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 <[email protected]>
+%%
+%% 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).