当前位置:  开发笔记 > 后端 > 正文

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

如何解决《Ruby:如何通过HTTP将文件作为multipart/form-data发布?》经验,为你挑选了7个好方法。

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

发布文本字段很简单,net/http rdocs就有一个例子,但我无法弄清楚如何发布文件.

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



1> Pedro..:

我喜欢RestClient.它将ne​​t/http与多部分表单数据等酷炫功能封装在一起:

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

它还支持流媒体.

gem install rest-client 会让你开始.


自首次发布以来,API已经发生了一些变化,现在调用multipart就像:RestClient.post'http:// localhost:3000/foo',:upload => File.new('/ path/tofile'))见http://github.com/archiloque/rest-client了解更多详情.
这看起来很少
rest_client不支持提供请求标头.许多REST应用程序需要/期望特定类型的标头,因此休息客户端在这种情况下不起作用.例如,JIRA需要一个令牌X-Atlassian-Token.

2> eric..:

我不能说尼克西格的多部门图书馆的好东西.

它将对多部分发布的支持直接添加到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|


那个人为我做了,正是我正在寻找的,确切地说应该包括什么,而不需要宝石.Ruby远远领先,但远远落后.
Multipart不支持请求标头.因此,如果您想要使用JIRA REST接口,那么multipart将浪费宝贵的时间.

3> Cody Brimhal..:

curb看起来是一个很好的解决方案,但如果它不能满足您的需求,您可以使用Net::HTTP.多部分表单帖子只是一个精心格式化的字符串,带有一些额外的标题.似乎每个需要做多部分帖子的Ruby程序员最终都会为它编写自己的小库,这让我想知道为什么这个功能不是内置的.也许是......无论如何,为了您的阅读乐趣,我会继续在这里提出我的解决方案.这段代码基于我在几个博客上找到的例子,但我很遗憾我找不到链接了.所以我想我必须自己承担所有功劳......

我为此编写的模块包含一个公共类,用于从哈希StringFile对象生成表单数据和标题.例如,如果您想发布一个名为"title"的字符串参数和名为"document"的文件参数的表单,您将执行以下操作:

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

那么你只需做一个正常POSTNet::HTTP:

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-types宝石):

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


此帖中的代码根据WTFPL(http://sam.zoy.org/wtfpl/)获得许可.请享用!

4> Vladimir Roz..:

另一个只使用标准库:

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

尝试了很多方法,但只有这对我有用.


谢谢你.一个小问题,第1行应该是:`uri = URI('https://some.end.point/some/path')`那样你以后可以毫无错误地调用`uri.port`和`uri.host`上.
这是正确的答案。人们应该在可能的情况下停止使用包装宝石,然后再回到基础知识。

5> Alex..:

这是我的解决方案,尝试在这篇文章中提供的其他可用,我用它来上传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


这看起来很不错,但是如果你的@username包含"foo && rm -rf /",这会非常糟糕:-P

6> airmanx86..:

快进到2017年,ruby stdlib net/http从1.9.3开始内置

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.1中测试过这个



7> kch..:

好的,这是一个使用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]

推荐阅读
coco2冰冰
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有