Skip to content

Commit

Permalink
Merge pull request #6 from erlcloud/do_canon_headers
Browse files Browse the repository at this point in the history
Do canonical headers attempt #2
  • Loading branch information
Evgeny Bob authored Dec 30, 2016
2 parents 476e950 + f1f2394 commit 93200b8
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 84 deletions.
7 changes: 4 additions & 3 deletions src/lhttpc_client.erl
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ request(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) ->
%% socket used is new, it also makes the pool gen_server its controlling process.
%% @end
%%------------------------------------------------------------------------------
execute(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) ->
execute(From, Host, Port, Ssl, Path, Method, Hdrs0, Body, Options) ->
UploadWindowSize = proplists:get_value(partial_upload, Options),
PartialUpload = proplists:is_defined(partial_upload, Options),
PartialDownload = proplists:is_defined(partial_download, Options),
Expand All @@ -135,6 +135,7 @@ execute(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) ->
ProxyUrl when is_list(ProxyUrl) ->
lhttpc_lib:parse_url(ProxyUrl)
end,
Hdrs = lhttpc_lib:canonical_headers(Hdrs0),
{ChunkedUpload, Request} = lhttpc_lib:format_request(Path, NormalizedMethod,
Hdrs, Host, Port, Body, PartialUpload),
%SocketRequest = {socket, self(), Host, Port, Ssl},
Expand Down Expand Up @@ -432,7 +433,7 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) ->
NewStatus = {NewStatusCode, Reason},
read_response(State, NewVsn, NewStatus, Hdrs);
{ok, {http_header, _, Name, _, Value}} ->
Header = {lhttpc_lib:maybe_atom_to_list(Name), Value},
Header = lhttpc_lib:canonical_header({Name, Value}),
read_response(State, Vsn, Status, [Header | Hdrs]);
{ok, http_eoh} when StatusCode >= 100, StatusCode =< 199 ->
% RFC 2616, section 10.1:
Expand Down Expand Up @@ -784,7 +785,7 @@ read_trailers(Socket, Ssl, Trailers, Hdrs) ->
{ok, http_eoh} ->
{Trailers, Hdrs};
{ok, {http_header, _, Name, _, Value}} ->
Header = {lhttpc_lib:maybe_atom_to_list(Name), Value},
Header = lhttpc_lib:canonical_header({Name, Value}),
read_trailers(Socket, Ssl, [Header | Trailers], [Header | Hdrs]);
{error, {http_error, Data}} ->
erlang:error({bad_trailer, Data})
Expand Down
99 changes: 41 additions & 58 deletions src/lhttpc_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
normalize_method/1,
maybe_atom_to_list/1,
format_hdrs/1,
dec/1
dec/1,
canonical_headers/1,
canonical_header/1
]).

-include("lhttpc_types.hrl").
Expand Down Expand Up @@ -73,30 +75,44 @@ header_value(Hdr, Hdrs) ->
%% @doc
%% Returns the value associated with the `Header' in `Headers'.
%% `Header' must be a lowercase string, since every header is mangled to
%% check the match. If no match is found, `Default' is returned.
%% check the match. `Headers' must be canonical.
%% If no match is found, `Default' is returned.
%% @end
%%------------------------------------------------------------------------------
-spec header_value(string(), headers(), term()) -> term().
header_value(Hdr, [{Hdr, Value} | _], _) ->
case is_list(Value) of
true -> string:strip(Value);
false -> Value
end;
header_value(Hdr, [{ThisHdr, Value}| Hdrs], Default) when is_atom(ThisHdr) ->
header_value(Hdr, [{atom_to_list(ThisHdr), Value}| Hdrs], Default);
header_value(Hdr, [{ThisHdr, Value}| Hdrs], Default) when is_binary(ThisHdr) ->
header_value(Hdr, [{binary_to_list(ThisHdr), Value}| Hdrs], Default);
header_value(Hdr, [{ThisHdr, Value}| Hdrs], Default) ->
case string:equal(string:to_lower(ThisHdr), Hdr) of
true -> case is_list(Value) of
true -> string:strip(Value);
false -> Value
end;
header_value(MaybeUpperHdr, Headers, Default) ->
Hdr = string:to_lower(MaybeUpperHdr),
case lists:keyfind(Hdr, 1, Headers) of
false ->
header_value(Hdr, Hdrs, Default)
end;
header_value(_, [], Default) ->
Default.
Default;
{_, Value} when is_list(Value) ->
string:strip(Value);
{_, Value} ->
%% ransomr: not sure why we only need to strip list values, but
%% but leaving as-is
Value
end.

%%------------------------------------------------------------------------------
%% @doc
%% @end
%%------------------------------------------------------------------------------
canonical_headers(Headers) ->
[canonical_header(Header) || Header <- Headers].

%%------------------------------------------------------------------------------
%% @doc
%% @end
%%------------------------------------------------------------------------------
canonical_header({Name, Value}) ->
{canonical_header_name(Name), Value}.

