Current section
40 Versions
Jump to
Current section
40 Versions
Compare versions
6
files changed
+63
additions
-17
deletions
| @@ -1,5 +1,8 @@ | |
| 1 1 | # Changelog |
| 2 2 | |
| 3 | + ## v0.6.0 (2020-12-15) |
| 4 | + * Add ability to stream the request body for HTTP/1.x requests. |
| 5 | + |
| 3 6 | ## v0.5.2 (2020-11-10) |
| 4 7 | * Fix deprecation in nimble_options. |
| @@ -39,4 +39,4 @@ | |
| 39 39 | {<<"optional">>,false}, |
| 40 40 | {<<"repository">>,<<"hexpm">>}, |
| 41 41 | {<<"requirement">>,<<"~> 0.4">>}]]}. |
| 42 | - {<<"version">>,<<"0.5.2">>}. |
| 42 | + {<<"version">>,<<"0.6.0">>}. |
| @@ -165,6 +165,10 @@ defmodule Finch do | |
| 165 165 | |
| 166 166 | @doc """ |
| 167 167 | Builds an HTTP request to be sent with `request/3` or `stream/4`. |
| 168 | + |
| 169 | + When making HTTP/1.x requests, it is possible to send the request body in a streaming fashion. |
| 170 | + In order to do so, the `body` parameter needs to take form of a tuple `{:stream, body_stream}`, |
| 171 | + where `body_stream` is a `Stream`. This feature is not yet supported for HTTP/2 requests. |
| 168 172 | """ |
| 169 173 | @spec build(Request.method(), Request.url(), Request.headers(), Request.body()) :: Request.t() |
| 170 174 | defdelegate build(method, url, headers \\ [], body \\ nil), to: Request |
| @@ -108,29 +108,55 @@ defmodule Finch.Conn do | |
| 108 108 | |
| 109 109 | start_time = Telemetry.start(:request, metadata, extra_measurements) |
| 110 110 | |
| 111 | - case HTTP1.request(conn.mint, req.method, full_path, req.headers, req.body) do |
| 111 | + case HTTP1.request(conn.mint, req.method, full_path, req.headers, stream_or_body(req.body)) do |
| 112 112 | {:ok, mint, ref} -> |
| 113 | - Telemetry.stop(:request, start_time, metadata, extra_measurements) |
| 114 | - start_time = Telemetry.start(:response, metadata, extra_measurements) |
| 115 | - |
| 116 | - case receive_response([], acc, fun, mint, ref, receive_timeout) do |
| 117 | - {:ok, mint, acc} -> |
| 118 | - Telemetry.stop(:response, start_time, metadata, extra_measurements) |
| 119 | - {:ok, %{conn | mint: mint}, acc} |
| 113 | + case maybe_stream_request_body(mint, ref, req.body, receive_timeout) do |
| 114 | + {:ok, mint} -> |
| 115 | + Telemetry.stop(:request, start_time, metadata, extra_measurements) |
| 116 | + start_time = Telemetry.start(:response, metadata, extra_measurements) |
| 117 | + response = receive_response([], acc, fun, mint, ref, receive_timeout) |
| 118 | + handle_response(response, conn, metadata, start_time, extra_measurements) |
| 120 119 | |
| 121 120 | {:error, mint, error} -> |
| 122 | - metadata = Map.put(metadata, :error, error) |
| 123 | - Telemetry.stop(:response, start_time, metadata, extra_measurements) |
| 124 | - {:error, %{conn | mint: mint}, error} |
| 121 | + handle_request_error( |
| 122 | + conn, |
| 123 | + mint, |
| 124 | + error, |
| 125 | + metadata, |
| 126 | + start_time, |
| 127 | + extra_measurements |
| 128 | + ) |
| 125 129 | end |
| 126 130 | |
| 127 131 | {:error, mint, error} -> |
| 128 | - metadata = Map.put(metadata, :error, error) |
| 129 | - Telemetry.stop(:request, start_time, metadata, extra_measurements) |
| 130 | - {:error, %{conn | mint: mint}, error} |
| 132 | + handle_request_error(conn, mint, error, metadata, start_time, extra_measurements) |
| 131 133 | end |
| 132 134 | end |
| 133 135 | |
| 136 | + defp stream_or_body({:stream, _}), do: :stream |
| 137 | + defp stream_or_body(body), do: body |
| 138 | + |
| 139 | + defp handle_request_error(conn, mint, error, metadata, start_time, extra_measurements) do |
| 140 | + metadata = Map.put(metadata, :error, error) |
| 141 | + Telemetry.stop(:request, start_time, metadata, extra_measurements) |
| 142 | + {:error, %{conn | mint: mint}, error} |
| 143 | + end |
| 144 | + |
| 145 | + defp maybe_stream_request_body(mint, ref, {:stream, stream}, _timeout) do |
| 146 | + with {:ok, mint} <- stream_request_body(mint, ref, stream) do |
| 147 | + HTTP1.stream_request_body(mint, ref, :eof) |
| 148 | + end |
| 149 | + end |
| 150 | + |
| 151 | + defp maybe_stream_request_body(mint, _, _, _), do: {:ok, mint} |
| 152 | + |
| 153 | + defp stream_request_body(mint, ref, stream) do |
| 154 | + Enum.reduce_while(stream, {:ok, mint}, fn |
| 155 | + chunk, {:ok, mint} -> {:cont, HTTP1.stream_request_body(mint, ref, chunk)} |
| 156 | + _chunk, error -> {:halt, error} |
| 157 | + end) |
| 158 | + end |
| 159 | + |
| 134 160 | def close(%{mint: nil} = conn), do: conn |
| 135 161 | |
| 136 162 | def close(conn) do |
| @@ -138,6 +164,19 @@ defmodule Finch.Conn do | |
| 138 164 | %{conn | mint: mint} |
| 139 165 | end |
| 140 166 | |
| 167 | + defp handle_response(response, conn, metadata, start_time, extra_measurements) do |
| 168 | + case response do |
| 169 | + {:ok, mint, acc} -> |
| 170 | + Telemetry.stop(:response, start_time, metadata, extra_measurements) |
| 171 | + {:ok, %{conn | mint: mint}, acc} |
| 172 | + |
| 173 | + {:error, mint, error} -> |
| 174 | + metadata = Map.put(metadata, :error, error) |
| 175 | + Telemetry.stop(:response, start_time, metadata, extra_measurements) |
| 176 | + {:error, %{conn | mint: mint}, error} |
| 177 | + end |
| 178 | + end |
| 179 | + |
| 141 180 | defp receive_response([], acc, fun, mint, ref, timeout) do |
| 142 181 | case HTTP1.recv(mint, 0, timeout) do |
| 143 182 | {:ok, mint, entries} -> |
| @@ -47,7 +47,7 @@ defmodule Finch.Request do | |
| 47 47 | @typedoc """ |
| 48 48 | Optional request body. |
| 49 49 | """ |
| 50 | - @type body() :: iodata() | nil |
| 50 | + @type body() :: iodata() | {:stream, Enumerable.t()} | nil |
| 51 51 | |
| 52 52 | @type t :: %Finch.Request{} |
Loading more files…