summaryrefslogtreecommitdiff
path: root/deps/mochiweb/examples/keepalive/keepalive.erl
diff options
context:
space:
mode:
Diffstat (limited to 'deps/mochiweb/examples/keepalive/keepalive.erl')
-rw-r--r--deps/mochiweb/examples/keepalive/keepalive.erl81
1 files changed, 81 insertions, 0 deletions
diff --git a/deps/mochiweb/examples/keepalive/keepalive.erl b/deps/mochiweb/examples/keepalive/keepalive.erl
new file mode 100644
index 00000000..965a17eb
--- /dev/null
+++ b/deps/mochiweb/examples/keepalive/keepalive.erl
@@ -0,0 +1,81 @@
+-module(keepalive).
+
+%% your web app can push data to clients using a technique called comet long
+%% polling. browsers make a request and your server waits to send a
+%% response until data is available. see wikipedia for a better explanation:
+%% http://en.wikipedia.org/wiki/Comet_(programming)#Ajax_with_long_polling
+%%
+%% since the majority of your http handlers will be idle at any given moment,
+%% you might consider making them hibernate while they wait for more data from
+%% another process. however, since the execution stack is discarded when a
+%% process hibernates, the handler would usually terminate after your response
+%% code runs. this means http keep alives wouldn't work; the handler process
+%% would terminate after each response and close its socket rather than
+%% returning to the big @mochiweb_http@ loop and processing another request.
+%%
+%% however, if mochiweb exposes a continuation that encapsulates the return to
+%% the top of the big loop in @mochiweb_http@, we can call that after the
+%% response. if you do that then control flow returns to the proper place,
+%% and keep alives work like they would if you hadn't hibernated.
+
+-export([ start/1, loop/1
+ ]).
+
+%% internal export (so hibernate can reach it)
+-export([ resume/3
+ ]).
+
+-define(LOOP, {?MODULE, loop}).
+
+start(Options = [{port, _Port}]) ->
+ mochiweb_http:start([{name, ?MODULE}, {loop, ?LOOP} | Options]).
+
+loop(Req) ->
+ Path = Req:get(path),
+ case string:tokens(Path, "/") of
+ ["longpoll" | RestOfPath] ->
+ %% the "reentry" is a continuation -- what @mochiweb_http@
+ %% needs to do to start its loop back at the top
+ Reentry = mochiweb_http:reentry(?LOOP),
+
+ %% here we could send a message to some other process and hope
+ %% to get an interesting message back after a while. for
+ %% simplicity let's just send ourselves a message after a few
+ %% seconds
+ erlang:send_after(2000, self(), "honk honk"),
+
+ %% since we expect to wait for a long time before getting a
+ %% reply, let's hibernate. memory usage will be minimized, so
+ %% we won't be wasting memory just sitting in a @receive@
+ proc_lib:hibernate(?MODULE, resume, [Req, RestOfPath, Reentry]),
+
+ %% we'll never reach this point, and this function @loop/1@
+ %% won't ever return control to @mochiweb_http@. luckily
+ %% @resume/3@ will take care of that.
+ io:format("not gonna happen~n", []);
+
+ _ ->
+ ok(Req, io_lib:format("some other page: ~p", [Path]))
+ end,
+
+ io:format("restarting loop normally in ~p~n", [Path]),
+ ok.
+
+%% this is the function that's called when a message arrives.
+resume(Req, RestOfPath, Reentry) ->
+ receive
+ Msg ->
+ Text = io_lib:format("wake up message: ~p~nrest of path: ~p", [Msg, RestOfPath]),
+ ok(Req, Text)
+ end,
+
+ %% if we didn't call @Reentry@ here then the function would finish and the
+ %% process would exit. calling @Reentry@ takes care of returning control
+ %% to @mochiweb_http@
+ io:format("reentering loop via continuation in ~p~n", [Req:get(path)]),
+ Reentry(Req).
+
+ok(Req, Response) ->
+ Req:ok({_ContentType = "text/plain",
+ _Headers = [],
+ Response}).