| -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([loop/1, start/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 = mochiweb_request:get(path, Req), |
| 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", |
| [mochiweb_request:get(path, Req)]), |
| Reentry(Req). |
| |
| ok(Req, Response) -> |
| mochiweb_request:ok({_ContentType = "text/plain", |
| _Headers = [], Response}, |
| Req). |