Skip to main content

src/aws@internal@http_streaming.erl

-module(aws@internal@http_streaming).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/aws/internal/http_streaming.gleam").
-export([default_send/1, default_send_http2/1, with_timeout_and_tls/2, with_timeout_tls_http2/2]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(
    " Streaming HTTP send for the SDK runtime. Sits between the\n"
    " generated service code and the Erlang FFI in `aws_streaming_ffi`:\n"
    " translates a Gleam `Request(BitArray)` into the parameters\n"
    " `httpc:request/4` expects, dispatches via the streaming FFI,\n"
    " then wraps the response back into a `Response(StreamingBody)`\n"
    " so call sites read identically to the buffered transport.\n"
    "\n"
    " This is the foundational piece for `S3.GetObject` on multi-GB\n"
    " objects, event-stream operations (Transcribe, Kinesis), and any\n"
    " future S3 transfer manager — none of those can pre-buffer the\n"
    " full response into memory.\n"
).

-file("src/aws/internal/http_streaming.gleam", 125).
-spec translate_error(gleam@erlang@atom:atom_()) -> aws@internal@http_send:http_error().
translate_error(Reason) ->
    case erlang:atom_to_binary(Reason) of
        <<"failed_to_connect"/utf8>> ->
            {connect_failed, <<"could not connect to host"/utf8>>};

        <<"response_timeout"/utf8>> ->
            timeout;

        <<"timeout"/utf8>> ->
            timeout;

        <<"invalid_utf8_response"/utf8>> ->
            {invalid_body, <<"response body was not valid UTF-8"/utf8>>};

        Other ->
            {other, Other}
    end.

-file("src/aws/internal/http_streaming.gleam", 118).
-spec to_string_or_empty(bitstring()) -> binary().
to_string_or_empty(B) ->
    case gleam@bit_array:to_string(B) of
        {ok, S} ->
            S;

        {error, _} ->
            <<""/utf8>>
    end.

-file("src/aws/internal/http_streaming.gleam", 110).
-spec list_decode_headers(list({bitstring(), bitstring()})) -> list({binary(),
    binary()}).
list_decode_headers(Raw) ->
    gleam@list:map(
        Raw,
        fun(Pair) ->
            {to_string_or_empty(erlang:element(1, Pair)),
                to_string_or_empty(erlang:element(2, Pair))}
        end
    ).

-file("src/aws/internal/http_streaming.gleam", 78).
-spec send_with(
    gleam@http@request:request(bitstring()),
    integer(),
    boolean(),
    boolean()
) -> {ok, gleam@http@response:response(aws@streaming:streaming_body())} |
    {error, aws@internal@http_send:http_error()}.
send_with(Req, Timeout_ms, Verify_tls, Http2) ->
    Url = begin
        _pipe = Req,
        _pipe@1 = gleam@http@request:to_uri(_pipe),
        gleam@uri:to_string(_pipe@1)
    end,
    case aws_streaming_ffi:streaming_send(
        erlang:element(2, Req),
        Url,
        erlang:element(3, Req),
        erlang:element(4, Req),
        Timeout_ms,
        Verify_tls,
        Http2
    ) of
        {ok, {Status, Headers, Chunks}} ->
            {ok,
                {response,
                    Status,
                    list_decode_headers(Headers),
                    aws@streaming:from_chunks(Chunks)}};

        {error, Reason} ->
            {error, translate_error(Reason)}
    end.

-file("src/aws/internal/http_streaming.gleam", 31).
?DOC(
    " Default streaming sender. Same TLS / timeout defaults as\n"
    " `http_send.default_send`; the response body is a `StreamingBody`\n"
    " carrying chunks as they arrived on the wire. Pass this as\n"
    " `ClientConfig.streaming_http_send` for the SDK runtime, or call\n"
    " it directly when wiring custom callers.\n"
).
-spec default_send(gleam@http@request:request(bitstring())) -> {ok,
        gleam@http@response:response(aws@streaming:streaming_body())} |
    {error, aws@internal@http_send:http_error()}.
default_send(Req) ->
    send_with(Req, 30 * 1000, true, false).

-file("src/aws/internal/http_streaming.gleam", 47).
?DOC(
    " HTTP/2 variant of `default_send`. Adds `{http_version, \"HTTP/2\"}`\n"
    " to the httpc option list; servers that don't speak HTTP/2 negotiate\n"
    " down to HTTP/1.1 via ALPN. Use this for endpoints known to benefit:\n"
    " S3 multipart uploads, Bedrock streaming responses, Transcribe.\n"
    " Caller-facing knob lives at `runtime.with_http2`.\n"
).
-spec default_send_http2(gleam@http@request:request(bitstring())) -> {ok,
        gleam@http@response:response(aws@streaming:streaming_body())} |
    {error, aws@internal@http_send:http_error()}.
default_send_http2(Req) ->
    send_with(Req, 30 * 1000, true, true).

-file("src/aws/internal/http_streaming.gleam", 62).
?DOC(
    " Send a `StreamingSend` configurable on timeout and TLS verification.\n"
    " Use this for live-tested object-streaming GETs that need either\n"
    " fast-fail (IMDS-style 2s timeouts) or extra patience (multi-GB\n"
    " downloads).\n"
).
-spec with_timeout_and_tls(integer(), boolean()) -> fun((gleam@http@request:request(bitstring())) -> {ok,
        gleam@http@response:response(aws@streaming:streaming_body())} |
    {error, aws@internal@http_send:http_error()}).
with_timeout_and_tls(Timeout_ms, Verify_tls) ->
    fun(Req) -> send_with(Req, Timeout_ms, Verify_tls, false) end.

-file("src/aws/internal/http_streaming.gleam", 71).
?DOC(
    " HTTP/2 + custom timeout / TLS builder. Same as `with_timeout_and_tls`\n"
    " but adds the HTTP/2 option to the httpc call.\n"
).
-spec with_timeout_tls_http2(integer(), boolean()) -> fun((gleam@http@request:request(bitstring())) -> {ok,
        gleam@http@response:response(aws@streaming:streaming_body())} |
    {error, aws@internal@http_send:http_error()}).
with_timeout_tls_http2(Timeout_ms, Verify_tls) ->
    fun(Req) -> send_with(Req, Timeout_ms, Verify_tls, true) end.