PageRenderTime 123ms CodeModel.GetById 60ms app.highlight 7ms RepoModel.GetById 54ms app.codeStats 0ms

/examples/keepalive/keepalive.erl

http://github.com/basho/mochiweb
Erlang | 81 lines | 33 code | 14 blank | 34 comment | 0 complexity | 9a69f47aa85b971305d40ee1486069d4 MD5 | raw file
 1-module(keepalive).
 2
 3%% your web app can push data to clients using a technique called comet long
 4%% polling.  browsers make a request and your server waits to send a
 5%% response until data is available.  see wikipedia for a better explanation:
 6%% http://en.wikipedia.org/wiki/Comet_(programming)#Ajax_with_long_polling
 7%%
 8%% since the majority of your http handlers will be idle at any given moment,
 9%% you might consider making them hibernate while they wait for more data from
10%% another process.  however, since the execution stack is discarded when a
11%% process hibernates, the handler would usually terminate after your response
12%% code runs.  this means http keep alives wouldn't work; the handler process
13%% would terminate after each response and close its socket rather than
14%% returning to the big @mochiweb_http@ loop and processing another request.
15%%
16%% however, if mochiweb exposes a continuation that encapsulates the return to
17%% the top of the big loop in @mochiweb_http@, we can call that after the
18%% response.  if you do that then control flow returns to the proper place,
19%% and keep alives work like they would if you hadn't hibernated.
20
21-export([ start/1, loop/1
22        ]).
23
24%% internal export (so hibernate can reach it)
25-export([ resume/3
26        ]).
27
28-define(LOOP, {?MODULE, loop}).
29
30start(Options = [{port, _Port}]) ->
31    mochiweb_http:start([{name, ?MODULE}, {loop, ?LOOP} | Options]).
32
33loop(Req) ->
34    Path = Req:get(path),
35    case string:tokens(Path, "/") of
36        ["longpoll" | RestOfPath] ->
37            %% the "reentry" is a continuation -- what @mochiweb_http@
38            %% needs to do to start its loop back at the top
39            Reentry = mochiweb_http:reentry(?LOOP),
40
41            %% here we could send a message to some other process and hope
42            %% to get an interesting message back after a while.  for
43            %% simplicity let's just send ourselves a message after a few
44            %% seconds
45            erlang:send_after(2000, self(), "honk honk"),
46
47            %% since we expect to wait for a long time before getting a
48            %% reply, let's hibernate.  memory usage will be minimized, so
49            %% we won't be wasting memory just sitting in a @receive@
50            proc_lib:hibernate(?MODULE, resume, [Req, RestOfPath, Reentry]),
51
52            %% we'll never reach this point, and this function @loop/1@
53            %% won't ever return control to @mochiweb_http@.  luckily
54            %% @resume/3@ will take care of that.
55            io:format("not gonna happen~n", []);
56
57        _ ->
58            ok(Req, io_lib:format("some other page: ~p", [Path]))
59    end,
60
61    io:format("restarting loop normally in ~p~n", [Path]),
62    ok.
63
64%% this is the function that's called when a message arrives.
65resume(Req, RestOfPath, Reentry) ->
66    receive
67        Msg ->
68            Text = io_lib:format("wake up message: ~p~nrest of path: ~p", [Msg, RestOfPath]),
69            ok(Req, Text)
70    end,
71
72    %% if we didn't call @Reentry@ here then the function would finish and the
73    %% process would exit.  calling @Reentry@ takes care of returning control
74    %% to @mochiweb_http@
75    io:format("reentering loop via continuation in ~p~n", [Req:get(path)]),
76    Reentry(Req).
77
78ok(Req, Response) ->
79    Req:ok({_ContentType = "text/plain",
80            _Headers = [],
81            Response}).