Permalink
Browse files

add a configurable dispatch module

Allow an application to supply its own dispatch module for a server by
setting the new dispatchmod variable in the server config. The module is
expected to export a dispatch/1 function that returns one of the following
atoms:

* done: this indicates the dispatch module has handled the request and sent
  the response, and Yaws should go look for a new request on this
  connection

* closed: same as "done" but also indicates the dispatch module has closed
  the connection

* continue: this tells Yaws to continue with the normal dispatch path
  • Loading branch information...
1 parent 1e7a122 commit 0c86ddcdff726d2400bdf4c31068758f49bb11e3 @vinoski vinoski committed Sep 25, 2012
Showing with 146 additions and 29 deletions.
  1. +24 −0 doc/yaws.tex
  2. +2 −1 include/yaws.hrl
  3. +26 −0 man/yaws.conf.5
  4. +3 −1 src/yaws.erl
  5. +4 −0 src/yaws_config.erl
  6. +49 −26 src/yaws_server.erl
  7. +7 −0 test/conf/stdconf.conf
  8. +1 −1 test/t2/Makefile
  9. +13 −0 test/t2/app_test.erl
  10. +17 −0 test/t2/dispatchmod_tester.erl
View
@@ -2692,6 +2692,30 @@ \section{Server Part}
on everything except any file found in directories \verb+icons+,
\verb+js+ and \verb+top/static+ relative to the docroot.
+\item \verb+dispatchmod = DispatchModule+ ---
+ Set \verb+DispatchModule+ as a server-specific request
+ dispatching module. \Yaws\ expects \verb+DispatchModule+ to
+ export a \verb+dispatch/1+ function. When it receives a
+ request, \Yaws\ passes an \verb+#arg{}+ record to the dispatch
+ module's \verb+dispatch/1+ function, which returns one of the
+ following atom results:
+ \begin{itemize}
+ \item \verb+done+ --- this indicates the dispatch module
+ handled the request itself and already sent the response,
+ and \Yaws\ should resume watching for new requests on the
+ connection
+ \item \verb+closed+ --- same as \verb+done+ but the
+ \verb+DispatchModule+ also closed the connection
+ \item \verb+continue+ --- the dispatch module has decided
+ not to handle the request, and instead wants \Yaws\ to
+ perform its regular request dispatching
+ \end{itemize}
+ Note that when \verb+DispatchModule+ handles a request itself,
+ \Yaws\ does not support tracing, increment statistics
+ counters or allow traffic shaping for that request. It does
+ however still keep track of maximum keepalive uses on the
+ connection.
+
\item \verb+errormod_404 = Module+ ---
It is possible to set a special module that handles 404 Not Found
messages. The function\\ \verb+Module:out404(Arg, GC, SC)+ will be
View
@@ -242,7 +242,8 @@
php_handler = {cgi, "/usr/bin/php-cgi"},
shaper,
deflate_options,
- mime_types_info % if undefined, global config is used
+ mime_types_info, % if undefined, global config is used
+ dispatch_mod % custom dispatch module
}).
View
@@ -634,6 +634,32 @@ except any file found in directories, 'icons', 'js' and 'top/static' relative to
the docroot.
.TP
+\fBdispatchmod = DispatchModule\fR
+Set \fIDispatchModule\fR as a server-specific request dispatching
+module. Yaws expects \fIDispatchModule\fR to export a \fIdispatch/1\fR
+function. When it receives a request, Yaws passes an \fI#arg{}\fR record to
+the dispatch module's \fIdispatch/1\fR function, which returns one of the
+following atom results:
+
+.RS 12
+\fBdone\fR - this indicates the dispatch module handled the request itself
+and already sent the response, and Yaws should resume watching for new
+requests on the connection
+
+\fBclosed\fR - same as \fIdone\fR but the \fIDispatchModule\fR also closed
+the connection
+
+\fBcontinue\fR - the dispatch module has decided not to handle the request,
+and instead wants Yaws to perform its regular request dispatching
+.RE
+
+.IP
+Note that when \fIDispatchModule\fR handles a request itself, Yaws does not
+support tracing, increment statistics counters or allow traffic shaping for
+that request. It does however still keep track of maximum keepalive uses on
+the connection.
+
+.TP
\fBerrormod_404 = Module\fR
It is possible to set a special module that handles 404 Not Found messages. The
function \fIModule:out404(Arg, GC, SC)\fR will be invoked. The arguments are
View
@@ -286,7 +286,9 @@ setup_sconf(SL, SC) ->
deflate_options = lkup(deflate_options, SL,
SC#sconf.deflate_options),
mime_types_info = lkup(mime_types_info, SL,
- SC#sconf.mime_types_info)
+ SC#sconf.mime_types_info),
+ dispatch_mod = lkup(dispatchmod, SL,
+ SC#sconf.dispatch_mod)
}.
expand_auth(SL) ->
View
@@ -1220,6 +1220,10 @@ fload(FD, server, GC, C, Cs, Lno, Chars) ->
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
+ ["dispatchmod", '=', DispatchMod] ->
+ C2 = C#sconf{dispatch_mod = list_to_atom(DispatchMod)},
+ fload(FD, server, GC, C2, Cs, Lno+1, Next);
+
["expires", '=' | Expires] ->
case parse_expires(Expires, []) of
{ok, L} ->
View
@@ -1105,7 +1105,7 @@ acceptor0(GS, Top) ->
%%% Internal functions
%%%----------------------------------------------------------------------
-aloop(CliSock, {IP,Port}, GS, Num) ->
+aloop(CliSock, {IP,Port}=IPPort, GS, Num) ->
case yaws_trace:get_type(GS#gs.gconf) of
undefined ->
ok;
@@ -1135,24 +1135,45 @@ aloop(CliSock, {IP,Port}, GS, Num) ->
{Req, H} = fix_abs_uri(Req0, H0),
?Debug("{Req, H} = ~p~n", [{Req, H}]),
SC = pick_sconf(GS#gs.gconf, H, GS#gs.group),
- ?Debug("SC: ~s", [?format_record(SC, sconf)]),
- ?TC([{record, SC, sconf}]),
- ?Debug("Headers = ~s~n", [?format_record(H, headers)]),
- ?Debug("Request = ~s~n", [?format_record(Req, http_request)]),
- run_trace_filter(GS, IP, Req, H),
put(outh, #outh{}),
put(sc, SC),
- yaws_stats:hit(),
- check_keepalive_maxuses(GS, Num),
- Call = case yaws_shaper:check(SC, IP) of
- allow ->
- call_method(Req#http_request.method,CliSock,
- {IP,Port},Req,H);
- {deny, Status, Msg} ->
- deliver_xxx(CliSock, Req, Status, Msg)
- end,
- Call2 = fix_keepalive_maxuses(Call),
- handle_method_result(Call2, CliSock, {IP,Port}, GS, Req, H, Num);
+ DispatchResult = case SC#sconf.dispatch_mod of
+ undefined ->
+ continue;
+ DispatchMod ->
+ Arg = make_arg(SC, CliSock, IPPort, H, Req, undefined),
+ ok = inet:setopts(CliSock, [{packet, raw}, {active, false}]),
+ DispatchMod:dispatch(Arg)
+ end,
+ case DispatchResult of
+ done ->
+ erase_transients(),
+ case exceed_keepalive_maxuses(GS, Num) of
+ true -> {ok, Num+1};
+ false -> aloop(CliSock, IPPort, GS, Num+1)
+ end;
+ closed ->
+ %% Dispatcher closed the socket
+ erase_transients(),
+ {ok, Num+1};
+ continue ->
+ ?Debug("SC: ~s", [?format_record(SC, sconf)]),
+ ?TC([{record, SC, sconf}]),
+ ?Debug("Headers = ~s~n", [?format_record(H, headers)]),
+ ?Debug("Request = ~s~n", [?format_record(Req, http_request)]),
+ run_trace_filter(GS, IP, Req, H),
+ yaws_stats:hit(),
+ check_keepalive_maxuses(GS, Num),
+ Call = case yaws_shaper:check(SC, IP) of
+ allow ->
+ call_method(Req#http_request.method,CliSock,
+ IPPort,Req,H);
+ {deny, Status, Msg} ->
+ deliver_xxx(CliSock, Req, Status, Msg)
+ end,
+ Call2 = fix_keepalive_maxuses(Call),
+ handle_method_result(Call2, CliSock, IPPort, GS, Req, H, Num)
+ end;
closed ->
case yaws_trace:get_type(GS#gs.gconf) of
undefined -> ok;
@@ -1190,15 +1211,15 @@ run_trace_filter(GS, IP, Req, H) ->
%% process dictionary outh variable if required to say that the
%% connection has exceeded its maxuses.
check_keepalive_maxuses(GS, Num) ->
+ Flag = exceed_keepalive_maxuses(GS, Num),
+ put(outh, (get(outh))#outh{exceedmaxuses=Flag}).
+
+exceed_keepalive_maxuses(GS, Num) ->
case (GS#gs.gconf)#gconf.keepalive_maxuses of
- nolimit ->
- ok;
- 0 ->
- ok;
- N when Num+1 < N ->
- ok;
- _N ->
- put(outh, (get(outh))#outh{exceedmaxuses=true})
+ nolimit -> false;
+ 0 -> false;
+ N when Num+1 < N -> false;
+ _N -> true
end.
%% Change to Res to 'done' if we've exceeded our maxuses.
@@ -1207,7 +1228,7 @@ fix_keepalive_maxuses(Res) ->
continue ->
case (get(outh))#outh.exceedmaxuses of
true ->
- done; %% no keepalive this time!
+ done; % no keepalive this time!
_ ->
Res
end;
@@ -1569,6 +1590,8 @@ no_body_method(CliSock, IPPort, Req, Head) ->
make_arg(CliSock0, IPPort, Head, Req, Bin) ->
SC = get(sc),
+ make_arg(SC, CliSock0, IPPort, Head, Req, Bin).
+make_arg(SC, CliSock0, IPPort, Head, Req, Bin) ->
CliSock = case yaws:is_ssl(SC) of
nossl ->
CliSock0;
@@ -198,6 +198,13 @@ keepalive_timeout = 10000
</server>
<server localhost>
+ port = 8011
+ listen = 0.0.0.0
+ docroot = %YTOP%/www
+ dispatchmod = dispatchmod_tester
+</server>
+
+<server localhost>
port = 8443
listen = 0.0.0.0
docroot = %YTOP%/www
View
@@ -5,7 +5,7 @@ include ../support/include.mk
#
all: conf setup app_test.beam streamtest.beam jsontest.beam posttest.beam \
reentranttest.beam phptest.beam rewritetest.beam shapertest.beam \
- flushtest.beam throwtest.beam
+ flushtest.beam throwtest.beam dispatchmod_tester.beam
@echo "all ok"
View
@@ -18,6 +18,7 @@ start() ->
test2(),
test3(),
test_appmod(),
+ test_dispatchmod(),
test_streamcontent(),
sendfile_get(),
test_json(),
@@ -246,6 +247,18 @@ test_appmod() ->
?line "true" = proplists:get_value(?APPMOD_HEADER, Headers4),
ok.
+test_dispatchmod() ->
+ io:format("dispatchmod test\n", []),
+ Uri1 = "http://localhost:8011/done",
+ ?line {ok, "204", Headers1, _} = ibrowse:send_req(Uri1, [], get),
+ ?line "true" = proplists:get_value("X-DispatchMod", Headers1),
+ Uri2 = "http://localhost:8011/closed",
+ ?line {ok, "200", Headers2, _} = ibrowse:send_req(Uri2, [], get),
+ ?line "close" = proplists:get_value("Connection", Headers2),
+ Uri3 = "http://localhost:8011/index.yaws",
+ ?line {ok, "200", _, _} = ibrowse:send_req(Uri3, [], get),
+ ok.
+
test_streamcontent() ->
io:format("streamcontent_test\n",[]),
Uri1 = "http://localhost:8000/streamtest/1",
@@ -0,0 +1,17 @@
+-module(dispatchmod_tester).
+-export([dispatch/1]).
+-include("../../include/yaws_api.hrl").
+
+dispatch(#arg{clisock=Sock}=A) ->
+ {abs_path, Path} = (A#arg.req)#http_request.path,
+ case Path of
+ "/done" ->
+ ok = gen_tcp:send(Sock, <<"HTTP/1.1 204 No Content\r\nX-DispatchMod: true\r\n\r\n">>),
+ done;
+ "/closed" ->
+ ok = gen_tcp:send(Sock, <<"HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n">>),
+ gen_tcp:close(Sock),
+ closed;
+ _ ->
+ continue
+ end.

0 comments on commit 0c86ddc

Please sign in to comment.