canonical_header_name(Name) when is_list(Name) ->
string:to_lower(Name);
canonical_header_name(Name) when is_atom(Name) ->
canonical_header_name(atom_to_list(Name));
canonical_header_name(Name) when is_binary(Name) ->
canonical_header_name(binary_to_list(Name)).

%%------------------------------------------------------------------------------
%% @spec (Item) -> OtherItem
Expand Down Expand Up @@ -190,8 +206,7 @@ dec(Else) -> Else.
%%------------------------------------------------------------------------------
-spec format_hdrs(headers()) -> [string()].
format_hdrs(Headers) ->
NormalizedHeaders = normalize_headers(Headers),
format_hdrs(NormalizedHeaders, []).
format_hdrs(Headers, []).

%%==============================================================================
%% Internal functions
Expand Down Expand Up @@ -285,46 +300,14 @@ split_port(_,[$/ | _] = Path, Port) ->
split_port(Scheme, [P | T], Port) ->
split_port(Scheme, T, [P | Port]).

%%------------------------------------------------------------------------------
%% @private
%% @spec normalize_headers(RawHeaders) -> Headers
%% RawHeaders = [{atom() | binary() | string(), binary() | string()}]
%% Headers = headers()
%% @doc Turns the headers into binaries suitable for inclusion in a HTTP request
%% line.
%% @end
%%------------------------------------------------------------------------------
-spec normalize_headers(raw_headers()) -> headers().
normalize_headers(Headers) ->
normalize_headers(Headers, []).

%%------------------------------------------------------------------------------
%% @private
%% @doc
%% @end
%%------------------------------------------------------------------------------
-spec normalize_headers(raw_headers(), headers()) -> headers().
normalize_headers([{Header, Value} | T], Acc) when is_list(Header) ->
NormalizedHeader = try list_to_existing_atom(Header)
catch
error:badarg -> Header
end,
NewAcc = [{NormalizedHeader, Value} | Acc],
normalize_headers(T, NewAcc);
normalize_headers([{Header, Value} | T], Acc) ->
NewAcc = [{Header, Value} | Acc],
normalize_headers(T, NewAcc);
normalize_headers([], Acc) ->
Acc.

%%------------------------------------------------------------------------------
%% @private
%% @doc
%% @end
%%------------------------------------------------------------------------------
format_hdrs([{Hdr, Value} | T], Acc) ->
NewAcc =
[maybe_atom_to_list(Hdr), ": ", maybe_atom_to_list(Value), "\r\n" | Acc],
[Hdr, ": ", Value, "\r\n" | Acc],
format_hdrs(T, NewAcc);
format_hdrs([], Acc) ->
[Acc, "\r\n"].
Expand Down Expand Up @@ -386,15 +369,15 @@ add_content_headers(Hdrs, Body, false) ->
case header_value("content-length", Hdrs) of
undefined ->
ContentLength = integer_to_list(iolist_size(Body)),
[{"Content-Length", ContentLength} | Hdrs];
[{"content-length", ContentLength} | Hdrs];
_ -> % We have a content length
Hdrs
end;
add_content_headers(Hdrs, _Body, true) ->
case {header_value("content-length", Hdrs),
header_value("transfer-encoding", Hdrs)} of
{undefined, undefined} ->
[{"Transfer-Encoding", "chunked"} | Hdrs];
[{"transfer-encoding", "chunked"} | Hdrs];
{undefined, TransferEncoding} ->
case string:to_lower(TransferEncoding) of
"chunked" -> Hdrs;
Expand Down
24 changes: 12 additions & 12 deletions test/lhttpc_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ simple_response(Module, Socket, _Request, _Headers, Body) ->
Socket,
[
"HTTP/1.1 200 OK\r\n"
"Content-type: text/plain\r\nContent-length: 14\r\n"
"Content-type: text/plain\r\nContent-Length: 14\r\n"
"X-Test-Orig-Body: ", Body, "\r\n\r\n"
?DEFAULT_STRING
]
Expand All @@ -884,7 +884,7 @@ large_response(Module, Socket, _, _, _) ->
[
"HTTP/1.1 200 OK\r\n"
"Content-type: text/plain\r\n"
"Content-length: ", integer_to_list(ContentLength), "\r\n\r\n"
"Content-Length: ", integer_to_list(ContentLength), "\r\n\r\n"
]
),
Module:send(Socket, BodyPart),
Expand Down Expand Up @@ -959,15 +959,15 @@ empty_body(Module, Socket, _, _, _) ->
Module:send(
Socket,
"HTTP/1.1 200 OK\r\n"
"Content-type: text/plain\r\nContent-length: 0\r\n\r\n"
"Content-type: text/plain\r\nContent-Length: 0\r\n\r\n"
).

