रूबी: HTTP के माध्यम से मल्टीपार्ट / फॉर्म-डेटा के रूप में फाइल कैसे पोस्ट करें?


113

मैं एक HTTP POST करना चाहता हूँ जो किसी ब्राउज़र से पोस्ट किया गया HMTL फॉर्म जैसा दिखता है। विशेष रूप से, कुछ पाठ फ़ील्ड और फ़ाइल फ़ील्ड पोस्ट करें।

पाठ फ़ील्ड पोस्ट करना सीधा है, वहाँ नेट / http rdocs में एक उदाहरण है, लेकिन मैं यह पता नहीं लगा सकता कि इसके साथ एक फ़ाइल कैसे पोस्ट करें।

नेट :: HTTP सबसे अच्छा विचार नहीं है। अंकुश अच्छा लग रहा है।

जवाबों:


103

मुझे RestClient पसंद है । यह मल्टीपर्ट फॉर्म डेटा जैसी शांत विशेषताओं के साथ नेट / http को इनकैप्सुलेट करता है:

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

यह स्ट्रीमिंग का भी समर्थन करता है।

gem install rest-client आप शुरू कर देंगे।


मुझे लगता है कि वापस ले लो, फ़ाइल अपलोड अब काम करते हैं। समस्या मैं अब आ रहा है सर्वर 302 देता है और बाकी क्लाइंट आरएफसी (जो कोई ब्राउज़र नहीं करता है) का अनुसरण करता है और एक अपवाद को फेंक देता है (क्योंकि ब्राउज़र को इस व्यवहार के बारे में चेतावनी दी जाती है)। अन्य विकल्प पर अंकुश लगा है, लेकिन मैंने कभी भी खिड़कियों में अंकुश लगाने का कोई सौभाग्य नहीं पाया है।
मैट वोल्फ

7
RestClient.post ': एपीआई एक छोटे के बाद से यह पहली पोस्ट किया गया था बदल गया है, बहुखण्डीय अब की तरह शुरू हो जाती है स्थानीय होस्ट: 3000 / foo ',: अपलोड => File.new ( '/ path / tofile')) देखें github.com/ अधिक विवरण के लिए आर्किलोक / रेस्ट-क्लाइंट
क्लिंटन

2
rest_client अनुरोध हेडर की आपूर्ति का समर्थन नहीं करता है। कई REST अनुप्रयोगों को विशिष्ट प्रकार के हेडर की आवश्यकता होती है, इसलिए बाकी क्लाइंट उस स्थिति में काम नहीं करेंगे। उदाहरण के लिए JIRA को टोकन X-Atlassian-Token की आवश्यकता होती है।
14:13 पर onknows

क्या फ़ाइल अपलोड प्रगति प्राप्त करना संभव है? जैसे 40% अपलोड है।
अंकुश

1
gem install rest-clientऔर require 'rest_client'भागों को जोड़ने के लिए +1 । वह जानकारी बहुत अधिक रूबी उदाहरणों से छूटी हुई है।
डैंसलमो

36

मैं Nick Sieger की मल्टीपार्ट-पोस्ट लाइब्रेरी के बारे में पर्याप्त अच्छी बातें नहीं कह सकता।

यह नेट :: HTTP पर सीधे पोस्ट करने के लिए मल्टीपार्ट पोस्टिंग के लिए समर्थन जोड़ता है, जिससे आपकी आवश्यकता को हटाने के लिए मैन्युअल रूप से सीमाओं या बड़े पुस्तकालयों के बारे में चिंता हो सकती है जो आपके स्वयं के मुकाबले अलग लक्ष्य हो सकते हैं।

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

आप यहां लाइब्रेरी देख सकते हैं: http://github.com/nicksieger/multipart-post

या इसके साथ स्थापित करें:

$ sudo gem install multipart-post

यदि आप SSL के माध्यम से जुड़ रहे हैं, तो आपको इस तरह से कनेक्शन शुरू करने की आवश्यकता है:

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|

3
उस एक ने मेरे लिए यह किया, ठीक वही जो मैं देख रहा था और ठीक उसी तरह जिसे मणि की आवश्यकता के बिना शामिल किया जाना चाहिए। रूबी इतनी आगे है, फिर भी इतनी पीछे है।
ट्रे

भयानक, यह एक भगवान भेजने के रूप में आता है! फ़ाइल अपलोड का समर्थन करने के लिए OAuth रत्न को बंद करने के लिए इसका इस्तेमाल किया। मुझे केवल 5 मिनट लगे।
मथायस

@matthias मैं OAuth रत्न के साथ फ़ोटो अपलोड करने का प्रयास कर रहा हूं, लेकिन विफल रहा। क्या आप मुझे अपने बंदरों के उदाहरण दे सकते हैं?
हूपो

