Multipart/form-data builder and parser with MIME type detection and streaming support
gem install philiprehberger-multipartMultipart/form-data builder and parser with MIME type detection and streaming support
Add to your Gemfile:
gem "philiprehberger-multipart"
Or install directly:
gem install philiprehberger-multipart
require "philiprehberger/multipart"
builder = Philiprehberger::Multipart.build do
field :name, 'Alice'
field :email, 'alice@example.com'
end
builder.to_s # => multipart body string
builder.content_type # => "multipart/form-data; boundary=..."
builder = Philiprehberger::Multipart.build do
field :name, 'Alice'
file :avatar, '/path/to/photo.png'
end
builder.to_s # => multipart body with file data
builder.boundary # => the boundary string
require "stringio"
io = StringIO.new("CSV,data,here")
builder = Philiprehberger::Multipart.build do
file :upload, io, filename: 'data.csv', content_type: 'text/csv'
end
builder.to_s # => multipart body streamed from IO
# Auto-detected from filename when content_type is not provided
builder = Philiprehberger::Multipart.build do
file :doc, '/path/to/report.pdf' # => application/pdf
file :img, '/path/to/photo.jpg' # => image/jpeg
file :data, '/path/to/config.json' # => application/json
end
# Direct lookup
Philiprehberger::Multipart::MimeTypes.lookup('photo.jpg') # => "image/jpeg"
Philiprehberger::Multipart::MimeTypes.lookup('unknown.xyz') # => "application/octet-stream"
body = "--boundary\r\n" \
"Content-Disposition: form-data; name=\"field\"\r\n" \
"\r\n" \
"value\r\n" \
"--boundary--\r\n"
parts = Philiprehberger::Multipart.parse(body, content_type: 'multipart/form-data; boundary=boundary')
parts.first.name # => :field
parts.first.body # => "value"
parts.first.file? # => false
builder = Philiprehberger::Multipart.build do
field :name, 'Alice'
file :avatar, '/path/to/photo.png'
end
# Stream directly to a file or socket without buffering the full body
File.open('body.dat', 'wb') { |f| builder.write_to(f) }
# Content-Length for HTTP headers
builder.content_length # => 1234
builder.headers # => { "Content-Type" => "multipart/form-data; boundary=...", "Content-Length" => "1234" }
builder = Philiprehberger::Multipart.build do |b|
b.field("name", "Alice")
b.file("avatar", "avatar.png")
end
builder.field_names # => ["name", "avatar"]
builder = Philiprehberger::Multipart.build do
field :name, 'Alice'
file :avatar, '/path/to/photo.png'
end
# Look up a part by name (String or Symbol both work)
builder.part(:avatar) # => #<Part name=:avatar ...>
builder.part('avatar') # => same Part
# Tweak attributes after the fact — changes flow through to #to_s
builder.part('avatar').content_type = 'image/webp'
# Returns nil when no part matches
builder.part(:missing) # => nil
builder = Philiprehberger::Multipart.build do
field :name, 'Alice'
file :avatar, '/path/to/photo.png'
end
builder.size # => same as builder.content_length
builder.to_h
# => {
# boundary: "----PhiliprehbergerMultipart...",
# parts: [
# { name: "name", filename: nil, content_type: nil, size: 5 },
# { name: "avatar", filename: "photo.png", content_type: "image/png", size: 4096 }
# ]
# }
builder = Philiprehberger::Multipart.build(boundary: 'my-boundary') do
field :key, 'value'
end
builder.content_type # => "multipart/form-data; boundary=my-boundary"
| Method | Description |
|---|---|
Multipart.build(boundary: nil, &block) | Build a multipart body using the DSL |
Multipart.parse(body, content_type:) | Parse an incoming multipart/form-data body |
MimeTypes.lookup(filename) | Look up MIME type from a filename extension |
Builder#field(name, value) | Add a text field |
Builder#file(name, path_or_io, filename:, content_type:) | Add a file from path or IO object |
Builder#part(name) | Look up the first part with a matching name (Symbol or String), or nil |
Builder#field_names | Array of part names (as strings) in insertion order |
Builder#merge(other) | Append parts from another Builder without re-encoding |
Builder#to_s | Render the multipart body as a string |
Builder#content_type | Content-Type header value with boundary |
Builder#boundary | The multipart boundary string |
Builder#write_to(io) | Stream the multipart body to an IO object |
Builder#content_length | Byte size of the body for Content-Length headers |
Builder#size | Alias for content_length |
Builder#to_h | Structured { boundary:, parts: [...] } summary |
Builder#headers | Hash with Content-Type and Content-Length headers |
Part#name | The field name |
Part#value | The part value / body content |
Part#body | Alias for value |
Part#filename | The original filename (nil for text fields) |
Part#content_type | The MIME content type (nil for text fields) |
Part#file? | Whether this part is a file upload |
Part#text? | Whether this part is a plain text field (inverse of file?) |
Part#size | Byte size of the part's value |
bundle install
bundle exec rspec
bundle exec rubocop
If you find this project useful: