Ruby:如何通过HTTP将文件作为多部分/表单数据发布?
我想做一个HTTP POST,它看起来像是从浏览器发布的HMTL表单。具体来说,发布一些文本字段和文件字段 发布文本字段很简单,在net/httprdocs中有一个例子,但是我不知道如何发布一个文件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看起来不是最好的主意。看起来不错。好的,下面是一个使用路缘的简单示例
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_datacurb
看起来是一个很好的解决方案,但如果它不能满足您的需要,您可以使用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)
})