From 3f0b598edff1f411e31aca18ea96562b0d724257 Mon Sep 17 00:00:00 2001 From: Gustaf Sjoberg Date: Wed, 22 Mar 2017 09:18:43 +0100 Subject: Add transform_header_name http option. --- doc/src/manual/gun.asciidoc | 6 ++++++ src/gun_http.erl | 17 +++++++++++++---- src/gun_http2.erl | 2 +- test/gun_SUITE.erl | 13 +++++++++++++ 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/doc/src/manual/gun.asciidoc b/doc/src/manual/gun.asciidoc index 7b9ddd0..7c794ee 100644 --- a/doc/src/manual/gun.asciidoc +++ b/doc/src/manual/gun.asciidoc @@ -61,6 +61,12 @@ keepalive => pos_integer():: empty line when the connection is idle. Gun only makes a best effort here as servers usually have configurable limits to drop idle connections. Defaults to 5000. +transform_header_name => fun((LowercaseName :: binary()) -> TransformedName :: binary()) | undefined:: + A function that will be applied to all header names before they + are sent to the server. Gun assumes that all header names are in + lower case. This function is useful if you, for example, need to + re-case header names in the event that the server incorrectly + considers header name case to be significant. version => 'HTTP/1.1' | 'HTTP/1.0':: HTTP version to use. Defaults to 'HTTP/1.1'. diff --git a/src/gun_http.erl b/src/gun_http.erl index 2388182..3766ca5 100644 --- a/src/gun_http.erl +++ b/src/gun_http.erl @@ -51,7 +51,8 @@ streams = [] :: [#stream{}], in = head :: io(), in_state = {0, 0} :: {non_neg_integer(), non_neg_integer()}, - out = head :: io() + out = head :: io(), + transform_header_name :: fun((binary()) -> binary()) }). check_options(Opts) -> @@ -68,6 +69,8 @@ do_check_options([Opt={content_handlers, Handlers}|Opts]) -> ok -> do_check_options(Opts); error -> {error, {options, {http, Opt}}} end; +do_check_options([{transform_header_name, F}|Opts]) when is_function(F) -> + do_check_options(Opts); do_check_options([Opt|_]) -> {error, {options, {http, Opt}}}. @@ -76,8 +79,9 @@ name() -> http. init(Owner, Socket, Transport, Opts) -> Version = maps:get(version, Opts, 'HTTP/1.1'), Handlers = maps:get(content_handlers, Opts, [gun_data]), + TransformHeaderName = maps:get(transform_header_name, Opts, fun (N) -> N end), #http_state{owner=Owner, socket=Socket, transport=Transport, version=Version, - content_handlers=Handlers}. + content_handlers=Handlers, transform_header_name=TransformHeaderName}. %% Stop looping when we got no more data. handle(<<>>, State) -> @@ -255,7 +259,8 @@ request(State=#http_state{socket=Socket, transport=Transport, version=Version, body_chunked -> [{<<"transfer-encoding">>, <<"chunked">>}|Headers3]; _ -> Headers3 end, - Transport:send(Socket, cow_http:request(Method, Path, Version, Headers4)), + Headers5 = transform_header_names(State, Headers4), + Transport:send(Socket, cow_http:request(Method, Path, Version, Headers5)), new_stream(State#http_state{connection=Conn, out=Out}, StreamRef, ReplyTo, Method). request(State=#http_state{socket=Socket, transport=Transport, version=Version, @@ -266,15 +271,19 @@ request(State=#http_state{socket=Socket, transport=Transport, version=Version, false -> [{<<"host">>, [Host, $:, integer_to_binary(Port)]}|Headers2]; true -> Headers2 end, + Headers4 = transform_header_names(State, Headers3), %% We use Headers2 because this is the smallest list. Conn = conn_from_headers(Version, Headers2), Transport:send(Socket, [ cow_http:request(Method, Path, Version, [ {<<"content-length">>, integer_to_binary(iolist_size(Body))} - |Headers3]), + |Headers4]), Body]), new_stream(State#http_state{connection=Conn}, StreamRef, ReplyTo, Method). +transform_header_names(#http_state{transform_header_name = Fun}, Headers) -> + lists:keymap(Fun, 1, Headers). + %% We are expecting a new stream. data(State=#http_state{out=head}, StreamRef, ReplyTo, _, _) -> error_stream_closed(State, StreamRef, ReplyTo); diff --git a/src/gun_http2.erl b/src/gun_http2.erl index 6073119..2195037 100644 --- a/src/gun_http2.erl +++ b/src/gun_http2.erl @@ -173,7 +173,7 @@ frame(settings_ack, State) -> %% @todo =#http2_state{next_settings=_NextSettings %% PUSH_PROMISE frame. %% @todo Continuation. frame({push_promise, StreamID, head_fin, PromisedStreamID, HeaderBlock}, - State=#http2_state{owner=Owner, decode_state=DecodeState0}) -> + State=#http2_state{decode_state=DecodeState0}) -> case get_stream_by_id(PromisedStreamID, State) of false -> case get_stream_by_id(StreamID, State) of diff --git a/test/gun_SUITE.erl b/test/gun_SUITE.erl index 171c874..505f365 100644 --- a/test/gun_SUITE.erl +++ b/test/gun_SUITE.erl @@ -139,3 +139,16 @@ retry_timeout(_) -> after 400 -> error(gone_too_late) end. + +transform_header_name(_) -> + doc("The reply_to option allows using a separate process for requests."), + {ok, Pid} = gun:open("google.com", 443, #{ + protocols => [http], + http_opts => #{ + transform_header_name => fun(<<"host">>) -> <<"HOST">>; (N) -> N end + } + }), + {ok, http} = gun:await_up(Pid), + Ref = gun:get(Pid, "/", [{<<"host">>, <<"google.com">>}]), + {response, _, _, _} = gun:await(Pid, Ref), + ok. -- cgit v1.2.3