Browse files

Merge pull request #95 from vinoski/drop-param-mods

use tuple modules instead of parameterized modules
  • Loading branch information...
2 parents 5ed0946 + 23a1d48 commit 2fb6dcc7f4978cc2299f9f197a954dbf5ff515eb @etrepum etrepum committed Jan 23, 2013
Showing with 224 additions and 194 deletions.
  1. +9 −5 src/mochifmt_records.erl
  2. +10 −7 src/mochifmt_std.erl
  3. +177 −162 src/mochiweb_request.erl
  4. +28 −20 src/mochiweb_response.erl
View
14 src/mochifmt_records.erl
@@ -9,20 +9,24 @@
%% M:format("{0.bar}", [#rec{bar=foo}]).
%% foo
--module(mochifmt_records, [Recs]).
+-module(mochifmt_records).
-author('bob@mochimedia.com').
--export([get_value/2]).
+-export([new/1, get_value/3]).
-get_value(Key, Rec) when is_tuple(Rec) and is_atom(element(1, Rec)) ->
+new([{_Rec, RecFields}]=Recs) when is_list(RecFields) ->
+ {?MODULE, Recs}.
+
+get_value(Key, Rec, {?MODULE, Recs})
+ when is_tuple(Rec) and is_atom(element(1, Rec)) ->
try begin
Atom = list_to_existing_atom(Key),
{_, Fields} = proplists:lookup(element(1, Rec), Recs),
element(get_rec_index(Atom, Fields, 2), Rec)
end
catch error:_ -> mochifmt:get_value(Key, Rec)
end;
-get_value(Key, Args) ->
- mochifmt:get_value(Key, Args).
+get_value(Key, Args, {?MODULE, _Recs}=THIS) ->
+ mochifmt:get_value(Key, Args, THIS).
get_rec_index(Atom, [Atom | _], Index) ->
Index;
View
17 src/mochifmt_std.erl
@@ -3,23 +3,26 @@
%% @doc Template module for a mochifmt formatter.
--module(mochifmt_std, []).
+-module(mochifmt_std).
-author('bob@mochimedia.com').
--export([format/2, get_value/2, format_field/2, get_field/2, convert_field/2]).
+-export([new/0, format/3, get_value/3, format_field/3, get_field/3, convert_field/3]).
-format(Format, Args) ->
+new() ->
+ {?MODULE}.
+
+format(Format, Args, {?MODULE}=THIS) ->
mochifmt:format(Format, Args, THIS).
-get_field(Key, Args) ->
+get_field(Key, Args, {?MODULE}=THIS) ->
mochifmt:get_field(Key, Args, THIS).
-convert_field(Key, Args) ->
+convert_field(Key, Args, {?MODULE}) ->
mochifmt:convert_field(Key, Args).
-get_value(Key, Args) ->
+get_value(Key, Args, {?MODULE}) ->
mochifmt:get_value(Key, Args).
-format_field(Arg, Format) ->
+format_field(Arg, Format, {?MODULE}=THIS) ->
mochifmt:format_field(Arg, Format, THIS).
%%
View
339 src/mochiweb_request.erl
@@ -3,25 +3,26 @@
%% @doc MochiWeb HTTP Request abstraction.
--module(mochiweb_request, [Socket, Method, RawPath, Version, Headers]).
+-module(mochiweb_request).
-author('bob@mochimedia.com').
-include_lib("kernel/include/file.hrl").
-include("internal.hrl").
-define(QUIP, "Any of you quaids got a smint?").
--export([get_header_value/1, get_primary_header_value/1, get_combined_header_value/1, get/1, dump/0]).
--export([send/1, recv/1, recv/2, recv_body/0, recv_body/1, stream_body/3]).
--export([start_response/1, start_response_length/1, start_raw_response/1]).
--export([respond/1, ok/1]).
--export([not_found/0, not_found/1]).
--export([parse_post/0, parse_qs/0]).
--export([should_close/0, cleanup/0]).
--export([parse_cookie/0, get_cookie_value/1]).
--export([serve_file/2, serve_file/3]).
--export([accepted_encodings/1]).
--export([accepts_content_type/1, accepted_content_types/1]).
+-export([new/5]).
+-export([get_header_value/2, get_primary_header_value/2, get_combined_header_value/2, get/2, dump/1]).
+-export([send/2, recv/2, recv/3, recv_body/1, recv_body/2, stream_body/4]).
+-export([start_response/2, start_response_length/2, start_raw_response/2]).
+-export([respond/2, ok/2]).
+-export([not_found/1, not_found/2]).
+-export([parse_post/1, parse_qs/1]).
+-export([should_close/1, cleanup/1]).
+-export([parse_cookie/1, get_cookie_value/2]).
+-export([serve_file/3, serve_file/4]).
+-export([accepted_encodings/2]).
+-export([accepts_content_type/2, accepted_content_types/2]).
-define(SAVE_QS, mochiweb_request_qs).
-define(SAVE_PATH, mochiweb_request_path).
@@ -35,6 +36,7 @@
%% @type key() = atom() | string() | binary()
%% @type value() = atom() | string() | binary() | integer()
%% @type headers(). A mochiweb_headers structure.
+%% @type request() = {mochiweb_request,[_Socket,_Method,_RawPath,_Version,_Headers]}
%% @type response(). A mochiweb_response parameterized module instance.
%% @type ioheaders() = headers() | [{key(), value()}].
@@ -44,53 +46,58 @@
% Maximum recv_body() length of 1MB
-define(MAX_RECV_BODY, (1024*1024)).
-%% @spec get_header_value(K) -> undefined | Value
+%% @spec new(Socket, Method, RawPath, Version, headers()) -> request()
+%% @doc Create a new request instance.
+new(Socket, Method, RawPath, Version, Headers) ->
+ {?MODULE, [Socket, Method, RawPath, Version, Headers]}.
+
+%% @spec get_header_value(K, request()) -> undefined | Value
%% @doc Get the value of a given request header.
-get_header_value(K) ->
+get_header_value(K, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) ->
mochiweb_headers:get_value(K, Headers).
-get_primary_header_value(K) ->
+get_primary_header_value(K, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) ->
mochiweb_headers:get_primary_value(K, Headers).
-get_combined_header_value(K) ->
+get_combined_header_value(K, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) ->
mochiweb_headers:get_combined_value(K, Headers).
%% @type field() = socket | scheme | method | raw_path | version | headers | peer | path | body_length | range
-%% @spec get(field()) -> term()
+%% @spec get(field(), request()) -> term()
%% @doc Return the internal representation of the given field. If
%% <code>socket</code> is requested on a HTTPS connection, then
%% an ssl socket will be returned as <code>{ssl, SslSocket}</code>.
%% You can use <code>SslSocket</code> with the <code>ssl</code>
%% application, eg: <code>ssl:peercert(SslSocket)</code>.
-get(socket) ->
+get(socket, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
Socket;
-get(scheme) ->
+get(scheme, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
case mochiweb_socket:type(Socket) of
plain ->
http;
ssl ->
https
end;
-get(method) ->
+get(method, {?MODULE, [_Socket, Method, _RawPath, _Version, _Headers]}) ->
Method;
-get(raw_path) ->
+get(raw_path, {?MODULE, [_Socket, _Method, RawPath, _Version, _Headers]}) ->
RawPath;
-get(version) ->
+get(version, {?MODULE, [_Socket, _Method, _RawPath, Version, _Headers]}) ->
Version;
-get(headers) ->
+get(headers, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) ->
Headers;
-get(peer) ->
+get(peer, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
case mochiweb_socket:peername(Socket) of
{ok, {Addr={10, _, _, _}, _Port}} ->
- case get_header_value("x-forwarded-for") of
+ case get_header_value("x-forwarded-for", THIS) 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
+ case get_header_value("x-forwarded-for", THIS) of
undefined ->
"127.0.0.1";
Hosts ->
@@ -101,7 +108,7 @@ get(peer) ->
{error, enotconn} ->
exit(normal)
end;
-get(path) ->
+get(path, {?MODULE, [_Socket, _Method, RawPath, _Version, _Headers]}) ->
case erlang:get(?SAVE_PATH) of
undefined ->
{Path0, _, _} = mochiweb_util:urlsplit_path(RawPath),
@@ -111,52 +118,52 @@ get(path) ->
Cached ->
Cached
end;
-get(body_length) ->
+get(body_length, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
case erlang:get(?SAVE_BODY_LENGTH) of
undefined ->
- BodyLength = body_length(),
+ BodyLength = body_length(THIS),
put(?SAVE_BODY_LENGTH, {cached, BodyLength}),
BodyLength;
{cached, Cached} ->
Cached
end;
-get(range) ->
- case get_header_value(range) of
+get(range, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ case get_header_value(range, THIS) of
undefined ->
undefined;
RawRange ->
mochiweb_http:parse_range_request(RawRange)
end.
-%% @spec dump() -> {mochiweb_request, [{atom(), term()}]}
+%% @spec dump(request()) -> {mochiweb_request, [{atom(), term()}]}
%% @doc Dump the internal representation to a "human readable" set of terms
%% for debugging/inspection purposes.
-dump() ->
+dump({?MODULE, [_Socket, Method, RawPath, Version, Headers]}) ->
{?MODULE, [{method, Method},
{version, Version},
{raw_path, RawPath},
{headers, mochiweb_headers:to_list(Headers)}]}.
-%% @spec send(iodata()) -> ok
+%% @spec send(iodata(), request()) -> ok
%% @doc Send data over the socket.
-send(Data) ->
+send(Data, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
case mochiweb_socket:send(Socket, Data) of
ok ->
ok;
_ ->
exit(normal)
end.
-%% @spec recv(integer()) -> binary()
+%% @spec recv(integer(), request()) -> binary()
%% @doc Receive Length bytes from the client as a binary, with the default
%% idle timeout.
-recv(Length) ->
- recv(Length, ?IDLE_TIMEOUT).
+recv(Length, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ recv(Length, ?IDLE_TIMEOUT, THIS).
-%% @spec recv(integer(), integer()) -> binary()
+%% @spec recv(integer(), integer(), request()) -> binary()
%% @doc Receive Length bytes from the client as a binary, with the given
%% Timeout in msec.
-recv(Length, Timeout) ->
+recv(Length, Timeout, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
case mochiweb_socket:recv(Socket, Length, Timeout) of
{ok, Data} ->
put(?SAVE_RECV, true),
@@ -165,12 +172,12 @@ recv(Length, Timeout) ->
exit(normal)
end.
-%% @spec body_length() -> undefined | chunked | unknown_transfer_encoding | integer()
+%% @spec body_length(request()) -> undefined | chunked | unknown_transfer_encoding | integer()
%% @doc Infer body length from transfer-encoding and content-length headers.
-body_length() ->
- case get_header_value("transfer-encoding") of
+body_length({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ case get_header_value("transfer-encoding", THIS) of
undefined ->
- case get_combined_header_value("content-length") of
+ case get_combined_header_value("content-length", THIS) of
undefined ->
undefined;
Length ->
@@ -183,16 +190,16 @@ body_length() ->
end.
-%% @spec recv_body() -> binary()
+%% @spec recv_body(request()) -> binary()
%% @doc Receive the body of the HTTP request (defined by Content-Length).
%% Will only receive up to the default max-body length of 1MB.
-recv_body() ->
- recv_body(?MAX_RECV_BODY).
+recv_body({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ recv_body(?MAX_RECV_BODY, THIS).
-%% @spec recv_body(integer()) -> binary()
+%% @spec recv_body(integer(), request()) -> binary()
%% @doc Receive the body of the HTTP request (defined by Content-Length).
%% Will receive up to MaxBody bytes.
-recv_body(MaxBody) ->
+recv_body(MaxBody, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
case erlang:get(?SAVE_BODY) of
undefined ->
% we could use a sane constant for max chunk size
@@ -206,30 +213,31 @@ recv_body(MaxBody) ->
true ->
{NewLength, [Bin | BinAcc]}
end
- end, {0, []}, MaxBody),
+ end, {0, []}, MaxBody, THIS),
put(?SAVE_BODY, Body),
Body;
Cached -> Cached
end.
-stream_body(MaxChunkSize, ChunkFun, FunState) ->
- stream_body(MaxChunkSize, ChunkFun, FunState, undefined).
+stream_body(MaxChunkSize, ChunkFun, FunState, {?MODULE,[_Socket,_Method,_RawPath,_Version,_Headers]}=THIS) ->
+ stream_body(MaxChunkSize, ChunkFun, FunState, undefined, THIS).
-stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength) ->
- Expect = case get_header_value("expect") of
+stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength,
+ {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ Expect = case get_header_value("expect", THIS) of
undefined ->
undefined;
Value when is_list(Value) ->
string:to_lower(Value)
end,
case Expect of
"100-continue" ->
- _ = start_raw_response({100, gb_trees:empty()}),
+ _ = start_raw_response({100, gb_trees:empty()}, THIS),
ok;
_Else ->
ok
end,
- case body_length() of
+ case body_length(THIS) of
undefined ->
undefined;
{unknown_transfer_encoding, Unknown} ->
@@ -238,68 +246,70 @@ stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength) ->
% In this case the MaxBody is actually used to
% determine the maximum allowed size of a single
% chunk.
- stream_chunked_body(MaxChunkSize, ChunkFun, FunState);
+ stream_chunked_body(MaxChunkSize, ChunkFun, FunState, THIS);
0 ->
<<>>;
Length when is_integer(Length) ->
case MaxBodyLength of
MaxBodyLength when is_integer(MaxBodyLength), MaxBodyLength < Length ->
exit({body_too_large, content_length});
_ ->
- stream_unchunked_body(Length, ChunkFun, FunState)
+ stream_unchunked_body(Length, ChunkFun, FunState, THIS)
end
end.
-%% @spec start_response({integer(), ioheaders()}) -> response()
+%% @spec start_response({integer(), ioheaders()}, request()) -> response()
%% @doc Start the HTTP response by sending the Code HTTP response and
%% ResponseHeaders. The server will set header defaults such as Server
%% and Date if not present in ResponseHeaders.
-start_response({Code, ResponseHeaders}) ->
+start_response({Code, ResponseHeaders}, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
HResponse = mochiweb_headers:make(ResponseHeaders),
HResponse1 = mochiweb_headers:default_from_list(server_headers(),
HResponse),
- start_raw_response({Code, HResponse1}).
+ start_raw_response({Code, HResponse1}, THIS).
-%% @spec start_raw_response({integer(), headers()}) -> response()
+%% @spec start_raw_response({integer(), headers()}, request()) -> response()
%% @doc Start the HTTP response by sending the Code HTTP response and
%% ResponseHeaders.
-start_raw_response({Code, ResponseHeaders}) ->
+start_raw_response({Code, ResponseHeaders}, {?MODULE, [_Socket, _Method, _RawPath, Version, _Headers]}=THIS) ->
F = fun ({K, V}, Acc) ->
[mochiweb_util:make_io(K), <<": ">>, V, <<"\r\n">> | Acc]
end,
End = lists:foldl(F, [<<"\r\n">>],
mochiweb_headers:to_list(ResponseHeaders)),
- send([make_version(Version), make_code(Code), <<"\r\n">> | End]),
+ send([make_version(Version), make_code(Code), <<"\r\n">> | End], THIS),
mochiweb:new_response({THIS, Code, ResponseHeaders}).
-%% @spec start_response_length({integer(), ioheaders(), integer()}) -> response()
+%% @spec start_response_length({integer(), ioheaders(), integer()}, request()) -> response()
%% @doc Start the HTTP response by sending the Code HTTP response and
%% ResponseHeaders including a Content-Length of Length. The server
%% will set header defaults such as Server
%% and Date if not present in ResponseHeaders.
-start_response_length({Code, ResponseHeaders, Length}) ->
+start_response_length({Code, ResponseHeaders, Length},
+ {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
HResponse = mochiweb_headers:make(ResponseHeaders),
HResponse1 = mochiweb_headers:enter("Content-Length", Length, HResponse),
- start_response({Code, HResponse1}).
+ start_response({Code, HResponse1}, THIS).
-%% @spec respond({integer(), ioheaders(), iodata() | chunked | {file, IoDevice}}) -> response()
+%% @spec respond({integer(), ioheaders(), iodata() | chunked | {file, IoDevice}}, request()) -> response()
%% @doc Start the HTTP response with start_response, and send Body to the
%% client (if the get(method) /= 'HEAD'). The Content-Length header
%% will be set by the Body length, and the server will insert header
%% defaults.
-respond({Code, ResponseHeaders, {file, IoDevice}}) ->
+respond({Code, ResponseHeaders, {file, IoDevice}},
+ {?MODULE, [_Socket, Method, _RawPath, _Version, _Headers]}=THIS) ->
Length = mochiweb_io:iodevice_size(IoDevice),
- Response = start_response_length({Code, ResponseHeaders, Length}),
+ Response = start_response_length({Code, ResponseHeaders, Length}, THIS),
case Method of
'HEAD' ->
ok;
_ ->
- mochiweb_io:iodevice_stream(fun send/1, IoDevice)
+ mochiweb_io:iodevice_stream(fun send/2, IoDevice)
end,
Response;
-respond({Code, ResponseHeaders, chunked}) ->
+respond({Code, ResponseHeaders, chunked}, {?MODULE, [_Socket, Method, _RawPath, Version, _Headers]}=THIS) ->
HResponse = mochiweb_headers:make(ResponseHeaders),
HResponse1 = case Method of
'HEAD' ->
@@ -320,35 +330,35 @@ respond({Code, ResponseHeaders, chunked}) ->
put(?SAVE_FORCE_CLOSE, true),
HResponse
end,
- start_response({Code, HResponse1});
-respond({Code, ResponseHeaders, Body}) ->
- Response = start_response_length({Code, ResponseHeaders, iolist_size(Body)}),
+ start_response({Code, HResponse1}, THIS);
+respond({Code, ResponseHeaders, Body}, {?MODULE, [_Socket, Method, _RawPath, _Version, _Headers]}=THIS) ->
+ Response = start_response_length({Code, ResponseHeaders, iolist_size(Body)}, THIS),
case Method of
'HEAD' ->
ok;
_ ->
- send(Body)
+ send(Body, THIS)
end,
Response.
-%% @spec not_found() -> response()
+%% @spec not_found(request()) -> response()
%% @doc Alias for <code>not_found([])</code>.
-not_found() ->
- not_found([]).
+not_found({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ not_found([], THIS).
-%% @spec not_found(ExtraHeaders) -> response()
+%% @spec not_found(ExtraHeaders, request()) -> response()
%% @doc Alias for <code>respond({404, [{"Content-Type", "text/plain"}
%% | ExtraHeaders], &lt;&lt;"Not found."&gt;&gt;})</code>.
-not_found(ExtraHeaders) ->
+not_found(ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
respond({404, [{"Content-Type", "text/plain"} | ExtraHeaders],
- <<"Not found.">>}).
+ <<"Not found.">>}, THIS).
-%% @spec ok({value(), iodata()} | {value(), ioheaders(), iodata() | {file, IoDevice}}) ->
+%% @spec ok({value(), iodata()} | {value(), ioheaders(), iodata() | {file, IoDevice}}, request()) ->
%% response()
%% @doc respond({200, [{"Content-Type", ContentType} | Headers], Body}).
-ok({ContentType, Body}) ->
- ok({ContentType, [], Body});
-ok({ContentType, ResponseHeaders, Body}) ->
+ok({ContentType, Body}, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ ok({ContentType, [], Body}, THIS);
+ok({ContentType, ResponseHeaders, Body}, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
HResponse = mochiweb_headers:make(ResponseHeaders),
case THIS:get(range) of
X when (X =:= undefined orelse X =:= fail) orelse Body =:= chunked ->
@@ -357,7 +367,7 @@ ok({ContentType, ResponseHeaders, Body}) ->
%% full response.
HResponse1 = mochiweb_headers:enter("Content-Type", ContentType,
HResponse),
- respond({200, HResponse1, Body});
+ respond({200, HResponse1, Body}, THIS);
Ranges ->
{PartList, Size} = range_parts(Body, Ranges),
case PartList of
@@ -366,36 +376,36 @@ ok({ContentType, ResponseHeaders, Body}) ->
ContentType,
HResponse),
%% could be 416, for now we'll just return 200
- respond({200, HResponse1, Body});
+ respond({200, HResponse1, Body}, THIS);
PartList ->
{RangeHeaders, RangeBody} =
mochiweb_multipart:parts_to_body(PartList, ContentType, Size),
HResponse1 = mochiweb_headers:enter_from_list(
[{"Accept-Ranges", "bytes"} |
RangeHeaders],
HResponse),
- respond({206, HResponse1, RangeBody})
+ respond({206, HResponse1, RangeBody}, THIS)
end
end.
-%% @spec should_close() -> bool()
+%% @spec should_close(request()) -> bool()
%% @doc Return true if the connection must be closed. If false, using
%% Keep-Alive should be safe.
-should_close() ->
+should_close({?MODULE, [_Socket, _Method, _RawPath, Version, _Headers]}=THIS) ->
ForceClose = erlang:get(?SAVE_FORCE_CLOSE) =/= undefined,
DidNotRecv = erlang:get(?SAVE_RECV) =:= undefined,
ForceClose orelse Version < {1, 0}
%% Connection: close
- orelse is_close(get_header_value("connection"))
+ orelse is_close(get_header_value("connection", THIS))
%% HTTP 1.0 requires Connection: Keep-Alive
orelse (Version =:= {1, 0}
- andalso get_header_value("connection") =/= "Keep-Alive")
+ andalso get_header_value("connection", THIS) =/= "Keep-Alive")
%% unread data left on the socket, can't safely continue
orelse (DidNotRecv
- andalso get_combined_header_value("content-length") =/= undefined
- andalso list_to_integer(get_combined_header_value("content-length")) > 0)
+ andalso get_combined_header_value("content-length", THIS) =/= undefined
+ andalso list_to_integer(get_combined_header_value("content-length", THIS)) > 0)
orelse (DidNotRecv
- andalso get_header_value("transfer-encoding") =:= "chunked").
+ andalso get_header_value("transfer-encoding", THIS) =:= "chunked").
is_close("close") ->
true;
@@ -404,20 +414,20 @@ is_close(S=[_C, _L, _O, _S, _E]) ->
is_close(_) ->
false.
-%% @spec cleanup() -> ok
+%% @spec cleanup(request()) -> ok
%% @doc Clean up any junk in the process dictionary, required before continuing
%% a Keep-Alive request.
-cleanup() ->
+cleanup({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}) ->
L = [?SAVE_QS, ?SAVE_PATH, ?SAVE_RECV, ?SAVE_BODY, ?SAVE_BODY_LENGTH,
?SAVE_POST, ?SAVE_COOKIE, ?SAVE_FORCE_CLOSE],
lists:foreach(fun(K) ->
erase(K)
end, L),
ok.
-%% @spec parse_qs() -> [{Key::string(), Value::string()}]
+%% @spec parse_qs(request()) -> [{Key::string(), Value::string()}]
%% @doc Parse the query string of the URL.
-parse_qs() ->
+parse_qs({?MODULE, [_Socket, _Method, RawPath, _Version, _Headers]}) ->
case erlang:get(?SAVE_QS) of
undefined ->
{_, QueryString, _} = mochiweb_util:urlsplit_path(RawPath),
@@ -428,17 +438,17 @@ parse_qs() ->
Cached
end.
-%% @spec get_cookie_value(Key::string) -> string() | undefined
+%% @spec get_cookie_value(Key::string, request()) -> string() | undefined
%% @doc Get the value of the given cookie.
-get_cookie_value(Key) ->
- proplists:get_value(Key, parse_cookie()).
+get_cookie_value(Key, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ proplists:get_value(Key, parse_cookie(THIS)).
-%% @spec parse_cookie() -> [{Key::string(), Value::string()}]
+%% @spec parse_cookie(request()) -> [{Key::string(), Value::string()}]
%% @doc Parse the cookie header.
-parse_cookie() ->
+parse_cookie({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
case erlang:get(?SAVE_COOKIE) of
undefined ->
- Cookies = case get_header_value("cookie") of
+ Cookies = case get_header_value("cookie", THIS) of
undefined ->
[];
Value ->
@@ -450,17 +460,17 @@ parse_cookie() ->
Cached
end.
-%% @spec parse_post() -> [{Key::string(), Value::string()}]
+%% @spec parse_post(request()) -> [{Key::string(), Value::string()}]
%% @doc Parse an application/x-www-form-urlencoded form POST. This
%% has the side-effect of calling recv_body().
-parse_post() ->
+parse_post({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
case erlang:get(?SAVE_POST) of
undefined ->
- Parsed = case recv_body() of
+ Parsed = case recv_body(THIS) of
undefined ->
[];
Binary ->
- case get_primary_header_value("content-type") of
+ case get_primary_header_value("content-type",THIS) of
"application/x-www-form-urlencoded" ++ _ ->
mochiweb_util:parse_qs(Binary);
_ ->
@@ -473,37 +483,39 @@ parse_post() ->
Cached
end.
-%% @spec stream_chunked_body(integer(), fun(), term()) -> term()
+%% @spec stream_chunked_body(integer(), fun(), term(), request()) -> term()
%% @doc The function is called for each chunk.
%% Used internally by read_chunked_body.
-stream_chunked_body(MaxChunkSize, Fun, FunState) ->
- case read_chunk_length() of
+stream_chunked_body(MaxChunkSize, Fun, FunState,
+ {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ case read_chunk_length(THIS) of
0 ->
- Fun({0, read_chunk(0)}, FunState);
+ Fun({0, read_chunk(0, THIS)}, FunState);
Length when Length > MaxChunkSize ->
- NewState = read_sub_chunks(Length, MaxChunkSize, Fun, FunState),
- stream_chunked_body(MaxChunkSize, Fun, NewState);
+ NewState = read_sub_chunks(Length, MaxChunkSize, Fun, FunState, THIS),
+ stream_chunked_body(MaxChunkSize, Fun, NewState, THIS);
Length ->
- NewState = Fun({Length, read_chunk(Length)}, FunState),
- stream_chunked_body(MaxChunkSize, Fun, NewState)
+ NewState = Fun({Length, read_chunk(Length, THIS)}, FunState),
+ stream_chunked_body(MaxChunkSize, Fun, NewState, THIS)
end.
-stream_unchunked_body(0, Fun, FunState) ->
+stream_unchunked_body(0, Fun, FunState, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}) ->
Fun({0, <<>>}, FunState);
-stream_unchunked_body(Length, Fun, FunState) when Length > 0 ->
+stream_unchunked_body(Length, Fun, FunState,
+ {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) when Length > 0 ->
PktSize = case Length > ?RECBUF_SIZE of
true ->
?RECBUF_SIZE;
false ->
Length
end,
- Bin = recv(PktSize),
+ Bin = recv(PktSize, THIS),
NewState = Fun({PktSize, Bin}, FunState),
- stream_unchunked_body(Length - PktSize, Fun, NewState).
+ stream_unchunked_body(Length - PktSize, Fun, NewState, THIS).
-%% @spec read_chunk_length() -> integer()
+%% @spec read_chunk_length(request()) -> integer()
%% @doc Read the length of the next HTTP chunk.
-read_chunk_length() ->
+read_chunk_length({?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
ok = mochiweb_socket:setopts(Socket, [{packet, line}]),
case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of
{ok, Header} ->
@@ -517,10 +529,10 @@ read_chunk_length() ->
exit(normal)
end.
-%% @spec read_chunk(integer()) -> Chunk::binary() | [Footer::binary()]
+%% @spec read_chunk(integer(), request()) -> Chunk::binary() | [Footer::binary()]
%% @doc Read in a HTTP chunk of the given length. If Length is 0, then read the
%% HTTP footers (as a list of binaries, since they're nominal).
-read_chunk(0) ->
+read_chunk(0, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
ok = mochiweb_socket:setopts(Socket, [{packet, line}]),
F = fun (F1, Acc) ->
case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of
@@ -536,40 +548,42 @@ read_chunk(0) ->
ok = mochiweb_socket:setopts(Socket, [{packet, raw}]),
put(?SAVE_RECV, true),
Footers;
-read_chunk(Length) ->
+read_chunk(Length, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
case mochiweb_socket:recv(Socket, 2 + Length, ?IDLE_TIMEOUT) of
{ok, <<Chunk:Length/binary, "\r\n">>} ->
Chunk;
_ ->
exit(normal)
end.
-read_sub_chunks(Length, MaxChunkSize, Fun, FunState) when Length > MaxChunkSize ->
- Bin = recv(MaxChunkSize),
+read_sub_chunks(Length, MaxChunkSize, Fun, FunState,
+ {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) when Length > MaxChunkSize ->
+ Bin = recv(MaxChunkSize, THIS),
NewState = Fun({size(Bin), Bin}, FunState),
- read_sub_chunks(Length - MaxChunkSize, MaxChunkSize, Fun, NewState);
+ read_sub_chunks(Length - MaxChunkSize, MaxChunkSize, Fun, NewState, THIS);
-read_sub_chunks(Length, _MaxChunkSize, Fun, FunState) ->
- Fun({Length, read_chunk(Length)}, FunState).
+read_sub_chunks(Length, _MaxChunkSize, Fun, FunState,
+ {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ Fun({Length, read_chunk(Length, THIS)}, FunState).
-%% @spec serve_file(Path, DocRoot) -> Response
+%% @spec serve_file(Path, DocRoot, request()) -> Response
%% @doc Serve a file relative to DocRoot.
-serve_file(Path, DocRoot) ->
- serve_file(Path, DocRoot, []).
+serve_file(Path, DocRoot, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ serve_file(Path, DocRoot, [], THIS).
-%% @spec serve_file(Path, DocRoot, ExtraHeaders) -> Response
+%% @spec serve_file(Path, DocRoot, ExtraHeaders, request()) -> Response
%% @doc Serve a file relative to DocRoot.
-serve_file(Path, DocRoot, ExtraHeaders) ->
+serve_file(Path, DocRoot, ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
case mochiweb_util:safe_relative_path(Path) of
undefined ->
- not_found(ExtraHeaders);
+ not_found(ExtraHeaders, THIS);
RelPath ->
FullPath = filename:join([DocRoot, RelPath]),
case filelib:is_dir(FullPath) of
true ->
- maybe_redirect(RelPath, FullPath, ExtraHeaders);
+ maybe_redirect(RelPath, FullPath, ExtraHeaders, THIS);
false ->
- maybe_serve_file(FullPath, ExtraHeaders)
+ maybe_serve_file(FullPath, ExtraHeaders, THIS)
end
end.
@@ -579,13 +593,14 @@ serve_file(Path, DocRoot, ExtraHeaders) ->
directory_index(FullPath) ->
filename:join([FullPath, "index.html"]).
-maybe_redirect([], FullPath, ExtraHeaders) ->
- maybe_serve_file(directory_index(FullPath), ExtraHeaders);
+maybe_redirect([], FullPath, ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ maybe_serve_file(directory_index(FullPath), ExtraHeaders, THIS);
-maybe_redirect(RelPath, FullPath, ExtraHeaders) ->
+maybe_redirect(RelPath, FullPath, ExtraHeaders,
+ {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}=THIS) ->
case string:right(RelPath, 1) of
"/" ->
- maybe_serve_file(directory_index(FullPath), ExtraHeaders);
+ maybe_serve_file(directory_index(FullPath), ExtraHeaders, THIS);
_ ->
Host = mochiweb_headers:get_value("host", Headers),
Location = "http://" ++ Host ++ "/" ++ RelPath ++ "/",
@@ -600,32 +615,32 @@ maybe_redirect(RelPath, FullPath, ExtraHeaders) ->
"<p>The document has moved <a href=\"">>,
Bottom = <<">here</a>.</p></body></html>\n">>,
Body = <<Top/binary, LocationBin/binary, Bottom/binary>>,
- respond({301, MoreHeaders, Body})
+ respond({301, MoreHeaders, Body}, THIS)
end.
-maybe_serve_file(File, ExtraHeaders) ->
+maybe_serve_file(File, ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
case file:read_file_info(File) of
{ok, FileInfo} ->
LastModified = httpd_util:rfc1123_date(FileInfo#file_info.mtime),
- case get_header_value("if-modified-since") of
+ case get_header_value("if-modified-since", THIS) of
LastModified ->
- respond({304, ExtraHeaders, ""});
+ respond({304, ExtraHeaders, ""}, THIS);
_ ->
case file:open(File, [raw, binary]) of
{ok, IoDevice} ->
ContentType = mochiweb_util:guess_mime(File),
Res = ok({ContentType,
[{"last-modified", LastModified}
| ExtraHeaders],
- {file, IoDevice}}),
+ {file, IoDevice}}, THIS),
ok = file:close(IoDevice),
Res;
_ ->
- not_found(ExtraHeaders)
+ not_found(ExtraHeaders, THIS)
end
end;
{error, _} ->
- not_found(ExtraHeaders)
+ not_found(ExtraHeaders, THIS)
end.
server_headers() ->
@@ -673,7 +688,7 @@ range_parts(Body0, Ranges) ->
end,
{lists:foldr(F, [], Ranges), Size}.
-%% @spec accepted_encodings([encoding()]) -> [encoding()] | bad_accept_encoding_value
+%% @spec accepted_encodings([encoding()], request()) -> [encoding()] | bad_accept_encoding_value
%% @type encoding() = string().
%%
%% @doc Returns a list of encodings accepted by a request. Encodings that are
@@ -697,8 +712,8 @@ range_parts(Body0, Ranges) ->
%% accepted_encodings(["gzip", "deflate", "identity"]) ->
%% ["deflate", "gzip", "identity"]
%%
-accepted_encodings(SupportedEncodings) ->
- AcceptEncodingHeader = case get_header_value("Accept-Encoding") of
+accepted_encodings(SupportedEncodings, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ AcceptEncodingHeader = case get_header_value("Accept-Encoding", THIS) of
undefined ->
"";
Value ->
@@ -713,7 +728,7 @@ accepted_encodings(SupportedEncodings) ->
)
end.
-%% @spec accepts_content_type(string() | binary()) -> boolean() | bad_accept_header
+%% @spec accepts_content_type(string() | binary(), request()) -> boolean() | bad_accept_header
%%
%% @doc Determines whether a request accepts a given media type by analyzing its
%% "Accept" header.
@@ -735,9 +750,9 @@ accepted_encodings(SupportedEncodings) ->
%% 5) For an "Accept" header with value "text/*; q=0.0, */*":
%% accepts_content_type("text/plain") -> false
%%
-accepts_content_type(ContentType1) ->
+accepts_content_type(ContentType1, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
ContentType = re:replace(ContentType1, "\\s", "", [global, {return, list}]),
- AcceptHeader = accept_header(),
+ AcceptHeader = accept_header(THIS),
case mochiweb_util:parse_qvalues(AcceptHeader) of
invalid_qvalue_string ->
bad_accept_header;
@@ -758,7 +773,7 @@ accepts_content_type(ContentType1) ->
(not lists:member({SuperType, 0.0}, QList))
end.
-%% @spec accepted_content_types([string() | binary()]) -> [string()] | bad_accept_header
+%% @spec accepted_content_types([string() | binary()], request()) -> [string()] | bad_accept_header
%%
%% @doc Filters which of the given media types this request accepts. This filtering
%% is performed by analyzing the "Accept" header. The returned list is sorted
@@ -784,11 +799,11 @@ accepts_content_type(ContentType1) ->
%% accepts_content_types(["application/json", "text/html"]) ->
%% ["text/html", "application/json"]
%%
-accepted_content_types(Types1) ->
+accepted_content_types(Types1, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
Types = lists:map(
fun(T) -> re:replace(T, "\\s", "", [global, {return, list}]) end,
Types1),
- AcceptHeader = accept_header(),
+ AcceptHeader = accept_header(THIS),
case mochiweb_util:parse_qvalues(AcceptHeader) of
invalid_qvalue_string ->
bad_accept_header;
@@ -824,8 +839,8 @@ accepted_content_types(Types1) ->
[Type || {_Q, Type} <- lists:sort(SortFun, TypesQ)]
end.
-accept_header() ->
- case get_header_value("Accept") of
+accept_header({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ case get_header_value("Accept", THIS) of
undefined ->
"*/*";
Value ->
View
48 src/mochiweb_response.erl
@@ -3,56 +3,64 @@
%% @doc Response abstraction.
--module(mochiweb_response, [Request, Code, Headers]).
+-module(mochiweb_response).
-author('bob@mochimedia.com').
-define(QUIP, "Any of you quaids got a smint?").
--export([get_header_value/1, get/1, dump/0]).
--export([send/1, write_chunk/1]).
+-export([new/3, get_header_value/2, get/2, dump/1]).
+-export([send/2, write_chunk/2]).
-%% @spec get_header_value(string() | atom() | binary()) -> string() | undefined
+%% @type response() = {atom(), [Request, Code, Headers]}
+
+%% @spec new(Request, Code, Headers) -> response()
+%% @doc Create a new mochiweb_response instance.
+new(Request, Code, Headers) ->
+ {?MODULE, [Request, Code, Headers]}.
+
+%% @spec get_header_value(string() | atom() | binary(), response()) ->
+%% string() | undefined
%% @doc Get the value of the given response header.
-get_header_value(K) ->
+get_header_value(K, {?MODULE, [_Request, _Code, Headers]}) ->
mochiweb_headers:get_value(K, Headers).
-%% @spec get(request | code | headers) -> term()
+%% @spec get(request | code | headers, response()) -> term()
%% @doc Return the internal representation of the given field.
-get(request) ->
+get(request, {?MODULE, [Request, _Code, _Headers]}) ->
Request;
-get(code) ->
+get(code, {?MODULE, [_Request, Code, _Headers]}) ->
Code;
-get(headers) ->
+get(headers, {?MODULE, [_Request, _Code, Headers]}) ->
Headers.
-%% @spec dump() -> {mochiweb_request, [{atom(), term()}]}
+%% @spec dump(response()) -> {mochiweb_request, [{atom(), term()}]}
%% @doc Dump the internal representation to a "human readable" set of terms
%% for debugging/inspection purposes.
-dump() ->
+dump({?MODULE, [Request, Code, Headers]}) ->
[{request, Request:dump()},
{code, Code},
{headers, mochiweb_headers:to_list(Headers)}].
-%% @spec send(iodata()) -> ok
+%% @spec send(iodata(), response()) -> ok
%% @doc Send data over the socket if the method is not HEAD.
-send(Data) ->
- case Request:get(method) of
+send(Data, {?MODULE, [Request, _Code, _Headers]}=THIS) ->
+ case Request:get(method, THIS) of
'HEAD' ->
ok;
_ ->
- Request:send(Data)
+ Request:send(Data, THIS)
end.
-%% @spec write_chunk(iodata()) -> ok
+%% @spec write_chunk(iodata(), response()) -> ok
%% @doc Write a chunk of a HTTP chunked response. If Data is zero length,
%% then the chunked response will be finished.
-write_chunk(Data) ->
- case Request:get(version) of
+write_chunk(Data, {?MODULE, [Request, _Code, _Headers]}=THIS) ->
+ case Request:get(version, THIS) of
Version when Version >= {1, 1} ->
Length = iolist_size(Data),
- send([io_lib:format("~.16b\r\n", [Length]), Data, <<"\r\n">>]);
+ send([io_lib:format("~.16b\r\n", [Length]), Data, <<"\r\n">>], THIS);
_ ->
- send(Data)
+ send(Data, THIS)
end.

0 comments on commit 2fb6dcc

Please sign in to comment.