HTTP.FormData (http_fetch v0.8.0)

HTTP form data and multipart/form-data encoding for file uploads.

This module provides a convenient API for building form submissions with support for both URL-encoded forms and multipart file uploads. It automatically chooses the appropriate encoding based on the presence of file fields.

Encoding Selection

  • URL-encoded (application/x-www-form-urlencoded): Used when form contains only text fields
  • Multipart (multipart/form-data): Used when form contains file fields

Features

  • Streaming file uploads: Efficiently upload large files using File.Stream
  • Automatic encoding: Selects appropriate encoding based on content
  • Boundary generation: Automatically generates unique multipart boundaries
  • Mixed content: Support for both text fields and files in the same form

Basic Usage

# Simple form with text fields
form = HTTP.FormData.new()
       |> HTTP.FormData.append_field("username", "john_doe")
       |> HTTP.FormData.append_field("email", "john@example.com")

HTTP.fetch("https://api.example.com/signup", method: "POST", body: form)

File Upload

# Single file upload
file_stream = File.stream!("document.pdf")
form = HTTP.FormData.new()
       |> HTTP.FormData.append_field("title", "My Document")
       |> HTTP.FormData.append_file("document", "document.pdf", file_stream, "application/pdf")

HTTP.fetch("https://api.example.com/upload", method: "POST", body: form)

Multiple Files

# Upload multiple files
form = HTTP.FormData.new()
       |> HTTP.FormData.append_field("description", "Photos from vacation")
       |> HTTP.FormData.append_file("photo1", "beach.jpg", File.stream!("beach.jpg"), "image/jpeg")
       |> HTTP.FormData.append_file("photo2", "sunset.jpg", File.stream!("sunset.jpg"), "image/jpeg")

HTTP.fetch("https://api.example.com/gallery", method: "POST", body: form)

Content Types

When uploading files, you can specify the MIME type. If not provided, it defaults to "application/octet-stream":

# With explicit content type
form |> HTTP.FormData.append_file("image", "photo.jpg", stream, "image/jpeg")

# With default content type
form |> HTTP.FormData.append_file("data", "data.bin", stream)

Summary

Functions

Adds a form field.

Generates a random boundary for multipart/form-data.

Gets the appropriate Content-Type header for the form data.

Creates a new empty FormData struct.

Converts FormData to HTTP body content with appropriate encoding.

Types

form_part()

@type form_part() ::
  {:field, String.t(), String.t()}
  | {:file, String.t(), String.t(), String.t(), File.Stream.t()}
  | {:file, String.t(), String.t(), String.t(), String.t()}

t()

@type t() :: %HTTP.FormData{boundary: String.t() | nil, parts: [form_part()]}

Functions

append_field(form, name, value)

@spec append_field(t(), String.t(), String.t()) :: t()

Adds a form field.

Examples

iex> HTTP.FormData.new() |> HTTP.FormData.append_field("name", "value")
%HTTP.FormData{parts: [{:field, "name", "value"}], boundary: nil}

append_file(form, name, filename, content, content_type \\ "application/octet-stream")

@spec append_file(
  t(),
  String.t(),
  String.t(),
  File.Stream.t() | String.t(),
  String.t()
) :: t()

Adds a file field for upload with streaming support.

Examples

iex> file_stream = File.stream!("test.txt")
iex> HTTP.FormData.new() |> HTTP.FormData.append_file("upload", "test.txt", file_stream)
%HTTP.FormData{parts: [{:file, "upload", "test.txt", "text/plain", %File.Stream{}}], boundary: nil}

generate_boundary()

@spec generate_boundary() :: String.t()

Generates a random boundary for multipart/form-data.

get_content_type(form_data)

@spec get_content_type(t()) :: String.t()

Gets the appropriate Content-Type header for the form data.

new()

@spec new() :: t()

Creates a new empty FormData struct.

Examples

iex> HTTP.FormData.new()
%HTTP.FormData{parts: [], boundary: nil}

to_body(form)

@spec to_body(t()) :: {:url_encoded, String.t()} | {:multipart, iodata(), String.t()}

Converts FormData to HTTP body content with appropriate encoding.

Returns {:url_encoded, body} for regular forms or {:multipart, body, boundary} for multipart. The multipart body is returned as iodata for memory efficiency with large file uploads.