curb
parece 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 todos los programadores de Ruby que necesitan hacer publicaciones de varias partes terminan 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 no poder encontrar más los enlaces. Así que supongo que debo tomarme todo el crédito por mí mismo ...
El módulo que escribí para esto contiene una clase pública, para generar los datos del formulario y los encabezados a partir de un hash de String
y File
objetos. Entonces, por ejemplo, si quisiera publicar un formulario con un parámetro de cadena llamado "título" y un parámetro de archivo llamado "documento", haría lo siguiente:
#prepare the query
data, headers = Multipart::Post.prepare_query("title" => my_string, "document" => my_file)
Entonces simplemente haces lo normal POST
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 quieras hacer el POST
. El punto es que Multipart
devuelve los datos y los encabezados que necesita enviar. ¡Y eso es! Simple, ¿verdad? Aquí está el código para el módulo Multipart (necesita la mime-types
gema):
# 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