From bfabe15f9a82d5d59b5e0ab9d514fad287861ca3 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Wed, 14 Feb 2024 14:40:55 +0100 Subject: [PATCH] Prepare for v0.4.9 --- CHANGELOG.md | 13 ++++++++++ README.md | 3 +++ lib/req.ex | 16 ++++++++++++ lib/req/steps.ex | 48 ++++++++++++++++++++++++++++++++--- mix.exs | 2 +- test/req/integration_test.exs | 41 ++++++++++++++++++++++++------ 6 files changed, 110 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b755e9c..50470add 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # CHANGELOG +## v0.4.9 (2024-02-14) + + * [`retry`]: Raise on invalid return from `:retry_delay` function + + * [`run_finch`]: Update to Finch 0.17 + + * [`run_finch`]: Deprecate `connect_options: [protocol: ...]` in favour of + `connect_options: [protocols: ...]]` which defaults to `[:http1, :http2]`, that is, + make request using HTTP/1 but if negotiated switch to HTTP/2 over the HTTP/1 connection. + + * New step: [`put_aws_sigv4`] - signs request with AWS Signature Version 4. + ## v0.4.8 (2023-12-11) * [`put_plug`]: Fix response streaming. Previously we were relying on unreleased @@ -782,6 +794,7 @@ See "Adapter" section in `Req.Request` module documentation for more information [`retry`]: https://hexdocs.pm/req/Req.Steps.html#retry/1 [`run_finch`]: https://hexdocs.pm/req/Req.Steps.html#run_finch/1 [`checksum`]: https://hexdocs.pm/req/Req.Steps.html#checksum/1 +[`put_aws_sigv4`]: https://hexdocs.pm/req/Req.Steps.html#put_aws_sigv4/1 [`Req.new/1`]: https://hexdocs.pm/req/Req.html#new/1 [`Req.request/2`]: https://hexdocs.pm/req/Req.html#request/2 diff --git a/README.md b/README.md index 39318c56..cca4d897 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ write new ones. * Range requests (via [`put_range`]) step.) + * Use AWS V4 Signature (via [`put_aws_sigv4`]) step.) + * Request body streaming (by setting `body: enumerable`.) * Response body streaming (by setting `into: fun | collectable`.) @@ -256,6 +258,7 @@ limitations under the License. [`retry`]: https://hexdocs.pm/req/Req.Steps.html#retry/1 [`run_finch`]: https://hexdocs.pm/req/Req.Steps.html#run_finch/1 [`checksum`]: https://hexdocs.pm/req/Req.Steps.html#checksum/1 +[`put_aws_sigv4`]: https://hexdocs.pm/req/Req.Steps.html#put_aws_sigv4/1 [Finch]: https://github.com/sneako/finch [cURL]: https://curl.se diff --git a/lib/req.ex b/lib/req.ex index acd20e17..b8e8f466 100644 --- a/lib/req.ex +++ b/lib/req.ex @@ -181,6 +181,22 @@ defmodule Req do * `:compress_body` - if set to `true`, compresses the request body using gzip (via [`compress_body`](`Req.Steps.compress_body/1`) step.) Defaults to `false`. + AWS Signature Version 4 options ([`put_aws_sigv4`](`Req.Steps.put_aws_sigv4/1`) step): + + * `:aws_sigv4` - if set, the AWS options to sign request: + + * `:access_key_id` - the AWS access key id. + + * `:secret_access_key` - the AWS secret access key. + + * `:service` - the AWS service. + + * `:region` - if set, AWS region. Defaults to `"us-east-1"`. + + This functionality requires [`:aws_signature`](https://hex.pm/packages/aws_signature) dependency: + + {:aws_signature, "~> 0.3.0"} + Response body options: * `:compressed` - if set to `true`, asks the server to return compressed response. diff --git a/lib/req/steps.ex b/lib/req/steps.ex index e14e4c8e..323746f2 100644 --- a/lib/req/steps.ex +++ b/lib/req/steps.ex @@ -1301,8 +1301,43 @@ defmodule Req.Steps do Code.ensure_loaded?(:aws_signature) end - # experimental - @doc false + @doc """ + Signs request with AWS Signature Version 4. + + This step requires [`:aws_signature`](https://hex.pm/packages/aws_signature) dependency: + + {:aws_signature, "~> 0.3.0"} + + ## Request Options + + * `:aws_sigv4` - if set, the AWS options to sign request: + + * `:access_key_id` - the AWS access key id. + + * `:secret_access_key` - the AWS secret access key. + + * `:service` - the AWS service. + + * `:region` - if set, AWS region. Defaults to `"us-east-1"`. + + ## Examples + + iex> req = + ...> Req.new( + ...> base_url: "https://s3.amazonaws.com", + ...> aws_sigv4: [ + ...> access_key_id: System.get_env("AWS_ACCESS_KEY_ID"), + ...> secret_access_key: System.get_env("AWS_SECRET_ACCESS_KEY"), + ...> service: :s3 + ...> ] + ...> ) + iex> + iex> %{status: 200} = Req.put!(req, "/bucket1/key1", body: "Hello, World!") + iex> resp = Req.get!(req, "/bucket1/key1").body + "Hello, World!" + + """ + @doc step: :request def put_aws_sigv4(request) do if aws_options = request.options[:aws_sigv4] do unless aws_signature_loaded?() do @@ -1346,8 +1381,13 @@ defmodule Req.Steps do region = Keyword.get(aws_options, :region, "us-east-1") now = NaiveDateTime.utc_now() |> NaiveDateTime.to_erl() + body = IO.iodata_to_binary(request.body || "") + + request = + request + |> Req.Request.put_new_header("host", request.url.host) + |> Req.Request.put_new_header("content-length", to_string(byte_size(body))) - request = Req.Request.put_new_header(request, "host", request.url.host) headers = for {name, values} <- request.headers, value <- values, do: {name, value} headers = @@ -1360,7 +1400,7 @@ defmodule Req.Steps do to_string(request.method), to_string(request.url), headers, - IO.iodata_to_binary(request.body || "") + body ) Req.update(request, headers: headers) diff --git a/mix.exs b/mix.exs index 88c84915..a054a906 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Req.MixProject do use Mix.Project - @version "0.4.8" + @version "0.4.9" @source_url "https://github.com/wojtekmach/req" def project do diff --git a/test/req/integration_test.exs b/test/req/integration_test.exs index a4b025c8..51f473c6 100644 --- a/test/req/integration_test.exs +++ b/test/req/integration_test.exs @@ -3,14 +3,18 @@ defmodule Req.IntegrationTest do @moduletag :integration - setup do - original_gl = Process.group_leader() - {:ok, capture_gl} = StringIO.open("") - Process.group_leader(self(), capture_gl) - - on_exit(fn -> - Process.group_leader(self(), original_gl) - end) + setup context do + if context[:doctest] do + original_gl = Process.group_leader() + {:ok, capture_gl} = StringIO.open("") + Process.group_leader(self(), capture_gl) + + on_exit(fn -> + Process.group_leader(self(), original_gl) + end) + else + :ok + end end doctest Req, @@ -38,4 +42,25 @@ defmodule Req.IntegrationTest do decompress_body: 1, handle_http_errors: 1 ] + + @aws_access_key_id System.get_env("REQ_AWS_ACCESS_KEY_ID") + @aws_secret_access_key System.get_env("REQ_AWS_SECRET_ACCESS_KEY") + @aws_bucket System.get_env("REQ_AWS_BUCKET") + + @tag :s3 + test "s3" do + req = + Req.new( + base_url: "https://#{@aws_bucket}.s3.amazonaws.com", + aws_sigv4: [ + access_key_id: @aws_access_key_id, + secret_access_key: @aws_secret_access_key, + service: :s3 + ] + ) + + now = to_string(DateTime.utc_now()) + %{status: 200} = Req.put!(req, url: "/key1", json: %{now: now}) + assert Req.get!(req, url: "/key1").body == %{"now" => now} + end end