Skip to content

Commit

Permalink
add a configurable dispatch module
Browse files Browse the repository at this point in the history
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
vinoski committed Oct 6, 2012
1 parent 1e7a122 commit 0c86ddc
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 29 deletions.
24 changes: 24 additions & 0 deletions doc/yaws.tex
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion include/yaws.hrl
Expand Up @@ -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
}).


Expand Down
26 changes: 26 additions & 0 deletions man/yaws.conf.5
Expand Up @@ -633,6 +633,32 @@ The above configuration will invoke the 'myapp' erlang module on everything
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
Expand Down
4 changes: 3 additions & 1 deletion src/yaws.erl
Expand Up @@ -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) ->
Expand Down
4 changes: 4 additions & 0 deletions src/yaws_config.erl
Expand Up @@ -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} ->
Expand Down
75 changes: 49 additions & 26 deletions src/yaws_server.erl
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions test/conf/stdconf.conf
Expand Up @@ -197,6 +197,13 @@ keepalive_timeout = 10000
index_files = index.html /testdir
</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
Expand Down
2 changes: 1 addition & 1 deletion test/t2/Makefile
Expand Up @@ -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"


Expand Down
13 changes: 13 additions & 0 deletions test/t2/app_test.erl
Expand Up @@ -18,6 +18,7 @@ start() ->
test2(),
test3(),
test_appmod(),
test_dispatchmod(),
test_streamcontent(),
sendfile_get(),
test_json(),
Expand Down Expand Up @@ -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",
Expand Down
17 changes: 17 additions & 0 deletions test/t2/dispatchmod_tester.erl
@@ -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.