diff options
author | Loïc Hoguin <[email protected]> | 2019-01-06 17:01:41 +0100 |
---|---|---|
committer | Loïc Hoguin <[email protected]> | 2019-01-06 17:01:41 +0100 |
commit | ab4878838fafbd453b41d031c9224b2ee8d2d956 (patch) | |
tree | 432a9fa69523af7637c3eeab0435b625c70a2cbd | |
parent | eee5a59bc1763fc2e1c8c6e7155939dd04d930b7 (diff) | |
download | gun-ab4878838fafbd453b41d031c9224b2ee8d2d956.tar.gz gun-ab4878838fafbd453b41d031c9224b2ee8d2d956.tar.bz2 gun-ab4878838fafbd453b41d031c9224b2ee8d2d956.zip |
Lowercase header names automatically and accept more types
Header names can now be provided as binary, string or atom
and Gun no longer requires them to be in lowercase. The list
of headers can also be provided as a map as well.
-rw-r--r-- | doc/src/guide/introduction.asciidoc | 5 | ||||
-rw-r--r-- | doc/src/manual/gun.asciidoc | 14 | ||||
-rw-r--r-- | doc/src/manual/gun.connect.asciidoc | 2 | ||||
-rw-r--r-- | doc/src/manual/gun.delete.asciidoc | 2 | ||||
-rw-r--r-- | doc/src/manual/gun.get.asciidoc | 2 | ||||
-rw-r--r-- | doc/src/manual/gun.head.asciidoc | 2 | ||||
-rw-r--r-- | doc/src/manual/gun.headers.asciidoc | 2 | ||||
-rw-r--r-- | doc/src/manual/gun.options.asciidoc | 2 | ||||
-rw-r--r-- | doc/src/manual/gun.patch.asciidoc | 2 | ||||
-rw-r--r-- | doc/src/manual/gun.post.asciidoc | 2 | ||||
-rw-r--r-- | doc/src/manual/gun.put.asciidoc | 2 | ||||
-rw-r--r-- | doc/src/manual/gun.request.asciidoc | 2 | ||||
-rw-r--r-- | doc/src/manual/gun.ws_upgrade.asciidoc | 2 | ||||
-rw-r--r-- | src/gun.erl | 73 | ||||
-rw-r--r-- | test/gun_SUITE.erl | 52 |
15 files changed, 121 insertions, 45 deletions
diff --git a/doc/src/guide/introduction.asciidoc b/doc/src/guide/introduction.asciidoc index 374f1f3..fa0e6c6 100644 --- a/doc/src/guide/introduction.asciidoc +++ b/doc/src/guide/introduction.asciidoc @@ -46,7 +46,4 @@ In the HTTP protocol, the method name is case sensitive. All standard method names are uppercase. Header names are case insensitive. Gun converts all the header names -to lowercase, and expects your application to provide lowercase header -names. - -The same applies to any other case insensitive value. +to lowercase, including request headers provided by your application. diff --git a/doc/src/manual/gun.asciidoc b/doc/src/manual/gun.asciidoc index dff927c..2726564 100644 --- a/doc/src/manual/gun.asciidoc +++ b/doc/src/manual/gun.asciidoc @@ -267,6 +267,16 @@ ws_opts (#{}):: Options specific to the Websocket protocol. +=== req_headers() + +[source,erlang] +---- +req_headers() :: [{binary() | string() | atom(), iodata()}] + | #{binary() | string() | atom() => iodata()} +---- + +Request headers. + === req_opts() [source,erlang] @@ -315,6 +325,10 @@ undocumented and must be set to `gun_ws_h`. == Changelog +* *2.0*: Introduce the type `req_headers()` and extend the + types accepted for header names for greater + interoperability. Header names are automatically + lowercased as well. * *2.0*: Function `gun:headers/4,5` introduced. * *2.0*: The `keepalive` option is now set to `infinity` by default for the HTTP/1.1 protocol, disabling it. diff --git a/doc/src/manual/gun.connect.asciidoc b/doc/src/manual/gun.connect.asciidoc index 483b24d..de40e34 100644 --- a/doc/src/manual/gun.connect.asciidoc +++ b/doc/src/manual/gun.connect.asciidoc @@ -19,7 +19,7 @@ connect(ConnPid, Destination, Headers, ReqOpts) ConnPid :: pid() Destination :: gun:connect_destination() -Headers :: [{binary(), iodata()}] +Headers :: gun:req_headers() ReqOpts :: gun:req_opts() StreamRef :: reference() ---- diff --git a/doc/src/manual/gun.delete.asciidoc b/doc/src/manual/gun.delete.asciidoc index feb64ff..7367adb 100644 --- a/doc/src/manual/gun.delete.asciidoc +++ b/doc/src/manual/gun.delete.asciidoc @@ -19,7 +19,7 @@ delete(ConnPid, Path, Headers, ReqOpts) ConnPid :: pid() Path :: iodata() -Headers :: [{binary(), iodata()}] +Headers :: gun:req_headers() ReqOpts :: gun:req_opts() StreamRef :: reference() ---- diff --git a/doc/src/manual/gun.get.asciidoc b/doc/src/manual/gun.get.asciidoc index 623c038..64feeab 100644 --- a/doc/src/manual/gun.get.asciidoc +++ b/doc/src/manual/gun.get.asciidoc @@ -19,7 +19,7 @@ get(ConnPid, Path, Headers, ReqOpts) ConnPid :: pid() Path :: iodata() -Headers :: [{binary(), iodata()}] +Headers :: gun:req_headers() ReqOpts :: gun:req_opts() StreamRef :: reference() ---- diff --git a/doc/src/manual/gun.head.asciidoc b/doc/src/manual/gun.head.asciidoc index 3d92e38..08ce86b 100644 --- a/doc/src/manual/gun.head.asciidoc +++ b/doc/src/manual/gun.head.asciidoc @@ -19,7 +19,7 @@ head(ConnPid, Path, Headers, ReqOpts) ConnPid :: pid() Path :: iodata() -Headers :: [{binary(), iodata()}] +Headers :: gun:req_headers() ReqOpts :: gun:req_opts() StreamRef :: reference() ---- diff --git a/doc/src/manual/gun.headers.asciidoc b/doc/src/manual/gun.headers.asciidoc index bab1b82..4abcb39 100644 --- a/doc/src/manual/gun.headers.asciidoc +++ b/doc/src/manual/gun.headers.asciidoc @@ -17,7 +17,7 @@ headers(ConnPid, Method, Path, Headers, ReqOpts) ConnPid :: pid() Method :: binary() Path :: iodata() -Headers :: [{binary(), iodata()}] +Headers :: gun:req_headers() ReqOpts :: gun:req_opts() StreamRef :: reference() ---- diff --git a/doc/src/manual/gun.options.asciidoc b/doc/src/manual/gun.options.asciidoc index 5b36f22..dfbba7b 100644 --- a/doc/src/manual/gun.options.asciidoc +++ b/doc/src/manual/gun.options.asciidoc @@ -19,7 +19,7 @@ options(ConnPid, Path, Headers, ReqOpts) ConnPid :: pid() Path :: iodata() -Headers :: [{binary(), iodata()}] +Headers :: gun:req_headers() ReqOpts :: gun:req_opts() StreamRef :: reference() ---- diff --git a/doc/src/manual/gun.patch.asciidoc b/doc/src/manual/gun.patch.asciidoc index c240a4d..cdc8f97 100644 --- a/doc/src/manual/gun.patch.asciidoc +++ b/doc/src/manual/gun.patch.asciidoc @@ -22,7 +22,7 @@ patch(ConnPid, Path, Headers, Body, ReqOpts) ConnPid :: pid() Path :: iodata() -Headers :: [{binary(), iodata()}] +Headers :: gun:req_headers() Body :: iodata() ReqOpts :: gun:req_opts() StreamRef :: reference() diff --git a/doc/src/manual/gun.post.asciidoc b/doc/src/manual/gun.post.asciidoc index aed8b3f..a545a32 100644 --- a/doc/src/manual/gun.post.asciidoc +++ b/doc/src/manual/gun.post.asciidoc @@ -22,7 +22,7 @@ post(ConnPid, Path, Headers, Body, ReqOpts) ConnPid :: pid() Path :: iodata() -Headers :: [{binary(), iodata()}] +Headers :: gun:req_headers() Body :: iodata() ReqOpts :: gun:req_opts() StreamRef :: reference() diff --git a/doc/src/manual/gun.put.asciidoc b/doc/src/manual/gun.put.asciidoc index a60849b..7807d68 100644 --- a/doc/src/manual/gun.put.asciidoc +++ b/doc/src/manual/gun.put.asciidoc @@ -22,7 +22,7 @@ put(ConnPid, Path, Headers, Body, ReqOpts) ConnPid :: pid() Path :: iodata() -Headers :: [{binary(), iodata()}] +Headers :: gun:req_headers() Body :: iodata() ReqOpts :: gun:req_opts() StreamRef :: reference() diff --git a/doc/src/manual/gun.request.asciidoc b/doc/src/manual/gun.request.asciidoc index f5c1cd8..0a50499 100644 --- a/doc/src/manual/gun.request.asciidoc +++ b/doc/src/manual/gun.request.asciidoc @@ -17,7 +17,7 @@ request(ConnPid, Method, Path, Headers, Body, ReqOpts) ConnPid :: pid() Method :: binary() Path :: iodata() -Headers :: [{binary(), iodata()}] +Headers :: gun:req_headers() Body :: iodata() ReqOpts :: gun:req_opts() StreamRef :: reference() diff --git a/doc/src/manual/gun.ws_upgrade.asciidoc b/doc/src/manual/gun.ws_upgrade.asciidoc index cb9da06..241d916 100644 --- a/doc/src/manual/gun.ws_upgrade.asciidoc +++ b/doc/src/manual/gun.ws_upgrade.asciidoc @@ -19,7 +19,7 @@ ws_upgrade(ConnPid, Path, Headers, WsOpts) ConnPid :: pid() Path :: iodata() -Headers :: [{binary(), iodata()}] +Headers :: gun:req_headers() WsOpts :: gun:ws_opts StreamRef :: reference() ---- diff --git a/src/gun.erl b/src/gun.erl index 55e70dd..75092f1 100644 --- a/src/gun.erl +++ b/src/gun.erl @@ -94,7 +94,9 @@ -export([not_connected/3]). -export([connected/3]). --type headers() :: [{binary(), iodata()}]. +-type req_headers() :: [{binary() | string() | atom(), iodata()}] + | #{binary() | string() | atom() => iodata()}. +-export_type([req_headers/0]). -type ws_close_code() :: 1000..4999. -type ws_frame() :: close | ping | pong @@ -320,11 +322,11 @@ shutdown(ServerPid) -> delete(ServerPid, Path) -> request(ServerPid, <<"DELETE">>, Path, [], <<>>). --spec delete(pid(), iodata(), headers()) -> reference(). +-spec delete(pid(), iodata(), req_headers()) -> reference(). delete(ServerPid, Path, Headers) -> request(ServerPid, <<"DELETE">>, Path, Headers, <<>>). --spec delete(pid(), iodata(), headers(), req_opts()) -> reference(). +-spec delete(pid(), iodata(), req_headers(), req_opts()) -> reference(). delete(ServerPid, Path, Headers, ReqOpts) -> request(ServerPid, <<"DELETE">>, Path, Headers, <<>>, ReqOpts). @@ -332,11 +334,11 @@ delete(ServerPid, Path, Headers, ReqOpts) -> get(ServerPid, Path) -> request(ServerPid, <<"GET">>, Path, [], <<>>). --spec get(pid(), iodata(), headers()) -> reference(). +-spec get(pid(), iodata(), req_headers()) -> reference(). get(ServerPid, Path, Headers) -> request(ServerPid, <<"GET">>, Path, Headers, <<>>). --spec get(pid(), iodata(), headers(), req_opts()) -> reference(). +-spec get(pid(), iodata(), req_headers(), req_opts()) -> reference(). get(ServerPid, Path, Headers, ReqOpts) -> request(ServerPid, <<"GET">>, Path, Headers, <<>>, ReqOpts). @@ -344,11 +346,11 @@ get(ServerPid, Path, Headers, ReqOpts) -> head(ServerPid, Path) -> request(ServerPid, <<"HEAD">>, Path, [], <<>>). --spec head(pid(), iodata(), headers()) -> reference(). +-spec head(pid(), iodata(), req_headers()) -> reference(). head(ServerPid, Path, Headers) -> request(ServerPid, <<"HEAD">>, Path, Headers, <<>>). --spec head(pid(), iodata(), headers(), req_opts()) -> reference(). +-spec head(pid(), iodata(), req_headers(), req_opts()) -> reference(). head(ServerPid, Path, Headers, ReqOpts) -> request(ServerPid, <<"HEAD">>, Path, Headers, <<>>, ReqOpts). @@ -356,82 +358,93 @@ head(ServerPid, Path, Headers, ReqOpts) -> options(ServerPid, Path) -> request(ServerPid, <<"OPTIONS">>, Path, [], <<>>). --spec options(pid(), iodata(), headers()) -> reference(). +-spec options(pid(), iodata(), req_headers()) -> reference(). options(ServerPid, Path, Headers) -> request(ServerPid, <<"OPTIONS">>, Path, Headers, <<>>). --spec options(pid(), iodata(), headers(), req_opts()) -> reference(). +-spec options(pid(), iodata(), req_headers(), req_opts()) -> reference(). options(ServerPid, Path, Headers, ReqOpts) -> request(ServerPid, <<"OPTIONS">>, Path, Headers, <<>>, ReqOpts). --spec patch(pid(), iodata(), headers()) -> reference(). +-spec patch(pid(), iodata(), req_headers()) -> reference(). patch(ServerPid, Path, Headers) -> headers(ServerPid, <<"PATCH">>, Path, Headers). --spec patch(pid(), iodata(), headers(), iodata() | req_opts()) -> reference(). +-spec patch(pid(), iodata(), req_headers(), iodata() | req_opts()) -> reference(). patch(ServerPid, Path, Headers, ReqOpts) when is_map(ReqOpts) -> headers(ServerPid, <<"PATCH">>, Path, Headers, ReqOpts); patch(ServerPid, Path, Headers, Body) -> request(ServerPid, <<"PATCH">>, Path, Headers, Body). --spec patch(pid(), iodata(), headers(), iodata(), req_opts()) -> reference(). +-spec patch(pid(), iodata(), req_headers(), iodata(), req_opts()) -> reference(). patch(ServerPid, Path, Headers, Body, ReqOpts) -> request(ServerPid, <<"PATCH">>, Path, Headers, Body, ReqOpts). --spec post(pid(), iodata(), headers()) -> reference(). +-spec post(pid(), iodata(), req_headers()) -> reference(). post(ServerPid, Path, Headers) -> headers(ServerPid, <<"POST">>, Path, Headers). --spec post(pid(), iodata(), headers(), iodata() | req_opts()) -> reference(). +-spec post(pid(), iodata(), req_headers(), iodata() | req_opts()) -> reference(). post(ServerPid, Path, Headers, ReqOpts) when is_map(ReqOpts) -> headers(ServerPid, <<"POST">>, Path, Headers, ReqOpts); post(ServerPid, Path, Headers, Body) -> request(ServerPid, <<"POST">>, Path, Headers, Body). --spec post(pid(), iodata(), headers(), iodata(), req_opts()) -> reference(). +-spec post(pid(), iodata(), req_headers(), iodata(), req_opts()) -> reference(). post(ServerPid, Path, Headers, Body, ReqOpts) -> request(ServerPid, <<"POST">>, Path, Headers, Body, ReqOpts). --spec put(pid(), iodata(), headers()) -> reference(). +-spec put(pid(), iodata(), req_headers()) -> reference(). put(ServerPid, Path, Headers) -> headers(ServerPid, <<"PUT">>, Path, Headers). --spec put(pid(), iodata(), headers(), iodata() | req_opts()) -> reference(). +-spec put(pid(), iodata(), req_headers(), iodata() | req_opts()) -> reference(). put(ServerPid, Path, Headers, ReqOpts) when is_map(ReqOpts) -> headers(ServerPid, <<"PUT">>, Path, Headers, ReqOpts); put(ServerPid, Path, Headers, Body) -> request(ServerPid, <<"PUT">>, Path, Headers, Body). --spec put(pid(), iodata(), headers(), iodata(), req_opts()) -> reference(). +-spec put(pid(), iodata(), req_headers(), iodata(), req_opts()) -> reference(). put(ServerPid, Path, Headers, Body, ReqOpts) -> request(ServerPid, <<"PUT">>, Path, Headers, Body, ReqOpts). %% Generic requests interface. --spec headers(pid(), iodata(), iodata(), headers()) -> reference(). +-spec headers(pid(), iodata(), iodata(), req_headers()) -> reference(). headers(ServerPid, Method, Path, Headers) -> headers(ServerPid, Method, Path, Headers, #{}). -%% @todo Accept header names as maps. --spec headers(pid(), iodata(), iodata(), headers(), req_opts()) -> reference(). +-spec headers(pid(), iodata(), iodata(), req_headers(), req_opts()) -> reference(). headers(ServerPid, Method, Path, Headers, ReqOpts) -> StreamRef = make_ref(), ReplyTo = maps:get(reply_to, ReqOpts, self()), - gen_statem:cast(ServerPid, {headers, ReplyTo, StreamRef, Method, Path, Headers}), + gen_statem:cast(ServerPid, {headers, ReplyTo, StreamRef, + Method, Path, normalize_headers(Headers)}), StreamRef. --spec request(pid(), iodata(), iodata(), headers(), iodata()) -> reference(). +-spec request(pid(), iodata(), iodata(), req_headers(), iodata()) -> reference(). request(ServerPid, Method, Path, Headers, Body) -> request(ServerPid, Method, Path, Headers, Body, #{}). -%% @todo Accept header names as maps. --spec request(pid(), iodata(), iodata(), headers(), iodata(), req_opts()) -> reference(). +-spec request(pid(), iodata(), iodata(), req_headers(), iodata(), req_opts()) -> reference(). request(ServerPid, Method, Path, Headers, Body, ReqOpts) -> StreamRef = make_ref(), ReplyTo = maps:get(reply_to, ReqOpts, self()), - gen_statem:cast(ServerPid, {request, ReplyTo, StreamRef, Method, Path, Headers, Body}), + gen_statem:cast(ServerPid, {request, ReplyTo, StreamRef, + Method, Path, normalize_headers(Headers), Body}), StreamRef. +normalize_headers([]) -> + []; +normalize_headers([{Name, Value}|Tail]) when is_binary(Name) -> + [{string:lowercase(Name), Value}|normalize_headers(Tail)]; +normalize_headers([{Name, Value}|Tail]) when is_list(Name) -> + [{string:lowercase(unicode:characters_to_binary(Name)), Value}|normalize_headers(Tail)]; +normalize_headers([{Name, Value}|Tail]) when is_atom(Name) -> + [{string:lowercase(atom_to_binary(Name, latin1)), Value}|normalize_headers(Tail)]; +normalize_headers(Headers) when is_map(Headers) -> + normalize_headers(maps:to_list(Headers)). + %% Streaming data. -spec data(pid(), reference(), fin | nofin, iodata()) -> ok. @@ -449,11 +462,11 @@ data(ServerPid, StreamRef, IsFin, Data) -> connect(ServerPid, Destination) -> connect(ServerPid, Destination, [], #{}). --spec connect(pid(), connect_destination(), headers()) -> reference(). +-spec connect(pid(), connect_destination(), req_headers()) -> reference(). connect(ServerPid, Destination, Headers) -> connect(ServerPid, Destination, Headers, #{}). --spec connect(pid(), connect_destination(), headers(), req_opts()) -> reference(). +-spec connect(pid(), connect_destination(), req_headers(), req_opts()) -> reference(). connect(ServerPid, Destination, Headers, ReqOpts) -> StreamRef = make_ref(), ReplyTo = maps:get(reply_to, ReqOpts, self()), @@ -639,13 +652,13 @@ cancel(ServerPid, StreamRef) -> ws_upgrade(ServerPid, Path) -> ws_upgrade(ServerPid, Path, []). --spec ws_upgrade(pid(), iodata(), headers()) -> reference(). +-spec ws_upgrade(pid(), iodata(), req_headers()) -> reference(). ws_upgrade(ServerPid, Path, Headers) -> StreamRef = make_ref(), gen_statem:cast(ServerPid, {ws_upgrade, self(), StreamRef, Path, Headers}), StreamRef. --spec ws_upgrade(pid(), iodata(), headers(), ws_opts()) -> reference(). +-spec ws_upgrade(pid(), iodata(), req_headers(), ws_opts()) -> reference(). ws_upgrade(ServerPid, Path, Headers, Opts) -> ok = gun_ws:check_options(Opts), StreamRef = make_ref(), diff --git a/test/gun_SUITE.erl b/test/gun_SUITE.erl index 957d2df..dd7b083 100644 --- a/test/gun_SUITE.erl +++ b/test/gun_SUITE.erl @@ -28,6 +28,19 @@ all() -> %% Tests. +atom_header_name(_) -> + doc("Header names may be given as atom."), + {ok, OriginPid, OriginPort} = init_origin(tcp, http), + {ok, Pid} = gun:open("localhost", OriginPort), + {ok, http} = gun:await_up(Pid), + _ = gun:get(Pid, "/", [ + {'User-Agent', "Gun/atom-headers"} + ]), + Data = receive_from(OriginPid), + Lines = binary:split(Data, <<"\r\n">>, [global]), + [<<"user-agent: Gun/atom-headers">>] = [L || <<"user-agent: ", _/bits>> = L <- Lines], + gun:close(Pid). + atom_hostname(_) -> doc("Hostnames may be given as atom."), {ok, OriginPid, OriginPort} = init_origin(tcp, http), @@ -207,6 +220,32 @@ killed_streams_http(_) -> error(timeout) end. +list_header_name(_) -> + doc("Header names may be given as list."), + {ok, OriginPid, OriginPort} = init_origin(tcp, http), + {ok, Pid} = gun:open("localhost", OriginPort), + {ok, http} = gun:await_up(Pid), + _ = gun:get(Pid, "/", [ + {"User-Agent", "Gun/list-headers"} + ]), + Data = receive_from(OriginPid), + Lines = binary:split(Data, <<"\r\n">>, [global]), + [<<"user-agent: Gun/list-headers">>] = [L || <<"user-agent: ", _/bits>> = L <- Lines], + gun:close(Pid). + +map_headers(_) -> + doc("Header names may be given as a map."), + {ok, OriginPid, OriginPort} = init_origin(tcp, http), + {ok, Pid} = gun:open("localhost", OriginPort), + {ok, http} = gun:await_up(Pid), + _ = gun:get(Pid, "/", #{ + <<"USER-agent">> => "Gun/map-headers" + }), + Data = receive_from(OriginPid), + Lines = binary:split(Data, <<"\r\n">>, [global]), + [<<"user-agent: Gun/map-headers">>] = [L || <<"user-agent: ", _/bits>> = L <- Lines], + gun:close(Pid). + reply_to(_) -> doc("The reply_to option allows using a separate process for requests."), do_reply_to(http), @@ -396,3 +435,16 @@ do_unix_socket_connect() -> after 250 -> error(timeout) end. + +uppercase_header_name(_) -> + doc("Header names may be given with uppercase characters."), + {ok, OriginPid, OriginPort} = init_origin(tcp, http), + {ok, Pid} = gun:open("localhost", OriginPort), + {ok, http} = gun:await_up(Pid), + _ = gun:get(Pid, "/", [ + {<<"USER-agent">>, "Gun/uppercase-headers"} + ]), + Data = receive_from(OriginPid), + Lines = binary:split(Data, <<"\r\n">>, [global]), + [<<"user-agent: Gun/uppercase-headers">>] = [L || <<"user-agent: ", _/bits>> = L <- Lines], + gun:close(Pid). |