Ruby:如何通过HTTP将文件作为多部分/表单数据发布?

时间:2022-06-14 23:08:32

I want to do an HTTP POST that looks like an HMTL form posted from a browser. Specifically, post some text fields and a file field.

我想做一个HTTP POST,看起来像从浏览器中发布的HMTL表单。特别是,发布一些文本字段和文件字段。

Posting text fields is straightforward, there's an example right there in the net/http rdocs, but I can't figure out how to post a file along with it.

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

Net::HTTP doesn't look like the best idea. curb is looking good.

HTTP看起来不是最好的方法。抑制是好看。

12 个解决方案

#1


86  

I like RestClient. It encapsulates net/http with cool features like multipart form data:

我喜欢RestClient。它用一些很酷的特性来封装net/http,比如多部分表单数据:

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.

gem安装rest-client将帮助您入门。

#2


33  

I can't say enough good things about Nick Sieger's multipart-post library.

关于尼克·西格(Nick Sieger)的multipart-post库,我要说的还不够多。

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.

它增加了对多部分直接发布到Net::HTTP的支持,消除了手动担心边界或大型库的需要,这些库可能与您自己的目标不同。

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

您可以在这里查看库: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:

如果您正在通过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|

#3


27  

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...

遏制看起来是一个很好的解决方案,但是如果它不能满足您的需求,您可以使用Net::HTTP来实现。多部分表单post只是一个格式严谨的字符串,包含一些额外的标题。似乎每个需要多部分文章的Ruby程序员最后都为它编写了自己的小库,这让我想知道为什么这个功能不是内置的。也许是……无论如何,为了你的阅读乐趣,我将继续,并给出我的解决方案。这段代码基于我在几个博客上找到的例子,但是我很遗憾我再也找不到链接了。所以我想我得把所有的功劳都归给自己……

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:

我为此编写的模块包含一个公共类,用于从字符串和文件对象的散列中生成表单数据和头部。因此,例如,如果您想发布一个带有名为“title”的字符串参数和名为“document”的文件参数的表单,您可以执行以下操作:

#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:

然后你只需要用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):

或者其他你想做的事情。重点是Multipart返回您需要发送的数据和头部。这是它!简单,是吧?这是多部分模块的代码(您需要mime类型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: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

#4


17  

Here is my solution after trying other ones available on this post, I'm using it to upload photo on TwitPic:

这是我的解决方案,在尝试了其他可用的在这篇文章,我正在用它上传照片在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

#5


8  

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]

#6


3  

Another one using only standard libraries:

另一个只使用标准库:

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

Tried a lot of approaches but only this was worked for me.

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

#7


2  

restclient did not work for me until I overrode create_file_field in RestClient::Payload::Multipart.

restclient直到在restclient:::有效负载::Multipart中重写create_file_field后才为我工作。

It was creating a 'Content-Disposition: multipart/form-data' in each part where it should be ‘Content-Disposition: form-data’.

它在每个部分都创建了“内容配置:多部分/表单数据”,应该是“内容配置:表单数据”。

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

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

My fork is here if you need it: git@github.com:kcrawford/rest-client.git

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

#8


1  

Well the solution with NetHttp has a drawback that is when posting big files it loads the whole file into memory first.

NetHttp的解决方案有一个缺点,就是当发布大文件时,它首先将整个文件加载到内存中。

After playing a bit with it I came up with the following solution:

玩了一会儿之后,我想到了以下解决方案:

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

#9


1  

there's also nick sieger's multipart-post to add to the long list of possible solutions.

尼克·西格(nick sieger)的《multipart-post》(multipart- part-post)也加入了可能解决方案的列表。

#10


1  

Fast forward to 2017, ruby stdlib net/http has this built-in since 1.9.3

快进到2017年,ruby stdlib net/http自1.9.3以来就内置了这个功能

Net::HTTPRequest#set_form): Added to support both application/x-www-form-urlencoded and multipart/form-data.

Net: HTTPRequest#set_form):添加来支持应用程序/x-www-form- urlencodes和多部分/表单数据。

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

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.

我们甚至可以使用不支持的IO:size来流数据。

Hoping that this answer can really help someone :)

希望这个答案能真正帮助某人:

P.S. I only tested this in ruby 2.3.1

我只在ruby 2.3.1中测试过。

#11


0  

I had the same problem (need to post to jboss web server). Curb works fine for me, except that it caused ruby to crash (ruby 1.8.7 on ubuntu 8.10) when I use session variables in the code.

我也有同样的问题(需要发布到jboss web服务器)。ruby在代码中使用会话变量会导致ruby崩溃(ubuntu 8.10上的ruby 1.8.7)。

I dig into the rest-client docs, could not find indication of multipart support. I tried the rest-client examples above but jboss said the http post is not multipart.