1
पैच मेरी स्क्रिप्ट (त्वरित-और-गंदे) के लिए काफी विशिष्ट था, लेकिन इस पर एक नज़र है और हो सकता है कि आप अधिक सामान्य दृष्टिकोण ( gist.github.com/974084 ) के साथ कुछ कर सकते हैं
Matthias

3
मल्टीपार्ट अनुरोध हेडर का समर्थन नहीं करता है। इसलिए यदि आप उदाहरण के लिए JIRA REST इंटरफ़ेस का उपयोग करना चाहते हैं, तो मल्टीपार्ट केवल मूल्यवान समय की बर्बादी होगी।
onknows

30

curbएक महान समाधान की तरह दिखता है, लेकिन अगर यह आपकी आवश्यकताओं को पूरा नहीं करता है, तो आप इसे कर सकते हैं Net::HTTP। एक मल्टीपार्ट फॉर्म पोस्ट कुछ अतिरिक्त हेडर के साथ एक सावधानी से स्वरूपित स्ट्रिंग है। ऐसा लगता है कि हर रूबी प्रोग्रामर को जो मल्टीपार्ट पोस्ट करने की जरूरत है, वह इसके लिए अपनी छोटी लाइब्रेरी लिखता है, जो मुझे आश्चर्यचकित करता है कि यह कार्यक्षमता अंतर्निहित क्यों नहीं है। शायद यह है ... वैसे भी, आपके पढ़ने की खुशी के लिए, मैं आगे बढ़ूंगा और यहां अपना समाधान दूंगा। यह कोड उन दो उदाहरणों पर आधारित है जो मुझे ब्लॉग के एक जोड़े पर मिले थे, लेकिन मुझे अफसोस है कि मुझे अब लिंक नहीं मिल रहे हैं। इसलिए मुझे लगता है कि मुझे सिर्फ अपने लिए सारा श्रेय लेना होगा ...

मैंने इसके लिए जो मॉड्यूल लिखा था, उसमें एक सार्वजनिक वर्ग शामिल है, प्रपत्र डेटा और हेडर को वस्तुओं Stringऔर Fileवस्तुओं के हैश से उत्पन्न करने के लिए । उदाहरण के लिए, यदि आप "शीर्षक" नामक स्ट्रिंग पैरामीटर और "दस्तावेज़" नामक फ़ाइल पैरामीटर के साथ एक फॉर्म पोस्ट करना चाहते हैं, तो आप निम्नलिखित कार्य करेंगे:

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

तो आप बस के POSTसाथ एक सामान्य करते हैं Net::HTTP:

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

या फिर आप करना चाहते हैं POST। मुद्दा यह है कि Multipartडेटा और हेडर जो आपको भेजने की आवश्यकता है, लौटाता है। और बस! सरल, सही? यहां मल्टीपार्ट मॉड्यूल के लिए कोड है (आपको 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

नमस्ते! इस कोड पर लाइसेंस क्या है? इसके अलावा: शीर्ष पर टिप्पणियों में इस पोस्ट के लिए URL जोड़ना अच्छा हो सकता है। धन्यवाद!
डॉकवाट

5
इस पोस्ट में कोड WTFPL ( sam.zoy.org/wtfpl ) के तहत लाइसेंस प्राप्त है । का आनंद लें!
कोड़ी ब्रिमहल

आपको FileParamक्लास के शुरुआती कॉल में फाइलस्ट्रेस पास नहीं करना चाहिए । to_multipartविधि में असाइनमेंट फ़ाइल सामग्री को फिर से कॉपी करता है, जो अनावश्यक है! इसके बजाय केवल फाइल डिस्क्रिप्टर पास करें और उसमें से पढ़ेंto_multipart
रोबर्ट

1
यह कोड महान है! क्योंकि यह काम करता है। रेस्ट-क्लाइंट और सीजर्स मल्टीपार्ट-पोस्ट डॉन 'टी सपोर्ट रिक्वेस्ट हेडर। यदि आपको अनुरोध शीर्षलेखों की आवश्यकता है, तो आप आराम-ग्राहक और सीगर्स मल्टीपार्ट पोस्ट के साथ बहुत मूल्यवान समय बर्बाद करेंगे।
onknows

दरअसल, @ तो, यह अब अनुरोध हेडर का समर्थन करता है। एरिक के जवाब पर मेरी टिप्पणी देखें
अलेक्जेंडरबर्ड

24

केवल मानक पुस्तकालयों का उपयोग कर एक और:

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

बहुत दृष्टिकोण की कोशिश की लेकिन केवल मेरे लिए यह काम किया गया था।


3
इसके लिए धन्यवाद। एक मामूली बिंदु, पंक्ति 1 होनी चाहिए: uri = URI('https://some.end.point/some/path') इस तरह से आप कॉल कर सकते हैं uri.portऔर uri.hostबाद में त्रुटि के बिना।
दाविदकोवस्की

1
एक मामूली बदलाव, अगर File.openFile.read
गतिहीन

1
अधिकांश मामलों में फ़ाइल नाम की आवश्यकता होती है, यह वह रूप है, जिसे मैंने जोड़ा: form_data = [['file', File.read (file_name), {filename: file_name}]]
ZsJosz

4
यह सही जवाब है। लोगों को संभव होने पर रैपर रत्नों का उपयोग करना बंद कर देना चाहिए और मूल बातों पर वापस जाना चाहिए।
कार्लोस रोक

18

इस पोस्ट पर उपलब्ध अन्य को आज़माने के बाद मेरा समाधान यहां है, मैं इसका उपयोग ट्विटपिक पर फोटो अपलोड करने के लिए कर रहा हूं:

  def upload(photo)
    `curl -F media=@#{photo.path} -F username=#{@username} -F password=#{@password} -F message='#{photo.title}' http://twitpic.com/api/uploadAndPost`
  end

1
थोड़ा हैकिंग लगने के बावजूद, यह इस सुझाव के लिए शायद मेरे लिए सबसे बड़ा समाधान है!
बो जीन्स

अनजाने के लिए बस एक नोट, मीडिया = @ ... वही है जो कर्ल चीज़ बनाता है ... एक फ़ाइल है और न केवल एक स्ट्रिंग। माणिक वाक्यविन्यास के साथ थोड़ा भ्रमित, लेकिन @ # {photo.path} #elling@photo.nath/ के समान नहीं है। यह समाधान सर्वश्रेष्ठ इम्हो में से एक है।
एवगेनी

7
यह अच्छा लग रहा है, लेकिन अगर आपके @username में "foo && rm -rf /" है, तो यह बहुत बुरा हो जाता है :-P
गैसपार्क

8

2017 के लिए फास्ट फॉरवर्ड, ruby stdlib net/httpइसमें 1.9.3 के बाद से अंतर्निहित है

नेट :: HTTPRequest # set_form): एप्लिकेशन / x-www-form-urlencoded और मल्टीपार्ट / फॉर्म-डेटा दोनों का समर्थन करने के लिए जोड़ा गया।

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

हम यहां तक ​​कि उपयोग कर सकते हैं IOजो :sizeप्रपत्र डेटा को स्ट्रीम करने के लिए समर्थन नहीं करता है ।

उम्मीद है कि यह जवाब वास्तव में किसी की मदद कर सकता है :)

