125 votes

Ruby : Comment envoyer un fichier via HTTP comme multipart/form-data ?

Je veux faire un POST de HTTP qui ressemble à une forme HMTL validée à partir d’un navigateur. Plus précisément, après certains champs de texte et un champ de fichier.

L’affichage des champs de texte est simple, il y a un exemple là dans le rdocs net/http, mais je ne peux pas comprendre comment publier un fichier avec elle.

Net::http ne ressemble pas à la meilleure idée. trottoir cherche bonne.

104voto

Pedro Points 1176

J’aime RestClient. Il encapsule net/http avec des fonctionnalités intéressantes comme des données de formulaire multipart :

Il prend également en charge le streaming.

``va vous aider à démarrer.

41voto

eric Points 781

Je ne peux pas dire assez de bonnes choses à propos de Nick Sieger est multipart-postes de la bibliothèque.

Il ajoute le support pour multipart les publier directement sur le Net::HTTP, la suppression de la nécessité de votre manuellement soucier des limites ou des grandes librairies qui peut avoir différents buts que votre propre.

Voici un petit exemple sur la façon de l'utiliser dans le 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

Vous pouvez consulter la bibliothèque ici: http://github.com/nicksieger/multipart-post

ou l'installer avec:

$ sudo gem install multipart-post

Si vous êtes à la connexion via SSL vous avez besoin pour démarrer la connexion comme ceci:

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|

31voto

Cody Brimhall Points 762

curb ressemble à une excellente solution, mais dans le cas où il ne répond pas à vos besoins, vous pouvez le faire avec Net::HTTP. Un formulaire multipart post est soigneusement une chaîne au format des en-têtes. Il semble que chaque programmeur Ruby qui doit faire multipart postes finit d'écrire leur propre bibliothèque de peu pour elle, ce qui me fait me demander pourquoi cette fonctionnalité n'est pas intégré. C'est peut-être... en tout cas, pour votre plaisir de la lecture, je vais aller de l'avant et donner ma solution ici. Ce code est basé sur des exemples que j'ai trouvé sur un couple de blogs, mais je regrette que je ne peux pas trouver les liens de plus. Donc je suppose que j'ai juste à prendre tout le crédit pour moi-même...

Le module que j'ai écrit pour ce contient une classe publique, pour générer les données du formulaire et les en-têtes d'un algorithme de hachage String et File objets. Ainsi, par exemple, si vous voulais poster un formulaire avec un paramètre de chaîne nommée "titre" et un fichier de paramètre nommé "document", vous devez effectuer les opérations suivantes:

#prepare the query
data, headers = Multipart::Post.prepare_query("title" => my_string, "document" => my_file)

Alors que vous venez de faire une activité normale POST avec Net::HTTP:

http = Net::HTTP.new(upload_uri.host, upload_uri.port)
res = http.start {|con| con.post(upload_uri.path, data, headers) }

Ou de quelque autre façon que vous voulez faire l' POST. Le point est que, Multipart renvoie les données et les en-têtes que vous devez envoyer. Et c'est tout! Simple, non? Voici le code pour le Multipart module (vous avez besoin de l' 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

19voto

Alex Points 7652

Voici ma solution après avoir essayé les autres ceux disponibles sur ce post, je l’utilise pour télécharger des photos sur TwitPic :

7voto

kch Points 25855

OK, voici un exemple simple à l’aide de trottoir.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X