Question

Je souhaite effectuer un envoi HTTP qui ressemble à un formulaire HMTL publié à partir d'un navigateur. Plus précisément, publiez des champs de texte et un champ de fichier.

La publication de champs de texte est simple, il existe un exemple dans les rdocs net / http, mais je ne vois pas comment poster un fichier avec.

Net :: HTTP ne semble pas être la meilleure idée. curb se présente bien.

Était-ce utile?

La solution

J'aime RestClient . Il encapsule net / http avec des fonctionnalités intéressantes telles que des 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 la diffusion en continu.

gem install rest-client vous aidera à démarrer.

Autres conseils

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

Il ajoute la prise en charge de la publication en plusieurs parties directement dans Net :: HTTP, ce qui vous évite 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 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 de la manière suivante:

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 semble être une excellente solution, mais si cela ne répond pas à vos besoins, vous pouvez le le faire avec Net :: HTTP . Une publication de formulaire en plusieurs parties 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 publier des articles en plusieurs parties finissent par écrire leur propre petite bibliothèque, ce qui me fait me demander pourquoi cette fonctionnalité n’est pas intégrée. Peut-être que c'est ... Quoi qu'il en soit, pour le plaisir de votre lecture, je vais donner la 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-même ...

Le module que j'ai écrit pour cela contient une classe publique, permettant de générer les données de formulaire et les en-têtes à partir d'un hachage d'objets String et Fichier . Ainsi, par exemple, si vous souhaitez publier un formulaire avec un paramètre de chaîne nommé "titre". 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)

Ensuite, vous effectuez un POST normal 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 que vous devez envoyer. Et c'est tout! Simple, non? Voici le code pour le module Multipart (vous avez besoin de la gem 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: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

Voici ma solution après avoir essayé d'autres solutions disponibles sur ce post. Je l'utilise pour télécharger une photo 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

Autre utilisant uniquement les bibliothèques standard:

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

J'ai essayé beaucoup d'approches mais seulement cela a fonctionné pour moi.

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]

Avance rapide jusqu'en 2017, ruby ?? stdlib net / http intègre cette fonction depuis la version 1.9.3

  

Net :: HTTPRequest # set_form): ajouté pour prendre en charge application / x-www-form-urlencoded et multipart / form-data.

https://ruby-doc.org/stdlib-2.3.1/libdoc/net/http/rdoc/Net/HTTPHeader.html#method-i-set_form

Nous pouvons même utiliser IO qui ne prend pas en charge : size pour diffuser les données de formulaire.

En espérant que cette réponse puisse vraiment aider quelqu'un:)

P.S. J'ai seulement testé cela en ruby ??2.3.1

restclient ne fonctionnait pas pour moi jusqu'à ce que je substitue create_file_field dans RestClient :: Payload :: Multipart.

Cela créait un contenu-Disposition: multipart / form-data ' dans chaque partie où il devrait figurer & # 8216; Content-Disposition: form-data & # 8217; .

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

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

La solution avec NetHttp présente un inconvénient: lorsqu’elle publie de gros fichiers, elle charge tout d’abord le fichier dans sa mémoire.

Après en avoir un peu joué, j'ai proposé la solution suivante:

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

il y a aussi le multipart-post de nick sieger à ajouter à la longue liste de solutions possibles.

J'ai eu le même problème (besoin de poster sur jboss web server). Curb fonctionne bien pour moi, sauf que cela a provoqué un crash de ruby ??(ruby 1.8.7 sur Ubuntu 8.10) lorsque j'utilise des variables de session dans le code.

Je me suis plongé dans la documentation rest-client, je n'ai pas trouvé d'indication de prise en charge en plusieurs parties. J'ai essayé les exemples rest-client ci-dessus mais jboss a dit que la publication http n'est pas une publication multiple.

La gemme multipart-post fonctionne plutôt bien avec Rails 4 Net :: HTTP, aucune autre gemme spéciale

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

https://github.com/Feuda/multipart-post/tree / patch-1

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top