Ruby:So veröffentlichen Sie eine Datei über HTTP als multipart/form-Daten?
Frage
Ich möchte eine HTTP-POST-das sieht aus wie ein HMTL-form geschrieben, aus einem browser.Insbesondere post-text-Feldern und eine Datei-Feld.
Posting-text-Felder ist einfach, es ist ein Beispiel direkt in das net/http rdocs, aber ich kann nicht herausfinden, wie ein post-Datei zusammen mit ihm.
Net::HTTP sieht nicht wie die beste Idee. Bordstein gut aussehend ist.
Lösung
Ich mag RestClient . Sie kapselt Netz / http mit coolen Features wie Multipart-Formulardaten:
require 'rest_client'
RestClient.post('http://localhost:3000/foo',
:name_of_file_param => File.new('/path/to/file'))
Es unterstützt auch Streaming.
gem install rest-client
werden Sie beginnen.
Andere Tipps
Ich kann nicht sagen, genug gute Dinge über Nick Sieger des mehrt-post-Bibliothek.
Es fügt Unterstützung für Multipart-Posting direkt zu Net :: HTTP, um Ihren Bedarf zu entfernen über Grenzen oder große Bibliotheken manuell zu befürchten, dass andere Ziele als Ihre eigenen haben.
Hier ist ein kleines Beispiel dafür, wie es aus dem 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
Sie können die Bibliothek überprüfen hier: http://github.com/nicksieger/multipart-post
oder installieren Sie es mit:
$ sudo gem install multipart-post
Wenn Sie über SSL Sie eine Verbindung herstellen müssen, die Verbindung wie folgt starten:
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
sieht aus wie eine große Lösung, aber falls es nicht Ihren Anforderungen entspricht, können Sie können tun es mit Net::HTTP
. Eine mehrteiliger Form Post ist nur ein sorgfältig formatierten String mit einigen zusätzlichen Header. Es scheint wie jeder Ruby-Programmierer, der mehren Beiträge tun muss, endet für sich ihre eigene kleine Bibliothek zu schreiben, das macht mich fragen, warum diese Funktionalität nicht eingebaut ist. Vielleicht ist es ... Wie auch immer, für Ihr Lesevergnügen, werde ich gehen Sie vor und geben hier meine Lösung. Dieser Code basiert weg von Beispielen, die ich auf ein paar Blogs gefunden, aber ich bedaure, dass ich nicht mehr die Links finden. Also ich denke, ich habe nur für mich den ganzen Kredit nehmen ...
Das Modul I für diese schrieb enthält eine öffentliche Klasse, zum Erzeugen der Formulardaten und Header aus einem Hash von String
und File
Objekten. So zum Beispiel, wenn Sie ein Formular mit einem String-Parameter namens „Titel“ und eine Datei Parameter mit dem Namen „Dokument“ veröffentlichen wollte, würden Sie wie folgt vor:
#prepare the query
data, headers = Multipart::Post.prepare_query("title" => my_string, "document" => my_file)
Dann tun Sie nur einen normalen POST
mit Net::HTTP
:
http = Net::HTTP.new(upload_uri.host, upload_uri.port)
res = http.start {|con| con.post(upload_uri.path, data, headers) }
Oder aber Sie sonst die POST
tun wollen. Der Punkt ist, dass Multipart
die Daten und Header zurückgibt, die Sie senden müssen. Und das ist es! Einfach, nicht wahr? Hier ist der Code für den Multipart-Modul (Sie müssen das mime-types
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
Hier ist meine Lösung nach anderen diejenigen, die versuchen, auf diesen Beitrag vorhanden, ich verwende es Foto auf TwitPic hochladen:
def upload(photo)
`curl -F media=@#{photo.path} -F username=#{@username} -F password=#{@password} -F message='#{photo.title}' http://twitpic.com/api/uploadAndPost`
end
Ein anderer nur mit Standardbibliotheken:
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
viele Ansätze ausprobiert, aber nur das war für mich gearbeitet.
Ok, hier ist ein einfaches Beispiel mit Kandare.
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]
Schneller Vorlauf bis 2017, ruby
stdlib
net/http
hat dies gebaut-in seit 1.9.3
Net::HTTPRequest#set_form):Hinzugefügt, um Unterstützung sowohl application/x-www-form-urlencoded und multipart/form-data.
https://ruby-doc.org/stdlib-2.3.1/libdoc/net/http/rdoc/Net/HTTPHeader.html#method-i-set_form
Wir können sogar mit IO
die nicht-Unterstützung :size
stream die form-Daten.
In der Hoffnung, dass diese Antwort kann wirklich jemandem helfen :)
P. S.Ich habe nur getestet, die in ruby-2.3.1
restclient funktionierte nicht für mich, bis ich create_file_field in RestClient overrode :: Payload :: Mehrteiliger.
Es wurde eine erstellen: in jedem Teil 'Content-Disposition multipart / form-data' , wo es sollte seine 'Content-Disposition: form-data'.
http://www.ietf.org/rfc/rfc2388.txt
Meine Gabel ist hier, wenn Sie es brauchen: git@github.com: kcrawford / rest-client.git
Nun, die Lösung mit NetHttp hat den Nachteil, dass, wenn große Dateien veröffentlichen es die gesamte Datei in den Speicher lädt zuerst.
Nach einem wenig mit ihm zu spielen kam ich mit folgenden Lösung:
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
gibt es auch nick Siegers mehrt-post auf die lange Liste der möglichen Lösungen hinzuzufügen.
Ich hatte das gleiche Problem (müssen schreiben Web-Server JBoss). Curb funktioniert gut für mich, mit der Ausnahme, dass es verursachte Rubin zum Absturz zu bringen (Rubin 1.8.7 auf Ubuntu 8.10), wenn ich Session-Variablen im Code verwenden.
ich graben sich in den Rest-Client-docs, nicht Anzeige mehrt Unterstützung finden konnten. Ich habe versucht, die Rest-Client Beispiele oben, aber Jboss sagte die http Post ist nicht multipart.
Der mehrt-post gem funktioniert recht gut mit Rails 4 Net :: HTTP, keine andere spezielle 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