ruby - net - rails send json request
Ruby: ¿Cómo publicar un archivo a través de HTTP como multipart/form-data? (12)
Aquí está mi solución después de probar otras disponibles en esta publicación, la estoy usando para subir fotos en 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
Quiero hacer un HTTP POST que se parece a un formulario HMTL publicado desde un navegador. Específicamente, publique algunos campos de texto y un campo de archivo.
Publicar campos de texto es sencillo, hay un ejemplo allí mismo en la red / http rdocs, pero no puedo encontrar la manera de publicar un archivo junto con él.
Net :: HTTP no parece ser la mejor idea. curb se ve bien
Avance rápido hasta 2017, ruby
stdlib
net/http
tiene esto incorporado desde 1.9.3
Net :: HTTPRequest # set_form): Se agregó para admitir aplicaciones / x-www-form-urlencoded y multipart / form-data.
https://ruby-doc.org/stdlib-2.3.1/libdoc/net/http/rdoc/Net/HTTPHeader.html#method-i-set_form
Incluso podemos usar IO
que no admite :size
para transmitir los datos del formulario.
Esperando que esta respuesta realmente pueda ayudar a alguien :)
PD: solo probé esto en ruby 2.3.1
Bueno, la solución con NetHttp tiene un inconveniente: cuando publica grandes archivos, primero carga todo el archivo en la memoria.
Después de jugar un poco con esto, se me ocurrió la siguiente solución:
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
La gema multiparte post funciona bastante bien con Rails 4 Net :: HTTP, no hay otra gema especial
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
Me gusta RestClient . Encapsula net / http con funciones geniales como datos de formulario multiparte:
require ''rest_client''
RestClient.post(''http://localhost:3000/foo'',
:name_of_file_param => File.new(''/path/to/file''))
También es compatible con la transmisión.
gem install rest-client
ayudará a comenzar.
No puedo decir suficientes cosas buenas sobre la biblioteca de publicaciones múltiples de Nick Sieger.
Agrega soporte para publicación de múltiples partes directamente a Net :: HTTP, lo que elimina la necesidad de preocuparse manualmente por los límites o grandes bibliotecas que pueden tener objetivos diferentes a los suyos.
Aquí hay un pequeño ejemplo sobre cómo usarlo desde el 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
Puede consultar la biblioteca aquí: http://github.com/nicksieger/multipart-post
o instalarlo con:
$ sudo gem install multipart-post
Si se conecta mediante SSL, debe iniciar la conexión de esta manera:
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|
Ok, aquí hay un ejemplo simple usando el bordillo.
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]
Otro que usa solo bibliotecas estándar:
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.read()
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
Intenté muchos enfoques, pero solo esto me funcionó.
Tuve el mismo problema (necesito publicar en el servidor web de jboss). Curb funciona bien para mí, excepto que causó que ruby se bloquee (ruby 1.8.7 en ubuntu 8.10) cuando uso variables de sesión en el código.
Indago en el resto de los documentos del cliente, no pude encontrar indicación de soporte multiparte. Probé los ejemplos de clientes restantes anteriores, pero jboss dijo que la publicación http no es multiparte.
restclient no funcionó para mí hasta que anulé create_file_field en RestClient :: Payload :: Multipart.
Estaba creando una ''Disposición del contenido: multipart / form-data'' en cada parte donde debería estar ''Content-Disposition: form-data'' .
http://www.ietf.org/rfc/rfc2388.txt
Mi tenedor está aquí si lo necesitas: [email protected]: kcrawford / rest-client.git
también hay publicaciones http://github.com/nicksieger/multipart-post nick sieger para agregar a la larga lista de posibles soluciones.
curb
ve como una gran solución, pero en caso de que no satisfaga sus necesidades, puede hacerlo con Net::HTTP
. Una publicación de formulario de varias partes es solo una cadena cuidadosamente formateada con algunos encabezados adicionales. Parece que cada programador de Ruby que necesita hacer publicaciones multiparte termina escribiendo su propia pequeña biblioteca, lo que me hace preguntarme por qué esta funcionalidad no está incorporada. Tal vez sea ... De todos modos, para su placer de lectura, seguiré adelante y daré mi solución aquí. Este código se basa en ejemplos que encontré en un par de blogs, pero lamento que ya no pueda encontrarlos. Así que supongo que debo tomar todo el mérito por mí mismo ...
El módulo que escribí para esto contiene una clase pública, para generar los datos del formulario y los encabezados de un hash de objetos String
y File
. Por ejemplo, si desea publicar un formulario con un parámetro de cadena denominado "título" y un parámetro de archivo llamado "documento", debe hacer lo siguiente:
#prepare the query
data, headers = Multipart::Post.prepare_query("title" => my_string, "document" => my_file)
Entonces solo haces un POST
normal con Net::HTTP
:
http = Net::HTTP.new(upload_uri.host, upload_uri.port)
res = http.start {|con| con.post(upload_uri.path, data, headers) }
O como quiera que quiera hacer el POST
. El punto es que Multipart
devuelve los datos y encabezados que necesita enviar. ¡Y eso es! Simple, ¿verdad? Aquí está el código para el módulo Multipart (necesitas la gema 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 <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