روبي:كيفية مشاركة الملفات عبر HTTP كما multipart/form-البيانات ؟
سؤال
أريد أن أفعل ما بعد 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