Web Standard APIs for Elixir
A protocol-agnostic, Web API-compliant library for Elixir that mirrors the JavaScript Fetch Standard.
Built with a "Dispatcher" architecture, Web provides a unified interface for HTTP, TCP, and connection-string-based protocols while maintaining a zero-buffer, streaming-first approach.
Quick Start (Script / Livebook)
You can try Web immediately without creating a project using Mix.install:
Mix.install([
{:web, "~> 0.1.0"}
])
# 1. Construct a new Web.URL
url = Web.URL.new("https://api.github.com/search/repositories")
# 2. Modify properties via URLSearchParams
params =
Web.URL.search_params(url)
|> Web.URLSearchParams.set("q", "elixir")
|> Web.URLSearchParams.append("sort", "stars")
# 3. Apply params back to the URL
url = Web.URL.search(url, Web.URLSearchParams.to_string(params))
# 4. Construct a Web.Request with the URL
request = Web.Request.new(url,
method: "GET",
headers: %{"Accept" => "application/vnd.github.v3+json"}
)
# 5. Send to Web.fetch
{:ok, response} = Web.fetch(request)
IO.puts("Fetching: #{Web.URL.href(url)}")
IO.puts("Status: #{response.status}")
# Stream the body lazily (Zero-Buffer)
# The body is an Elixir Stream yielding chunks as they arrive from the socket
response.body
|> Stream.take(5)
|> Enum.each(&IO.write/1)Key Features
- JS Fetch Parity: Familiar
Request,Response, andHeadersstructs. - Polymorphic Entry:
Web.fetch/2accepts a URL string, aWeb.URL, or aWeb.Requeststruct. - Pure Data Architecture:
Web.URLandWeb.URLSearchParamsare pure Elixir structs—no Agents or hidden state. - Overloaded Functional API:
Web.URL.href(url, "new_val")style setters that return new immutable structs. - Fetch-Style Redirects:
Web.Dispatcher.HTTPsupports"follow","manual", and"error"modes. - AbortController Support: Standardized cancellation for in-flight fetches and active body streams.
- Zero-Buffer Streaming:
Web.Response.bodyis an ElixirStreamyielding chunks directly from the socket. - Rclone-Style Resolution: Native support for connection strings (
remote:path/...).
Installation
Add :web to your dependencies in mix.exs:
def deps do
[
{:web, "~> 0.1.0"}
]
endCore Components
Request
The Request struct represents a complete I/O operation. It is protocol-agnostic, allowing the same struct to be used for HTTP, TCP, or custom dispatchers.
- Normalization: The
:headersfield is automatically converted into aHeadersstruct. - Signal Support: Pass a
AbortSignalvia the:signaloption to enable timeouts or manual cancellation. - Redirect Control: Supports
follow,manual, anderrormodes.
Response
The result of a successful fetch.
- Streaming Body: The
:bodyis anEnumerable(Stream), ensuring large resources are never fully buffered into memory. - Metadata: Includes the final
url(post-redirects),statuscode, and a normalizedHeaderscontainer.
Headers
A case-insensitive, multi-value container for protocol headers.
- Spec Parity: Implements
append,set,get,delete, andhas. - Multi-Value Support: Correctly handles multiple values for a single key, including the specific
getSetCookieexception. - Protocols: Implements
AccessandEnumerable, allowing forheaders["content-type"]andEnum.map(headers, ...).
URL & URLSearchParams
Pure data structs that handle both standard URIs and rclone-style connection strings.
- Direct Access: Access
url.protocol,url.hostname, orurl.pathnamedirectly via dot notation. - Overloaded Getters/Setters: Use
URL.href(url, "new_url")to parse and update the entire struct immutably. - Ordered Params:
URLSearchParamspreserves key order and duplicate keys.
AbortController& AbortSignal
Standardized mechanism for coordinating the cancellation of one or more asynchronous operations.
AbortController: The management object used to trigger cancellation. Create one with AbortController.new(). * **.signalProperty**: AAbortSignalinstance linked to the controller. This is the "read-only" observer passed tofetchto monitor the aborted state. * **AbortSignalState**: Signals include anabortedboolean and areasonfor the cancellation. * **AbortSignal.timeout(ms)**: Static helper that returns a signal that automatically aborts after a specified duration—perfect for handling request timeouts. * **AbortSignal.any(signals)**: Static helper that combines multiple signals into one; the combined signal aborts if ANY of the provided source signals abort. * **AbortSignal.abort(reason)**: Static helper that returns a signal that is already in an aborted state. ## Architecture - **Dispatcher**: The core behavior for all protocol handlers. - **Dispatcher.HTTP**: Powered byFinch, handles connection pooling and redirects internally. - **Dispatcher.TCP**: Base TCP implementation using:gen_tcpwith abort-aware streaming. - **Headers**: A case-insensitive, multi-value header container withAccessandEnumerablesupport. ## TestingWeb` is built with a commitment to reliability, featuring 100% test coverage including property-based tests for URL parsing and header normalization.bash mix test --cover