Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added webmachine c25ec2dfaba2 tip from http://bitbucket.org/justin/we…

…bmachine/

Patched dispatcher of webmachine to use a custom dispatcher.
  • Loading branch information...
commit d67e934e2f19b3f846a61e5070f738fe58adf29d 1 parent f189630
@mworrell mworrell authored
Showing with 2,901 additions and 937 deletions.
  1. +2 −2 Makefile
  2. +4 −1 THANKS
  3. +0 −8 include/wm_host_dispatch_list.hrl
  4. +4 −3 include/wm_reqdata.hrl
  5. +9 −0 include/wm_reqstate.hrl
  6. +0 −1  priv/skel/deps/.empty
  7. +0 −1  priv/skel/ebin/.empty
  8. +14 −2 priv/skel/src/skel.erl
  9. +4 −2 priv/skel/src/skel_sup.erl
  10. +3 −1 src/webmachine.app
  11. +21 −2 src/webmachine.erl
  12. +45 −24 src/webmachine_decision_core.erl
  13. +109 −18 src/webmachine_dispatcher.erl
  14. +24 −16 src/webmachine_error_handler.erl
  15. +0 −120 src/webmachine_host.erl
  16. +9 −5 src/webmachine_logger.erl
  17. +58 −66 src/webmachine_mochiweb.erl
  18. +11 −2 src/webmachine_multipart.erl
  19. +1 −1  src/webmachine_perf_logger.erl
  20. +551 −13 src/webmachine_request.erl
  21. +0 −606 src/webmachine_request_srv.erl
  22. +38 −14 src/webmachine_resource.erl
  23. +4 −2 src/webmachine_skel.erl
  24. +10 −11 src/webmachine_sup.erl
  25. +19 −2 src/webmachine_util.erl
  26. +30 −14 src/wrq.erl
  27. +67 −0 www/blogs.html
  28. +48 −0 www/contact.html
  29. +103 −0 www/css/style-1c.css
  30. +103 −0 www/css/style.css
  31. +292 −0 www/debugging.html
  32. +57 −0 www/diagram.html
  33. +121 −0 www/dispatcher.html
  34. +58 −0 www/docs.html
  35. +250 −0 www/example_resources.html
  36. BIN  www/favicon.ico
  37. BIN  www/images/WM200-crop.png
  38. BIN  www/images/basho-landscape.gif
  39. BIN  www/images/basic-trace-decision-tab.png
  40. BIN  www/images/basic-trace-labeled.png
  41. BIN  www/images/basic-trace-request-tab.png
  42. BIN  www/images/basic-trace-response-tab.png
  43. BIN  www/images/bg.gif
  44. BIN  www/images/blankbox.gif
  45. BIN  www/images/chash.gif
  46. BIN  www/images/easy-ops.gif
  47. BIN  www/images/gossip4.gif
  48. BIN  www/images/halfblankbox.gif
  49. BIN  www/images/http-headers-status-v3.png
  50. BIN  www/images/more.gif
  51. BIN  www/images/site.gif
  52. BIN  www/images/splash250.gif
  53. BIN  www/images/vclock.gif
  54. +73 −0 www/index.html
  55. +62 −0 www/intros.html
  56. +108 −0 www/mechanics.html
  57. +77 −0 www/quickstart.html
  58. +52 −0 www/reftrans.html
  59. +143 −0 www/reqdata.html
  60. +141 −0 www/resources.html
  61. +176 −0 www/streambody.html
