Domanda

Voglio fare un POST HTTP che assomigli a un modulo HMTL pubblicato da un browser. In particolare, pubblica alcuni campi di testo e un campo file.

La pubblicazione di campi di testo è semplice, c'è un esempio proprio in rete / http rdocs, ma non riesco a capire come pubblicare un file con esso.

Net :: HTTP non sembra la migliore idea. marciapiede sta bene.

È stato utile?

Soluzione

Mi piace RestClient . Incapsula net / http con funzioni interessanti come i dati di moduli multipart:

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

Supporta anche lo streaming.

gem install rest-client ti farà iniziare.

Altri suggerimenti

Non posso dire abbastanza cose positive sulla libreria multipart post di Nick Sieger.

Aggiunge il supporto per la pubblicazione multipart direttamente su Net :: HTTP, eliminando la necessità di preoccuparsi manualmente dei confini o delle grandi librerie che potrebbero avere obiettivi diversi dai propri.

Ecco un piccolo esempio su come usarlo dal 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

Puoi consultare la libreria qui: http://github.com/nicksieger/multipart-post

o installalo con:

$ sudo gem install multipart-post

Se ti connetti tramite SSL devi avviare la connessione in questo modo:

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|

frenare sembra un'ottima soluzione, ma nel caso in cui non soddisfi le tue esigenze, puoi farlo con Net :: HTTP . Un post di modulo multipart è solo una stringa formattata con cura con alcune intestazioni extra. Sembra che ogni programmatore di Ruby che ha bisogno di fare post in più parti finisca per scrivere la propria piccola libreria per questo, il che mi fa chiedere perché questa funzionalità non sia integrata. Forse è ... Comunque, per il tuo piacere di leggere, andrò avanti e darò la mia soluzione qui. Questo codice si basa su esempi che ho trovato su un paio di blog, ma mi dispiace di non poter più trovare i collegamenti. Quindi immagino che devo solo prendermi tutto il merito per me ...

Il modulo che ho scritto per questo contiene una classe pubblica, per generare i dati del modulo e le intestazioni da un hash di oggetti String e File . Ad esempio, se desideri pubblicare un modulo con un parametro stringa denominato " title " e un parametro di file chiamato " document " ;, dovresti fare quanto segue:

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

Quindi fai semplicemente un POST normale 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 comunque vuoi fare il POST . Il punto è che Multipart restituisce i dati e le intestazioni che è necessario inviare. E questo è tutto! Semplice vero? Ecco il codice per il modulo Multipart (è necessaria la gemma 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

Ecco la mia soluzione dopo averne provate altre disponibili in questo post, la sto usando per caricare foto su 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

Un altro che utilizza solo librerie 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

Ho provato molti approcci ma solo questo ha funzionato per me.

Ok, ecco un semplice esempio usando il marciapiede.

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]

Avanti veloce al 2017, ruby ?? stdlib net / http ha questo built-in dall'1.9.3

  

Net :: HTTPRequest # set_form): aggiunto per supportare sia application / x-www-form-urlencoded che multipart / form-data.

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

Possiamo persino usare IO che non supporta : size per lo streaming dei dati del modulo.

Sperando che questa risposta possa davvero aiutare qualcuno :)

P.S. L'ho provato solo in ruby ??2.3.1

restclient non ha funzionato per me fino a quando non ho scavalcato create_file_field in RestClient :: Payload :: Multipart.

Stava creando un "Disposizione dei contenuti: multipart / form-data" in ogni parte dove dovrebbe essere 'Disposizione dei contenuti: form-data' .

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

Il mio fork è qui se ne hai bisogno: git@github.com: kcrawford / rest-client.git

Beh, la soluzione con NetHttp ha uno svantaggio che quando si pubblicano file di grandi dimensioni carica prima l'intero file in memoria.

Dopo averci giocato un po ', ho trovato la seguente soluzione:

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

c'è anche il multipart-post di nick sieger da aggiungere alla lunga lista di possibili soluzioni.

Ho avuto lo stesso problema (è necessario pubblicare sul server Web jboss). Curb funziona bene per me, tranne per il fatto che ha causato l'arresto anomalo di ruby ??(ruby 1.8.7 su Ubuntu 8.10) quando uso variabili di sessione nel codice.

Scavo nei documenti del resto client, non sono riuscito a trovare l'indicazione del supporto multipart. Ho provato gli esempi di rest-client sopra, ma jboss ha detto che il post http non è multipart.

La gemma multipart-post funziona abbastanza bene con Rails 4 Net :: HTTP, nessun'altra gemma speciale

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top