Multipart Uploads

Note: introduced in 0.3.0.

The Multipart Plugin provides a friendlier API to upload files using the multipart/form-data) Content Type.

How to Use

File Uploads

If you want to upload files, your server will more than probably expect them to be sent using the multipart/form-data encoding. You can do that via the :form parameter under a multipart-enabled session:

path = "path/to/image.jpg"
# with pathnames
response = HTTPX.plugin(:multipart).post("https://example.com", form: {image: Pathname.new(path)})

# or with files
response = HTTPX.plugin(:multipart).post("https://example.com", form: {image: File.new(path)})


# 
# ...
# Content-Type: multipart/form-data; boundary=---------------------a324a748aade297ac4ffc87108bee9ecd8cb2c2fea ...
# "-----------------------2a6965a264bbf67e519ad96cbc970800cac79dd240
# ...
# Content-Disposition: form-data; name="image"; filename="image.jpg"
# Content-Type: image/jpeg
#
# \xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xFF\xDB\x00\x84\x00\x02\x02\x02

If :form gets passed string values, it’ll fallback to xxx-form-urlencoded encoding:

response = HTTPX.plugin(:multipart).post("https://example.com", form: {foo: "bar"})
# 
# ...
# foo=bar

Content Types

In multipart payloads, the content type of String values will be set as “text/plain”.

For files, the type will be inferred using one of the following strategies:

  • using the filemagic gem (if available);
  • using the marcel gem (if available);
  • using the mime-magic gem (if available);
  • shelling out to the file cli tool (if installed in the system);
  • application/octet-stream as fallback;
HTTPX.plugin(:multipart).post("https://example.com", form: {image: Pathname.new(path)})

caveat: the integrations above are oriented towards media files which typically contain information about its first bytes, such as photos and videos, but may not work as well with text-based mime-types (such as “text/csv”).

You can override passing the file (or string) inside an hash with the following options:

HTTPX.plugin(:multipart).post("https://example.com", form: {
  image: {
    content_type: "image/png",
    filename: "image.jpg",
    body: Pathname.new("/path/to/file-xxx-yyy-zzz-random-data-uuid")
  },
  metadata: {
    content_type: "application/json",
    body: JSON.dump({ location: "Japan", frame_number: 2 })
  }
})

you can also pass your own object responding to those fields as methods, and implementing #read(?Integer bytesize, ?String buffer):

class AwesomeVideo
  def content_type
    "video/mp4"
  end

  def filename
    "gangnam-style.mp4"
  end

  def read(*)
   .....
   .....
  end

  # optionally, you can implement
  # def close ; end
  # and
  # def rewind ; end
end

HTTPX.plugin(:multipart).post("https://example.com", form: {
  video: AwesomeVideo.new
})

decode multipart responses

**(since v0.17)

If your server responds with multipart content, it’s also possible to decode it:

require "httpx"
http = HTTPX.plugin(:multipart)
response = http.get("https://a.domain/responding-with-multipart")
parts = response.form

Parts are either plain strings (“text/plain” mime-type and the like), of “file parts”, object which immplement #original_filename, #content_type, and support the File API (#read, etc…).

Notes

For versions older than 0.11.0, you have to install the http-form_data gem, in order to use this feature!

For 0.11.0 or later, httpx still support HTTP::Part or HTTP::File values… however, you have to require the gem yourself.

This was an effort to ease the migration path whilst not breaking user code. But there were reasons not to rely on the gem, namely:

  • it doesn’t spare the user of annonating the content types;
  • it doesn’t rewind parts (so you have to generate a new object for every form);
  • it doesn’t close file descriptors;

So consider migrating your multipart forms.

Next: Persistent