Follow this rules when creating a multipart form: Specify enctype="multipart/form-data" attribute on a form tag. Add a name attribute to a single input type="file" tag. DO NOT add a name attribute to any other input, select or textarea tags.
An HTTP multipart request is an HTTP request that HTTP clients construct to send files and data over to an HTTP Server. It is commonly used by browsers and HTTP clients to upload files to the server.
With curl, you add each separate multipart with one -F (or --form ) flag and you then continue and add one -F for every input field in the form that you want to send. The above small example form has two parts, one named 'person' that is a plain text field and one named 'secret' that is a file. Copied!
UPDATE: I have created a video on sending multipart/form-data requests to explain this better. Actually, Postman can do this. You DON'T need to add any headers, Postman will do this for you automatically. Be careful with explicit Content-Type header.
I like RestClient. It encapsulates net/http with cool features like multipart form data:
require 'rest_client'
RestClient.post('http://localhost:3000/foo',
:name_of_file_param => File.new('/path/to/file'))
It also supports streaming.
gem install rest-client
will get you started.
I can't say enough good things about Nick Sieger's multipart-post library.
It adds support for multipart posting directly to Net::HTTP, removing your need to manually worry about boundaries or big libraries that may have different goals than your own.
Here is a little example on how to use it from the README:
require 'net/http/post/multipart'
url = URI.parse('http://www.example.com/upload')
File.open("./image.jpg") do |jpg|
req = Net::HTTP::Post::Multipart.new url.path,
"file" => UploadIO.new(jpg, "image/jpeg", "image.jpg")
res = Net::HTTP.start(url.host, url.port) do |http|
http.request(req)
end
end
You can check out the library here: http://github.com/nicksieger/multipart-post
or install it with:
$ sudo gem install multipart-post
If you're connecting via SSL you need to start the connection like this:
n = Net::HTTP.new(url.host, url.port)
n.use_ssl = true
# for debugging dev server
#n.verify_mode = OpenSSL::SSL::VERIFY_NONE
res = n.start do |http|
Another one using only standard libraries:
uri = URI('https://some.end.point/some/path')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'If you need some headers'
form_data = [['photos', photo.tempfile]] # or File.open() in case of local file
request.set_form form_data, 'multipart/form-data'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| # pay attention to use_ssl if you need it
http.request(request)
end
Tried a lot of approaches but only this was worked for me.
curb
looks like a great solution, but in case it doesn't meet your needs, you can do it with Net::HTTP
. A multipart form post is just a carefully-formatted string with some extra headers. It seems like every Ruby programmer who needs to do multipart posts ends up writing their own little library for it, which makes me wonder why this functionality isn't built-in. Maybe it is... Anyway, for your reading pleasure, I'll go ahead and give my solution here. This code is based off of examples I found on a couple of blogs, but I regret that I can't find the links anymore. So I guess I just have to take all the credit for myself...
The module I wrote for this contains one public class, for generating the form data and headers out of a hash of String
and File
objects. So for example, if you wanted to post a form with a string parameter named "title" and a file parameter named "document", you would do the following:
#prepare the query
data, headers = Multipart::Post.prepare_query("title" => my_string, "document" => my_file)
Then you just do a normal POST
with Net::HTTP
:
http = Net::HTTP.new(upload_uri.host, upload_uri.port)
res = http.start {|con| con.post(upload_uri.path, data, headers) }
Or however else you want to do the POST
. The point is that Multipart
returns the data and headers that you need to send. And that's it! Simple, right? Here's the code for the Multipart module (you need the mime-types
gem):
# Takes a hash of string and file parameters and returns a string of text
# formatted to be sent as a multipart form post.
#
# Author:: Cody Brimhall <mailto:[email protected]>
# Created:: 22 Feb 2008
# License:: Distributed under the terms of the WTFPL (http://www.wtfpl.net/txt/copying/)
require 'rubygems'
require 'mime/types'
require 'cgi'
module Multipart
VERSION = "1.0.0"
# Formats a given hash as a multipart form post
# If a hash value responds to :string or :read messages, then it is
# interpreted as a file and processed accordingly; otherwise, it is assumed
# to be a string
class Post
# We have to pretend we're a web browser...
USERAGENT = "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/523.10.6 (KHTML, like Gecko) Version/3.0.4 Safari/523.10.6"
BOUNDARY = "0123456789ABLEWASIEREISAWELBA9876543210"
CONTENT_TYPE = "multipart/form-data; boundary=#{ BOUNDARY }"
HEADER = { "Content-Type" => CONTENT_TYPE, "User-Agent" => USERAGENT }
def self.prepare_query(params)
fp = []
params.each do |k, v|
# Are we trying to make a file parameter?
if v.respond_to?(:path) and v.respond_to?(:read) then
fp.push(FileParam.new(k, v.path, v.read))
# We must be trying to make a regular parameter
else
fp.push(StringParam.new(k, v))
end
end
# Assemble the request body using the special multipart format
query = fp.collect {|p| "--" + BOUNDARY + "\r\n" + p.to_multipart }.join("") + "--" + BOUNDARY + "--"
return query, HEADER
end
end
private
# Formats a basic string key/value pair for inclusion with a multipart post
class StringParam
attr_accessor :k, :v
def initialize(k, v)
@k = k
@v = v
end
def to_multipart
return "Content-Disposition: form-data; name=\"#{CGI::escape(k)}\"\r\n\r\n#{v}\r\n"
end
end
# Formats the contents of a file or string for inclusion with a multipart
# form post
class FileParam
attr_accessor :k, :filename, :content
def initialize(k, filename, content)
@k = k
@filename = filename
@content = content
end
def to_multipart
# If we can tell the possible mime-type from the filename, use the
# first in the list; otherwise, use "application/octet-stream"
mime_type = MIME::Types.type_for(filename)[0] || MIME::Types["application/octet-stream"][0]
return "Content-Disposition: form-data; name=\"#{CGI::escape(k)}\"; filename=\"#{ filename }\"\r\n" +
"Content-Type: #{ mime_type.simplified }\r\n\r\n#{ content }\r\n"
end
end
end
Here is my solution after trying other ones available on this post, I'm using it to upload photo on TwitPic:
def upload(photo)
`curl -F media=@#{photo.path} -F username=#{@username} -F password=#{@password} -F message='#{photo.title}' http://twitpic.com/api/uploadAndPost`
end
Fast forward to 2017, ruby
stdlib
net/http
has this built-in since 1.9.3
Net::HTTPRequest#set_form): Added to support both application/x-www-form-urlencoded and multipart/form-data.
https://ruby-doc.org/stdlib-2.3.1/libdoc/net/http/rdoc/Net/HTTPHeader.html#method-i-set_form
We can even use IO
which does not support :size
to stream the form data.
Hoping that this answer can really help someone :)
P.S. I only tested this in ruby 2.3.1
Ok, here's a simple example using curb.
require 'yaml'
require 'curb'
# prepare post data
post_data = fields_hash.map { |k, v| Curl::PostField.content(k, v.to_s) }
post_data << Curl::PostField.file('file', '/path/to/file'),
# post
c = Curl::Easy.new('http://localhost:3000/foo')
c.multipart_form_post = true
c.http_post(post_data)
# print response
y [c.response_code, c.body_str]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With