@@ -623,6 +623,9 @@ defmodule Mint.HTTP do
623623
624624 This function always returns an updated connection to be stored over the old connection.
625625
626+ When streaming a body of arbitrary size, use `next_body_chunk/3` to split it
627+ into chunks that respect the current send window of the connection.
628+
626629 For information about transfer encoding and content length in HTTP/1, see
627630 `Mint.HTTP1.stream_request_body/3`.
628631
@@ -1065,6 +1068,102 @@ defmodule Mint.HTTP do
10651068 @ impl true
10661069 def put_proxy_headers ( conn , headers ) , do: conn_apply ( conn , :put_proxy_headers , [ conn , headers ] )
10671070
1071+ @ doc """
1072+ Returns the maximum number of body bytes that can be streamed on the connection
1073+ for the given `request_ref` right now.
1074+
1075+ The meaning of the returned value depends on the protocol of the underlying
1076+ connection:
1077+
1078+ * In HTTP/1, it is the operating-system socket send buffer (`:sndbuf`) cached
1079+ on the connection at connect time. HTTP/1 has no flow-control mechanism, so
1080+ this is purely an I/O sizing hint — passing a larger chunk to
1081+ `stream_request_body/3` is allowed and will not violate the protocol.
1082+
1083+ * In HTTP/2, it is the minimum of the connection-level and the per-request
1084+ flow-control windows. Sending more than this in a single `DATA` frame would
1085+ violate flow control, so the value is a hard upper bound. See
1086+ `Mint.HTTP2.get_window_size/2` for the underlying primitives.
1087+
1088+ Raises `ArgumentError` if `request_ref` is not associated with an active
1089+ streaming request.
1090+
1091+ See `next_body_chunk/3` for a higher-level helper that uses this value to
1092+ split a binary body into a sendable chunk.
1093+ """
1094+ @ doc since: "1.8.0"
1095+ @ impl true
1096+ @ spec next_body_chunk_size ( t ( ) , Types . request_ref ( ) ) :: non_neg_integer ( )
1097+ def next_body_chunk_size ( conn , ref ) , do: conn_apply ( conn , :next_body_chunk_size , [ conn , ref ] )
1098+
1099+ @ doc """
1100+ Splits off the next chunk of `body` that can be streamed on the connection right now.
1101+
1102+ This is a helper to be used together with `stream_request_body/3` when streaming a
1103+ request body of arbitrary size. Given a `body` binary, it inspects the connection
1104+ state via `next_body_chunk_size/2` to determine the largest chunk that can be sent
1105+ immediately, and returns that chunk together with the remainder to be streamed later.
1106+
1107+ The return value is a `{chunk, rest}` tuple such that `chunk <> rest == body`.
1108+ When `body` is empty (or smaller than the available send window), `rest` is `""`.
1109+
1110+ `body` must be a `t:binary/0`. If you have an `t:iodata/0` body, convert it
1111+ with `IO.iodata_to_binary/1` before calling this function.
1112+
1113+ This function performs no I/O — it only computes how to split the binary. You
1114+ still need to pass `chunk` to `stream_request_body/3` to actually send it on
1115+ the wire.
1116+
1117+ See `next_body_chunk_size/2` for the protocol-specific semantics of the chunk
1118+ size and a note on HTTP/1 vs HTTP/2 behavior.
1119+
1120+ ## When not to use this helper
1121+
1122+ This helper is convenient for small to medium request bodies that already live
1123+ as a single binary in memory. For large bodies — especially iodata assembled
1124+ from multiple parts, refc-binaries read from files, or streamed from another
1125+ source — calling `IO.iodata_to_binary/1` just to use this helper will:
1126+
1127+ * double peak memory usage while the temporary binary is built;
1128+ * defeat the scatter-gather (`writev/2`) optimization that `:gen_tcp.send`
1129+ and `:ssl.send` perform on iolists, forcing an extra full-buffer copy.
1130+
1131+ In those cases, drive the loop yourself: query `next_body_chunk_size/2` to
1132+ learn how many bytes you can send right now, take that prefix from your iodata
1133+ source without materializing the rest, hand it to `stream_request_body/3`, and
1134+ repeat. Mint's flow-control state is fully exposed through
1135+ `next_body_chunk_size/2` — this helper is just one ergonomic shape over it,
1136+ not the only supported one.
1137+
1138+ ## Examples
1139+
1140+ Streaming a body of arbitrary size by repeatedly chunking against the current
1141+ send window:
1142+
1143+ {:ok, conn, ref} = Mint.HTTP.request(conn, "POST", "/", headers, :stream)
1144+ {:ok, conn} = stream_body(conn, ref, payload)
1145+
1146+ defp stream_body(conn, ref, "") do
1147+ Mint.HTTP.stream_request_body(conn, ref, :eof)
1148+ end
1149+
1150+ defp stream_body(conn, ref, body) do
1151+ {chunk, rest} = Mint.HTTP.next_body_chunk(conn, ref, body)
1152+
1153+ with {:ok, conn} <- Mint.HTTP.stream_request_body(conn, ref, chunk) do
1154+ stream_body(conn, ref, rest)
1155+ end
1156+ end
1157+
1158+ """
1159+ @ doc since: "1.8.0"
1160+ @ spec next_body_chunk ( t ( ) , Types . request_ref ( ) , binary ( ) ) :: { binary ( ) , binary ( ) }
1161+ def next_body_chunk ( conn , ref , body ) when is_binary ( body ) do
1162+ chunk_size = min ( next_body_chunk_size ( conn , ref ) , byte_size ( body ) )
1163+ << chunk :: binary - size ( chunk_size ) , rest :: binary >> = body
1164+ { chunk , rest }
1165+ end
1166+
10681167 ## Helpers
10691168
10701169 defp conn_apply ( % UnsafeProxy { } , fun , args ) , do: apply ( UnsafeProxy , fun , args )
0 commit comments