View
4 Makefile
@@ -11,8 +11,8 @@ erl:
@$(ERL) -pa $(EBIN_DIRS) -noinput +B \
-eval 'case make:all() of up_to_date -> halt(0); error -> halt(1) end.'
-docs:
- @erl -noshell -run edoc_run application '$(APP)' '"."' '[]'
+edoc:
+ @$(ERL) -noshell -run edoc_run application '$(APP)' '"."' '[{preprocess, true},{includes, ["."]}]'
clean:
@echo "removing:"
View
5 THANKS
@@ -1,7 +1,8 @@
The following people have contributed to Webmachine:
-Justin Sheehy
Andy Gross
+Justin Sheehy
+John Muellerleile
Robert Ahrens
Jeremy Latt
Bryan Fink
@@ -11,4 +12,6 @@ Marc Worrell
Seth Falcon
Tuncer Ayaz
Martin Scholl
+Paul Mineiro
+Dave Smith
View
8 include/wm_host_dispatch_list.hrl
@@ -1,8 +0,0 @@
-%% @doc Dispatch list on a per-host basis.
-%% host = atom specificying the symbolic hostname, also set in the wm_reqdata. Use 'default' for the catch-all host
-%% hostname = the primary hostname, lowercase (eg. "www.example.com")
-%% hostalias = list of accepted aliases, lowercase (eg. [ "example.com", "example.net" ])
-%% redirect = boolean, set to true to redirect GET requests to the main host
-%% dispatch_list = list of {pathspec, resource, args}
-
--record(wm_host_dispatch_list, {host, hostname, hostalias, redirect, dispatch_list}).
View
7 include/wm_reqdata.hrl
@@ -1,7 +1,8 @@
--record(wm_reqdata, {method, version, peer, wmreq,
- host, disp_path, path, raw_path, path_info, path_tokens,
+-record(wm_reqdata, {method, version, peer, wm_state,
+ disp_path, path, raw_path, path_info, path_tokens,
app_root,response_code,max_recv_body,
req_cookie, req_qs, req_headers, req_body,
- resp_redirect, resp_headers, resp_body
+ resp_redirect, resp_headers, resp_body,
+ host_tokens, port
}).
View
9 include/wm_reqstate.hrl
@@ -0,0 +1,9 @@
+-record(reqstate, {socket=undefined,
+ metadata=dict:new(),
+ range=undefined,
+ peer=undefined,
+ reqdata=undefined,
+ bodyfetch=undefined,
+ log_data=undefined
+ }).
+
View
1  priv/skel/deps/.empty
@@ -1 +0,0 @@
-
View
1  priv/skel/ebin/.empty
@@ -1 +0,0 @@
-
View
16 priv/skel/src/skel.erl
@@ -5,7 +5,7 @@
-module(skel).
-author('author <author@example.com>').
--export([start/0, stop/0]).
+-export([start/0, start_link/0, stop/0]).
ensure_started(App) ->
case application:start(App) of
@@ -14,12 +14,24 @@ ensure_started(App) ->
{error, {already_started, App}} ->
ok
end.
-
+
+%% @spec start_link() -> {ok,Pid::pid()}
+%% @doc Starts the app for inclusion in a supervisor tree
+start_link() ->
+ skel_deps:ensure(),
+ ensure_started(crypto),
+ application:set_env(webmachine, webmachine_logger_module,
+ webmachine_logger),
+ ensure_started(webmachine),
+ skel_sup:start_link().
+
%% @spec start() -> ok
%% @doc Start the skel server.
start() ->
skel_deps:ensure(),
ensure_started(crypto),
+ application:set_env(webmachine, webmachine_logger_module,
+ webmachine_logger),
ensure_started(webmachine),
application:start(skel).
View
6 priv/skel/src/skel_sup.erl
@@ -41,8 +41,10 @@ upgrade() ->
%% @spec init([]) -> SupervisorTree
%% @doc supervisor callback.
init([]) ->
- Ip = case os:getenv("WEBMACHINE_IP") of false -> "0.0.0.0"; Any -> Any end,
- {ok, Dispatch} = file:consult("priv/dispatch.conf"),
+ Ip = case os:getenv("WEBMACHINE_IP") of false -> "0.0.0.0"; Any -> Any end,
+ {ok, Dispatch} = file:consult(filename:join(
+ [filename:dirname(code:which(?MODULE)),
+ "..", "priv", "dispatch.conf"])),
WebConfig = [
{ip, Ip},
{port, 8000},
View
4 src/webmachine.app
@@ -1,6 +1,6 @@
{application, webmachine,
[{description, "webmachine"},
- {vsn, "1.3"},
+ {vsn, "1.5"},
{modules, [
webmachine,
webmachine_app,
@@ -13,8 +13,10 @@
webmachine_resource,
webmachine_request,
webmachine_request_srv,
+ webmachine_skel,
webmachine_sup,
webmachine_mochiweb,
+ webmachine_multipart,
webmachine_util,
wrq,
wmtrace_resource
View
23 src/webmachine.erl
@@ -20,6 +20,10 @@
-export([start/0, stop/0]).
-export([new_request/2]).
+-include("webmachine_logger.hrl").
+-include_lib("include/wm_reqstate.hrl").
+-include_lib("include/wm_reqdata.hrl").
+
%% @spec start() -> ok
%% @doc Start the webmachine server.
start() ->
@@ -38,8 +42,23 @@ new_request(mochiweb, Request) ->
RawPath = Request:get(raw_path),
Version = Request:get(version),
Headers = Request:get(headers),
- {ok, Pid} = webmachine_request_srv:start_link(Socket, Method, RawPath, Version, Headers),
- webmachine_request:new(Pid).
+ InitState = #reqstate{socket=Socket,
+ reqdata=wrq:create(Method,Version,RawPath,Headers)},
+ InitReq = {webmachine_request,InitState},
+ {Peer, ReqState} = InitReq:get_peer(),
+ PeerState = ReqState#reqstate{reqdata=wrq:set_peer(Peer,
+ ReqState#reqstate.reqdata)},
+ LogData = #wm_log_data{start_time=now(),
+ method=Method,
+ headers=Headers,
+ peer=PeerState#reqstate.peer,
+ path=RawPath,
+ version=Version,
+ response_code=404,
+ response_length=0},
+ webmachine_request:new(PeerState#reqstate{log_data=LogData}).
+
+
View
69 src/webmachine_decision_core.erl
@@ -25,9 +25,9 @@
-export([do_log/1]).
-include("webmachine_logger.hrl").
-handle_request(Req, Resource) ->
- put(req, Req),
+handle_request(Resource, ReqState) ->
put(resource, Resource),
+ put(reqstate, ReqState),
try
d(v3b13)
catch
@@ -36,8 +36,18 @@ handle_request(Req, Resource) ->
end.
wrcall(X) ->
- Req = get(req),
- Req:call(X).
+ RS0 = get(reqstate),
+ Req = webmachine_request:new(RS0),
+ {Response, RS1} = Req:call(X),
+ put(reqstate, RS1),
+ Response.
+
+resource_call(Fun) ->
+ Resource = get(resource),
+ {Reply, NewResource, NewRS} = Resource:do(Fun,get()),
+ put(resource, NewResource),
+ put(reqstate, NewRS),
+ Reply.
get_header_val(H) -> wrcall({get_req_header, H}).
@@ -55,7 +65,9 @@ respond(Code) ->
404 ->
{ok, ErrorHandler} = application:get_env(webmachine, error_handler),
Reason = {none, none, []},
- ErrorHTML = ErrorHandler:render_error(Code, get(req), Reason),
+ {ErrorHTML,ReqState} = ErrorHandler:render_error(
+ Code, {webmachine_request,get(reqstate)}, Reason),
+ put(reqstate, ReqState),
wrcall({set_resp_body, ErrorHTML});
304 ->
wrcall({remove_resp_header, "Content-Type"}),
@@ -81,9 +93,7 @@ respond(Code) ->
LogData = LogData0#wm_log_data{resource_module=RMod,
end_time=EndTime},
spawn(fun() -> do_log(LogData) end),
- Resource:stop(),
- Req = get(req),
- Req:stop().
+ Resource:stop().
respond(Code, Headers) ->
wrcall({set_resp_headers, Headers}),
@@ -91,7 +101,8 @@ respond(Code, Headers) ->
error_response(Code, Reason) ->
{ok, ErrorHandler} = application:get_env(webmachine, error_handler),
- ErrorHTML = ErrorHandler:render_error(Code, get(req), Reason),
+ ErrorHTML = ErrorHandler:render_error(
+ Code, {webmachine_request,get(reqstate)}, Reason),
wrcall({set_resp_body, ErrorHTML}),
respond(Code).
error_response(Reason) ->
@@ -115,12 +126,10 @@ decision_flow({ErrCode, Reason}, _TestResult) when is_integer(ErrCode) ->
error_response(ErrCode, Reason).
do_log(LogData) ->
- LoggerModule =
- case application:get_env(webmachine, webmachine_logger_module) of
- {ok, Val} -> Val;
- _ -> webmachine_logger
- end,
- LoggerModule:log_access(LogData),
+ case application:get_env(webmachine, webmachine_logger_module) of
+ {ok, LoggerModule} -> LoggerModule:log_access(LogData);
+ _ -> nop
+ end,
case application:get_env(webmachine, enable_perf_logger) of
{ok, true} ->
webmachine_perf_logger:log(LogData);
@@ -128,12 +137,6 @@ do_log(LogData) ->
ignore
end.
-resource_call(Fun) ->
- Resource = get(resource),
- {Reply, NewResource} = Resource:do(Fun,get()),
- put(resource, NewResource),
- Reply.
-
log_decision(DecisionID) ->
Resource = get(resource),
Resource:log_d(DecisionID).
@@ -417,7 +420,9 @@ decision(v3n11) ->
end;
_ ->
case resource_call(process_post) of
- true -> stage1_ok;
+ true ->
+ encode_body_if_set(),
+ stage1_ok;
{halt, Code} -> respond(Code);
Err -> error_response(Err)
end
@@ -539,7 +544,21 @@ accept_helper() ->
[] -> {respond,415};
AcceptedContentList ->
F = hd(AcceptedContentList),
- resource_call(F)
+ case resource_call(F) of
+ true ->
+ encode_body_if_set(),
+ true;
+ Result -> Result
+ end
+ end.
+
+encode_body_if_set() ->
+ case wrcall(has_resp_body) of
+ true ->
+ Body = wrcall(resp_body),
+ wrcall({set_resp_body, encode_body(Body)}),
+ true;
+ _ -> false
end.
encode_body(Body) ->
@@ -555,6 +574,8 @@ encode_body(Body) ->
case Body of
{stream, StreamBody} ->
{stream, make_encoder_stream(Encoder, Charsetter, StreamBody)};
+ {writer, BodyFun} ->
+ {writer, {Encoder, Charsetter, BodyFun}};
_ ->
Encoder(Charsetter(iolist_to_binary(Body)))
end.
@@ -564,7 +585,7 @@ make_encoder_stream(Encoder, Charsetter, {Body, done}) ->
make_encoder_stream(Encoder, Charsetter, {Body, Next}) ->
{Encoder(Charsetter(Body)),
fun() -> make_encoder_stream(Encoder, Charsetter, Next()) end}.
-
+
choose_encoding(AccEncHdr) ->
Encs = [Enc || {Enc,_Fun} <- resource_call(encodings_provided)],
case webmachine_util:choose_encoding(Encs, AccEncHdr) of
View
127 src/webmachine_dispatcher.erl
@@ -19,8 +19,9 @@
-module(webmachine_dispatcher).
-author('Robert Ahrens <rahrens@basho.com>').
-author('Justin Sheehy <justin@basho.com>').
+-author('Bryan Fink <bryan@basho.com>').
--export([dispatch/2]).
+-export([dispatch/2, dispatch/3]).
-define(SEPARATOR, $\/).
-define(MATCH_ALL, '*').
@@ -30,6 +31,14 @@
%% @doc Interface for URL dispatching.
%% See also http://bitbucket.org/justin/webmachine/wiki/DispatchConfiguration
dispatch(PathAsString, DispatchList) ->
+ dispatch([], PathAsString, DispatchList).
+
+%% @spec dispatch(Host::string(), Path::string(),
+%% DispatchList::[matchterm()]) ->
+%% dispterm() | dispfail()
+%% @doc Interface for URL dispatching.
+%% See also http://bitbucket.org/justin/webmachine/wiki/DispatchConfiguration
+dispatch(HostAsString, PathAsString, DispatchList) ->
Path = string:tokens(PathAsString, [?SEPARATOR]),
% URIs that end with a trailing slash are implicitly one token
% "deeper" than we otherwise might think as we are "inside"
@@ -38,10 +47,57 @@ dispatch(PathAsString, DispatchList) ->
true -> 1;
_ -> 0
end,
- try_binding(DispatchList, Path, ExtraDepth).
+ {Host, Port} = split_host_port(HostAsString),
+ try_host_binding(DispatchList, lists:reverse(Host), Port,
+ Path, ExtraDepth).
+
+split_host_port(HostAsString) ->
+ case string:tokens(HostAsString, ":") of
+ [HostPart, PortPart] ->
+ {split_host(HostPart), list_to_integer(PortPart)};
+ [HostPart] ->
+ {split_host(HostPart), 80};
+ [] ->
+ %% no host header
+ {[], 80}
+ end.
+
+split_host(HostAsString) ->
+ string:tokens(HostAsString, ".").
-%% @type matchterm() = {[pathterm()], matchmod(), matchopts()}.
+%% @type matchterm() = hostmatchterm() | pathmatchterm().
% The dispatch configuration is a list of these terms, and the
+% first one whose host and path terms match the input is used.
+% Using a pathmatchterm() here is equivalent to using a hostmatchterm()
+% of the form {{['*'],'*'}, [pathmatchterm()]}.
+
+%% @type hostmatchterm() = {hostmatch(), [pathmatchterm()]}.
+% The dispatch configuration contains a list of these terms, and the
+% first one whose host and one pathmatchterm match is used.
+
+%% @type hostmatch() = [hostterm()] | {[hostterm()], portterm()}.
+% A host header (Host, X-Forwarded-For, etc.) will be matched against
+% this term. Using a raws [hostterm()] list is equivalent to using
+% {[hostterm()], '*'}.
+
+%% @type hostterm() = '*' | string() | atom().
+% A list of hostterms is matched against a '.'-separated hostname.
+% The '*' hosterm matches all remaining tokens, and is only allowed at
+% the head of the list.
+% A string hostterm will match a token of exactly the same string.
+% Any atom hostterm other than '*' will match any token and will
+% create a binding in the result if a complete match occurs.
+
+%% @type portterm() = '*' | integer() | atom().
+% A portterm is matched against the integer port after any ':' in
+% the hostname, or 80 if no port is found.
+% The '*' portterm patches any port
+% An integer portterm will match a port of exactly the same integer.
+% Any atom portterm other than '*' will match any port and will
+% create a binding in the result if a complete match occurs.
+
+%% @type pathmatchterm() = {[pathterm()], matchmod(), matchopts()}.
+% The dispatch configuration contains a list of these terms, and the
% first one whose list of pathterms matches the input path is used.
%% @type pathterm() = '*' | string() | atom().
@@ -80,28 +136,63 @@ dispatch(PathAsString, DispatchList) ->
%% @type dispfail() = {no_dispatch_match, pathtokens()}.
-try_binding([], PathTokens, _) ->
+try_host_binding([], Host, Port, Path, _Depth) ->
+ {no_dispatch_match, {Host, Port}, Path};
+try_host_binding([Dispatch|Rest], Host, Port, Path, Depth) ->
+ {{HostSpec,PortSpec},PathSpec} =
+ case Dispatch of
+ {{H,P},S} -> {{H,P},S};
+ {H,S} -> {{H,?MATCH_ALL},S};
+ S -> {{[?MATCH_ALL],?MATCH_ALL},[S]}
+ end,
+ case bind_port(PortSpec, Port, []) of
+ {ok, PortBindings} ->
+ case bind(lists:reverse(HostSpec), Host, PortBindings, 0) of
+ {ok, HostRemainder, HostBindings, _} ->
+ case try_path_binding(PathSpec, Path, HostBindings, Depth) of
+ {Mod, Props, PathRemainder, PathBindings,
+ AppRoot, StringPath} ->
+ {Mod, Props, HostRemainder, Port, PathRemainder,
+ PathBindings, AppRoot, StringPath};
+ {no_dispatch_match, _} ->
+ try_host_binding(Rest, Host, Port, Path, Depth)
+ end;
+ fail ->
+ try_host_binding(Rest, Host, Port, Path, Depth)
+ end;
+ fail ->
+ try_host_binding(Rest, Host, Port, Path, Depth)
+ end.
+
+bind_port(Port, Port, Bindings) -> {ok, Bindings};
+bind_port(?MATCH_ALL, _Port, Bindings) -> {ok, Bindings};
+bind_port(PortAtom, Port, Bindings) when is_atom(PortAtom) ->
+ {ok, [{PortAtom, Port}|Bindings]};
+bind_port(_, _, _) -> fail.
+
+try_path_binding([], PathTokens, _, _) ->
{no_dispatch_match, PathTokens};
-try_binding([{PathSchema, Mod, Props}|Rest], PathTokens, ExtraDepth) ->
- case bind_path(PathSchema, PathTokens, [], 0) of
- {ok, Remainder, Bindings, Depth} ->
- {Mod, Props, Remainder, Bindings,
+try_path_binding([{PathSchema, Mod, Props}|Rest], PathTokens,
+ Bindings, ExtraDepth) ->
+ case bind(PathSchema, PathTokens, Bindings, 0) of
+ {ok, Remainder, NewBindings, Depth} ->
+ {Mod, Props, Remainder, NewBindings,
calculate_app_root(Depth + ExtraDepth), reconstitute(Remainder)};
fail ->
- try_binding(Rest, PathTokens, ExtraDepth)
+ try_path_binding(Rest, PathTokens, Bindings, ExtraDepth)
end.
-bind_path([], [], Bindings, Depth) ->
+bind([], [], Bindings, Depth) ->
{ok, [], Bindings, Depth};
-bind_path([?MATCH_ALL], PathRest, Bindings, Depth) when is_list(PathRest) ->
- {ok, PathRest, Bindings, Depth + length(PathRest)};
-bind_path(_, [], _, _) ->
+bind([?MATCH_ALL], Rest, Bindings, Depth) when is_list(Rest) ->
+ {ok, Rest, Bindings, Depth + length(Rest)};
+bind(_, [], _, _) ->
fail;
-bind_path([Token|Rest],[Match|PathRest],Bindings,Depth) when is_atom(Token) ->
- bind_path(Rest, PathRest, [{Token, Match}|Bindings], Depth + 1);
-bind_path([Token|Rest], [Token|PathRest], Bindings, Depth) ->
- bind_path(Rest, PathRest, Bindings, Depth + 1);
-bind_path(_, _, _, _) ->
+bind([Token|RestToken],[Match|RestMatch],Bindings,Depth) when is_atom(Token) ->
+ bind(RestToken, RestMatch, [{Token, Match}|Bindings], Depth + 1);
+bind([Token|RestToken], [Token|RestMatch], Bindings, Depth) ->
+ bind(RestToken, RestMatch, Bindings, Depth + 1);
+bind(_, _, _, _) ->
fail.
reconstitute([]) -> "";
View
40 src/webmachine_error_handler.erl
@@ -25,35 +25,43 @@
-export([render_error/3]).
-render_error(404, Req, _Reason) ->
- Req:add_response_header("Content-Type", "text/html"),
- <<"<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD><BODY><H1>Not Found</H1>The requested document was not found on this server.<P><HR><ADDRESS>mochiweb+webmachine web server</ADDRESS></BODY></HTML>">>;
+render_error(Code, Req, Reason) ->
+ case Req:has_response_body() of
+ {true,_} -> Req:response_body();
+ {false,_} -> render_error_body(Code, Req:trim_state(), Reason)
+ end.
-render_error(500, Req, Reason) ->
- Req:add_response_header("Content-Type", "text/html"),
- error_logger:error_msg("webmachine error: path=~p~n~p~n", [Req:path(), Reason]),
+render_error_body(404, Req, _Reason) ->
+ {ok, ReqState} = Req:add_response_header("Content-Type", "text/html"),
+ {<<"<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD><BODY><H1>Not Found</H1>The requested document was not found on this server.<P><HR><ADDRESS>mochiweb+webmachine web server</ADDRESS></BODY></HTML>">>, ReqState};
+
+render_error_body(500, Req, Reason) ->
+ {ok, ReqState} = Req:add_response_header("Content-Type", "text/html"),
+ {Path,_} = Req:path(),
+ error_logger:error_msg("webmachine error: path=~p~n~p~n", [Path, Reason]),
STString = io_lib:format("~p", [Reason]),
ErrorStart = "<html><head><title>500 Internal Server Error</title></head><body><h1>Internal Server Error</h1>The server encountered an error while processing this request:<br><pre>",
ErrorEnd = "</pre><P><HR><ADDRESS>mochiweb+webmachine web server</ADDRESS></body></html>",
ErrorIOList = [ErrorStart,STString,ErrorEnd],
- erlang:iolist_to_binary(ErrorIOList);
+ {erlang:iolist_to_binary(ErrorIOList), ReqState};
-render_error(501, Req, _Reason) ->
- Req:add_response_header("Content-Type", "text/html"),
+render_error_body(501, Req, _Reason) ->
+ {ok, ReqState} = Req:add_response_header("Content-Type", "text/html"),
+ {Method,_} = Req:method(),
error_logger:error_msg("Webmachine does not support method ~p~n",
- [Req:method()]),
+ [Method]),
ErrorStr = io_lib:format("<html><head><title>501 Not Implemented</title>"
"</head><body><h1>Internal Server Error</h1>"
"The server does not support the ~p method.<br>"
"<P><HR><ADDRESS>mochiweb+webmachine web server"
"</ADDRESS></body></html>",
- [Req:method()]),
- erlang:iolist_to_binary(ErrorStr);
+ [Method]),
+ {erlang:iolist_to_binary(ErrorStr), ReqState};
-render_error(503, Req, _Reason) ->
- Req:add_response_header("Content-Type", "text/html"),
+render_error_body(503, Req, _Reason) ->
+ {ok, ReqState} = Req:add_response_header("Content-Type", "text/html"),
error_logger:error_msg("Webmachine cannot fulfill"
- "the request at this time"),
+ " the request at this time"),
ErrorStr = "<html><head><title>503 Service Unavailable</title>"
"</head><body><h1>Service Unavailable</h1>"
"The server is currently unable to handle "
@@ -61,5 +69,5 @@ render_error(503, Req, _Reason) ->
"or maintenance of the server.<br>"
"<P><HR><ADDRESS>mochiweb+webmachine web server"
"</ADDRESS></body></html>",
- list_to_binary(ErrorStr).
+ {list_to_binary(ErrorStr), ReqState}.
View
120 src/webmachine_host.erl
@@ -1,120 +0,0 @@
-%% @author Marc Worrell <marc@worrell.nl>
-%% @copyright 2009 Marc Worrell
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-
-%% @doc Virtual host support for webmachine
-
--module(webmachine_host).
--author('Marc Worrell <marc@worrell.nl>').
--export([get_host_dispatch_list/1]).
-
--include_lib("wm_host_dispatch_list.hrl").
-
-
-%% @doc Fetch the host and dispatch list for the request
-%% @spec get_host_dispatch_list(webmachine_request()) -> {ok, Host::atom(), DispatchList::list()} | {redirect, Hostname::string()} | no_host_match
-get_host_dispatch_list(Req) ->
- {ok, DispatchList} = application:get_env(webmachine, dispatch_list),
- case DispatchList of
- [#wm_host_dispatch_list{}|_] ->
- {Host, Port} = get_host(Req),
- case get_dispatch_host(Host, DispatchList) of
- {ok, DL} ->
- {ok, DL#wm_host_dispatch_list.host, DL#wm_host_dispatch_list.dispatch_list};
-
- undefined ->
- FoundHost = case get_dispatch_alias(Host, DispatchList) of
- {ok, _} = Found -> Found;
- undefined -> get_dispatch_default(DispatchList)
- end,
- case FoundHost of
- {ok, DL} ->
- case DL#wm_host_dispatch_list.redirect andalso is_hostname(DL#wm_host_dispatch_list.hostname) andalso Req:method() =:= 'GET' of
- true ->
- % Redirect, keep the port number
- Hostname = DL#wm_host_dispatch_list.hostname,
- Hostname1 = case Port of
- "80" -> Hostname;
- _ -> Hostname ++ [$:|Port]
- end,
- {redirect, Hostname1};
- false ->
- {ok, DL#wm_host_dispatch_list.host, DL#wm_host_dispatch_list.dispatch_list}
- end;
- undefined ->
- no_host_match
- end
- end;
- _ ->
- {ok, default, DispatchList}
- end.
-
-
-get_host(Req) ->
- Host = case Req:get_header_value("X-Forwarded-Host") of
- undefined ->
- case Req:get_header_value("X-Host") of
- undefined -> Req:get_header_value("Host");
- XHost -> XHost
- end;
- XFwdHost -> XFwdHost
- end,
- case Host of
- undefined ->
- {"", "80"};
- _ ->
- % Split the optional port number from the host name
- [H|Rest] = string:tokens(string:to_lower(Host), ":"),
- case Rest of
- [] -> {H, "80"};
- [Port|_] -> {H, Port}
- end
- end.
-
-
-%% @doc Search the host where the main hostname matches the requested host
-get_dispatch_host(_Host, []) ->
- undefined;
-get_dispatch_host(Host, [#wm_host_dispatch_list{hostname=Host} = DL|_]) ->
- {ok, DL};
-get_dispatch_host(Host, [_|Rest]) ->
- get_dispatch_host(Host, Rest).
-
-
-%% @doc Search the host where the req hostname is an alias of main host
-get_dispatch_alias(_Host, []) ->
- undefined;
-get_dispatch_alias(Host, [#wm_host_dispatch_list{hostalias=Alias} = DL|Rest]) ->
- case lists:member(Host, Alias) of
- true -> {ok, DL};
- false -> get_dispatch_alias(Host, Rest)
- end.
-
-
-%% @doc Search the host with the name 'default' for fallback of unknown hostnames.
-get_dispatch_default([]) ->
- undefined;
-get_dispatch_default([#wm_host_dispatch_list{host=default} = DL|_]) ->
- {ok, DL};
-get_dispatch_default([_|Rest]) ->
- get_dispatch_default(Rest).
-
-
-%% @doc Check if the hostname is a hostname suitable to redirect to
-is_hostname(undefined) -> false;
-is_hostname("") -> false;
-is_hostname("localhost") -> false;
-is_hostname("127.0.0.1") -> false;
-is_hostname(_) -> true.
-
View
14 src/webmachine_logger.erl
@@ -46,19 +46,23 @@ refresh(Time) ->
gen_server:cast(?MODULE, {refresh, Time}).
log_access(#wm_log_data{}=D) ->
- gen_server:call(?MODULE, {log_access, D}).
+ gen_server:cast(?MODULE, {log_access, D}).
-handle_call({log_access, LogData}, _From, State) ->
+handle_call(_Msg,_From,State) -> {noreply,State}.
+
+handle_cast({log_access, LogData}, State) ->
NewState = maybe_rotate(State, now()),
Msg = format_req(LogData),
log_write(NewState#state.handle, Msg),
- {reply, ok, NewState}.
-
+ {noreply, NewState};
handle_cast({refresh, Time}, State) ->
{noreply, maybe_rotate(State, Time)}.
+handle_info({_Label, {From, MRef}, get_modules}, State) ->
+ From ! {MRef, [?MODULE]},
+ {noreply, State};
handle_info(_Info, State) ->
- {ok, State}.
+ {noreply, State}.
terminate(_Reason, _State) ->
ok.
View
124 src/webmachine_mochiweb.erl
@@ -14,9 +14,6 @@
%% See the License for the specific language governing permissions and
%% limitations under the License.
-%% Changed by Marc Worrell, 2009.
-%% Added vhost support. Re-organized loop/1 to use webmachine_host.erl
-
%% @doc Mochiweb interface for webmachine.
-module(webmachine_mochiweb).
-author('Justin Sheehy <justin@basho.com>').
@@ -51,73 +48,68 @@ stop() ->
loop(MochiReq) ->
Req = webmachine:new_request(mochiweb, MochiReq),
- case webmachine_host:get_host_dispatch_list(Req) of
- {ok, Host, DispatchList} ->
- dispatch_request(Req, Host, DispatchList);
- {redirect, Hostname} ->
- Uri = "http://" ++ Hostname ++ Req:raw_path(),
- Req:add_response_header("Location", Uri),
- Req:send_response(301),
- LogData = Req:log_data(),
- LogModule =
- case application:get_env(webmachine,webmachine_logger_module) of
- {ok, Val} -> Val;
- _ -> webmachine_logger
- end,
- spawn(LogModule, log_access, [LogData]),
- Req:stop();
- no_host_match ->
- send_no_host(Req)
- end.
-
-dispatch_request(Req, Host, DispatchList) ->
- case webmachine_dispatcher:dispatch(Req:path(), DispatchList) of
- {no_dispatch_match, _UnmatchedPathTokens} ->
- send_404(Req, Host);
- {Mod, ModOpts, PathTokens, Bindings, AppRoot, StringPath} ->
+ Host = case host_headers(Req) of
+ [H|_] -> H;
+ [] -> []
+ end,
+ {Path, _} = Req:path(),
+ {Dispatch, ReqDispatch} = case application:get_env(webmachine, dispatcher) of
+ {ok, Dispatcher} ->
+ Dispatcher:dispatch(Host, Path, Req);
+ undefined ->
+ {ok, DispatchList} = application:get_env(webmachine, dispatch_list),
+ {webmachine_dispatcher:dispatch(Host, Path, DispatchList), Req}
+ end,
+ case Dispatch of
+ {no_dispatch_match, _UnmatchedHost, _UnmatchedPathTokens} ->
+ {ok, ErrorHandler} = application:get_env(webmachine, error_handler),
+ {ErrorHTML,ReqState1} =
+ ErrorHandler:render_error(404, ReqDispatch, {none, none, []}),
+ Req1 = {webmachine_request,ReqState1},
+ {ok,ReqState2} = Req1:append_to_response_body(ErrorHTML),
+ Req2 = {webmachine_request,ReqState2},
+ {ok,ReqState3} = Req2:send_response(404),
+ Req3 = {webmachine_request,ReqState3},
+ {LogData,_ReqState4} = Req3:log_data(),
+ case application:get_env(webmachine,webmachine_logger_module) of
+ {ok, LogModule} ->
+ spawn(LogModule, log_access, [LogData]);
+ _ -> nop
+ end;
+ {Mod, ModOpts, HostTokens, Port, PathTokens, Bindings,
+ AppRoot, StringPath} ->
BootstrapResource = webmachine_resource:new(x,x,x,x),
{ok, Resource} = BootstrapResource:wrap(Mod, ModOpts),
- Req:load_dispatch_data(Host, Bindings,PathTokens,AppRoot,StringPath,Req),
- Req:set_metadata('resource_module', Mod),
- Req:set_metadata('host', Host),
- webmachine_decision_core:handle_request(Req, Resource)
+ {ok,RS1} = ReqDispatch:load_dispatch_data(Bindings,HostTokens,Port,
+ PathTokens,AppRoot,StringPath),
+ XReq1 = {webmachine_request,RS1},
+ {ok,RS2} = XReq1:set_metadata('resource_module', Mod),
+ try
+ webmachine_decision_core:handle_request(Resource, RS2)
+ catch
+ error:_ ->
+ FailReq = {webmachine_request,RS2},
+ {ok,RS3} = FailReq:send_response(500),
+ PostFailReq = {webmachine_request,RS3},
+ {LogData,_RS4} = PostFailReq:log_data(),
+ case application:get_env(webmachine,
+ webmachine_logger_module) of
+ {ok, LogModule} ->
+ spawn(LogModule, log_access, [LogData]);
+ _ -> nop
+ end
+ end;
+ handled ->
+ nop
end.
-send_404(Req, Host) ->
- Req:set_metadata('host', Host),
- {ok, ErrorHandler} = application:get_env(webmachine, error_handler),
- ErrorHTML = ErrorHandler:render_error(404, Req, {none, none, []}),
- Req:append_to_response_body(ErrorHTML),
- Req:send_response(404),
- LogData = Req:log_data(),
- LogModule =
- case application:get_env(webmachine,webmachine_logger_module) of
- {ok, Val} -> Val;
- _ -> webmachine_logger
- end,
- spawn(LogModule, log_access, [LogData]),
- Req:stop().
-
get_option(Option, Options) ->
{proplists:get_value(Option, Options), proplists:delete(Option, Options)}.
-send_no_host(Req) ->
- ErrorHTML = <<"<html>
-<title>Site Unknown &mdash; Webmachine</title>
-<body>
- <h1>Who, What, Where?</h1>
- <p>Someone sent you here for a site I don't know.</p>
- <p>I guess I'm wrongly configured or you should have been somewhere else.</p>
- <p>I'm really sorry about this.</p>
-</body>
-</html>">>,
- Req:append_to_response_body(ErrorHTML),
- Req:send_response(404),
- LogData = Req:log_data(),
- LogModule =
- case application:get_env(webmachine,webmachine_logger_module) of
- {ok, Val} -> Val;
- _ -> webmachine_logger
- end,
- spawn(LogModule, log_access, [LogData]),
- Req:stop().
+host_headers(Req) ->
+ [ V || {V,_ReqState} <- [Req:get_header_value(H)
+ || H <- ["x-forwarded-for",
+ "x-forwarded-host",
+ "x-forwarded-server",
+ "host"]],
+ V /= undefined].
View
13 src/webmachine_multipart.erl
@@ -1,4 +1,5 @@
%% @author Justin Sheehy <justin@basho.com>
+%% @author Andy Gross <andy@basho.com>
%% @copyright 2009 Basho Technologies
%% @doc Utility for parsing multipart form bodies.
@@ -17,7 +18,8 @@
-module(webmachine_multipart).
-author('Justin Sheehy <justin@basho.com>').
--export([get_all_parts/2,stream_parts/2]).
+-author('Andy Gross <andy@basho.com>').
+-export([get_all_parts/2,stream_parts/2, find_boundary/1]).
-export([test_body/0,test_body2/0]).
% @type incoming_req_body() = binary().
@@ -41,11 +43,18 @@
% @type fcontent() = binary().
% The body content within a form part.
+% @doc Find the multipart boundary for a request.
+% @spec find_boundary(wrq:wm_reqdata()) -> boundary()
+find_boundary(ReqData) ->
+ ContentType = wrq:get_req_header("content-type", ReqData),
+ string:substr(ContentType, string:str(ContentType, "boundary=")
+ + length("boundary=")).
+
% @doc Turn a multipart form into component parts.
% @spec get_all_parts(incoming_req_body(), boundary()) -> [fpart()]
get_all_parts(Body, Boundary) when is_binary(Body), is_list(Boundary) ->
StreamStruct = send_streamed_body(Body,1024),
- getparts1(stream_parts(StreamStruct, "--" ++ Boundary), []).
+ getparts1(stream_parts(StreamStruct, Boundary), []).
% @doc Similar to get_all_parts/2, but for streamed/chunked bodies.
% Takes as input the result of wrq:stream_req_body/2, and provides
View
2  src/webmachine_perf_logger.erl
@@ -58,7 +58,7 @@ handle_cast({refresh, Time}, State) ->
{noreply, maybe_rotate(State, Time)}.
handle_info(_Info, State) ->
- {ok, State}.
+ {noreply, State}.
terminate(_Reason, _State) ->
ok.
View
564 src/webmachine_request.erl
@@ -17,11 +17,16 @@
%% @doc Webmachine HTTP Request Abstraction.
--module(webmachine_request, [Pid]).
+-module(webmachine_request, [ReqState]).
-author('Justin Sheehy <justin@basho.com>').
-author('Andy Gross <andy@basho.com>').
+-export([get_peer/0]). % used in initialization
+-export([call/1]). % internal switching interface, used by wrcall
+
+% actual interface for resource functions
-export([
+ trim_state/0,
get_reqdata/0,
set_reqdata/1,
socket/0,
@@ -30,6 +35,7 @@
disp_path/0,
path/0,
raw_path/0,
+ get_req_header/1,
req_headers/0,
req_body/1,
stream_req_body/1,
@@ -51,7 +57,6 @@
set_resp_body/1,
response_body/0,
has_response_body/0,
- stop/0,
do_redirect/0,
resp_redirect/0,
set_metadata/2,
@@ -67,13 +72,549 @@
get_qs_value/1,
get_qs_value/2,
range/0,
- log_data/0,
- call/1
+ log_data/0
]).
--define(TIMEOUT, 150000).
+-include("webmachine_logger.hrl").
+-include_lib("include/wm_reqstate.hrl").
+-include_lib("include/wm_reqdata.hrl").
+
+-define(WMVSN, "1.5.1").
+-define(QUIP, "that tip is the fix").
+-define(IDLE_TIMEOUT, infinity).
+
+trim_state() ->
+ TrimData = (ReqState#reqstate.reqdata)#wm_reqdata{wm_state='WMSTATE'},
+ webmachine_request:new(ReqState#reqstate{reqdata=TrimData}).
+
+get_peer() ->
+ case ReqState#reqstate.peer of
+ undefined ->
+ Socket = ReqState#reqstate.socket,
+ Peer = case inet:peername(Socket) of
+ {ok, {Addr={10, _, _, _}, _Port}} ->
+ case get_header_value("x-forwarded-for") of
+ {undefined, _} ->
+ inet_parse:ntoa(Addr);
+ {Hosts, _} ->
+ string:strip(lists:last(string:tokens(Hosts, ",")))
+ end;
+ {ok, {{127, 0, 0, 1}, _Port}} ->
+ case get_header_value("x-forwarded-for") of
+ {undefined, _} ->
+ "127.0.0.1";
+ {Hosts, _} ->
+ string:strip(lists:last(string:tokens(Hosts, ",")))
+ end;
+ {ok, {Addr, _Port}} ->
+ inet_parse:ntoa(Addr)
+ end,
+ NewReqState = ReqState#reqstate{peer=Peer},
+ {Peer, NewReqState};
+ _ ->
+ {ReqState#reqstate.peer, ReqState}
+ end.
+
+call(socket) -> {ReqState#reqstate.socket,ReqState};
+call(get_reqdata) -> {ReqState#reqstate.reqdata, ReqState};
+call({set_reqdata, RD}) -> {ok, ReqState#reqstate{reqdata=RD}};
+call(method) -> {wrq:method(ReqState#reqstate.reqdata), ReqState};
+call(version) -> {wrq:version(ReqState#reqstate.reqdata), ReqState};
+call(raw_path) -> {wrq:raw_path(ReqState#reqstate.reqdata), ReqState};
+call(req_headers) -> {wrq:req_headers(ReqState#reqstate.reqdata), ReqState};
+call({req_body, MaxRecvBody}) ->
+ case ReqState#reqstate.bodyfetch of
+ stream ->
+ {stream_conflict, ReqState};
+ _ ->
+ RD=(ReqState#reqstate.reqdata)#wm_reqdata{
+ max_recv_body=MaxRecvBody},
+ NewReqState=ReqState#reqstate{reqdata=RD},
+ case RD#wm_reqdata.req_body of
+ not_fetched_yet ->
+ NewBody = do_recv_body(NewReqState),
+ NewRD = RD#wm_reqdata{req_body=NewBody},
+ {NewBody, NewReqState#reqstate{
+ bodyfetch=standard,reqdata=NewRD}};
+ X ->
+ {X, ReqState#reqstate{bodyfetch=standard}}
+ end
+ end;
+call({stream_req_body, MaxHunk}) ->
+ case ReqState#reqstate.bodyfetch of
+ standard ->
+ {stream_conflict, ReqState};
+ _ ->
+ {recv_stream_body(ReqState, MaxHunk),
+ ReqState#reqstate{bodyfetch=stream}}
+ end;
+call(resp_headers) -> {wrq:resp_headers(ReqState#reqstate.reqdata), ReqState};
+call(resp_redirect) -> {wrq:resp_redirect(ReqState#reqstate.reqdata), ReqState};
+call({get_resp_header, HdrName}) ->
+ Reply = mochiweb_headers:get_value(HdrName,
+ wrq:resp_headers(ReqState#reqstate.reqdata)),
+ {Reply, ReqState};
+call(get_path_info) ->
+ PropList = dict:to_list(wrq:path_info(ReqState#reqstate.reqdata)),
+ {PropList, ReqState};
+call({get_path_info, Key}) ->
+ {wrq:path_info(Key, ReqState#reqstate.reqdata), ReqState};
+call(peer) -> get_peer();
+call(range) -> get_range();
+call(response_code) -> {wrq:response_code(ReqState#reqstate.reqdata), ReqState};
+call(app_root) -> {wrq:app_root(ReqState#reqstate.reqdata), ReqState};
+call(disp_path) -> {wrq:disp_path(ReqState#reqstate.reqdata), ReqState};
+call(path) -> {wrq:path(ReqState#reqstate.reqdata), ReqState};
+call({get_req_header, K}) ->
+ {wrq:get_req_header(K, ReqState#reqstate.reqdata), ReqState};
+call({set_response_code, Code}) ->
+ {ok, ReqState#reqstate{reqdata=wrq:set_response_code(
+ Code, ReqState#reqstate.reqdata)}};
+call({set_resp_header, K, V}) ->
+ {ok, ReqState#reqstate{reqdata=wrq:set_resp_header(
+ K, V, ReqState#reqstate.reqdata)}};
+call({set_resp_headers, Hdrs}) ->
+ {ok, ReqState#reqstate{reqdata=wrq:set_resp_headers(
+ Hdrs, ReqState#reqstate.reqdata)}};
+call({remove_resp_header, K}) ->
+ {ok, ReqState#reqstate{reqdata=wrq:remove_resp_header(
+ K, ReqState#reqstate.reqdata)}};
+call({merge_resp_headers, Hdrs}) ->
+ {ok, ReqState#reqstate{reqdata=wrq:merge_resp_headers(
+ Hdrs, ReqState#reqstate.reqdata)}};
+call({append_to_response_body, Data}) ->
+ {ok, ReqState#reqstate{reqdata=wrq:append_to_response_body(
+ Data, ReqState#reqstate.reqdata)}};
+call({set_disp_path, P}) ->
+ {ok, ReqState#reqstate{reqdata=wrq:set_disp_path(
+ P, ReqState#reqstate.reqdata)}};
+call(do_redirect) ->
+ {ok, ReqState#reqstate{reqdata=wrq:do_redirect(true,
+ ReqState#reqstate.reqdata)}};
+call({send_response, Code}) ->
+ {Reply, NewState} =
+ case Code of
+ 200 ->
+ send_ok_response();
+ _ ->
+ send_response(Code)
+ end,
+ LogData = NewState#reqstate.log_data,
+ NewLogData = LogData#wm_log_data{finish_time=now()},
+ {Reply, NewState#reqstate{log_data=NewLogData}};
+call(resp_body) -> {wrq:resp_body(ReqState#reqstate.reqdata), ReqState};
+call({set_resp_body, Body}) ->
+ {ok, ReqState#reqstate{reqdata=wrq:set_resp_body(Body,
+ ReqState#reqstate.reqdata)}};
+call(has_resp_body) ->
+ Reply = case wrq:resp_body(ReqState#reqstate.reqdata) of
+ undefined -> false;
+ <<>> -> false;
+ [] -> false;
+ _ -> true
+ end,
+ {Reply, ReqState};
+call({get_metadata, Key}) ->
+ Reply = case dict:find(Key, ReqState#reqstate.metadata) of
+ {ok, Value} -> Value;
+ error -> undefined
+ end,
+ {Reply, ReqState};
+call({set_metadata, Key, Value}) ->
+ NewDict = dict:store(Key, Value, ReqState#reqstate.metadata),
+ {ok, ReqState#reqstate{metadata=NewDict}};
+call(path_tokens) -> {wrq:path_tokens(ReqState#reqstate.reqdata), ReqState};
+call(req_cookie) -> {wrq:req_cookie(ReqState#reqstate.reqdata), ReqState};
+call(req_qs) -> {wrq:req_qs(ReqState#reqstate.reqdata), ReqState};
+call({load_dispatch_data, PathProps, HostTokens, Port,
+ PathTokens, AppRoot, DispPath}) ->
+ PathInfo = dict:from_list(PathProps),
+ NewState = ReqState#reqstate{reqdata=wrq:load_dispatch_data(
+ PathInfo,HostTokens,Port,PathTokens,AppRoot,
+ DispPath,ReqState#reqstate.reqdata)},
+ {ok, NewState};
+call(log_data) -> {ReqState#reqstate.log_data, ReqState}.
+
+get_header_value(K) ->
+ {wrq:get_req_header(K, ReqState#reqstate.reqdata), ReqState}.
+
+get_outheader_value(K) ->
+ {mochiweb_headers:get_value(K,
+ wrq:resp_headers(ReqState#reqstate.reqdata)), ReqState}.
+
+send(Socket, Data) ->
+ case gen_tcp:send(Socket, iolist_to_binary(Data)) of
+ ok -> ok;
+ {error,closed} -> ok;
+ _ -> exit(normal)
+ end.
+
+send_stream_body(Socket, X) -> send_stream_body(Socket, X, 0).
+send_stream_body(Socket, {<<>>, done}, SoFar) ->
+ send_chunk(Socket, <<>>),
+ SoFar;
+send_stream_body(Socket, {Data, done}, SoFar) ->
+ Size = send_chunk(Socket, Data),
+ send_chunk(Socket, <<>>),
+ Size + SoFar;
+send_stream_body(Socket, {<<>>, Next}, SoFar) ->
+ send_stream_body(Socket, Next(), SoFar);
+send_stream_body(Socket, {[], Next}, SoFar) ->
+ send_stream_body(Socket, Next(), SoFar);
+send_stream_body(Socket, {Data, Next}, SoFar) ->
+ Size = send_chunk(Socket, Data),
+ send_stream_body(Socket, Next(), Size + SoFar).
+
+send_writer_body(Socket, {Encoder, Charsetter, BodyFun}) ->
+ put(bytes_written, 0),
+ Writer = fun(Data) ->
+ Size = send_chunk(Socket, Encoder(Charsetter(Data))),
+ put(bytes_written, get(bytes_written) + Size),
+ Size
+ end,
+ BodyFun(Writer),
+ send_chunk(Socket, <<>>),
+ get(bytes_written).
+
+send_chunk(Socket, Data) ->
+ Size = iolist_size(Data),
+ send(Socket, mochihex:to_hex(Size)),
+ send(Socket, <<"\r\n">>),
+ send(Socket, Data),
+ send(Socket, <<"\r\n">>),
+ Size.
+
+send_ok_response() ->
+ RD0 = ReqState#reqstate.reqdata,
+ {Range, State} = get_range(),
+ case Range of
+ X when X =:= undefined; X =:= fail ->
+ send_response(200);
+ Ranges ->
+ {PartList, Size} = range_parts(RD0, Ranges),
+ case PartList of
+ [] -> %% no valid ranges
+ %% could be 416, for now we'll just return 200
+ send_response(200);
+ PartList ->
+ {RangeHeaders, RangeBody} = parts_to_body(PartList, Size),
+ RespHdrsRD = wrq:set_resp_headers(
+ [{"Accept-Ranges", "bytes"} | RangeHeaders], RD0),
+ RespBodyRD = wrq:set_resp_body(
+ RangeBody, RespHdrsRD),
+ NewState = State#reqstate{reqdata=RespBodyRD},
+ send_response(206, NewState)
+ end
+ end.
+
+send_response(Code) -> send_response(Code,ReqState).
+send_response(Code, PassedState=#reqstate{reqdata=RD}) ->
+ Body0 = wrq:resp_body(RD),
+ {Body,Length} = case Body0 of
+ {stream, StreamBody} -> {{stream, StreamBody}, chunked};
+ {writer, WriteBody} -> {{writer, WriteBody}, chunked};
+ _ -> {Body0, iolist_size([Body0])}
+ end,
+ send(PassedState#reqstate.socket,
+ [make_version(wrq:version(RD)),
+ make_code(Code), <<"\r\n">> |
+ make_headers(Code, Length, RD)]),
+ FinalLength = case wrq:method(RD) of
+ 'HEAD' -> Length;
+ _ ->
+ case Body of
+ {stream, Body2} ->
+ send_stream_body(PassedState#reqstate.socket, Body2);
+ {writer, Body2} ->
+ send_writer_body(PassedState#reqstate.socket, Body2);
+ _ ->
+ send(PassedState#reqstate.socket, Body),
+ Length
+ end
+ end,
+ InitLogData = PassedState#reqstate.log_data,
+ FinalLogData = InitLogData#wm_log_data{response_code=Code,
+ response_length=FinalLength},
+ {ok, PassedState#reqstate{reqdata=wrq:set_response_code(Code, RD),
+ log_data=FinalLogData}}.
+
+%% @doc Infer body length from transfer-encoding and content-length headers.
+body_length() ->
+ case get_header_value("transfer-encoding") of
+ {undefined, _} ->
+ case get_header_value("content-length") of
+ {undefined, _} -> undefined;
+ {Length, _} -> list_to_integer(Length)
+ end;
+ {"chunked", _} -> chunked;
+ Unknown -> {unknown_transfer_encoding, Unknown}
+ end.
+
+%% @doc Receive the body of the HTTP request (defined by Content-Length).
+%% Will only receive up to the default max-body length
+do_recv_body(PassedState=#reqstate{reqdata=RD}) ->
+ MRB = RD#wm_reqdata.max_recv_body,
+ read_whole_stream(recv_stream_body(PassedState, MRB), [], MRB, 0).
+
+read_whole_stream({Hunk,_}, _, MaxRecvBody, SizeAcc)
+ when SizeAcc + byte_size(Hunk) > MaxRecvBody ->
+ {error, req_body_too_large};
+read_whole_stream({Hunk,Next}, Acc0, MaxRecvBody, SizeAcc) ->
+ HunkSize = byte_size(Hunk),
+ if SizeAcc + HunkSize > MaxRecvBody ->
+ {error, req_body_too_large};
+ true ->
+ Acc = [Hunk|Acc0],
+ case Next of
+ done -> iolist_to_binary(lists:reverse(Acc));
+ _ -> read_whole_stream(Next(), Acc,
+ MaxRecvBody, SizeAcc + HunkSize)
+ end
+ end.
+
+recv_stream_body(PassedState=#reqstate{reqdata=RD}, MaxHunkSize) ->
+ case get_header_value("expect") of
+ {"100-continue", _} ->
+ send(PassedState#reqstate.socket,
+ [make_version(wrq:version(RD)),
+ make_code(100), <<"\r\n">>]);
+ _Else ->
+ ok
+ end,
+ case body_length() of
+ {unknown_transfer_encoding, X} -> exit({unknown_transfer_encoding, X});
+ undefined -> {<<>>, done};
+ 0 -> {<<>>, done};
+ chunked -> recv_chunked_body(PassedState#reqstate.socket, MaxHunkSize);
+ Length -> recv_unchunked_body(PassedState#reqstate.socket,
+ MaxHunkSize, Length)
+ end.
+
+recv_unchunked_body(Socket, MaxHunk, DataLeft) ->
+ case MaxHunk >= DataLeft of
+ true ->
+ {ok,Data1} = gen_tcp:recv(Socket,DataLeft,?IDLE_TIMEOUT),
+ {Data1, done};
+ false ->
+ {ok,Data2} = gen_tcp:recv(Socket,MaxHunk,?IDLE_TIMEOUT),
+ {Data2,
+ fun() -> recv_unchunked_body(
+ Socket, MaxHunk, DataLeft-MaxHunk)
+ end}
+ end.
+
+recv_chunked_body(Socket, MaxHunk) ->
+ case read_chunk_length(Socket) of
+ 0 -> {<<>>, done};
+ ChunkLength -> recv_chunked_body(Socket,MaxHunk,ChunkLength)
+ end.
+recv_chunked_body(Socket, MaxHunk, LeftInChunk) ->
+ case MaxHunk >= LeftInChunk of
+ true ->
+ {ok,Data1} = gen_tcp:recv(Socket,LeftInChunk,?IDLE_TIMEOUT),
+ {Data1,
+ fun() -> recv_chunked_body(Socket, MaxHunk)
+ end};
+ false ->
+ {ok,Data2} = gen_tcp:recv(Socket,MaxHunk,?IDLE_TIMEOUT),
+ {Data2,
+ fun() -> recv_chunked_body(Socket, MaxHunk, LeftInChunk-MaxHunk)
+ end}
+ end.
-call(Message) -> gen_server:call(Pid, Message, ?TIMEOUT).
+read_chunk_length(Socket) ->
+ inet:setopts(Socket, [{packet, line}]),
+ case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of
+ {ok, Header} ->
+ inet:setopts(Socket, [{packet, raw}]),
+ Splitter = fun (C) ->
+ C =/= $\r andalso C =/= $\n andalso C =/= $
+ end,
+ {Hex, _Rest} = lists:splitwith(Splitter, binary_to_list(Header)),
+ case Hex of
+ [] -> 0;
+ _ -> erlang:list_to_integer(Hex, 16)
+ end;
+ _ ->
+ exit(normal)
+ end.
+
+get_range() ->
+ case get_header_value("range") of
+ {undefined, _} ->
+ {undefined, ReqState#reqstate{range=undefined}};
+ {RawRange, _} ->
+ Range = parse_range_request(RawRange),
+ {Range, ReqState#reqstate{range=Range}}
+ end.
+
+range_parts(_RD=#wm_reqdata{resp_body={file, IoDevice}}, Ranges) ->
+ Size = iodevice_size(IoDevice),
+ F = fun (Spec, Acc) ->
+ case range_skip_length(Spec, Size) of
+ invalid_range ->
+ Acc;
+ V ->
+ [V | Acc]
+ end
+ end,
+ LocNums = lists:foldr(F, [], Ranges),
+ {ok, Data} = file:pread(IoDevice, LocNums),
+ Bodies = lists:zipwith(fun ({Skip, Length}, PartialBody) ->
+ {Skip, Skip + Length - 1, PartialBody}
+ end,
+ LocNums, Data),
+ {Bodies, Size};
+
+range_parts(RD=#wm_reqdata{resp_body={stream, {Hunk,Next}}}, Ranges) ->
+ % for now, streamed bodies are read in full for range requests
+ MRB = RD#wm_reqdata.max_recv_body,
+ range_parts(read_whole_stream({Hunk,Next}, [], MRB, 0), Ranges);
+
+range_parts(_RD=#wm_reqdata{resp_body=Body0}, Ranges) ->
+ Body = iolist_to_binary(Body0),
+ Size = size(Body),
+ F = fun(Spec, Acc) ->
+ case range_skip_length(Spec, Size) of
+ invalid_range ->
+ Acc;
+ {Skip, Length} ->
+ <<_:Skip/binary, PartialBody:Length/binary, _/binary>> = Body,
+ [{Skip, Skip + Length - 1, PartialBody} | Acc]
+ end
+ end,
+ {lists:foldr(F, [], Ranges), Size}.
+
+range_skip_length(Spec, Size) ->
+ case Spec of
+ {none, R} when R =< Size, R >= 0 ->
+ {Size - R, R};
+ {none, _OutOfRange} ->
+ {0, Size};
+ {R, none} when R >= 0, R < Size ->
+ {R, Size - R};
+ {_OutOfRange, none} ->
+ invalid_range;
+ {Start, End} when 0 =< Start, Start =< End, End < Size ->
+ {Start, End - Start + 1};
+ {_OutOfRange, _End} ->
+ invalid_range
+ end.
+
+parse_range_request(RawRange) when is_list(RawRange) ->
+ try
+ "bytes=" ++ RangeString = RawRange,
+ Ranges = string:tokens(RangeString, ","),
+ lists:map(fun ("-" ++ V) ->
+ {none, list_to_integer(V)};
+ (R) ->
+ case string:tokens(R, "-") of
+ [S1, S2] ->
+ {list_to_integer(S1), list_to_integer(S2)};
+ [S] ->
+ {list_to_integer(S), none}
+ end
+ end,
+ Ranges)
+ catch
+ _:_ ->
+ fail
+ end.
+
+parts_to_body([{Start, End, Body}], Size) ->
+ %% return body for a range reponse with a single body
+ ContentType =
+ case get_outheader_value("content-type") of
+ {undefined, _} ->
+ "text/html";
+ {CT, _} ->
+ CT
+ end,
+ HeaderList = [{"Content-Type", ContentType},
+ {"Content-Range",
+ ["bytes ",
+ make_io(Start), "-", make_io(End),
+ "/", make_io(Size)]}],
+ {HeaderList, Body};
+parts_to_body(BodyList, Size) when is_list(BodyList) ->
+ %% return
+ %% header Content-Type: multipart/byteranges; boundary=441934886133bdee4
+ %% and multipart body
+ ContentType =
+ case get_outheader_value("content-type") of
+ {undefined, _} ->
+ "text/html";
+ {CT, _} ->
+ CT
+ end,
+ Boundary = mochihex:to_hex(crypto:rand_bytes(8)),
+ HeaderList = [{"Content-Type",
+ ["multipart/byteranges; ",
+ "boundary=", Boundary]}],
+ MultiPartBody = multipart_body(BodyList, ContentType, Boundary, Size),
+ {HeaderList, MultiPartBody}.
+
+multipart_body([], _ContentType, Boundary, _Size) ->
+ ["--", Boundary, "--\r\n"];
+multipart_body([{Start, End, Body} | BodyList], ContentType, Boundary, Size) ->
+ ["--", Boundary, "\r\n",
+ "Content-Type: ", ContentType, "\r\n",
+ "Content-Range: ",
+ "bytes ", make_io(Start), "-", make_io(End),
+ "/", make_io(Size), "\r\n\r\n",
+ Body, "\r\n"
+ | multipart_body(BodyList, ContentType, Boundary, Size)].
+
+iodevice_size(IoDevice) ->
+ {ok, Size} = file:position(IoDevice, eof),
+ {ok, 0} = file:position(IoDevice, bof),
+ Size.
+
+make_io(Atom) when is_atom(Atom) ->
+ atom_to_list(Atom);
+make_io(Integer) when is_integer(Integer) ->
+ integer_to_list(Integer);
+make_io(Io) when is_list(Io); is_binary(Io) ->
+ Io.
+
+make_code(X) when is_integer(X) ->
+ [integer_to_list(X), [" " | httpd_util:reason_phrase(X)]];
+make_code(Io) when is_list(Io); is_binary(Io) ->
+ Io.
+
+make_version({1, 0}) ->
+ <<"HTTP/1.0 ">>;
+make_version(_) ->
+ <<"HTTP/1.1 ">>.
+
+make_headers(Code, Length, RD) ->
+ Hdrs0 = case Code of
+ 304 ->
+ mochiweb_headers:make(wrq:resp_headers(RD));
+ _ ->
+ case Length of
+ chunked ->
+ mochiweb_headers:enter(
+ "Transfer-Encoding","chunked",
+ mochiweb_headers:make(wrq:resp_headers(RD)));
+ _ ->
+ mochiweb_headers:enter(
+ "Content-Length",integer_to_list(Length),
+ mochiweb_headers:make(wrq:resp_headers(RD)))
+ end
+ end,
+ ServerHeader = "MochiWeb/1.1 WebMachine/" ++ ?WMVSN ++ " (" ++ ?QUIP ++ ")",
+ WithSrv = mochiweb_headers:enter("Server", ServerHeader, Hdrs0),
+ Hdrs = case mochiweb_headers:get_value("date", WithSrv) of
+ undefined ->
+ mochiweb_headers:enter("Date", httpd_util:rfc1123_date(), WithSrv);
+ _ ->
+ WithSrv
+ end,
+ F = fun({K, V}, Acc) ->
+ [make_io(K), <<": ">>, V, <<"\r\n">> | Acc]
+ end,
+ lists:foldl(F, [<<"\r\n">>], mochiweb_headers:to_list(Hdrs)).
get_reqdata() -> call(get_reqdata).
@@ -129,14 +670,11 @@ parse_qs() -> req_qs().
get_qs_value(Key) -> proplists:get_value(Key, req_qs()).
get_qs_value(Key, Default) -> proplists:get_value(Key, req_qs(), Default).
-stop() -> gen_server:cast(Pid, stop).
-
set_resp_body(Body) -> call({set_resp_body, Body}).
resp_body() -> call(resp_body).
response_body() -> resp_body().
get_req_header(K) -> call({get_req_header, K}).
-get_header_value(K) -> get_req_header(K).
set_resp_header(K, V) -> call({set_resp_header, K, V}).
add_response_header(K, V) -> set_resp_header(K, V).
@@ -156,8 +694,6 @@ do_redirect() -> call({do_redirect}).
resp_redirect() -> call({resp_redirect}).
-send_response(Code) -> call({send_response, Code}).
-
get_metadata(Key) -> call({get_metadata, Key}).
set_metadata(Key, Value) -> call({set_metadata, Key, Value}).
@@ -172,7 +708,9 @@ get_path_tokens() -> path_tokens().
app_root() -> call(app_root).
get_app_root() -> app_root().
-load_dispatch_data(Host, Bindings, PathTokens, AppRoot, DispPath, Req) ->
- call({load_dispatch_data, Host, Bindings, PathTokens, AppRoot, DispPath, Req}).
+load_dispatch_data(Bindings, HostTokens, Port, PathTokens,
+ AppRoot, DispPath) ->
+ call({load_dispatch_data, Bindings, HostTokens, Port,
+ PathTokens, AppRoot, DispPath}).
log_data() -> call(log_data).
View
606 src/webmachine_request_srv.erl
@@ -1,606 +0,0 @@
-%% @author Andy Gross <andy@basho.com>
-%% @author Justin Sheehy <justin@basho.com>
-%% @copyright 2007-2009 Basho Technologies
-%% Portions derived from code Copyright 2007-2008 Bob Ippolito, Mochi Media
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-
--module(webmachine_request_srv).
--author('Justin Sheehy <justin@basho.com>').
--author('Andy Gross <andy@basho.com>').
--behaviour(gen_server).
--export([start_link/5]).
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
--include("webmachine_logger.hrl").
--include_lib("include/wm_reqdata.hrl").
-
--define(WMVSN, "1.3").
--define(QUIP, "scale automagically into the cloud").
-
-% 120 second default idle timeout
--define(IDLE_TIMEOUT, infinity).
--record(state, {socket=undefined,
- metadata=dict:new(),
- range=undefined,
- peer=undefined,
- reqdata=undefined,
- bodyfetch=undefined,
- log_data=#wm_log_data{}
- }).
-
-start_link(Socket, Method, RawPath, Version, Headers) ->
- gen_server:start_link(?MODULE,
- [Socket, Method, RawPath, Version, Headers], []).
-
-init([Socket, Method, RawPath, Version, Headers]) ->
- %%process_flag(trap_exit, true),
- %% Calling get_peer() here is a little bit of an ugly way to populate the
- %% client IP address but it will do for now.
- {Peer, State} = get_peer(#state{socket=Socket,
- reqdata=wrq:create(Method,Version,RawPath,Headers)}),
- PeerState = State#state{reqdata=wrq:set_peer(Peer,State#state.reqdata)},
- LogData = #wm_log_data{start_time=now(),
- method=Method,
- headers=Headers,
- peer=State#state.peer,
- path=RawPath,
- version=Version,
- response_code=404,
- response_length=0},
- {ok, PeerState#state{log_data=LogData}}.
-
-handle_call(socket, _From, State) ->
- Reply = State#state.socket,
- {reply, Reply, State};
-handle_call(get_reqdata, _From, State) ->
- {reply, State#state.reqdata, State};
-handle_call({set_reqdata, RD=#wm_reqdata{req_body=RBody}}, _From, State) ->
- TheRD = case RBody of
- not_fetched_yet ->
- OldRD = State#state.reqdata,
- OldBody = OldRD#wm_reqdata.req_body,
- RD#wm_reqdata{req_body=OldBody};
- _ ->
- RD
- end,
- {reply, ok, State#state{reqdata=TheRD}};
-handle_call(method, _From, State) ->
- {reply, wrq:method(State#state.reqdata), State};
-handle_call(version, _From, State) ->
- {reply, wrq:version(State#state.reqdata), State};
-handle_call(raw_path, _From, State) ->
- {reply, wrq:raw_path(State#state.reqdata), State};
-handle_call(req_headers, _From, State) ->
- {reply, wrq:req_headers(State#state.reqdata), State};
-handle_call(req_body, _From, State=#state{bodyfetch=stream}) ->
- {reply, stream_conflict, State};
-handle_call({req_body, MaxRecvBody}, _From, State0=#state{reqdata=RD0}) ->
- RD=RD0#wm_reqdata{max_recv_body=MaxRecvBody},
- State=State0#state{reqdata=RD},
- {Body, FinalState} = case RD#wm_reqdata.req_body of
- not_fetched_yet ->
- NewBody = do_recv_body(State),
- NewRD = RD#wm_reqdata{req_body=NewBody},
- {NewBody, State#state{bodyfetch=standard,reqdata=NewRD}};
- X ->
- {X, State#state{bodyfetch=standard}}
- end,
- {reply, Body, FinalState};
-handle_call({stream_req_body,_}, _From, State=#state{bodyfetch=standard}) ->
- {reply, stream_conflict, State};
-handle_call({stream_req_body, MaxHunk}, _From, State) ->
- {reply, recv_stream_body(State, MaxHunk), State#state{bodyfetch=stream}};
-handle_call(resp_headers, _From, State) ->
- {reply, wrq:resp_headers(State#state.reqdata), State};
-handle_call(resp_redirect, _From, State) ->
- {reply, wrq:resp_redirect(State#state.reqdata), State};
-handle_call({get_resp_header, HdrName}, _From, State) ->
- Reply = mochiweb_headers:get_value(HdrName,
- wrq:resp_headers(State#state.reqdata)),
- {reply, Reply, State};
-handle_call(get_path_info, _From, State) ->
- PropList = dict:to_list(wrq:path_info(State#state.reqdata)),
- {reply, PropList, State};
-handle_call({get_path_info, Key}, _From, State) ->
- {reply, wrq:path_info(Key, State#state.reqdata), State};
-handle_call(peer, _From, State) ->
- {Reply, NewState} = get_peer(State),
- {reply, Reply, NewState};
-handle_call(range, _From, State) ->
- {Reply, NewState} = get_range(State),
- {reply, Reply, NewState};
-handle_call(response_code, _From, State) ->
- {reply, wrq:response_code(State#state.reqdata), State};
-handle_call(app_root, _From, State) ->
- {reply, wrq:app_root(State#state.reqdata), State};
-handle_call(disp_path, _From, State) ->
- {reply, wrq:disp_path(State#state.reqdata), State};
-handle_call(path, _From, State) ->
- {reply, wrq:path(State#state.reqdata), State};
-handle_call({get_req_header, K}, _From, State) ->
- {reply, wrq:get_req_header(K, State#state.reqdata), State};
-handle_call({set_response_code, Code}, _From, State) ->
- NewState = State#state{reqdata=wrq:set_response_code(
- Code, State#state.reqdata)},
- {reply, ok, NewState};
-handle_call({set_resp_header, K, V}, _From, State) ->
- NewState = State#state{reqdata=wrq:set_resp_header(
- K, V, State#state.reqdata)},
- {reply, ok, NewState};
-handle_call({set_resp_headers, Hdrs}, _From, State) ->
- NewState = State#state{reqdata=wrq:set_resp_headers(
- Hdrs, State#state.reqdata)},
- {reply, ok, NewState};
-handle_call({remove_resp_header, K}, _From, State) ->
- NewState = State#state{reqdata=wrq:remove_resp_header(
- K, State#state.reqdata)},
- {reply, ok, NewState};
-handle_call({merge_resp_headers, Hdrs}, _From, State) ->
- NewState = State#state{reqdata=wrq:merge_resp_headers(
- Hdrs, State#state.reqdata)},
- {reply, ok, NewState};
-handle_call({append_to_response_body, Data}, _From, State) ->
- NewState = State#state{reqdata=wrq:append_to_response_body(
- Data, State#state.reqdata)},
- {reply, ok, NewState};
-handle_call({set_disp_path, P}, _From, State) ->
- NewState = State#state{reqdata=wrq:set_disp_path(
- P, State#state.reqdata)},
- {reply, ok, NewState};
-handle_call(do_redirect, _From, State) ->
- NewState = State#state{reqdata=wrq:do_redirect(true,
- State#state.reqdata)},
- {reply, ok, NewState};
-handle_call({send_response, Code}, _From, State) ->
- {Reply, NewState} =
- case Code of
- 200 ->
- send_ok_response(Code, State);
- _ ->
- send_response(Code, State)
- end,
- LogData = NewState#state.log_data,
- NewLogData = LogData#wm_log_data{finish_time=now()},
- {reply, Reply, NewState#state{log_data=NewLogData}};
-handle_call(resp_body, _From, State) ->
- {reply, wrq:resp_body(State#state.reqdata), State};
-handle_call({set_resp_body, Body}, _From, State) ->
- NewState = State#state{reqdata=wrq:set_resp_body(Body,
- State#state.reqdata)},
- {reply, ok, NewState};
-handle_call(has_resp_body, _From, State) ->
- Reply = case wrq:resp_body(State#state.reqdata) of
- undefined -> false;
- <<>> -> false;
- [] -> false;
- _ -> true
- end,
- {reply, Reply, State};
-handle_call({get_metadata, Key}, _From, State) ->
- Reply = case dict:find(Key, State#state.metadata) of
- {ok, Value} -> Value;
- error -> undefined
- end,
- {reply, Reply, State};
-handle_call({set_metadata, Key, Value}, _From, State) ->
- NewDict = dict:store(Key, Value, State#state.metadata),
- {reply, ok, State#state{metadata=NewDict}};
-handle_call(path_tokens, _From, State) ->
- {reply, wrq:path_tokens(State#state.reqdata), State};
-handle_call(req_cookie, _From, State) ->
- {reply, wrq:req_cookie(State#state.reqdata), State};
-handle_call(req_qs, _From, State) ->
- {reply, wrq:req_qs(State#state.reqdata), State};
-handle_call({load_dispatch_data, Host,PathProps,PathTokens,AppRoot,DispPath,WMReq},
- _From, State) ->
- PathInfo = dict:from_list(PathProps),
- NewState = State#state{reqdata=wrq:load_dispatch_data(
- Host,PathInfo,PathTokens,AppRoot,DispPath,WMReq,State#state.reqdata)},
- {reply, ok, NewState};
-handle_call(log_data, _From, State) -> {reply, State#state.log_data, State}.
-
-handle_cast(stop, State) -> {stop, normal, State}.
-
-handle_info(_Info, State) -> {noreply, State}.
-
-terminate(_Reason, _State) -> ok.
-
-code_change(_OldVsn, State, _Extra) -> {ok, State}.
-
-get_peer(State) ->
- case State#state.peer of
- undefined ->
- Socket = State#state.socket,
- Peer = case inet:peername(Socket) of
- {ok, {Addr={10, _, _, _}, _Port}} ->
- case get_header_value("x-forwarded-for", State) of
- {undefined, _} ->
- inet_parse:ntoa(Addr);
- {Hosts, _} ->
- string:strip(lists:last(string:tokens(Hosts, ",")))
- end;
- {ok, {{127, 0, 0, 1}, _Port}} ->
- case get_header_value("x-forwarded-for", State) of
- {undefined, _} ->
- "127.0.0.1";
- {Hosts, _} ->
- string:strip(lists:last(string:tokens(Hosts, ",")))
- end;
- {ok, {Addr, _Port}} ->
- inet_parse:ntoa(Addr)
- end,
- NewState = State#state{peer=Peer},
- {Peer, NewState};
- _ ->
- {State#state.peer, State}
- end.
-
-get_header_value(K, State) ->
- {wrq:get_req_header(K, State#state.reqdata), State}.
-
-get_outheader_value(K, State) ->
- {mochiweb_headers:get_value(K,
- wrq:resp_headers(State#state.reqdata)), State}.
-
-send(Socket, Data) ->
- case gen_tcp:send(Socket, iolist_to_binary(Data)) of
- ok -> ok;
- {error,closed} -> ok;
- _ -> exit(normal)
- end.
-
-send_stream_body(Socket, X) -> send_stream_body(Socket, X, 0).
-send_stream_body(Socket, {Data, done}, SoFar) ->
- Size = send_chunk(Socket, Data),
- send_chunk(Socket, <<>>),
- Size + SoFar;
-send_stream_body(Socket, {Data, Next}, SoFar) ->
- Size = send_chunk(Socket, Data),
- send_stream_body(Socket, Next(), Size + SoFar).
-
-send_chunk(Socket, Data) ->
- Size = iolist_size(Data),
- send(Socket, mochihex:to_hex(Size)),
- send(Socket, <<"\r\n">>),
- send(Socket, Data),
- send(Socket, <<"\r\n">>),
- Size.
-
-send_ok_response(200, InitState) ->
- RD0 = InitState#state.reqdata,
- {Range, State} = get_range(InitState),
- case Range of
- X when X =:= undefined; X =:= fail ->
- send_response(200, State);
- Ranges ->
- {PartList, Size} = range_parts(RD0, Ranges),
- case PartList of
- [] -> %% no valid ranges
- %% could be 416, for now we'll just return 200
- send_response(200, State);
- PartList ->
- {RangeHeaders, RangeBody} =
- parts_to_body(PartList, State, Size),
- RespHdrsRD = wrq:set_resp_headers(
- [{"Accept-Ranges", "bytes"} | RangeHeaders], RD0),
- RespBodyRD = wrq:set_resp_body(
- RangeBody, RespHdrsRD),
- NewState = State#state{reqdata=RespBodyRD},
- send_response(206, NewState)
- end
- end.
-
-send_response(Code, State=#state{reqdata=RD}) ->
- Body0 = wrq:resp_body(RD),
- {Body,Length} = case Body0 of
- {stream, StreamBody} -> {StreamBody, chunked};
- _ -> {Body0, iolist_size([Body0])}
- end,
- send(State#state.socket,
- [make_version(wrq:version(RD)),
- make_code(Code), <<"\r\n">> |
- make_headers(Code, Length, RD)]),
- FinalLength = case wrq:method(RD) of
- 'HEAD' -> Length;
- _ ->
- case Length of
- chunked -> send_stream_body(State#state.socket, Body);
- _ -> send(State#state.socket, Body), Length
- end
- end,
- InitLogData = State#state.log_data,
- FinalLogData = InitLogData#wm_log_data{response_code=Code,
- response_length=FinalLength},
- {ok, State#state{reqdata=wrq:set_response_code(Code, RD),
- log_data=FinalLogData}}.
-
-%% @spec body_length(state()) -> undefined | chunked | unknown_transfer_encoding | integer()
-%% @doc Infer body length from transfer-encoding and content-length headers.
-body_length(State) ->
- case get_header_value("transfer-encoding", State) of
- {undefined, _} ->
- case get_header_value("content-length", State) of
- {undefined, _} -> undefined;
- {Length, _} -> list_to_integer(Length)
- end;
- {"chunked", _} -> chunked;
- Unknown -> {unknown_transfer_encoding, Unknown}
- end.
-
-%% @spec do_recv_body(state()) -> binary()
-%% @doc Receive the body of the HTTP request (defined by Content-Length).
-%% Will only receive up to the default max-body length
-do_recv_body(State=#state{reqdata=RD}) ->
- MRB = RD#wm_reqdata.max_recv_body,
- read_whole_stream(recv_stream_body(State, MRB), [], MRB, 0).
-
-read_whole_stream({Hunk,_}, _, MaxRecvBody, SizeAcc)
- when SizeAcc + byte_size(Hunk) > MaxRecvBody ->
- {error, req_body_too_large};
-read_whole_stream({Hunk,Next}, Acc0, MaxRecvBody, SizeAcc) ->
- HunkSize = byte_size(Hunk),
- if SizeAcc + HunkSize > MaxRecvBody ->
- {error, req_body_too_large};
- true ->
- Acc = [Hunk|Acc0],
- case Next of
- done -> iolist_to_binary(lists:reverse(Acc));
- _ -> read_whole_stream(Next(), Acc,
- MaxRecvBody, SizeAcc + HunkSize)
- end
- end.
-
-recv_stream_body(State = #state{reqdata=RD}, MaxHunkSize) ->
- case get_header_value("expect", State) of
- {"100-continue", _} ->
- send(State#state.socket,
- [make_version(wrq:version(RD)),
- make_code(100), <<"\r\n">>]);
- _Else ->
- ok
- end,
- case body_length(State) of
- {unknown_transfer_encoding, X} -> exit({unknown_transfer_encoding, X});
- undefined -> {<<>>, done};
- 0 -> {<<>>, done};
- chunked -> recv_chunked_body(State#state.socket, MaxHunkSize);
- Length -> recv_unchunked_body(State#state.socket, MaxHunkSize, Length)
- end.
-
-recv_unchunked_body(Socket, MaxHunk, DataLeft) ->
- case MaxHunk >= DataLeft of
- true ->
- {ok,Data1} = gen_tcp:recv(Socket,DataLeft,?IDLE_TIMEOUT),
- {Data1, done};
- false ->
- {ok,Data2} = gen_tcp:recv(Socket,MaxHunk,?IDLE_TIMEOUT),
- {Data2,
- fun() -> recv_unchunked_body(
- Socket, MaxHunk, DataLeft-MaxHunk)
- end}
- end.
-
-recv_chunked_body(Socket, MaxHunk) ->
- case read_chunk_length(Socket) of
- 0 -> {<<>>, done};
- ChunkLength -> recv_chunked_body(Socket,MaxHunk,ChunkLength)
- end.
-recv_chunked_body(Socket, MaxHunk, LeftInChunk) ->
- case MaxHunk >= LeftInChunk of
- true ->
- {ok,Data1} = gen_tcp:recv(Socket,LeftInChunk,?IDLE_TIMEOUT),
- {Data1,
- fun() -> recv_chunked_body(Socket, MaxHunk)
- end};
- false ->
- {ok,Data2} = gen_tcp:recv(Socket,MaxHunk,?IDLE_TIMEOUT),
- {Data2,
- fun() -> recv_chunked_body(Socket, MaxHunk, LeftInChunk-MaxHunk)
- end}
- end.
-
-read_chunk_length(Socket) ->
- inet:setopts(Socket, [{packet, line}]),
- case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of
- {ok, Header} ->
- inet:setopts(Socket, [{packet, raw}]),
- Splitter = fun (C) ->
- C =/= $\r andalso C =/= $\n andalso C =/= $
- end,
- {Hex, _Rest} = lists:splitwith(Splitter, binary_to_list(Header)),
- case Hex of
- [] -> 0;
- _ -> erlang:list_to_integer(Hex, 16)
- end;
- _ ->
- exit(normal)
- end.
-
-get_range(State) ->
- case get_header_value("range", State) of
- {undefined, _} ->
- {undefined, State#state{range=undefined}};
- {RawRange, _} ->
- Range = parse_range_request(RawRange),
- {Range, State#state{range=Range}}
- end.
-
-range_parts(_RD=#wm_reqdata{resp_body={file, IoDevice}}, Ranges) ->
- Size = iodevice_size(IoDevice),
- F = fun (Spec, Acc) ->
- case range_skip_length(Spec, Size) of
- invalid_range ->
- Acc;