/examples/keepalive/keepalive.erl
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}).