HTTP.Response (http_fetch v0.8.0)
HTTP response struct implementing the Browser Fetch API Response interface.
This module represents an HTTP response with full compatibility with the Browser Fetch API standard. It supports both buffered and streaming responses, body consumption tracking, response cloning, and multiple read formats.
Browser Fetch API Compatibility
This module implements the JavaScript Fetch API Response interface:
status- HTTP status code (e.g., 200, 404, 500)status_text- Status message ("OK", "Not Found", etc.)ok- Boolean for success status (200-299)headers- Response headers asHTTP.Headersstructbody- Response body as binary (nil for streaming responses)body_used- Track if body has been consumedurl- The requested URL asURIstructredirected- Whether response was redirectedtype- Response type (:basic, :cors, :error, :opaque)stream- Stream process PID for streaming responses (nil for buffered)
Response Methods
json/1- Parse response as JSONtext/1- Read response as textarrayBuffer/1- Read response as binaryblob/1- Read response as Blob with metadataclone/1- Clone response for multiple reads
Elixir-Specific Differences
Immutability: Unlike JavaScript, Elixir responses are immutable. The body_used
property won't automatically update across function calls. Use clone/1 before
multiple reads.
Synchronous Returns: Methods like json() and text() return values directly
instead of Promises, following Elixir conventions.
Stream Handling: Large responses use Elixir processes for streaming instead of ReadableStream.
Struct Fields
status- HTTP status code (e.g., 200, 404, 500)status_text- Status message (e.g., "OK", "Not Found")ok- Boolean indicating success (true for 200-299)headers- Response headers asHTTP.Headersstructbody- Response body as binary (nil for streaming responses)body_used- Whether body has been consumed (Browser API behavior)url- The requested URL asURIstructredirected- Whether response was redirectedtype- Response type (:basic, :cors, :error, :opaque)stream- Stream process PID for streaming responses (nil for buffered)
Streaming vs Buffered Responses
Responses are automatically streamed when:
- Content-Length > 5MB
- Content-Length header is missing/unknown
Buffered responses have the complete body in the body field:
%HTTP.Response{
status: 200,
body: "response data",
stream: nil
}Streaming responses have body: nil and a stream PID:
%HTTP.Response{
status: 200,
body: nil,
stream: #PID<0.123.0>
}Usage
# Simple text response
{:ok, response} = HTTP.fetch("https://example.com") |> HTTP.Promise.await()
text = HTTP.Response.text(response)
# JSON parsing
{:ok, response} = HTTP.fetch("https://api.example.com/data") |> HTTP.Promise.await()
{:ok, json} = HTTP.Response.json(response)
# Write to file (works with both streaming and buffered)
{:ok, response} = HTTP.fetch("https://example.com/file.zip") |> HTTP.Promise.await()
:ok = HTTP.Response.write_to(response, "/tmp/file.zip")
# Get specific header
content_type = HTTP.Response.get_header(response, "content-type")
# Parse Content-Type
{media_type, params} = HTTP.Response.content_type(response)Streaming Responses
For streaming responses, use read_all/1 or write_to/2 to consume the stream:
# Read entire stream into memory
body = HTTP.Response.read_all(response)
# Write stream directly to file (more memory efficient)
:ok = HTTP.Response.write_to(response, "/path/to/file")
Summary
Functions
Alias for arrayBuffer/1 following Elixir naming conventions.
Reads the response body as raw binary data (equivalent to JavaScript's ArrayBuffer).
Reads the response body as a Blob (binary data with metadata).
Creates a duplicate of the response, allowing the body to be read multiple times.
Parses the Content-Type header to extract media type and parameters.
Gets a response header value by name (case-insensitive).
Parses the response body as JSON using Elixir's built-in JSON module (available in Elixir 1.18+).
Creates a new Response struct with Browser Fetch API fields populated.
Reads the entire response body as binary.
Reads the response body and parses it as JSON.
Reads the response body as text.
Writes the response body to a file.
Types
@type response_type() :: :basic | :cors | :error | :opaque
@type t() :: %HTTP.Response{ body: binary() | nil, body_used: boolean(), headers: HTTP.Headers.t(), ok: boolean(), redirected: boolean(), status: integer(), status_text: String.t(), stream: pid() | nil, type: response_type(), url: URI.t() | nil }
Functions
Alias for arrayBuffer/1 following Elixir naming conventions.
Reads the response body as raw binary data (equivalent to JavaScript's ArrayBuffer).
Returns the body as an Elixir binary. For streaming responses, this reads the entire stream into memory.
Examples
iex> response = HTTP.Response.new(status: 200, body: <<1, 2, 3, 4>>)
iex> HTTP.Response.arrayBuffer(response)
<<1, 2, 3, 4>>
@spec blob(t()) :: HTTP.Blob.t()
Reads the response body as a Blob (binary data with metadata).
Returns an HTTP.Blob struct containing the body data, MIME type extracted
from the Content-Type header, and size in bytes.
Examples
iex> response = HTTP.Response.new(
...> status: 200,
...> body: <<1, 2, 3, 4>>,
...> headers: HTTP.Headers.new([{"content-type", "image/png"}])
...> )
iex> blob = HTTP.Response.blob(response)
iex> blob.type
"image/png"
iex> blob.size
4
Creates a duplicate of the response, allowing the body to be read multiple times.
For buffered responses, this creates a shallow copy with the body duplicated and
body_used reset to false.
For streaming responses, this creates a "tee" that splits the stream into two independent streams that can be consumed separately.
Examples
# Clone buffered response
response = HTTP.Response.new(status: 200, body: "data")
clone = HTTP.Response.clone(response)
# Read original
text1 = HTTP.Response.text(response)
# Read clone independently
text2 = HTTP.Response.text(clone)
# Both contain the same data
text1 == text2 # true
# Clone streaming response
response = HTTP.Response.new(status: 200, stream: stream_pid)
clone = HTTP.Response.clone(response)
# Both can be read independently
data1 = HTTP.Response.read_all(response)
data2 = HTTP.Response.read_all(clone)
Parses the Content-Type header to extract media type and parameters.
Examples
iex> response = %HTTP.Response{headers: HTTP.Headers.new([{"Content-Type", "application/json; charset=utf-8"}])}
iex> HTTP.Response.content_type(response)
{"application/json", %{"charset" => "utf-8"}}
iex> response = %HTTP.Response{headers: HTTP.Headers.new([{"Content-Type", "text/plain"}])}
iex> HTTP.Response.content_type(response)
{"text/plain", %{}}
Gets a response header value by name (case-insensitive).
Examples
iex> response = %HTTP.Response{headers: HTTP.Headers.new([{"Content-Type", "application/json"}])}
iex> HTTP.Response.get_header(response, "content-type")
"application/json"
iex> response = %HTTP.Response{headers: HTTP.Headers.new([{"Content-Type", "application/json"}])}
iex> HTTP.Response.get_header(response, "missing")
nil
Parses the response body as JSON using Elixir's built-in JSON module (available in Elixir 1.18+).
Returns:
{:ok, map | list}if the body is valid JSON.{:error, reason}if the body cannot be parsed as JSON.
Note: This method is deprecated in favor of read_as_json/1 for streaming responses.
Creates a new Response struct with Browser Fetch API fields populated.
This is the recommended way to create Response structs. It automatically
derives status_text and ok from the status code, and sets proper defaults
for all Browser API fields.
Parameters
opts- Keyword list with fields::status- HTTP status code (default: 0):headers- HTTP.Headers struct (default: empty headers):body- Response body binary (default: nil):url- Request URL (default: nil):stream- Stream PID for streaming responses (default: nil):redirected- Whether response was redirected (default: false):type- Response type (default: :basic)
The following fields are automatically computed:
status_text- Derived from status code via HTTP.StatusTextok- Set to true if status in 200..299body_used- Always initialized to false
Examples
iex> response = HTTP.Response.new(status: 200, body: "OK", url: URI.parse("https://example.com"))
iex> response.status
200
iex> response.status_text
"OK"
iex> response.ok
true
iex> response.body
"OK"
iex> response.body_used
false
iex> response = HTTP.Response.new(status: 404, headers: HTTP.Headers.new())
iex> response.status
404
iex> response.status_text
"Not Found"
iex> response.ok
false
Reads the entire response body as binary.
For streaming responses, this will consume the entire stream into memory. For non-streaming responses, returns the existing body.
Examples
iex> response = HTTP.Response.new(status: 200, body: "Hello World")
iex> HTTP.Response.read_all(response)
"Hello World"
Reads the response body and parses it as JSON.
For streaming responses, this will read the entire stream before parsing.
Returns:
{:ok, map | list}if the body is valid JSON.{:error, reason}if the body cannot be parsed as JSON.
Examples
iex> response = HTTP.Response.new(status: 200, body: ~s({"key": "value"}))
iex> HTTP.Response.read_as_json(response)
{:ok, %{"key" => "value"}}
Reads the response body as text.
For streaming responses, this will read the entire stream into memory.
Note: Due to Elixir's immutability, the body_used field exists for API
compatibility but doesn't prevent multiple reads. Use clone/1 for clarity
when reading multiple times.
Examples
iex> response = HTTP.Response.new(status: 200, body: "Hello")
iex> HTTP.Response.text(response)
"Hello"
Writes the response body to a file.
For streaming responses, this will read the entire stream and write it to the file. For non-streaming responses, it will write the existing body directly.
Parameters
response: The HTTP response to writefile_path: The path to write the file to
Returns
:okon success{:error, reason}on failure
Examples
iex> response = %HTTP.Response{body: "file content", stream: nil}
iex> HTTP.Response.write_to(response, "/tmp/test.txt")
:ok