Question Ruby: Comment publier un fichier via HTTP en multipart / form-data?


Je veux faire un HTTP POST qui ressemble à un formulaire HMTL publié à partir d'un navigateur. Spécifiquement, publiez des champs de texte et un champ de fichier.

Publier des champs de texte est simple, il y a un exemple dans le net / http rdocs, mais je n'arrive pas à comprendre comment publier un fichier avec lui.

Net :: HTTP ne ressemble pas à la meilleure idée. freiner est bon.


95
2017-10-08 18:31


origine


Réponses:


J'aime RestClient. Il encapsule net / http avec des fonctionnalités intéressantes telles que les données de formulaire en plusieurs parties:

require 'rest_client'
RestClient.post('http://localhost:3000/foo', 
  :name_of_file_param => File.new('/path/to/file'))

Il prend également en charge le streaming.

gem install rest-client va vous aider à démarrer


87
2017-11-25 04:03



Je ne peux pas dire assez de bonnes choses à propos de la bibliothèque multipartite de Nick Sieger.

Il ajoute la prise en charge de la publication en plusieurs parties directement sur Net :: HTTP, éliminant ainsi le besoin de vous soucier manuellement des limites ou des grandes bibliothèques qui peuvent avoir des objectifs différents des vôtres.

Voici un petit exemple d'utilisation de la 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 installez-le avec:

$ sudo gem install multipart-post

Si vous vous connectez via SSL, vous devez 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|

33
2018-04-08 22:12



curb ressemble à une excellente solution, mais si elle ne répond pas à vos besoins, vous pouvez le faire avec Net::HTTP. Un post de formulaire multipart est juste une chaîne soigneusement formatée avec des en-têtes supplémentaires. Il semble que tous les programmeurs Ruby qui ont besoin de faire des publications en plusieurs parties finissent par écrire leur propre petite bibliothèque, ce qui me demande pourquoi cette fonctionnalité n'est pas intégrée. Peut-être que c’est ... En tout cas, pour votre plaisir de lecture, je vais aller de l’avant et donner ma solution ici. Ce code est basé sur des exemples que j'ai trouvés sur quelques blogs, mais je regrette de ne plus pouvoir trouver les liens. Donc je suppose que je dois juste prendre tout le crédit pour moi ...

Le module que j'ai écrit pour cela contient une classe publique, pour générer les données de formulaire et les en-têtes à partir d'un hachage de String et File objets. Par exemple, si vous souhaitez publier un formulaire avec un paramètre de chaîne nommé "title" et un paramètre de fichier nommé "document", procédez comme suit:

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

Alors tu fais juste un normal 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 bien si vous voulez faire le POST. Le fait est que Multipart renvoie les données et les en-têtes à envoyer. Et c'est tout! Simple, non? Voici le code du module Multipart (vous avez besoin du mime-types gemme):

# 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

27
2017-10-17 18:24



Voici ma solution après avoir essayé d'autres disponibles sur ce post, je l'utilise pour télécharger des photos sur 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

17
2017-12-26 13:09



Ok, voici un exemple simple utilisant curb.

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]

8
2017-10-08 18:55



Un autre utilisant uniquement des bibliothèques standard:

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

Essayé beaucoup d'approches mais seulement cela a été travaillé pour moi.


4
2017-10-10 14:29



restclient n'a pas fonctionné pour moi jusqu'à ce que je surclasse create_file_field dans RestClient :: Payload :: Multipart.

Il créait un 'Content-Disposition: multipart / form-data' dans chaque partie où il devrait être 'Content-Disposition: form-data'.

http://www.ietf.org/rfc/rfc2388.txt

Mon fork est là si vous en avez besoin: git@github.com: kcrawford / rest-client.git


2
2018-01-14 23:05