রুবি: এইচটিটিপি-র মাধ্যমে কোনও ফাইলকে মাল্টিপার্ট / ফর্ম-ডেটা হিসাবে কীভাবে পোস্ট করা যায়?


112

আমি একটি HTTP পোস্ট করতে চাই যা ব্রাউজার থেকে পোস্ট করা এইচএমটিএল ফর্মের মতো লাগে। বিশেষত, কিছু পাঠ্য ক্ষেত্র এবং একটি ফাইল ক্ষেত্র পোস্ট করুন।

পাঠ্য ক্ষেত্রগুলি পোস্ট করা সোজা, নেট / HTTP rdocs এ ঠিক সেখানে একটি উদাহরণ রয়েছে তবে আমি কীভাবে কোনও ফাইল কীভাবে পোস্ট করব তা আমি বুঝতে পারি না।

নেট :: এইচটিটিপি সেরা ধারণার মতো দেখাচ্ছে না। কার্ব ভালো লাগছে।

উত্তর:


102

আমি রেস্টক্লিয়েন্ট পছন্দ করি । এটি মাল্টিপার্ট ফর্ম ডেটার মতো দুর্দান্ত বৈশিষ্ট্যগুলি সহ নেট / HT কে encapsulates:

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

এটি স্ট্রিমিং সমর্থন করে।

gem install rest-client আপনি শুরু করতে হবে।


আমি এটি ফিরিয়ে নিই, ফাইল আপলোড এখন কাজ করে। সার্ভারটি 302 দেয় এবং বাকী ক্লায়েন্টটি আরএফসি অনুসরণ করে (যা কোনও ব্রাউজার করেন না) এবং ব্যতিক্রম ছুঁড়ে ফেলেছে (যেহেতু ব্রাউজারগুলি এই আচরণ সম্পর্কে সতর্ক করার কথা বলেছে) I'm অন্য বিকল্পটি কার্বক তবে আমি উইন্ডোতে কার্ব ইনস্টল করার কোনও ভাগ্য কখনও পাইনি।
ম্যাট ওল্ফ

7
প্রথম পোস্ট হওয়ার পরে এআইপিআই কিছুটা পরিবর্তন হয়েছে, মাল্টিপার্ট এখন ডাকা হচ্ছে যেমন: রেস্টক্লিয়েন্ট.পস্ট ' লোকালহোস্ট: 3000 / foo ',: আপলোড => ফাইল.নেউ ('/ পাথ / টফিল')) github.com/ দেখুন আরও তথ্যের জন্য আর্কিলিক / রেস্ট-ক্লায়েন্ট
ক্লিনটন

2
rest_client অনুরোধ শিরোনাম সরবরাহ করে না। অনেকগুলি REST অ্যাপ্লিকেশনগুলিকে নির্দিষ্ট ধরণের শিরোলেখের প্রয়োজন / আশা করা হয় তাই বিশ্রামের ক্লায়েন্ট সে ক্ষেত্রে কাজ করবে না। উদাহরণস্বরূপ JIRA এর জন্য একটি টোকেন এক্স-আটলাসিয়ান-টোকেন প্রয়োজন।
onknows

ফাইল আপলোডের অগ্রগতি পাওয়া কি সম্ভব? যেমন 40% আপলোড হয়েছে।
অঙ্কুশ

1
gem install rest-clientএবং require 'rest_client'অংশগুলি যোগ করার জন্য +1 । সেই তথ্যটি অনেকগুলি রুবি উদাহরণ থেকে বাদ পড়েছে।
ডানসালমো

36

নিক সিজারের মাল্টিপার্ট-পোস্ট লাইব্রেরি সম্পর্কে আমি যথেষ্ট ভাল জিনিস বলতে পারি না।

এটি সরাসরি নেট :: এইচটিটিপিতে মাল্টিপার্ট পোস্টিংয়ের জন্য সমর্থন যোগ করে, আপনার নিজের থেকে আলাদা লক্ষ্য থাকতে পারে এমন সীমানা বা বড় লাইব্রেরি সম্পর্কে ম্যানুয়ালি উদ্বেগের আপনার প্রয়োজন সরিয়ে দেয়।

এখানে কিভাবে থেকে এটা ব্যবহার করার জন্য একটু উদাহরণ 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/m Multipart-post

বা এটি দিয়ে ইনস্টল করুন:

$ sudo gem install multipart-post

আপনি যদি এসএসএলের মাধ্যমে সংযোগ স্থাপন করেন তবে আপনাকে এই জাতীয় সংযোগটি শুরু করতে হবে:

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
এইটি আমার জন্য এটি করেছিল, ঠিক আমি যা খুঁজছিলাম এবং রত্নের প্রয়োজন ছাড়াই ঠিক কী অন্তর্ভুক্ত করা উচিত। রুবি এত এগিয়ে, তবুও অনেক পিছনে।
ট্রে

দুর্দান্ত, এটি comesশ্বরের প্রেরণের মতো আসে! ফাইল আপলোডগুলি সমর্থন করার জন্য এটি OAuth রত্নকে monkeypatch করতে ব্যবহৃত হয়েছিল। আমাকে মাত্র 5 মিনিট সময় নিয়েছে।
ম্যাথিয়াস

@ মথিয়াস আমি ওআউথ রত্নের সাথে ফটো আপলোড করার চেষ্টা করছি, কিন্তু ব্যর্থ হয়েছিল। আপনি আমাকে আপনার বানকিপ্যাচের কিছু উদাহরণ দিতে পারেন?
হুপো

