Ich möchte ein HTTP POST erstellen, das wie ein HMTL-Formular aussieht, das von einem Browser bereitgestellt wird. Veröffentlichen Sie insbesondere einige Textfelder und ein Dateifeld.
Das Versenden von Textfeldern ist unkompliziert. Es gibt ein Beispiel in den net/http-Rdocs, aber ich kann nicht herausfinden, wie eine Datei zusammen mit der Datei gepostet wird.
Net :: HTTP sieht nicht nach der besten Idee aus. curb sieht gut aus.
Ich mag RestClient . Es kapselt net/http mit coolen Funktionen wie mehrteiligen 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
wird Ihnen den Einstieg erleichtern.
Ich kann nicht genug Gutes über Nick Siegers multipart-post-Bibliothek sagen.
Dadurch wird die Unterstützung für das mehrteilige Posting direkt in Net :: HTTP unterstützt, sodass Sie sich nicht mehr manuell um Grenzen oder große Bibliotheken kümmern müssen, die andere Ziele haben als Ihre eigenen.
Hier ein kleines Beispiel, wie man es aus der README verwendet:
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 hier besuchen: http://github.com/nicksieger/multipart-post
oder installiere es mit:
$ Sudo gem install multipart-post
Wenn Sie sich über SSL verbinden, müssen Sie 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 nach einer großartigen Lösung aus, aber falls dies nicht Ihren Anforderungen entspricht, können Sie can mit Net::HTTP
tun. Ein mehrteiliger Formularposten ist nur eine sorgfältig formatierte Zeichenfolge mit zusätzlichen Kopfzeilen. Es scheint, als würde jeder Ruby-Programmierer, der mehrteilige Posts schreiben muss, seine eigene kleine Bibliothek dafür schreiben, was mich wundert, warum diese Funktionalität nicht eingebaut ist. Vielleicht ist es ... Wie auch immer, zu Ihrem Lesevergnügen werde ich meine Lösung hier angeben. Dieser Code basiert auf Beispielen, die ich in einigen Blogs gefunden habe, aber ich bedaure, dass ich die Links nicht mehr finden kann. Ich denke, ich muss mich einfach alle Ehre machen ...
Das Modul, das ich dafür geschrieben habe, enthält eine öffentliche Klasse zum Generieren der Formulardaten und Header aus einem Hash von String
- und File
-Objekten. Wenn Sie beispielsweise ein Formular mit einem String-Parameter namens "title" und einem Dateiparameter namens "document" veröffentlichen möchten, würden Sie Folgendes tun:
#prepare the query
data, headers = Multipart::Post.prepare_query("title" => my_string, "document" => my_file)
Dann machen Sie einfach ein normales 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 Sie möchten jedoch POST
. Der Punkt ist, dass Multipart
die Daten und Header zurückgibt, die Sie senden müssen. Und das ist es! Einfach, richtig? Hier ist der Code für das Multipart-Modul (Sie benötigen den 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:[email protected]>
# 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, nachdem ich andere ausprobiert habe, die in diesem Post verfügbar sind. Ich benutze sie, um ein Foto auf TwitPic hochzuladen:
def upload(photo)
`curl -F [email protected]#{photo.path} -F username=#{@username} -F password=#{@password} -F message='#{photo.title}' http://twitpic.com/api/uploadAndPost`
end
Eine andere, die nur Standardbibliotheken verwendet:
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
Ich habe viele Ansätze ausprobiert, aber nur das hat für mich funktioniert.
Ok, hier ist ein einfaches Beispiel mit dem Bordstein.
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 dieses seit 1.9.3 eingebaut
Net :: HTTPRequest # set_form): Zur Unterstützung von application/x-www-form-urlencoded und multipart/form-data hinzugefügt.
https://Ruby-doc.org/stdlib-2.3.1/libdoc/net/http/rdoc/Net/HTTPHeader.html#method-i-set_form
Wir können sogar IO
verwenden, das :size
nicht unterstützt, um die Formulardaten zu streamen.
Hoffen, dass diese Antwort wirklich jemandem helfen kann :)
P.S. Ich habe das nur in Ruby 2.3.1 getestet
restclient funktionierte nicht für mich, bis ich create_file_field in RestClient :: Payload :: Multipart überschrieben habe.
Es wurde ein 'Content-Disposition: Multipart/Form-Data' in jedem Teil erstellt, wo es 'Content-Disposition: Form-Data' sein sollte.
http://www.ietf.org/rfc/rfc2388.txt
Meine Gabel ist hier, wenn Sie es brauchen: [email protected]: kcrawford/rest-client.git
es gibt auch nick sieger's multipart-post , um die lange Liste möglicher Lösungen hinzuzufügen.
Nun, die Lösung mit NetHttp hat den Nachteil, dass beim Posten großer Dateien die gesamte Datei zuerst in den Speicher geladen wird.
Nachdem ich ein bisschen damit gespielt hatte, kam ich zu folgender 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
Ich hatte das gleiche Problem (muss auf jboss Webserver gepostet werden). Curb funktioniert gut für mich, außer dass Ruby abstürzt (Ruby 1.8.7 auf Ubuntu 8.10), wenn ich im Code Sitzungsvariablen verwende.
Ich griff in die Rest-Client-Dokumente, konnte keine Hinweise auf Unterstützung für mehrere Teile finden. Ich habe die Rest-Client-Beispiele oben ausprobiert, aber jboss sagte, dass der http-Beitrag nicht mehrteilig ist.
Der Multipart-Post-Edelstein funktioniert ziemlich gut mit Rails 4 Net :: HTTP, keinem anderen besonderen Edelstein
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