Pass Parameters

The HTTP protocol allows to carry additional data either by using the request URI, or by using the HTTP body, for which there are a few already widely used standards. HTTPX aims at solving the majority.

:params

Using the :params option will encode the parameters in the URI query in the x-www-form-urlencoded format:

# GET https://google.com?q=john
HTTPX.get("http://google.com", params: { "q" => "john" })

:body

Using the :body option will pass the raw data to the request body. You can pass:

  • raw strings;
  • an IO object (i.e. a File, or a StringIO object);
  • an object which implements #each and yields strings (such as ["a", "b", "c"]);

The content type will be set to application/octet-stream, unless stated explicitly otherwise.

# body as a string
HTTPX.post("https://example.com", body: "thiswillbethebody")

# body as IO object
HTTPX.post("https://example.com", body: File.open("large-file.txt"))
HTTPX.post("https://example.com", body: StringIO.new("largetext"))

# As an object implementing each

HTTPX.post("https://example.com", body: %w[this will be the body])
# or
fib = Enumerator.new do |y|
  a = b = 1
  loop do
    y << a.to_s
    a, b = b, a + b
    break if a > (2 ** 32)
  end
end
HTTPX.post("https://example.com", body: fib)

# Note: The `content-length` header won't be sent in this case, and chunks will be immediately sent to the server when yielded.

:json

Using the :json option will encode the parameters in the application/json format:

# POSTed as '{"foo":"bar"}'
HTTPX.post("https://example.com", json: { "foo" => "bar" })

:form

Using the :form option will encode the parameters in the request body encoded in the x-www-form-urlencoded format:

# POSTed as "foo=bar"
HTTPX.post("https://example.com", form: { "foo" => "bar" })

multipart/form-data

If the :form param contains files or pathnames (or parameters with a given specification explained below), the request will be encoded as multipart/form-data:

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

# or with files
response = HTTPX.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

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.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.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.post("https://example.com", form: {
  video: AwesomeVideo.new
})

Chunked

If using “HTTP/1.1”, you can set the header, and httpx will encode it using the chunked transfer encoding immediately:

HTTPX.with(headers: { "transfer-encoding" => "chunked" })
     .post("https://example.com", body: %w[this will be the body])

Expect: 100-continue

If you want to give the server the opportunity to validate the HTTP request headers before the body is sent, you can just add the expect header, and the client will seamlessly respect it:

HTTPX.post("https://example.com", headers: { "expect" => "100-continue" }, body: File.open("body.txt"))

However, if you aren’t sure if the server you’re requesting from does not handle it, you are strongly recommended to use the expect plugin instead, which gracefully degrades to sending the body anyway after a timeout if the server doesn’t.

Next: Pass Headers