Packages
hackney
4.5.2
4.7.2
4.7.1
4.7.0
4.6.1
4.6.0
4.5.2
4.5.1
4.5.0
4.4.5
4.4.3
4.4.2
4.4.1
4.4.0
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.0
4.0.3
4.0.2
4.0.1
4.0.0
3.2.1
3.2.0
3.1.2
3.1.1
3.1.0
3.0.3
3.0.2
3.0.1
3.0.0
retired
2.0.1
2.0.0
2.0.0-beta.1
1.25.0
1.24.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.2
1.18.1
1.18.0
1.17.4
1.17.3
1.17.2
1.17.1
1.17.0
1.16.0
1.15.2
1.15.1
1.15.0
1.14.3
1.14.2
1.14.0
1.13.0
1.12.1
1.12.0
1.11.0
1.10.1
1.10.0
1.9.0
1.8.6
1.8.5
1.8.4
1.8.3
1.8.2
1.8.0
1.7.1
1.7.0
1.6.6
retired
1.6.5
1.6.4
retired
1.6.3
1.6.2
1.6.1
1.6.0
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.10
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.0
1.0.6
1.0.5
1.0.2
1.0.1
0.15.2
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
Simple HTTP client with HTTP/1.1, HTTP/2, and HTTP/3 support
Current section
Files
Jump to
Current section
Files
src/hackney_middleware.erl
%%% -*- erlang -*-
%%%
%%% This file is part of hackney released under the Apache 2 license.
%%% See the NOTICE for more information.
%%%
%%% @doc RoundTripper-style middleware for hackney:request/1..5.
%%%
%%% A middleware is a fun `fun((Request, Next) -> Response)'. `Next' is
%%% itself a fun that takes a (possibly rewritten) request and returns
%%% the response. Middleware can observe, rewrite, short-circuit, or
%%% wrap the downstream call.
%%%
%%% Chains are outermost-first: `[A, B, C]' means A wraps B wraps C, so
%%% the request flows `A -> B -> C -> transport' and the response
%%% unwinds the same way. This matches Go's `http.RoundTripper', Plug,
%%% Rack and Tower-rs.
%%%
%%% Middleware wraps the user-facing `hackney:request/1..5' only. The
%%% low-level `hackney:connect/*' + `hackney:send_request/2' path is
%%% intentionally unchanged.
%%%
%%% Example — a minimal duration logger:
%%% ```
%%% Log = fun(Req, Next) ->
%%% T0 = erlang:monotonic_time(millisecond),
%%% Resp = Next(Req),
%%% logger:info("~p ~s -> ~pms",
%%% [maps:get(method, Req),
%%% hackney_url:unparse_url(maps:get(url, Req)),
%%% erlang:monotonic_time(millisecond) - T0]),
%%% Resp
%%% end,
%%% hackney:get(URL, [], <<>>, [{middleware, [Log]}]).
%%% '''
-module(hackney_middleware).
-export([apply_chain/3,
resolve_chain/1]).
-export_type([request/0, response/0, next/0, middleware/0]).
-include("hackney_lib.hrl").
-type request() :: #{method := atom() | binary(),
url := #hackney_url{},
headers := [{binary(), binary()}],
body := term(),
options := [term()]}.
-type response() :: {ok, integer(), list(), binary()}
| {ok, integer(), list()}
| {ok, pid()}
| {error, term()}.
-type next() :: fun((request()) -> response()).
-type middleware() :: fun((request(), next()) -> response()).
%% @doc Apply `Chain' (outermost-first) around `Terminal'.
%%
%% The returned response is whatever the outermost middleware produces;
%% crashes propagate to the caller.
-spec apply_chain([middleware()], request(), next()) -> response().
apply_chain([], Req, Terminal) ->
Terminal(Req);
apply_chain(Chain, Req, Terminal) when is_list(Chain) ->
Next = build(Chain, Terminal),
Next(Req).
%% @doc Pick the middleware chain for a request.
%%
%% Per-request `{middleware, List}' in `Options' replaces the global
%% `application:get_env(hackney, middleware)' value. No implicit merge.
-spec resolve_chain([term()]) -> [middleware()].
resolve_chain(Options) ->
case lists:keyfind(middleware, 1, Options) of
{middleware, List} when is_list(List) ->
List;
false ->
case application:get_env(hackney, middleware) of
{ok, List} when is_list(List) -> List;
_ -> []
end
end.
%% ============================================================================
%% Internal
%% ============================================================================
%% Fold from the right so the head of Chain becomes the outermost call.
build(Chain, Terminal) ->
lists:foldr(
fun(M, Next) when is_function(M, 2) ->
fun(R) -> M(R, Next) end
end,
Terminal,
Chain).