Ruby:如何通过HTTP发布文件作为multipart / form-data?

我想做一个HTTP POST,看起来像从浏览器发布的HMTL表单。 具体而言,发布一些文本字段和文件字段。

发布文本字段非常简单,在net / http rdocs中有一个例子,但是我不知道如何发布文件。

Net :: HTTP看起来不是最好的主意。 路边看起来不错。

我喜欢RestClient 。 它封装了networking/ http与酷的function,如多部分forms的数据:

require 'rest_client' RestClient.post('http://localhost:3000/foo', :name_of_file_param => File.new('/path/to/file')) 

它也支持stream媒体。

gem install rest-client会让你开始。

我不能说尼克·西格的多部分后期图书馆的好东西。

它增加了对直接到Net :: HTTP的多部分发布的支持,消除了手动担心边界或大型图书馆可能有不同于你自己的目标的需要。

这里有一个关于如何从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 

你可以在这里查看图书馆: http : //github.com/nicksieger/multipart-post

或者通过以下方式安装

 $ sudo gem install multipart-post 

如果你通过SSL连接,你需要像这样开始连接:

 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| 

curb看起来像一个很好的解决scheme,但如果它不能满足您的需求,您可以使用Net::HTTP 。 多部分forms的post只是一个仔细格式化的string,有一些额外的头。 似乎每个需要做多部分post的Ruby程序员最终都会为它编写自己的小型库,这让我想知道为什么这个function不是内置的。 也许是…无论如何,为了您的阅读乐趣,我会继续前进,在这里给我解决scheme。 这个代码是基于我在几个博客上find的例子,但是我很遗憾,我找不到链接了。 所以我想我只需要为自己承担所有的功劳

我为此编写的模块包含一个公共类,用于从StringFile对象的散列中生成表单数据和标题。 例如,如果你想发布一个名为“title”的string参数和一个名为“document”的文件参数,你可以这样做:

 #prepare the query data, headers = Multipart::Post.prepare_query("title" => my_string, "document" => my_file) 

然后,你只需要使用Net::HTTP进行正常的POST

 http = Net::HTTP.new(upload_uri.host, upload_uri.port) res = http.start {|con| con.post(upload_uri.path, data, headers) } 

否则,你想要做POST 。 重点在于Multipart返回您需要发送的数据和标题。 而就是这样! 很简单,对吧? 这里是Multipart模块的代码(你需要mime-typesgem):

 # 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:brimhall@somuchwit.com> # 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 

这是我的解决scheme,尝试其他可用的这篇文章后,我用它来上传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 

好的,这里有一个简单的例子,用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] 

RestClient不工作,直到我覆盖RestClient :: Payload :: Multipart中的create_file_field。

它在每个应该是“内容处置:表单数据”的部分创build一个“Content-Disposition:multipart / form-data”

http://www.ietf.org/rfc/rfc2388.txt

我的叉子在这里,如果你需要的话:git@github.com:kcrawford / rest-client.git

那么使用NetHttp的解决scheme有一个缺点,就是在发布大文件时,首先将整个文件加载到内存中。

玩了一下后,我想出了以下解决scheme:

 class Multipart def initialize( file_names ) @file_names = file_names end def post( to_url ) boundary = '----RubyMultipartClient' + rand(1000000).to_s + 'ZZZZZ' parts = [] streams = [] @file_names.each do |param_name, filepath| pos = filepath.rindex('/') filename = filepath[pos + 1, filepath.length - pos] parts << StringPart.new ( "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"" + param_name.to_s + "\"; filename=\"" + filename + "\"\r\n" + "Content-Type: video/x-msvideo\r\n\r\n") stream = File.open(filepath, "rb") streams << stream parts << StreamPart.new (stream, File.size(filepath)) end parts << StringPart.new ( "\r\n--" + boundary + "--\r\n" ) post_stream = MultipartStream.new( parts ) url = URI.parse( to_url ) req = Net::HTTP::Post.new(url.path) req.content_length = post_stream.size req.content_type = 'multipart/form-data; boundary=' + boundary req.body_stream = post_stream res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) } streams.each do |stream| stream.close(); end res end end class StreamPart def initialize( stream, size ) @stream, @size = stream, size end def size @size end def read ( offset, how_much ) @stream.read ( how_much ) end end class StringPart def initialize ( str ) @str = str end def size @str.length end def read ( offset, how_much ) @str[offset, how_much] end end class MultipartStream def initialize( parts ) @parts = parts @part_no = 0; @part_offset = 0; end def size total = 0 @parts.each do |part| total += part.size end total end def read ( how_much ) if @part_no >= @parts.size return nil; end how_much_current_part = @parts[@part_no].size - @part_offset how_much_current_part = if how_much_current_part > how_much how_much else how_much_current_part end how_much_next_part = how_much - how_much_current_part current_part = @parts[@part_no].read(@part_offset, how_much_current_part ) if how_much_next_part > 0 @part_no += 1 @part_offset = 0 next_part = read ( how_much_next_part ) current_part + if next_part next_part else '' end else @part_offset += how_much_current_part current_part end end end 

还有尼克·西格的多部分职位,以增加可能的解决scheme的长长的名单。

我有同样的问题(需要张贴到JBoss Web服务器)。 当我在代码中使用会话variables时,Curb对我来说工作的很好,除了它导致ruby崩溃(ruby在Ubuntu 8.10上的1.8.7)。

我深入到其他客户端文档,无法find多部分支持的指示。 我尝试了上面的其他客户端的例子,但jboss说http后不是多部分。

Rails 4 Net :: HTTP的multipart-post gem工作得非常好,没有其他特殊的gem

 def model_params require_params = params.require(:model).permit(:param_one, :param_two, :param_three, :avatar) require_params[:avatar] = model_params[:avatar].present? ? UploadIO.new(model_params[:avatar].tempfile, model_params[:avatar].content_type, model_params[:avatar].original_filename) : nil require_params end require 'net/http/post/multipart' url = URI.parse('http://www.example.com/upload') Net::HTTP.start(url.host, url.port) do |http| req = Net::HTTP::Post::Multipart.new(url, model_params) key = "authorization_key" req.add_field("Authorization", key) #add to Headers http.use_ssl = (url.scheme == "https") http.request(req) end 

https://github.com/Feuda/multipart-post/tree/patch-1

快进到2017年, ruby stdlib net/http从1.9.3开始就有了这个内置的function

Net :: HTTPRequest#set_form):添加为支持application / x-www-form-urlencoded和multipart / form-data。

https://ruby-doc.org/stdlib-2.3.1/libdoc/net/http/rdoc/Net/HTTPHeader.html#method-i-set_form

我们甚至可以使用不支持的IO :size来传输表单数据。

希望这个答案可以真正帮助别人:)

PS我只用ruby 2.3.1testing过

另一个只使用标准库:

 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.read() 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 

尝试了很多方法,但只有这对我有效。