我深入研究了rest-client文档,没有发现多部分支持的迹象。我尝试了上面的rest-client示例,但是jboss说http post不是多部分的。

#12


0  

The multipart-post gem works pretty well with Rails 4 Net::HTTP, no other special gem

multipart-post gem与Rails 4 Net::HTTP合作得很好,没有其他特殊的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

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

#1


86  

I like RestClient. It encapsulates net/http with cool features like multipart form data:

我喜欢RestClient。它用一些很酷的特性来封装net/http,比如多部分表单数据:

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.

gem安装rest-client将帮助您入门。

#2


33  

I can't say enough good things about Nick Sieger's multipart-post library.

关于尼克·西格(Nick Sieger)的multipart-post库,我要说的还不够多。

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.

它增加了对多部分直接发布到Net::HTTP的支持,消除了手动担心边界或大型库的需要,这些库可能与您自己的目标不同。

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

您可以在这里查看库: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:

如果您正在通过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|

#3


27  

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...

遏制看起来是一个很好的解决方案,但是如果它不能满足您的需求,您可以使用Net::HTTP来实现。多部分表单post只是一个格式严谨的字符串,包含一些额外的标题。似乎每个需要多部分文章的Ruby程序员最后都为它编写了自己的小库,这让我想知道为什么这个功能不是内置的。也许是……无论如何,为了你的阅读乐趣,我将继续,并给出我的解决方案。这段代码基于我在几个博客上找到的例子,但是我很遗憾我再也找不到链接了。所以我想我得把所有的功劳都归给自己……

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:

我为此编写的模块包含一个公共类,用于从字符串和文件对象的散列中生成表单数据和头部。因此,例如,如果您想发布一个带有名为“title”的字符串参数和名为“document”的文件参数的表单,您可以执行以下操作:

#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:

然后你只需要用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):

或者其他你想做的事情。重点是Multipart返回您需要发送的数据和头部。这是它!简单,是吧?这是多部分模块的代码(您需要mime类型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: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

#4


17  

Here is my solution after trying other ones available on this post, I'm using it to upload photo on TwitPic:

这是我的解决方案,在尝试了其他可用的在这篇文章,我正在用它上传照片在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

#5


8  

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]

#6


3  

Another one using only standard libraries:

另一个只使用标准库:

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

Tried a lot of approaches but only this was worked for me.

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

#7


2  

restclient did not work for me until I overrode create_file_field in RestClient::Payload::Multipart.

restclient直到在restclient:::有效负载::Multipart中重写create_file_field后才为我工作。

It was creating a 'Content-Disposition: multipart/form-data' in each part where it should be ‘Content-Disposition: form-data’.

它在每个部分都创建了“内容配置:多部分/表单数据”,应该是“内容配置:表单数据”。

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

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

My fork is here if you need it: git@github.com:kcrawford/rest-client.git

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

#8


1  

Well the solution with NetHttp has a drawback that is when posting big files it loads the whole file into memory first.

NetHttp的解决方案有一个缺点,就是当发布大文件时,它首先将整个文件加载到内存中。

After playing a bit with it I came up with the following solution:

玩了一会儿之后,我想到了以下解决方案:

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

#9


1  

there's also nick sieger's multipart-post to add to the long list of possible solutions.

尼克·西格(nick sieger)的《multipart-post》(multipart- part-post)也加入了可能解决方案的列表。

#10


1  

Fast forward to 2017, ruby stdlib net/http has this built-in since 1.9.3

快进到2017年,ruby stdlib net/http自1.9.3以来就内置了这个功能

Net::HTTPRequest#set_form): Added to support both application/x-www-form-urlencoded and multipart/form-data.

Net: HTTPRequest#set_form):添加来支持应用程序/x-www-form- urlencodes和多部分/表单数据。

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

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.

我们甚至可以使用不支持的IO:size来流数据。

Hoping that this answer can really help someone :)

希望这个答案能真正帮助某人:

P.S. I only tested this in ruby 2.3.1

我只在ruby 2.3.1中测试过。

#11


0  

I had the same problem (need to post to jboss web server). Curb works fine for me, except that it caused ruby to crash (ruby 1.8.7 on ubuntu 8.10) when I use session variables in the code.

我也有同样的问题(需要发布到jboss web服务器)。ruby在代码中使用会话变量会导致ruby崩溃(ubuntu 8.10上的ruby 1.8.7)。

I dig into the rest-client docs, could not find indication of multipart support. I tried the rest-client examples above but jboss said the http post is not multipart.

我深入研究了rest-client文档,没有发现多部分支持的迹象。我尝试了上面的rest-client示例,但是jboss说http post不是多部分的。

#12


0  

The multipart-post gem works pretty well with Rails 4 Net::HTTP, no other special gem

multipart-post gem与Rails 4 Net::HTTP合作得很好,没有其他特殊的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

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