1
প্যাচটি আমার স্ক্রিপ্টের জন্য বেশ সুনির্দিষ্ট ছিল (দ্রুত এবং নোংরা), তবে এটি দেখুন এবং সম্ভবত আপনি আরও জেনেরিক পদ্ধতির সাথে কিছুটা পেতে পারেন ( gist.github.com/974084 )
ম্যাথিয়াস

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

ওহে! এই কোডে লাইসেন্স কী? এছাড়াও: উপরের মন্তব্যে এই পোস্টের জন্য ইউআরএল যুক্ত করা ভাল হতে পারে। ধন্যবাদ!
ডকচ্যাট

5
এই পোস্টের কোডটি ডাব্লুটিএফপিএল ( sam.zoy.org/wtfpl ) এর অধীনে লাইসেন্সযুক্ত । উপভোগ করুন!
কোডি ব্রিমহল

FileParamক্লাসের আরম্ভ কলকে আপনার ফাইল স্ট্রিম পাস করা উচিত নয় । to_multipartপদ্ধতিতে অ্যাসাইনমেন্টটি ফাইলের সামগ্রীটি আবার অনুলিপি করে যা অপ্রয়োজনীয়! এর পরিবর্তে শুধুমাত্র ফাইল বর্ণনাকারী প্রেরণ এবং এটা থেকে পড়াto_multipart
mober

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.openনয়File.read
অনিল ইয়ানদুরি

1
বেশিরভাগ ক্ষেত্রে ফাইলের নাম প্রয়োজন হয়, এটি আমি ফর্মটি কীভাবে যুক্ত করেছি: form_data = [['ফাইল', ফাইল.ড্রেড (ফাইল_নাম), name ফাইলের নাম:
ফাইল_নাম

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
কিছুটা হ্যাকিশ মনে হলেও, এই পরামর্শের জন্য এটি আমার পক্ষে সম্ভবত সবচেয়ে উত্তম সমাধান!
বো জিনেস

অজানাদের জন্য কেবল একটি নোট, মিডিয়া = @ ... যা কার্লকে এমন জিনিস বানায় যে ... কেবল একটি স্ট্রিং নয় ফাইল is রুবি সিনট্যাক্সের সাথে কিছুটা বিভ্রান্তিকর, তবে @ # {ফটো.পাথ {#{@photo.path as এর মতো নয়} এই সমাধানটি সেরা ইমোগুলির মধ্যে একটি।
ইভজেনি

7
এটি দেখতে দুর্দান্ত দেখাচ্ছে তবে যদি আপনার @ ব্যবহারকারীর নামটিতে "foo &&mm -rf /" থাকে তবে এটি বেশ খারাপ হয়ে যায় :
পি

8

ruby stdlib net/http২০১ to- তে দ্রুত এগিয়ে চলেছে, 1.9.3 থেকে এই বিল্ট-ইন রয়েছে

নেট :: HTTPRequest # সেট_ফর্ম): অ্যাপ্লিকেশন / 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ফর্ম ডেটা প্রবাহিত করতে সমর্থন করে না ।

আশা করছি যে এই উত্তরটি সত্যিই কাউকে সাহায্য করতে পারে :)

পিএস আমি কেবল রুবি ২.৩.১ এ এটি পরীক্ষা করেছি


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

আমি রেস্টক্লিয়েন্ট :: পেইলোড :: মাল্টিপার্টে ক্রিয়েট_ফিলিফিল্ডকে ওভাররড না করা পর্যন্ত রেস্টক্লিয়েন্ট আমার পক্ষে কাজ করে না।

এটি প্রতিটি অংশে একটি 'বিষয়বস্তু-বিশৃঙ্খলা: মাল্টিপার্ট / ফর্ম-ডেটা' তৈরি করছিল যেখানে এটি 'বিষয়বস্তু-বিভাজন: ফর্ম-ডেটা' হওয়া উচিত

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

আপনার কাঁটা দরকার হলে এখানেই রয়েছেন: git@github.com: kcrawford / rest-client.git


এটি সর্বশেষতম বিশ্রামবারে স্থির করা হয়েছে।

1

ঠিক আছে নেটহট্টের সাথে সমাধানটির একটি অসুবিধা রয়েছে যা বড় ফাইলগুলি পোস্ট করার সময় এটি পুরো ফাইলটিকে মেমরির মধ্যে প্রথমে লোড করে।

এটির সাথে কিছুটা খেলার পরে আমি নিম্নলিখিত সমাধান নিয়ে এসেছি:

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 ওয়েব সার্ভারে পোস্ট করা দরকার)। আমার পক্ষে কার্ব সুক্ষ্মভাবে কাজ করে, যদি আমি কোডটিতে সেশন ভেরিয়েবল ব্যবহার করি তখন এটি রুবি ক্র্যাশ হয়ে যায় (উবুন্টু ৮.১০ তে রুবি ১.৮..7)।

আমি বাকী-ক্লায়েন্ট ডক্স খনন করেছি, মাল্টিপার্ট সাপোর্টের ইঙ্গিত পাইনি। আমি উপরের বাকী-ক্লায়েন্টের উদাহরণগুলি চেষ্টা করেছিলাম তবে জবিস বলেছেন যে HTTP পোস্টটি মাল্টিপার্ট নয়।


0

মাল্টিপার্ট-পোস্ট মণিটি রেল 4 নেট :: এইচটিটিপি, অন্য কোনও বিশেষ রত্নের সাথে বেশ ভালভাবে কাজ করে

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

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.