روبي:كيفية مشاركة الملفات عبر HTTP كما multipart/form-البيانات ؟

StackOverflow https://stackoverflow.com/questions/184178

  •  06-07-2019
  •  | 
  •  

سؤال

أريد أن أفعل ما بعد HTTP يبدو HMTL شكل نشرت من المتصفح.على وجه التحديد, بعد بعض الحقول النصية و ملفات المجال.

نشر حقول النص واضح وصريح ، هناك مثال هناك في net/http rdocs, ولكن أنا لا يمكن معرفة كيفية نشر ملف جنبا إلى جنب معها.

صافي::HTTP لا تبدو أفضل فكرة. كبح تبحث جيدة.

هل كانت مفيدة؟

المحلول

RestClient . أنها تلخص صافي / HTTP مع الميزات الرائعة مثل بيانات النموذج متعدد الأجزاء:

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

وكما يدعم الدفق.

وgem install rest-client سوف تحصل على انك بدأته.

نصائح أخرى

لا أستطيع أن أقول ما يكفي من الأشياء الجيدة حول نيك الفائزون هي متعددة-وظيفة المكتبة.

فإنه يضيف دعم متعددة نشر مباشرة إلى صافي::HTTP إزالة تحتاج إلى يدويا تقلق حول الحدود أو المكتبات الكبيرة التي قد يكون لها أهداف مختلفة من الخاصة بك.

هنا مثال على كيفية استخدامها من التمهيدي:

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|

و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

وهنا هو بلدي الحل بعد محاولة منها الأخرى المتاحة على هذا المنصب، وأنا استخدامه لتحميل الصورة على 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

وآخر واحد باستخدام مكتبات القياسية فقط:

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

وحاول الكثير من النهج ولكن هذا فقط وقد عملت بالنسبة لي.

وطيب، وهنا مثال بسيط باستخدام كبح.

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]

وسريع إلى الأمام إلى 2017، ruby stdlib net/http لديه هذا المدمج في منذ 1.9.3

<اقتباس فقرة>   

ونت :: HTTPRequest # set_form): واضاف لدعم كل التطبيق / x-شبكة الاتصالات العالمية urlencoded شكل و/ شكل بيانات متعددة الأجزاء

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

ويمكننا حتى استخدام IO الذي لا يعتمد :size لدفق بيانات النموذج.

وعلى أمل أن هذه الإجابة يمكن أن تساعد حقا شخص:)

وP.S. أنا فقط اختبار هذا في روبي 2.3.1

ولم restclient لا تعمل بالنسبة لي حتى أنني تجاهلت create_file_field في RestClient :: :: الحمولة متعددة الأجزاء.

وكان خلق على 'ترتيب المحتوى: متعدد الأجزاء / شكل بيانات' في كل جزء حيث ينبغي أن يكون على 'ترتيب المحتوى: استمارة البيانات ". <قوية / P>

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

وبلدي شوكة هنا إذا كنت في حاجة إليها: git@github.com: kcrawford / الراحة-client.git

وحسنا الحل مع 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

وهناك أيضا شق SIEGER في متعددة في مرحلة ما بعد لإضافته إلى قائمة طويلة من الحلول الممكنة.

وكان لي نفس المشكلة (الحاجة إلى إضافة إلى جبوس خادم الويب). كبح يعمل بشكل جيد بالنسبة لي، إلا أنه كان سببا روبي تعطل (روبي 1.8.7 على أوبونتو 8.10) عند استخدام متغيرات جلسة في التعليمات البرمجية.

وأحفر في مستندات بقية العميل، لا يمكن أن تجد دليلا على دعم متعدد الأجزاء. حاولت الأمثلة بقية العميل أعلاه ولكن قال جبوس منصب HTTP ليست متعددة.

وجوهره متعددة الوظائف تعمل بشكل جيد جدا مع القضبان 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 / التصحيح 1

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top