पी एस मैं केवल रूबी 2.3.1 में यह परीक्षण किया


7

ठीक है, यहाँ अंकुश का उपयोग कर एक सरल उदाहरण है।

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]

3

restclient ने मेरे लिए तब तक काम नहीं किया जब तक कि मैं RestClient :: Payload :: Multipart में create_file_field को ओवरराइड नहीं करता।

यह प्रत्येक भाग में एक 'कंटेंट-डिस्पोजल: मल्टीपार्ट / फॉर्म-डेटा' बना रहा था, जहाँ इसे 'कंटेंट-डिस्पोज़: फॉर्म-डेटा' होना चाहिए ।

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

यदि आपको इसकी आवश्यकता है तो मेरा कांटा यहाँ है: git@github.com: kcrawford / rest-client.git


यह नवीनतम पुनर्स्थापना में तय किया गया है।

1

खैर NetHttp के साथ समाधान में एक खामी है कि बड़ी फ़ाइलों को पोस्ट करते समय यह पूरी फ़ाइल को पहले मेमोरी में लोड करता है।

इसके साथ थोड़ा खेलने के बाद मैं निम्नलिखित समाधान के साथ आया:

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

क्लास स्ट्रीमपार्ट क्या है?
मार्लिन पियर्स

1

संभावित समाधानों की लंबी सूची में जोड़ने के लिए निक सीजर्स मल्टीपार्ट-पोस्ट भी है।


1
मल्टीपार्ट-पोस्ट अनुरोध हेडर का समर्थन नहीं करता है।
onknows

दरअसल, @ तो, यह अब अनुरोध हेडर का समर्थन करता है। एरिक के जवाब पर मेरी टिप्पणी देखें
अलेक्जेंडरबर्ड

0

मुझे एक ही समस्या थी (वेब ​​सर्वर jboss पोस्ट करने की आवश्यकता)। अंकुश मेरे लिए ठीक काम करता है, सिवाय इसके कि जब मैं कोड में सत्र चर का उपयोग करता हूं तो यह दुर्घटनाग्रस्त हो गया था।

मैं बाकी-क्लाइंट डॉक्स में खुदाई करता हूं, मल्टीपार्ट समर्थन का संकेत नहीं मिला। मैंने ऊपर के बाकी क्लाइंट उदाहरणों की कोशिश की, लेकिन jboss ने कहा कि http पोस्ट मल्टीपार्ट नहीं है।


0

मल्टीपार्ट-पोस्ट रत्न रेल 4 नेट :: HTTP, कोई अन्य विशेष रत्न के साथ बहुत अच्छा काम करता है

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.