Commits

Anonymous committed d72b909

removal of the request_srv process

Comments (0)

Files changed (13)

include/wm_reqdata.hrl

--record(wm_reqdata, {method, version, peer, wmreq,
+-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,

include/wm_reqstate.hrl

+-record(reqstate, {socket=undefined,
+                   metadata=dict:new(),
+                   range=undefined,
+                   peer=undefined,
+                   reqdata=undefined,
+                   bodyfetch=undefined,
+                   log_data=undefined
+                  }).
+

priv/skel/src/skel.erl

 start_link() ->
     skel_deps:ensure(),
     ensure_started(crypto),
+    application:set_env(webmachine, webmachine_logger_module, 
+                        webmachine_logger),
     ensure_started(webmachine),
     skel_sup:start_link().
 
 start() ->
     skel_deps:ensure(),
     ensure_started(crypto),
+    application:set_env(webmachine, webmachine_logger_module, 
+                        webmachine_logger),
     ensure_started(webmachine),
     application:start(skel).
 

src/webmachine.erl

 -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() ->
     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}).
+
+
 
 
   

src/webmachine_decision_core.erl

 -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
     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}).
 
 	404 ->
 	    {ok, ErrorHandler} = application:get_env(webmachine, error_handler),
 	    Reason = {none, none, []},
-	    ErrorHTML = ErrorHandler:render_error(Code, get(req), Reason),
+	    ErrorHTML = ErrorHandler:render_error(
+                          Code, {webmachine_request,get(reqstate)}, Reason),
             wrcall({set_resp_body, ErrorHTML});
         304 ->
             wrcall({remove_resp_header, "Content-Type"}),
     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}),
 
 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) ->
     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);
 	    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).

src/webmachine_error_handler.erl

 
 render_error(Code, Req, Reason) ->
     case Req:has_response_body() of
