|
@@ -59,7 +59,7 @@ peer(Req) ->
|
|
host(Req) ->
|
|
host(Req) ->
|
|
{Req#http_req.host, Req}.
|
|
{Req#http_req.host, Req}.
|
|
|
|
|
|
--spec raw_host(Req::#http_req{}) -> {RawHost::string(), Req::#http_req{}}.
|
|
|
|
|
|
+-spec raw_host(Req::#http_req{}) -> {RawHost::binary(), Req::#http_req{}}.
|
|
raw_host(Req) ->
|
|
raw_host(Req) ->
|
|
{Req#http_req.raw_host, Req}.
|
|
{Req#http_req.raw_host, Req}.
|
|
|
|
|
|
@@ -72,18 +72,18 @@ port(Req) ->
|
|
path(Req) ->
|
|
path(Req) ->
|
|
{Req#http_req.path, Req}.
|
|
{Req#http_req.path, Req}.
|
|
|
|
|
|
--spec raw_path(Req::#http_req{}) -> {RawPath::string(), Req::#http_req{}}.
|
|
|
|
|
|
+-spec raw_path(Req::#http_req{}) -> {RawPath::binary(), Req::#http_req{}}.
|
|
raw_path(Req) ->
|
|
raw_path(Req) ->
|
|
{Req#http_req.raw_path, Req}.
|
|
{Req#http_req.raw_path, Req}.
|
|
|
|
|
|
--spec qs_val(Name::string(), Req::#http_req{})
|
|
|
|
- -> {Value::string() | true | undefined, Req::#http_req{}}.
|
|
|
|
|
|
+-spec qs_val(Name::binary(), Req::#http_req{})
|
|
|
|
+ -> {Value::binary() | true | undefined, Req::#http_req{}}.
|
|
%% @equiv qs_val(Name, Req) -> qs_val(Name, Req, undefined)
|
|
%% @equiv qs_val(Name, Req) -> qs_val(Name, Req, undefined)
|
|
qs_val(Name, Req) ->
|
|
qs_val(Name, Req) ->
|
|
qs_val(Name, Req, undefined).
|
|
qs_val(Name, Req, undefined).
|
|
|
|
|
|
--spec qs_val(Name::string(), Req::#http_req{}, Default)
|
|
|
|
- -> {Value::string() | true | Default, Req::#http_req{}}
|
|
|
|
|
|
+-spec qs_val(Name::binary(), Req::#http_req{}, Default)
|
|
|
|
+ -> {Value::binary() | true | Default, Req::#http_req{}}
|
|
when Default::term().
|
|
when Default::term().
|
|
qs_val(Name, Req=#http_req{raw_qs=RawQs, qs_vals=undefined}, Default) ->
|
|
qs_val(Name, Req=#http_req{raw_qs=RawQs, qs_vals=undefined}, Default) ->
|
|
QsVals = parse_qs(RawQs),
|
|
QsVals = parse_qs(RawQs),
|
|
@@ -95,25 +95,25 @@ qs_val(Name, Req, Default) ->
|
|
end.
|
|
end.
|
|
|
|
|
|
-spec qs_vals(Req::#http_req{})
|
|
-spec qs_vals(Req::#http_req{})
|
|
- -> {list({Name::string(), Value::string() | true}), Req::#http_req{}}.
|
|
|
|
|
|
+ -> {list({Name::binary(), Value::binary() | true}), Req::#http_req{}}.
|
|
qs_vals(Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) ->
|
|
qs_vals(Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) ->
|
|
QsVals = parse_qs(RawQs),
|
|
QsVals = parse_qs(RawQs),
|
|
qs_vals(Req#http_req{qs_vals=QsVals});
|
|
qs_vals(Req#http_req{qs_vals=QsVals});
|
|
qs_vals(Req=#http_req{qs_vals=QsVals}) ->
|
|
qs_vals(Req=#http_req{qs_vals=QsVals}) ->
|
|
{QsVals, Req}.
|
|
{QsVals, Req}.
|
|
|
|
|
|
--spec raw_qs(Req::#http_req{}) -> {RawQs::string(), Req::#http_req{}}.
|
|
|
|
|
|
+-spec raw_qs(Req::#http_req{}) -> {RawQs::binary(), Req::#http_req{}}.
|
|
raw_qs(Req) ->
|
|
raw_qs(Req) ->
|
|
{Req#http_req.raw_qs, Req}.
|
|
{Req#http_req.raw_qs, Req}.
|
|
|
|
|
|
-spec binding(Name::atom(), Req::#http_req{})
|
|
-spec binding(Name::atom(), Req::#http_req{})
|
|
- -> {Value::string() | undefined, Req::#http_req{}}.
|
|
|
|
|
|
+ -> {Value::binary() | undefined, Req::#http_req{}}.
|
|
%% @equiv binding(Name, Req) -> binding(Name, Req, undefined)
|
|
%% @equiv binding(Name, Req) -> binding(Name, Req, undefined)
|
|
binding(Name, Req) ->
|
|
binding(Name, Req) ->
|
|
binding(Name, Req, undefined).
|
|
binding(Name, Req, undefined).
|
|
|
|
|
|
-spec binding(Name::atom(), Req::#http_req{}, Default)
|
|
-spec binding(Name::atom(), Req::#http_req{}, Default)
|
|
- -> {Value::string() | Default, Req::#http_req{}} when Default::term().
|
|
|
|
|
|
+ -> {Value::binary() | Default, Req::#http_req{}} when Default::term().
|
|
binding(Name, Req, Default) ->
|
|
binding(Name, Req, Default) ->
|
|
case lists:keyfind(Name, 1, Req#http_req.bindings) of
|
|
case lists:keyfind(Name, 1, Req#http_req.bindings) of
|
|
{Name, Value} -> {Value, Req};
|
|
{Name, Value} -> {Value, Req};
|
|
@@ -121,18 +121,18 @@ binding(Name, Req, Default) ->
|
|
end.
|
|
end.
|
|
|
|
|
|
-spec bindings(Req::#http_req{})
|
|
-spec bindings(Req::#http_req{})
|
|
- -> {list({Name::atom(), Value::string()}), Req::#http_req{}}.
|
|
|
|
|
|
+ -> {list({Name::atom(), Value::binary()}), Req::#http_req{}}.
|
|
bindings(Req) ->
|
|
bindings(Req) ->
|
|
{Req#http_req.bindings, Req}.
|
|
{Req#http_req.bindings, Req}.
|
|
|
|
|
|
--spec header(Name::atom() | string(), Req::#http_req{})
|
|
|
|
- -> {Value::string() | undefined, Req::#http_req{}}.
|
|
|
|
|
|
+-spec header(Name::atom() | binary(), Req::#http_req{})
|
|
|
|
+ -> {Value::binary() | undefined, Req::#http_req{}}.
|
|
%% @equiv header(Name, Req) -> header(Name, Req, undefined)
|
|
%% @equiv header(Name, Req) -> header(Name, Req, undefined)
|
|
header(Name, Req) ->
|
|
header(Name, Req) ->
|
|
header(Name, Req, undefined).
|
|
header(Name, Req, undefined).
|
|
|
|
|
|
--spec header(Name::atom() | string(), Req::#http_req{}, Default)
|
|
|
|
- -> {Value::string() | Default, Req::#http_req{}} when Default::term().
|
|
|
|
|
|
+-spec header(Name::atom() | binary(), Req::#http_req{}, Default)
|
|
|
|
+ -> {Value::binary() | Default, Req::#http_req{}} when Default::term().
|
|
header(Name, Req, Default) ->
|
|
header(Name, Req, Default) ->
|
|
case lists:keyfind(Name, 1, Req#http_req.headers) of
|
|
case lists:keyfind(Name, 1, Req#http_req.headers) of
|
|
{Name, Value} -> {Value, Req};
|
|
{Name, Value} -> {Value, Req};
|
|
@@ -154,26 +154,28 @@ body(Req) ->
|
|
case Length of
|
|
case Length of
|
|
undefined -> {error, badarg};
|
|
undefined -> {error, badarg};
|
|
_Any ->
|
|
_Any ->
|
|
- Length2 = list_to_integer(Length),
|
|
|
|
|
|
+ Length2 = list_to_integer(binary_to_list(Length)),
|
|
body(Length2, Req2)
|
|
body(Length2, Req2)
|
|
end.
|
|
end.
|
|
|
|
|
|
%% @todo We probably want to configure the timeout.
|
|
%% @todo We probably want to configure the timeout.
|
|
-spec body(Length::non_neg_integer(), Req::#http_req{})
|
|
-spec body(Length::non_neg_integer(), Req::#http_req{})
|
|
-> {ok, Body::binary(), Req::#http_req{}} | {error, Reason::atom()}.
|
|
-> {ok, Body::binary(), Req::#http_req{}} | {error, Reason::atom()}.
|
|
|
|
+body(Length, Req=#http_req{body_state=waiting, buffer=Buffer})
|
|
|
|
+ when Length =:= byte_size(Buffer) ->
|
|
|
|
+ {ok, Buffer, Req#http_req{body_state=done, buffer= <<>>}};
|
|
body(Length, Req=#http_req{socket=Socket, transport=Transport,
|
|
body(Length, Req=#http_req{socket=Socket, transport=Transport,
|
|
- body_state=waiting}) ->
|
|
|
|
- Transport:setopts(Socket, [{packet, raw}]),
|
|
|
|
- case Transport:recv(Socket, Length, 5000) of
|
|
|
|
- {ok, Body} -> {ok, Body, Req#http_req{body_state=done}};
|
|
|
|
|
|
+ body_state=waiting, buffer=Buffer}) when Length > byte_size(Buffer) ->
|
|
|
|
+ case Transport:recv(Socket, Length - byte_size(Buffer), 5000) of
|
|
|
|
+ {ok, Body} -> {ok, << Buffer/binary, Body/binary >>, Req#http_req{body_state=done, buffer= <<>>}};
|
|
{error, Reason} -> {error, Reason}
|
|
{error, Reason} -> {error, Reason}
|
|
end.
|
|
end.
|
|
|
|
|
|
-spec body_qs(Req::#http_req{})
|
|
-spec body_qs(Req::#http_req{})
|
|
- -> {list({Name::string(), Value::string() | true}), Req::#http_req{}}.
|
|
|
|
|
|
+ -> {list({Name::binary(), Value::binary() | true}), Req::#http_req{}}.
|
|
body_qs(Req) ->
|
|
body_qs(Req) ->
|
|
{ok, Body, Req2} = body(Req),
|
|
{ok, Body, Req2} = body(Req),
|
|
- {parse_qs(binary_to_list(Body)), Req2}.
|
|
|
|
|
|
+ {parse_qs(Body), Req2}.
|
|
|
|
|
|
%% Response API.
|
|
%% Response API.
|
|
|
|
|
|
@@ -182,91 +184,90 @@ body_qs(Req) ->
|
|
reply(Code, Headers, Body, Req=#http_req{socket=Socket,
|
|
reply(Code, Headers, Body, Req=#http_req{socket=Socket,
|
|
transport=Transport, connection=Connection,
|
|
transport=Transport, connection=Connection,
|
|
resp_state=waiting}) ->
|
|
resp_state=waiting}) ->
|
|
- StatusLine = ["HTTP/1.1 ", status(Code), "\r\n"],
|
|
|
|
|
|
+ StatusLine = <<"HTTP/1.1 ", (status(Code))/binary, "\r\n">>,
|
|
DefaultHeaders = [
|
|
DefaultHeaders = [
|
|
- {"Connection", atom_to_connection(Connection)},
|
|
|
|
- {"Content-Length", integer_to_list(iolist_size(Body))}
|
|
|
|
|
|
+ {<<"Connection">>, atom_to_connection(Connection)},
|
|
|
|
+ {<<"Content-Length">>, list_to_binary(integer_to_list(iolist_size(Body)))}
|
|
],
|
|
],
|
|
Headers2 = lists:keysort(1, Headers),
|
|
Headers2 = lists:keysort(1, Headers),
|
|
Headers3 = lists:ukeymerge(1, Headers2, DefaultHeaders),
|
|
Headers3 = lists:ukeymerge(1, Headers2, DefaultHeaders),
|
|
- Headers4 = [[Key, ": ", Value, "\r\n"] || {Key, Value} <- Headers3],
|
|
|
|
- Transport:send(Socket, [StatusLine, Headers4, "\r\n", Body]),
|
|
|
|
|
|
+ Headers4 = [<< Key/binary, ": ", Value/binary, "\r\n">> || {Key, Value} <- Headers3],
|
|
|
|
+ Transport:send(Socket, [StatusLine, Headers4, <<"\r\n">>, Body]),
|
|
{ok, Req#http_req{resp_state=done}}.
|
|
{ok, Req#http_req{resp_state=done}}.
|
|
|
|
|
|
%% Internal.
|
|
%% Internal.
|
|
|
|
|
|
--spec parse_qs(Qs::string()) -> list({Name::string(), Value::string() | true}).
|
|
|
|
|
|
+-spec parse_qs(Qs::binary()) -> list({Name::binary(), Value::binary() | true}).
|
|
|
|
+parse_qs(<<>>) ->
|
|
|
|
+ [];
|
|
parse_qs(Qs) ->
|
|
parse_qs(Qs) ->
|
|
- Tokens = string:tokens(Qs, "&"),
|
|
|
|
- [case string:chr(Token, $=) of
|
|
|
|
- 0 ->
|
|
|
|
- {Token, true};
|
|
|
|
- N ->
|
|
|
|
- {Name, [$=|Value]} = lists:split(N - 1, Token),
|
|
|
|
- {Name, Value}
|
|
|
|
|
|
+ Tokens = binary:split(Qs, <<"&">>, [global, trim]),
|
|
|
|
+ [case binary:split(Token, <<"=">>) of
|
|
|
|
+ [Token] -> {Token, true};
|
|
|
|
+ [Name, Value] -> {Name, Value}
|
|
end || Token <- Tokens].
|
|
end || Token <- Tokens].
|
|
|
|
|
|
--spec atom_to_connection(Atom::keepalive | close) -> string().
|
|
|
|
|
|
+-spec atom_to_connection(Atom::keepalive | close) -> binary().
|
|
atom_to_connection(keepalive) ->
|
|
atom_to_connection(keepalive) ->
|
|
- "keep-alive";
|
|
|
|
|
|
+ <<"keep-alive">>;
|
|
atom_to_connection(close) ->
|
|
atom_to_connection(close) ->
|
|
- "close".
|
|
|
|
-
|
|
|
|
--spec status(Code::http_status()) -> string().
|
|
|
|
-status(100) -> "100 Continue";
|
|
|
|
-status(101) -> "101 Switching Protocols";
|
|
|
|
-status(102) -> "102 Processing";
|
|
|
|
-status(200) -> "200 OK";
|
|
|
|
-status(201) -> "201 Created";
|
|
|
|
-status(202) -> "202 Accepted";
|
|
|
|
-status(203) -> "203 Non-Authoritative Information";
|
|
|
|
-status(204) -> "204 No Content";
|
|
|
|
-status(205) -> "205 Reset Content";
|
|
|
|
-status(206) -> "206 Partial Content";
|
|
|
|
-status(207) -> "207 Multi-Status";
|
|
|
|
-status(226) -> "226 IM Used";
|
|
|
|
-status(300) -> "300 Multiple Choices";
|
|
|
|
-status(301) -> "301 Moved Permanently";
|
|
|
|
-status(302) -> "302 Found";
|
|
|
|
-status(303) -> "303 See Other";
|
|
|
|
-status(304) -> "304 Not Modified";
|
|
|
|
-status(305) -> "305 Use Proxy";
|
|
|
|
-status(306) -> "306 Switch Proxy";
|
|
|
|
-status(307) -> "307 Temporary Redirect";
|
|
|
|
-status(400) -> "400 Bad Request";
|
|
|
|
-status(401) -> "401 Unauthorized";
|
|
|
|
-status(402) -> "402 Payment Required";
|
|
|
|
-status(403) -> "403 Forbidden";
|
|
|
|
-status(404) -> "404 Not Found";
|
|
|
|
-status(405) -> "405 Method Not Allowed";
|
|
|
|
-status(406) -> "406 Not Acceptable";
|
|
|
|
-status(407) -> "407 Proxy Authentication Required";
|
|
|
|
-status(408) -> "408 Request Timeout";
|
|
|
|
-status(409) -> "409 Conflict";
|
|
|
|
-status(410) -> "410 Gone";
|
|
|
|
-status(411) -> "411 Length Required";
|
|
|
|
-status(412) -> "412 Precondition Failed";
|
|
|
|
-status(413) -> "413 Request Entity Too Large";
|
|
|
|
-status(414) -> "414 Request-URI Too Long";
|
|
|
|
-status(415) -> "415 Unsupported Media Type";
|
|
|
|
-status(416) -> "416 Requested Range Not Satisfiable";
|
|
|
|
-status(417) -> "417 Expectation Failed";
|
|
|
|
-status(418) -> "418 I'm a teapot";
|
|
|
|
-status(422) -> "422 Unprocessable Entity";
|
|
|
|
-status(423) -> "423 Locked";
|
|
|
|
-status(424) -> "424 Failed Dependency";
|
|
|
|
-status(425) -> "425 Unordered Collection";
|
|
|
|
-status(426) -> "426 Upgrade Required";
|
|
|
|
-status(500) -> "500 Internal Server Error";
|
|
|
|
-status(501) -> "501 Not Implemented";
|
|
|
|
-status(502) -> "502 Bad Gateway";
|
|
|
|
-status(503) -> "503 Service Unavailable";
|
|
|
|
-status(504) -> "504 Gateway Timeout";
|
|
|
|
-status(505) -> "505 HTTP Version Not Supported";
|
|
|
|
-status(506) -> "506 Variant Also Negotiates";
|
|
|
|
-status(507) -> "507 Insufficient Storage";
|
|
|
|
-status(510) -> "510 Not Extended";
|
|
|
|
-status(L) when is_list(L) -> L.
|
|
|
|
|
|
+ <<"close">>.
|
|
|
|
+
|
|
|
|
+-spec status(Code::http_status()) -> binary().
|
|
|
|
+status(100) -> <<"100 Continue">>;
|
|
|
|
+status(101) -> <<"101 Switching Protocols">>;
|
|
|
|
+status(102) -> <<"102 Processing">>;
|
|
|
|
+status(200) -> <<"200 OK">>;
|
|
|
|
+status(201) -> <<"201 Created">>;
|
|
|
|
+status(202) -> <<"202 Accepted">>;
|
|
|
|
+status(203) -> <<"203 Non-Authoritative Information">>;
|
|
|
|
+status(204) -> <<"204 No Content">>;
|
|
|
|
+status(205) -> <<"205 Reset Content">>;
|
|
|
|
+status(206) -> <<"206 Partial Content">>;
|
|
|
|
+status(207) -> <<"207 Multi-Status">>;
|
|
|
|
+status(226) -> <<"226 IM Used">>;
|
|
|
|
+status(300) -> <<"300 Multiple Choices">>;
|
|
|
|
+status(301) -> <<"301 Moved Permanently">>;
|
|
|
|
+status(302) -> <<"302 Found">>;
|
|
|
|
+status(303) -> <<"303 See Other">>;
|
|
|
|
+status(304) -> <<"304 Not Modified">>;
|
|
|
|
+status(305) -> <<"305 Use Proxy">>;
|
|
|
|
+status(306) -> <<"306 Switch Proxy">>;
|
|
|
|
+status(307) -> <<"307 Temporary Redirect">>;
|
|
|
|
+status(400) -> <<"400 Bad Request">>;
|
|
|
|
+status(401) -> <<"401 Unauthorized">>;
|
|
|
|
+status(402) -> <<"402 Payment Required">>;
|
|
|
|
+status(403) -> <<"403 Forbidden">>;
|
|
|
|
+status(404) -> <<"404 Not Found">>;
|
|
|
|
+status(405) -> <<"405 Method Not Allowed">>;
|
|
|
|
+status(406) -> <<"406 Not Acceptable">>;
|
|
|
|
+status(407) -> <<"407 Proxy Authentication Required">>;
|
|
|
|
+status(408) -> <<"408 Request Timeout">>;
|
|
|
|
+status(409) -> <<"409 Conflict">>;
|
|
|
|
+status(410) -> <<"410 Gone">>;
|
|
|
|
+status(411) -> <<"411 Length Required">>;
|
|
|
|
+status(412) -> <<"412 Precondition Failed">>;
|
|
|
|
+status(413) -> <<"413 Request Entity Too Large">>;
|
|
|
|
+status(414) -> <<"414 Request-URI Too Long">>;
|
|
|
|
+status(415) -> <<"415 Unsupported Media Type">>;
|
|
|
|
+status(416) -> <<"416 Requested Range Not Satisfiable">>;
|
|
|
|
+status(417) -> <<"417 Expectation Failed">>;
|
|
|
|
+status(418) -> <<"418 I'm a teapot">>;
|
|
|
|
+status(422) -> <<"422 Unprocessable Entity">>;
|
|
|
|
+status(423) -> <<"423 Locked">>;
|
|
|
|
+status(424) -> <<"424 Failed Dependency">>;
|
|
|
|
+status(425) -> <<"425 Unordered Collection">>;
|
|
|
|
+status(426) -> <<"426 Upgrade Required">>;
|
|
|
|
+status(500) -> <<"500 Internal Server Error">>;
|
|
|
|
+status(501) -> <<"501 Not Implemented">>;
|
|
|
|
+status(502) -> <<"502 Bad Gateway">>;
|
|
|
|
+status(503) -> <<"503 Service Unavailable">>;
|
|
|
|
+status(504) -> <<"504 Gateway Timeout">>;
|
|
|
|
+status(505) -> <<"505 HTTP Version Not Supported">>;
|
|
|
|
+status(506) -> <<"506 Variant Also Negotiates">>;
|
|
|
|
+status(507) -> <<"507 Insufficient Storage">>;
|
|
|
|
+status(510) -> <<"510 Not Extended">>;
|
|
|
|
+status(B) when is_binary(B) -> B.
|
|
|
|
|
|
%% Tests.
|
|
%% Tests.
|
|
|
|
|
|
@@ -275,12 +276,12 @@ status(L) when is_list(L) -> L.
|
|
parse_qs_test_() ->
|
|
parse_qs_test_() ->
|
|
%% {Qs, Result}
|
|
%% {Qs, Result}
|
|
Tests = [
|
|
Tests = [
|
|
- {"", []},
|
|
|
|
- {"a=b", [{"a", "b"}]},
|
|
|
|
- {"aaa=bbb", [{"aaa", "bbb"}]},
|
|
|
|
- {"a&b", [{"a", true}, {"b", true}]},
|
|
|
|
- {"a=b&c&d=e", [{"a", "b"}, {"c", true}, {"d", "e"}]},
|
|
|
|
- {"a=b=c=d=e&f=g", [{"a", "b=c=d=e"}, {"f", "g"}]}
|
|
|
|
|
|
+ {<<"">>, []},
|
|
|
|
+ {<<"a=b">>, [{<<"a">>, <<"b">>}]},
|
|
|
|
+ {<<"aaa=bbb">>, [{<<"aaa">>, <<"bbb">>}]},
|
|
|
|
+ {<<"a&b">>, [{<<"a">>, true}, {<<"b">>, true}]},
|
|
|
|
+ {<<"a=b&c&d=e">>, [{<<"a">>, <<"b">>}, {<<"c">>, true}, {<<"d">>, <<"e">>}]},
|
|
|
|
+ {<<"a=b=c=d=e&f=g">>, [{<<"a">>, <<"b=c=d=e">>}, {<<"f">>, <<"g">>}]}
|
|
],
|
|
],
|
|
[{Qs, fun() -> R = parse_qs(Qs) end} || {Qs, R} <- Tests].
|
|
[{Qs, fun() -> R = parse_qs(Qs) end} || {Qs, R} <- Tests].
|
|
|
|
|