aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/src/guide/introduction.asciidoc5
-rw-r--r--doc/src/manual/gun.asciidoc14
-rw-r--r--doc/src/manual/gun.connect.asciidoc2
-rw-r--r--doc/src/manual/gun.delete.asciidoc2
-rw-r--r--doc/src/manual/gun.get.asciidoc2
-rw-r--r--doc/src/manual/gun.head.asciidoc2
-rw-r--r--doc/src/manual/gun.headers.asciidoc2
-rw-r--r--doc/src/manual/gun.options.asciidoc2
-rw-r--r--doc/src/manual/gun.patch.asciidoc2
-rw-r--r--doc/src/manual/gun.post.asciidoc2
-rw-r--r--doc/src/manual/gun.put.asciidoc2
-rw-r--r--doc/src/manual/gun.request.asciidoc2
-rw-r--r--doc/src/manual/gun.ws_upgrade.asciidoc2
-rw-r--r--src/gun.erl73
-rw-r--r--test/gun_SUITE.erl52
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).