-        true -> Req:response_body();
-        false -> render_error_body(Code, Req, Reason)
+        {true,_} -> Req:response_body();
+        {false,_} -> render_error_body(Code, Req, Reason)
     end.
 
 render_error_body(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>">>;
+    {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) ->
-    Req:add_response_header("Content-Type", "text/html"),
-    error_logger:error_msg("webmachine error: path=~p~n~p~n", [Req:path(), 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_body(501, Req, _Reason) ->
-    Req:add_response_header("Content-Type", "text/html"),
+    {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_body(503, Req, _Reason) ->
-    Req:add_response_header("Content-Type", "text/html"),
+    {ok, ReqState} = Req:add_response_header("Content-Type", "text/html"),
     error_logger:error_msg("Webmachine cannot fulfill"
                            " the request at this time"),
     ErrorStr = "<html><head><title>503 Service Unavailable</title>"
                "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}.
 

src/webmachine_logger.erl

     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)}.
 

src/webmachine_mochiweb.erl

                [H|_] -> H;
                [] -> []
            end,
-    case webmachine_dispatcher:dispatch(Host, Req:path(), DispatchList) of
+    {Path, _} = Req:path(),
+    case webmachine_dispatcher:dispatch(Host, Path, DispatchList) of
         {no_dispatch_match, _UnmatchedHost, _UnmatchedPathTokens} ->
             {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();
+	    {ErrorHTML,ReqState1} = 
+                ErrorHandler:render_error(404, Req, {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(Bindings,HostTokens,Port,PathTokens,
-                                   AppRoot,StringPath,Req),
-	    Req:set_metadata('resource_module', Mod),
-            webmachine_decision_core:handle_request(Req, Resource)
+            {ok,RS1} = Req:load_dispatch_data(Bindings,HostTokens,Port,
+                                              PathTokens,AppRoot,StringPath),
+            XReq1 = {webmachine_request,RS1},
+            {ok,RS2} = XReq1:set_metadata('resource_module', Mod),
+            webmachine_decision_core:handle_request(Resource, RS2)
     end.
 
 get_option(Option, Options) ->
     {proplists:get_value(Option, Options), proplists:delete(Option, Options)}.
 
 host_headers(Req) ->
-    [ V || V <- [Req:get_header_value(H)
-                 || H <- ["x-forwarded-for",
-                          "x-forwarded-host",
-                          "x-forwarded-server",
-                          "host"]],
+    [ V || {V,_ReqState} <- [Req:get_header_value(H)
+                             || H <- ["x-forwarded-for",
+                                      "x-forwarded-host",
+                                      "x-forwarded-server",
+                                      "host"]],
            V /= undefined].

src/webmachine_request.erl

 
 %% @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([
          get_reqdata/0,
          set_reqdata/1,
          disp_path/0,
 	 path/0,
 	 raw_path/0,
+         get_req_header/1,
 	 req_headers/0,
 	 req_body/1,
 	 stream_req_body/1,
          set_resp_body/1,
 	 response_body/0,
 	 has_response_body/0,
-	 stop/0,
          do_redirect/0,
          resp_redirect/0,
 	 set_metadata/2,
 	 get_metadata/1,
 	 get_path_info/0,
 	 get_path_info/1,
-	 load_dispatch_data/7,
+	 load_dispatch_data/6,
 	 get_path_tokens/0,
 	 get_app_root/0,
 	 parse_cookie/0,
 	 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").
 
-call(Message) -> gen_server:call(Pid, Message, ?TIMEOUT).
+-define(WMVSN, "1.5.1").
+-define(QUIP, "hack the charles gibson").
+-define(IDLE_TIMEOUT, infinity).
+
+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_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} -> {StreamBody, 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 Length of
+                chunked -> send_stream_body(PassedState#reqstate.socket, Body);
+                _ -> 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.
+
+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).
 
 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).
 
 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}).
 get_app_root() -> app_root().
 
 load_dispatch_data(Bindings, HostTokens, Port, PathTokens,
-                   AppRoot, DispPath, Req) ->
+                   AppRoot, DispPath) ->
     call({load_dispatch_data, Bindings, HostTokens, Port,
-          PathTokens, AppRoot, DispPath, Req}).
+          PathTokens, AppRoot, DispPath}).
 
 log_data() -> call(log_data).

src/webmachine_request_srv.erl

-%% @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.5").
--define(QUIP, "hack the charles gibson").
-
-% 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, PathProps,HostTokens,Port,
-             PathTokens,AppRoot,DispPath,WMReq},
-            _From, State) ->
-    PathInfo = dict:from_list(PathProps),
-    NewState = State#state{reqdata=wrq:load_dispatch_data(
-               PathInfo,HostTokens,Port,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, {<<>>, 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_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;
-                    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}], State, Size) ->
-    %% return body for a range reponse with a single body
-    ContentType = 
-	case get_outheader_value("content-type", State) 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, State, Size) when is_list(BodyList) ->
-    %% return
-    %% header Content-Type: multipart/byteranges; boundary=441934886133bdee4
-    %% and multipart body
-    ContentType = 
-	case get_outheader_value("content-type", State) 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)).
-

src/webmachine_resource.erl

 -export([wrap/2]).
 -export([do/2,log_d/1,stop/0]).
 
+-include_lib("include/wm_reqdata.hrl").
+-include_lib("include/wm_reqstate.hrl").
+
 default(ping) ->
     no_default;
 default(service_available) ->
 
 do(Fun, ReqProps) when is_atom(Fun) andalso is_list(ReqProps) ->
     Self = proplists:get_value(resource, ReqProps),
