module HTTP::FormData

Overview

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

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"
require "tempfile"

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 = Tempfile.open("upload") do |file|
        IO.copy(part.body, file)
      end
    end
  end

  unless name && file
    context.response.status_code = 400
    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/builder.cr
http/formdata/parser.cr
http/formdata/part.cr
http/formdata.cr

Class Method Summary

Class Method Detail

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

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.

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


[View source]
def self.build(io, boundary = Multipart.generate_boundary, &block) #

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.

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


[View source]
def self.parse(io, boundary, &block) #

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

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


[View source]
def self.parse(request : HTTP::Request, &block) #

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

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


[View source]
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.


[View source]