From 7e44daca383f48220607d3f5945e72c2fed737f6 Mon Sep 17 00:00:00 2001 From: Silvio Sepulveda Date: Tue, 10 Feb 2026 08:54:02 +0100 Subject: [PATCH] feat: upgrade to hackney v3 --- README.md | 4 ++++ rebar.config | 8 +++++--- rebar.lock | 43 +++++++++++++++++++----------------------- src/restc.erl | 14 ++++++-------- test/mochiweb_util.erl | 11 +++++------ test/restc_SUITE.erl | 6 ++---- 6 files changed, 41 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index f62495d..0ddff50 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ restclient is a library to help with consuming RESTful web services. It supports encoding and decoding JSON, Percent and XML and comes with a convenience function for working with urls and query parameters. +__IMPORTANT__: The library has been updated to use `hackney v3`, but the usage +is backwards compatible with `v1`. In case an older version is required, it must +be explicitly overriden by the top level application. + ## USAGE Include restclient as a rebar dependency with: diff --git a/rebar.config b/rebar.config index 1247e6a..908887e 100644 --- a/rebar.config +++ b/rebar.config @@ -1,6 +1,7 @@ -{deps, [{hackney,"1.20.1"}, - {jsx,"v3.1.0"}, - {erlsom,"1.5.1"} +{deps, [{jsx,"v3.1.0"}, + {erlsom,"1.5.1"}, + %% Starting from version 2, it is bigger than 8MB (max allowed in hex) + {hackney, {git, "git@github.com:benoitc/hackney.git", {tag, "3.0.2"}}} ]}. {project_plugins, [rebar3_proper, erlfmt]}. @@ -15,6 +16,7 @@ {erl_opts, [nowarn_export_all]}, {deps, [ proper , {meck, "0.8.13"} + , {mochiweb, "v3.3.0"} ]} ]} ]}. diff --git a/rebar.lock b/rebar.lock index a53757f..c6a481f 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,35 +1,30 @@ {"1.2.0", -[{<<"certifi">>,{pkg,<<"certifi">>,<<"2.6.1">>},1}, +[{<<"certifi">>,{pkg,<<"certifi">>,<<"2.16.0">>},1}, {<<"erlsom">>,{pkg,<<"erlsom">>,<<"1.5.1">>},0}, - {<<"hackney">>,{pkg,<<"hackney">>,<<"1.20.1">>},0}, - {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},1}, + {<<"hackney">>, + {git,"git@github.com:benoitc/hackney.git", + {ref,"1b6f536e5e47b69457f81b05417246cde64a38b4"}}, + 0}, + {<<"idna">>,{pkg,<<"idna">>,<<"7.1.0">>},1}, {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},0}, - {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},1}, - {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},1}, - {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},1}, - {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.6">>},1}, - {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},1}]}. + {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.4.0">>},1}, + {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.4.1">>},1}, + {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.7">>},1}]}. [ {pkg_hash,[ - {<<"certifi">>, <<"DBAB8E5E155A0763EEA978C913CA280A6B544BFA115633FA20249C3D396D9493">>}, + {<<"certifi">>, <<"A4EDFC1D2DA3424D478A3271133BF28E0EC5E6FD8C009AAB5A4AE980CB165CE9">>}, {<<"erlsom">>, <<"C8FE2BABD33FF0846403F6522328B8AB676F896B793634CFE7EF181C05316C03">>}, - {<<"hackney">>, <<"8D97AEC62DDDDD757D128BFD1DF6C5861093419F8F7A4223823537BAD5D064E2">>}, - {<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>}, + {<<"idna">>, <<"1067A13043538129602D2F2CE6899D8713125C7D19734AA557CE2E3EA55BD4F1">>}, {<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>}, - {<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>}, - {<<"mimerl">>, <<"67E2D3F571088D5CFD3E550C383094B47159F3EEE8FFA08E64106CDF5E981BE3">>}, - {<<"parse_trans">>, <<"16328AB840CC09919BD10DAB29E431DA3AF9E9E7E7E6F0089DD5A2D2820011D8">>}, - {<<"ssl_verify_fun">>, <<"CF344F5692C82D2CD7554F5EC8FD961548D4FD09E7D22F5B62482E5AEAEBD4B0">>}, - {<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}]}, + {<<"mimerl">>, <<"3882A5CA67FBBE7117BA8947F27643557ADEC38FA2307490C4C4207624CB213B">>}, + {<<"parse_trans">>, <<"6E6AA8167CB44CC8F39441D05193BE6E6F4E7C2946CB2759F015F8C56B76E5FF">>}, + {<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>}]}, {pkg_hash_ext,[ - {<<"certifi">>, <<"524C97B4991B3849DD5C17A631223896272C6B0AF446778BA4675A1DFF53BB7E">>}, + {<<"certifi">>, <<"8A64F6669D85E9CC0E5086FCF29A5B13DE57A13EFA23D3582874B9A19303F184">>}, {<<"erlsom">>, <<"7965485494C5844DD127656AC40F141AADFA174839EC1BE1074E7EDF5B4239EB">>}, - {<<"hackney">>, <<"FE9094E5F1A2A2C0A7D10918FEE36BFEC0EC2A979994CFF8CFE8058CD9AF38E3">>}, - {<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>}, + {<<"idna">>, <<"6AE959A025BF36DF61A8CAB8508D9654891B5426A84C44D82DEAFFD6DDF8C71F">>}, {<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>}, - {<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>}, - {<<"mimerl">>, <<"F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323">>}, - {<<"parse_trans">>, <<"07CD9577885F56362D414E8C4C4E6BDF10D43A8767ABB92D24CBE8B24C54888B">>}, - {<<"ssl_verify_fun">>, <<"BDB0D2471F453C88FF3908E7686F86F9BE327D065CC1EC16FA4540197EA04680">>}, - {<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}]} + {<<"mimerl">>, <<"13AF15F9F68C65884ECCA3A3891D50A7B57D82152792F3E19D88650AA126B144">>}, + {<<"parse_trans">>, <<"620A406CE75DADA827B82E453C19CF06776BE266F5A67CFF34E1EF2CBB60E49A">>}, + {<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>}]} ]. diff --git a/src/restc.erl b/src/restc.erl index 58c2e6f..6d93b2a 100644 --- a/src/restc.erl +++ b/src/restc.erl @@ -150,7 +150,9 @@ request(Method, Type, Url, Expect, Headers0, Body, Options) -> Retries = proplists:get_value(retries, Options, 0), request_loop(Method, Type, Url, Expect, Headers, Body, Options, Retries). -request_loop(Method, Type, Url, Expect, Headers, Body, Options, Retries) -> +request_loop(Method, Type, Url, Expect, Headers, Body, Options0, Retries) -> + %% Always ask for the body to provide backwards compatibility + Options = [{with_body, true} | Options0], Response = parse_response( do_request(Method, Type, Url, Headers, Body, Options), Options), @@ -241,18 +243,14 @@ check_expect(_Status, []) -> check_expect(Status, Expect) -> lists:member(Status, Expect). -parse_response({ok, 204, Headers, Client}, _Opts) -> - ok = hackney:close(Client), +parse_response({ok, 204, Headers, _Body}, _Opts) -> {ok, 204, Headers, []}; -parse_response({ok, Status, Headers, Client}, Opts) -> +parse_response({ok, Status, Headers, Body}, Opts) -> NormalizedHeaders = normalize_headers(Headers), {<<"content-type">>, ContentType} = content_type(NormalizedHeaders, ?DEFAULT_CTYPE), Type = parse_type(ContentType), - case hackney:body(Client) of - {ok, Body} -> {ok, Status, Headers, restc_body:decode(Type, Body, Opts)}; - {error, _}=E -> E - end; + {ok, Status, Headers, restc_body:decode(Type, Body, Opts)}; parse_response({error, Type}, _Opts) -> {error, Type}. diff --git a/test/mochiweb_util.erl b/test/mochiweb_util.erl index d13aba4..c80df1d 100644 --- a/test/mochiweb_util.erl +++ b/test/mochiweb_util.erl @@ -687,12 +687,11 @@ parse_header_test() -> ok. guess_mime_test() -> - "text/plain" = guess_mime(""), - "text/plain" = guess_mime(".text"), - "application/zip" = guess_mime(".zip"), - "application/zip" = guess_mime("x.zip"), - "text/html" = guess_mime("x.html"), - "application/xhtml+xml" = guess_mime("x.xhtml"), + ?assertEqual("text/plain", guess_mime("")), + ?assertEqual("text/plain", guess_mime("x.text")), + ?assertEqual("application/zip", guess_mime("x.zip")), + ?assertEqual("text/html", guess_mime("x.html")), + ?assertEqual("application/xhtml+xml", guess_mime("x.xhtml")), ok. path_split_test() -> diff --git a/test/restc_SUITE.erl b/test/restc_SUITE.erl index bfdafd6..bbe21ac 100644 --- a/test/restc_SUITE.erl +++ b/test/restc_SUITE.erl @@ -276,8 +276,7 @@ type_is_json__making_request_with_xml_accept_header__accept_header_overrides_typ mock_hackney_success(Code) -> mock_hackney_success(Code, [], <<>>). mock_hackney_success(Code, Headers, Body) -> - meck:expect(hackney, request, ['_', '_', '_', '_', '_'], meck:val({ok, Code, Headers, client})), - meck:expect(hackney, body, fun(client) -> {ok, Body} end). + meck:expect(hackney, request, ['_', '_', '_', '_', '_'], meck:val({ok, Code, Headers, Body})). mock_hackney_error(Error) -> meck:expect(hackney, request, ['_', '_', '_', '_', '_'], meck:val({error, Error})). @@ -285,8 +284,7 @@ mock_hackney_error(Error) -> mock_hackney_eventual_success(Code, AmountOfErrors) -> ErrorCalls = error_calls(AmountOfErrors), meck:expect(hackney, request, ['_', '_', '_', '_', '_'], - meck:seq(ErrorCalls ++ [{ok, Code, [], client}])), - meck:expect(hackney, body, fun(client) -> {ok, <<>>} end). + meck:seq(ErrorCalls ++ [{ok, Code, [], <<>>}])). error_calls(0) -> []; error_calls(N) -> error_calls(N, []).