HTTP::FormData - Crystal 1.14.0

# module HTTP::FormData

##
Overview

Contains utilities for parsing `multipart/form-data` messages, which are commonly used for encoding HTML form data.

NOTE To use `FormData`, you must explicitly import it with `require "http"`

###
Examples

Commonly, you'll want to parse a from response from a HTTP request, and process it. An example server which performs this task is shown below.

    require "http"

    server = HTTP::Server.new do |context|
      name = nil
      file = nil
      HTTP::FormData.parse(context.request) do |part|
        case part.name
        when "name"
          name = part.body.gets_to_end
        when "file"
          file = File.tempfile("upload") do |file|
            IO.copy(part.body, file)
          end
        end
      end

      unless name && file
        context.response.respond_with_status(:bad_request)
        next
      end

      context.response << file.path
    end

    server.bind_tcp 8085
    server.listen

To test the server, use the curl command below.

    $ curl http://localhost:8085/ -F name=foo -F file=@/path/to/test.file
    /tmp/upload.Yxn7cc

Another common case is sending formdata to a server using HTTP::Client. Here is an example showing how to upload a file to the server above in crystal.

    require "http"

    IO.pipe do |reader, writer|
      channel = Channel(String).new(1)

      spawn do
        HTTP::FormData.build(writer) do |formdata|
          channel.send(formdata.content_type)

          formdata.field("name", "foo")
          File.open("foo.png") do |file|
            metadata = HTTP::FormData::FileMetadata.new(filename: "foo.png")
            headers = HTTP::Headers{"Content-Type" => "image/png"}
            formdata.file("file", file, metadata, headers)
          end
        end

        writer.close
      end

      headers = HTTP::Headers{"Content-Type" => channel.receive}
      response = HTTP::Client.post("http://localhost:8085/", body: reader, headers: headers)

      puts "Response code #{response.status_code}"
      puts "File path: #{response.body}"
    end

##
Defined in:

http/formdata.cr
http/formdata/builder.cr
http/formdata/parser.cr
http/formdata/part.cr

##
Class Method Summary

- .build(response : HTTP::Server::Response, boundary = MIME::Multipart.generate_boundary, &)

  Builds a multipart/form-data message, yielding a `FormData::Builder` object to the block which writes to response using *boundary.

- .build(io, boundary = MIME::Multipart.generate_boundary, &)

  Builds a multipart/form-data message, yielding a `FormData::Builder` object to the block which writes to io using boundary.

- .parse(io, boundary, &)

  Parses a multipart/form-data message, yielding a `FormData::Part`.

- .parse(request : HTTP::Request, &)

  Parses a multipart/form-data message, yielding a `FormData::Part`.

- .parse_content_disposition(content_disposition) : Tuple(String, FileMetadata)

  Parses a `Content-Disposition` header string into a field name and `FileMetadata`.

##
Class Method Detail

def self.build(response : HTTP::Server::Response, boundary = MIME::Multipart.generate_boundary, &) #

Builds a multipart/form-data message, yielding a `FormData::Builder` object to the block which writes to response using *boundary. Content-Type is set on response and `Builder#finish` is called on the builder when the block returns.

    require "http"

    io = IO::Memory.new
    response = HTTP::Server::Response.new io
    HTTP::FormData.build(response, "boundary") do |builder|
      builder.field("foo", "bar")
    end
    response.close

    response.headers["Content-Type"] # => "multipart/form-data; boundary=\"boundary\""
    io.to_s                          # => "HTTP/1.1 200 OK\r\nContent-Type: multipart/form-data; boundary=\"boundary\"\r\nContent-Length: 75\r\n\r\n--boundary\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n--boundary--"

See: `FormData::Builder`

def self.build(io, boundary = MIME::Multipart.generate_boundary, &) #

Builds a multipart/form-data message, yielding a `FormData::Builder` object to the block which writes to io using boundary. `Builder#finish` is called on the builder when the block returns.

    require "http"

    io = IO::Memory.new
    HTTP::FormData.build(io, "boundary") do |builder|
      builder.field("foo", "bar")
    end
    io.to_s # => "--boundary\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n--boundary--"

See: `FormData::Builder`

def self.parse(io, boundary, &) #

Parses a multipart/form-data message, yielding a `FormData::Part`.

    require "http"

    form_data = "--aA40\r\nContent-Disposition: form-data; name=\"field1\"\r\n\r\nfield data\r\n--aA40--"
    HTTP::FormData.parse(IO::Memory.new(form_data), "aA40") do |part|
      part.name             # => "field1"
      part.body.gets_to_end # => "field data"
    end

See: `FormData::Parser`

def self.parse(request : HTTP::Request, &) #

Parses a multipart/form-data message, yielding a `FormData::Part`.

    require "http"

    headers = HTTP::Headers{"Content-Type" => "multipart/form-data; boundary=aA40"}
    body = "--aA40\r\nContent-Disposition: form-data; name=\"field1\"\r\n\r\nfield data\r\n--aA40--"
    request = HTTP::Request.new("POST", "/", headers, body)

    HTTP::FormData.parse(request) do |part|
      part.name             # => "field1"
      part.body.gets_to_end # => "field data"
    end

See: `FormData::Parser`

def self.parse_content_disposition(content_disposition) : Tuple(String, FileMetadata) #

Parses a `Content-Disposition` header string into a field name and `FileMetadata`. Please note that the `Content-Disposition` header for `multipart/form-data` is not compatible with the original definition in RFC 2183, but are instead specified in RFC 2388.