Ruby: Как опубликовать файл через HTTP как multipart / form-data?
110107 просмотра
12 ответа
Я хочу сделать HTTP POST, который выглядит как форма HMTL, отправленная из браузера. В частности, опубликовать некоторые текстовые поля и поле файла.
Публикация текстовых полей проста, есть пример прямо в rdocs net / http, но я не могу понять, как разместить файл вместе с ним.
Net :: HTTP не выглядит лучшей идеей. Снаряженная выглядит хорошо.
Автор: kch Источник Размещён: 13.11.2019 11:33Ответы (12)
96 плюса
Мне нравится RestClient . Он инкапсулирует net / http с классными функциями, такими как данные из нескольких частей:
require 'rest_client'
RestClient.post('http://localhost:3000/foo',
:name_of_file_param => File.new('/path/to/file'))
Он также поддерживает потоковую передачу.
gem install rest-client
начну.
36 плюса
Я не могу сказать достаточно хороших слов о многопостовой библиотеке Ника Сигера.
Он добавляет поддержку составной публикации непосредственно в Net :: 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|
Автор: eric
Размещён: 08.04.2010 10:12
29 плюса
curb
выглядит как отличное решение, но в случае, если оно не отвечает вашим потребностям, вы можете сделать это с Net::HTTP
. Пост из нескольких частей - это просто тщательно отформатированная строка с некоторыми дополнительными заголовками. Кажется, что каждый программист на Ruby, которому нужно создавать многочастные посты, заканчивает тем, что пишет свою собственную маленькую библиотеку для этого, что заставляет меня задуматься, почему эта функциональность не является встроенной. Может быть, это ... Во всяком случае, для вашего удовольствия от чтения, я пойду дальше и дам свое решение здесь. Этот код основан на примерах, которые я нашел в нескольких блогах, но я сожалею, что больше не могу найти ссылки. Так что, думаю, мне просто нужно взять на себя всю заслугу ...
Модуль я написал для этого содержит один общий класс, для формирования данных формы и заголовков из хэша String
и File
объектов. Так, например, если вы хотите опубликовать форму со строковым параметром с именем «title» и параметром файла с именем «document», вы должны сделать следующее:
#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
возвращает данные и заголовки, которые вам нужно отправить. Вот и все! Просто, правда? Вот код для модуля 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
Автор: Cody Brimhall
Размещён: 17.10.2008 06:24
18 плюса
Еще один, использующий только стандартные библиотеки:
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
Перепробовал много подходов, но только это сработало для меня.
Автор: Vladimir Rozhkov Размещён: 10.10.2017 02:2917 плюса
Вот мое решение после того, как я попробовал другие, доступные в этом посте, я использую его для загрузки фото на 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
Автор: Alex
Размещён: 26.12.2008 01:09
8 плюса
Перенесемся в 2017 год, ruby
stdlib
net/http
имеет встроенный с 1.9.3
Net :: HTTPRequest # set_form): добавлено для поддержки как application / x-www-form-urlencoded, так и multipart / form-data.
https://ruby-doc.org/stdlib-2.3.1/libdoc/net/http/rdoc/Net/HTTPHeader.html#method-i-set_form
Мы можем даже использовать IO
который не поддерживает :size
потоковую передачу данных формы.
Надеясь, что этот ответ действительно может кому-то помочь :)
PS Я проверял это только в ruby 2.3.1
Автор: airmanx86 Размещён: 31.08.2017 08:007 плюса
Хорошо, вот простой пример использования бордюра.
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]
Автор: kch
Размещён: 08.10.2008 06:55
3 плюса
restclient не работал для меня, пока я не переопределил create_file_field в RestClient :: Payload :: Multipart.
Он создавал «Content-Disposition: multipart / form-data» в каждой части, где он должен быть «Content-Disposition: form-data» .
http://www.ietf.org/rfc/rfc2388.txt
Мой форк здесь, если вам это нужно: git@github.com: kcrawford / rest-client.git
Автор: user243633 Размещён: 14.01.2010 11:051 плюс
У решения с 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
Автор: Stanislav Vitvitskiy
Размещён: 25.12.2008 03:46
1 плюс
есть также многочастный пост Ника Сигера, чтобы добавить к длинному списку возможных решений.
Автор: Jan Berkel Размещён: 23.04.2009 12:260 плюса
У меня была такая же проблема (нужно публиковать на веб-сервере jboss). У меня отлично работает curb, за исключением того, что он вызывал крах ruby (ruby 1.8.7 в ubuntu 8.10), когда я использую переменные сессии в коде.
Я копаюсь в документации остальных клиентов, не могу найти указание на поддержку нескольких частей. Я попробовал приведенные выше примеры rest-client, но jboss сказал, что сообщение http не является составным.
Автор: zd Размещён: 05.02.2009 07:010 плюса
Gem, состоящий из нескольких частей, довольно хорошо работает с Rails 4 Net :: 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
Автор: Feuda Размещён: 16.12.2016 03:41Вопросы из категории :
- ruby Вызов команд оболочки из Ruby
- ruby Почему в Ruby нет реального StringBuffer или StringIO?
- ruby Как создать случайный 10-значный номер в рубине?
- ruby Каков наилучший способ конвертировать массив в хеш в Ruby
- ruby Determine file type in Ruby
- ruby Раскладка каталога для чистого Ruby-проекта
- http Окончательное руководство по аутентификации на основе форм
- http Как бы вы внедрили аутентификацию на основе FORM без резервной базы данных?
- http Как загрузить файл через HTTP с помощью Python?
- http Как мы контролируем кэширование веб-страниц во всех браузерах?
- http В чем разница между POST и PUT HTTP REQUEST?
- http Есть ли простой способ запросить URL в Python и НЕ следовать перенаправлениям?
- post Как я могу отправить HTTP-запрос POST на сервер из Excel, используя VBA?
- post Как вы отправляете сообщение в iframe?
- post Ruby: Как опубликовать файл через HTTP как multipart / form-data?
- post Получить параметры в URL с CodeIgniter
- post Предотвращение двойного HTTP POST