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

Ruby:如何通过HTTP将文件作为多部分/表单数据发布?,ruby,http,post,Ruby,Http,Post,我想做一个HTTP POST,它看起来像是从浏览器发布的HMTL表单。具体来说,发布一些文本字段和文件字段 发布文本字段很简单,在net/httprdocs中有一个例子,但是我不知道如何发布一个文件 HTTP看起来不是最好的主意。看起来不错。好的,下面是一个使用路缘的简单示例 require 'yaml' require 'curb' # prepare post data post_data = fields_hash.map { |k, v| Curl::PostField.content

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

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


HTTP看起来不是最好的主意。看起来不错。

好的,下面是一个使用路缘的简单示例

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]
需要“yaml”
需要“路缘”
#准备post数据
post|u data=fields_hash.map{| k,v | Curl::PostField.content(k,v.to|s)}

post_data
curb
看起来是一个很好的解决方案,但如果它不能满足您的需要,您可以使用
Net::HTTP
来实现。多部分表单post只是一个精心格式化的字符串,带有一些额外的标题。似乎每个需要做多部分文章的Ruby程序员最终都会为它编写自己的小库,这让我想知道为什么这个功能不是内置的。也许是。。。不管怎样,为了你的阅读乐趣,我会在这里给出我的解决方案。这段代码是基于我在几个博客上找到的示例编写的,但很遗憾,我再也找不到链接了。所以我想我只能把所有的功劳都归我自己了

我为此编写的模块包含一个公共类,用于从
String
File
对象的散列中生成表单数据和标题。因此,例如,如果您想发布一个包含名为“title”的字符串参数和名为“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
返回需要发送的数据和标题。就这样!很简单,对吧?以下是多部分模块的代码(您需要
mime类型
gem):

#获取字符串和文件参数的散列并返回文本字符串
#格式化为作为多部分表单post发送。
#
#作者:科迪·布里姆霍尔
#创建日期:2008年2月22日
#许可证::根据WTFPL条款分发(http://www.wtfpl.net/txt/copying/)
需要“rubygems”
需要“mime/类型”
需要“cgi”
模块多部分
VERSION=“1.0.0”
#将给定哈希格式化为多部分表单post
#如果哈希值响应:string或:read消息,则为
#解释为文件并进行相应处理;否则,假定
#一串
班岗
#我们必须假装我们是一个网络浏览器。。。
USERAGENT=“Mozilla/5.0(Macintosh;U;PPC Mac OS X;en-us)AppleWebKit/523.10.6(KHTML,类似Gecko)版本/3.0.4 Safari/523.10.6”
BOUNDARY=“0123456789ABLEWASIEREISAWELBA9876543210”
CONTENT_TYPE=“多部分/表单数据;边界=#{boundary}”
头={“内容类型”=>Content\u类型,“用户代理”=>USERAGENT}
定义自我准备查询(参数)
fp=[]
每个参数都有| k,v|
#我们正在尝试创建一个文件参数吗?
如果v.respond_to?(:path)和v.respond_to?(:read),则
fp.push(FileParam.new(k,v.path,v.read))
#我们一定是在尝试创建一个常规参数
其他的
fp.push(StringParam.new(k,v))
结束
结束
#使用特殊的多部分格式组装请求正文
query=fp.collect{| p |“--”+BOUNDARY+“\r\n”+p.to_multipart}.join(“”+“--”+BOUNDARY+”--”
返回查询,标题
结束
结束
私有的
#格式化基本字符串键/值对以包含在多部分post中
类StringParam
属性存取器:k,:v
def初始化(k,v)
@k=k
@v=v
结束
def到_多部分
return“Content Disposition:form data;name=\”{CGI::escape(k)}\“\r\n\r\n{v}\r\n”
结束
结束
#格式化文件或字符串的内容以包含在多部分中
#标杆
类FileParam
属性访问器:k,:文件名,:内容
def初始化(k、文件名、内容)
@k=k
@filename=文件名
@内容=内容
结束
def到_多部分
#如果我们可以从文件名中分辨出可能的mime类型,请使用
#排名第一;否则,请使用“应用程序/八位字节流”
mime_type=mime::Types.type_代表(文件名)[0]| | mime::Types[“应用程序/八位字节流”][0]
返回“内容处置:表单数据;名称=\”{CGI::escape(k)}\“文件名=\”{filename}\“\r\n”+
“内容类型:#{mime_Type.simplified}\r\n\r\n#{Content}\r\n”
结束
结束
结束
我喜欢。它使用多部分表单数据等酷炫功能封装了net/http:

require 'rest_client'
RestClient.post('http://localhost:3000/foo', 
  :name_of_file_param => File.new('/path/to/file'))
它还支持流媒体


gem安装rest客户端
将让您开始。

使用NetHttp的解决方案有一个缺点,即在发布大文件时,它首先将整个文件加载到内存中

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

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
类多部分
def初始化(文件名)
@文件名=文件名
结束
def post(至_url)
边界='---RubyMultipartClient'+rand(1000000).to_s+'ZZZZZ'
部分=[]
流=[]
@文件名。每个do参数名,文件路径|
pos=filepath.rindex(“/”)
filename=filepath[pos+1,filepath.length-pos]

部分这是我的解决方案,在尝试了本文中提供的其他解决方案后,我正在使用它在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
我也有同样的问题(需要发布到JBossWeb服务器)。对我来说,路缘很好,只是当我在代码中使用会话变量时,它导致ruby崩溃(ubuntu 8.10上的ruby 1.8.7)


我翻遍了rest客户端文档,找不到多部分支持的迹象。我尝试了上面的rest客户机示例,但jboss说http post不是多部分的。

在可能的解决方案的长长列表中,还有nick sieger的

restclient在我重写restclient::Payload::Multipart中的create_file_字段之前并不适用于我

def upload(photo) `curl -F media=@#{photo.path} -F username=#{@username} -F password=#{@password} -F message='#{photo.title}' http://twitpic.com/api/uploadAndPost` end

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
$ sudo gem install multipart-post
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|
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
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
HTTP.post("https://here-you-go.com/upload",
          form: {
            file: HTTP::FormData::File.new(file_path)
          })