summaryrefslogtreecommitdiff
path: root/deps/mochiweb/examples/keepalive/keepalive.erl
blob: 965a17eba495ae406a2297742b46da0bb0ff39ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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}).