copy_body(Module, Socket, _, _, Body) ->
Module:send(
Socket,
[
"HTTP/1.1 200 OK\r\n"
"Content-type: text/plain\r\nContent-length: "
"Content-type: text/plain\r\nContent-Length: "
++ integer_to_list(size(Body)) ++ "\r\n\r\n",
Body
]
Expand All @@ -979,7 +979,7 @@ copy_body_100_continue(Module, Socket, _, _, Body) ->
[
"HTTP/1.1 100 Continue\r\n\r\n"
"HTTP/1.1 200 OK\r\n"
"Content-type: text/plain\r\nContent-length: "
"Content-type: text/plain\r\nContent-Length: "
++ integer_to_list(size(Body)) ++ "\r\n\r\n",
Body
]
Expand All @@ -991,7 +991,7 @@ respond_and_close(Module, Socket, _, _, Body) ->
Socket,
"HTTP/1.1 200 OK\r\n"
"Connection: close\r\n"
"Content-type: text/plain\r\nContent-length: 14\r\n\r\n"
"Content-type: text/plain\r\nContent-Length: 14\r\n\r\n"
?DEFAULT_STRING
),
{error, closed} = Module:recv(Socket, 0),
Expand All @@ -1003,7 +1003,7 @@ respond_and_wait(Module, Socket, _, _, Body) ->
Module:send(
Socket,
"HTTP/1.1 200 OK\r\n"
"Content-type: text/plain\r\nContent-length: 14\r\n\r\n"
"Content-type: text/plain\r\nContent-Length: 14\r\n\r\n"
?DEFAULT_STRING
),
% We didn't signal a connection close, but we want the client to do that
Expand All @@ -1017,7 +1017,7 @@ pre_1_1_server(Module, Socket, _, _, Body) ->
Module:send(
Socket,
"HTTP/1.0 200 OK\r\n"
"Content-type: text/plain\r\nContent-length: 14\r\n\r\n"
"Content-type: text/plain\r\nContent-Length: 14\r\n\r\n"
?DEFAULT_STRING
),
% We didn't signal a connection close, but we want the client to do that
Expand All @@ -1032,7 +1032,7 @@ pre_1_1_server_keep_alive(Module, Socket, _, _, _) ->
"HTTP/1.0 200 OK\r\n"
"Content-type: text/plain\r\n"
"Connection: Keep-Alive\r\n"
"Content-length: 14\r\n\r\n"
"Content-Length: 14\r\n\r\n"
?DEFAULT_STRING
).

Expand All @@ -1041,7 +1041,7 @@ very_slow_response(Module, Socket, _, _, _) ->
Module:send(
Socket,
"HTTP/1.1 200 OK\r\n"
"Content-type: text/plain\r\nContent-length: 14\r\n\r\n"
"Content-type: text/plain\r\nContent-Length: 14\r\n\r\n"
?DEFAULT_STRING
).

Expand Down Expand Up @@ -1129,7 +1129,7 @@ close_connection(Module, Socket, _, _, _) ->
Module:send(
Socket,
"HTTP/1.1 200 OK\r\n"
"Content-type: text/plain\r\nContent-length: 14\r\n\r\n"
"Content-type: text/plain\r\nContent-Length: 14\r\n\r\n"
),
Module:close(Socket).

Expand All @@ -1144,7 +1144,7 @@ not_modified_response(Module, Socket, _Request, _Headers, _Body) ->

basic_auth_responder(User, Passwd) ->
fun(Module, Socket, _Request, Headers, _Body) ->
case proplists:get_value("Authorization", Headers) of
case lhttpc_lib:header_value("Authorization", Headers) of
undefined ->
Module:send(
Socket,
Expand Down
16 changes: 5 additions & 11 deletions test/webserver.erl
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,8 @@ read_chunks(Module, Socket, Acc) ->

read_trailers(Module, Socket, Hdrs) ->
case Module:recv(Socket, 0) of
{ok, {http_header, _, Name, _, Value}} when is_atom(Name) ->
Trailer = {atom_to_list(Name), Value},
read_trailers(Module, Socket, [Trailer | Hdrs]);
{ok, {http_header, _, Name, _, Value}} when is_list(Name) ->
Trailer = {Name, Value},
{ok, {http_header, _, Name, _, Value}} ->
Trailer = lhttpc_lib:canonical_header({Name, Value}),
read_trailers(Module, Socket, [Trailer | Hdrs]);
{ok, http_eoh} -> Hdrs
end.
Expand All @@ -94,14 +91,11 @@ server_loop(Module, Socket, Request, Headers, Responders) ->
case Module:recv(Socket, 0) of
{ok, {http_request, _, _, _} = NewRequest} ->
server_loop(Module, Socket, NewRequest, Headers, Responders);
{ok, {http_header, _, Field, _, Value}} when is_atom(Field) ->
NewHeaders = [{atom_to_list(Field), Value} | Headers],
server_loop(Module, Socket, Request, NewHeaders, Responders);
{ok, {http_header, _, Field, _, Value}} when is_list(Field) ->
NewHeaders = [{Field, Value} | Headers],
{ok, {http_header, _, Field, _, Value}} ->
NewHeaders = [lhttpc_lib:canonical_header({Field,Value}) | Headers],
server_loop(Module, Socket, Request, NewHeaders, Responders);
{ok, http_eoh} ->
RequestBody = case proplists:get_value("Content-Length", Headers) of
RequestBody = case lhttpc_lib:header_value("Content-Length", Headers) of
undefined ->
<<>>;
"0" ->
Expand Down

0 comments on commit 93200b8

Please sign in to comment.