-    Req = proplists:get_value(req, ReqProps),
-    RD0 = Req:get_reqdata(),
-    {Reply, RD1, NewModState} = handle_wm_call(Fun, RD0),
+    RState0 = proplists:get_value(reqstate, ReqProps),
+    put(tmp_reqstate, empty),
+    {Reply, ReqData, NewModState} = handle_wm_call(Fun, 
+                    (RState0#reqstate.reqdata)#wm_reqdata{wm_state=RState0}),
     case Reply of
         {error, Err} -> {Err, Self};
-        _ -> 
-            Req:set_reqdata(RD1),
+        _ ->
+            ReqState = case get(tmp_reqstate) of
+                empty -> RState0;
+                X -> X
+            end,
             {Reply,
-            webmachine_resource:new(R_Mod, NewModState, R_ModExports, R_Trace)}
+            webmachine_resource:new(R_Mod, NewModState, R_ModExports, R_Trace),
+            ReqState#reqstate{reqdata=ReqData}}
     end.
 
 handle_wm_call(Fun, ReqData) ->
         no_default ->
             resource_call(Fun, ReqData);
         Default ->
-            case dict:is_key(Fun, R_ModExports) of % XXX SLOW PROBABLY
+            case dict:is_key(Fun, R_ModExports) of
                 true ->
                     resource_call(Fun, ReqData);
                 false ->

src/webmachine_sup.erl

     supervisor:start_link({local, ?MODULE}, ?MODULE, []).
 
 start_logger(BaseDir) ->
-    LoggerModule = 
-	case application:get_env(webmachine, webmachine_logger_module) of
-	    {ok, Val} -> Val;
-	    _ -> webmachine_logger
-	end,
-    ChildSpec = 
-	{webmachine_logger,
-	 {LoggerModule, start_link, [BaseDir]},
-	 permanent, 5000, worker, dynamic},
-    supervisor:start_child(?MODULE, ChildSpec).
+    case application:get_env(webmachine, webmachine_logger_module) of
+        {ok, LoggerModule} ->
+            ChildSpec = 
+                {webmachine_logger,
+                 {LoggerModule, start_link, [BaseDir]},
+                 permanent, 5000, worker, dynamic},
+            supervisor:start_child(?MODULE, ChildSpec);
+        _ -> nop
+    end.
 
 start_perf_logger(BaseDir) ->
     ChildSpec = 
 -module(wrq).
 -author('Justin Sheehy <justin@basho.com>').
 
--export([create/4,load_dispatch_data/8]).
+-export([create/4,load_dispatch_data/7]).
 -export([method/1,version/1,peer/1,disp_path/1,path/1,raw_path/1,path_info/1,
          response_code/1,req_cookie/1,req_qs/1,req_headers/1,req_body/1,
          stream_req_body/2,resp_redirect/1,resp_headers/1,resp_body/1,
 
 % @type reqdata(). The opaque data type used for req/resp data structures.
 -include_lib("include/wm_reqdata.hrl").
+-include_lib("include/wm_reqstate.hrl").
 
 create(Method,Version,RawPath,Headers) ->
     create(#wm_reqdata{method=Method,version=Version,
                        raw_path=RawPath,req_headers=Headers,
-      wmreq=defined_in_load_dispatch_data,
+      wm_state=defined_on_call,
       path="defined_in_create",
       req_cookie=defined_in_create,
       req_qs=defined_in_create,
     ReqQS = mochiweb_util:parse_qs(QueryString),
     RD#wm_reqdata{path=Path,req_cookie=Cookie,req_qs=ReqQS}.
 load_dispatch_data(PathInfo, HostTokens, Port, PathTokens, AppRoot,
-                   DispPath, WMReq, RD) ->
+                   DispPath, RD) ->
     RD#wm_reqdata{path_info=PathInfo,host_tokens=HostTokens,
                   port=Port,path_tokens=PathTokens,
-                  app_root=AppRoot,disp_path=DispPath,wmreq=WMReq}.
+                  app_root=AppRoot,disp_path=DispPath}.
 
 method(_RD = #wm_reqdata{method=Method}) -> Method.
 
 
 req_headers(_RD = #wm_reqdata{req_headers=ReqH}) -> ReqH. % mochiheaders
 
-req_body(_RD = #wm_reqdata{wmreq=WMReq,max_recv_body=MRB}) ->
-    maybe_conflict_body(WMReq:req_body(MRB)).
+req_body(_RD = #wm_reqdata{wm_state=ReqState0,max_recv_body=MRB}) ->
+    {ReqResp, ReqState} = {webmachine_request, ReqState0}:req_body(MRB),
+    put(tmp_reqstate, ReqState),
+    maybe_conflict_body(ReqResp).
 
-stream_req_body(_RD = #wm_reqdata{wmreq=WMReq}, MaxHunk) ->
-    maybe_conflict_body(WMReq:stream_req_body(MaxHunk)).
+stream_req_body(_RD = #wm_reqdata{wm_state=ReqState0}, MaxHunk) ->
+    {ReqResp, ReqState} =
+        {webmachine_request, ReqState0}:stream_req_body(MaxHunk),
+    put(tmp_reqstate, ReqState),
+    maybe_conflict_body(ReqResp).
 
 max_recv_body(_RD = #wm_reqdata{max_recv_body=X}) when is_integer(X) -> X.