Ruby : HTTP를 통해 multipart / form-data로 파일을 게시하는 방법은 무엇입니까?
브라우저에서 게시 된 HMTL 양식처럼 보이는 HTTP POST를 수행하고 싶습니다. 특히 일부 텍스트 필드와 파일 필드를 게시하십시오.
텍스트 필드를 게시하는 것은 간단합니다. net / http rdocs에 예제가 있지만 함께 파일을 게시하는 방법을 알 수 없습니다.
Net :: HTTP는 최선의 생각이 아닙니다. 연석 이 좋아 보인다.
나는 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 시작합니다.
Nick Sieger의 multipart-post 라이브러리에 대해 충분히 좋은 말을 할 수 없습니다.
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|
curb외모는 훌륭한 솔루션을 좋아하지만, 그것은 당신의 요구 사항을 충족하지 않는 경우에, 당신은 할 수 와 함께 할 Net::HTTP. 멀티 파트 양식 게시물은 몇 가지 추가 헤더가있는 신중하게 형식화 된 문자열입니다. 멀티 파트 게시물을 작성해야하는 모든 루비 프로그래머는 결국 자신의 작은 라이브러리를 작성하는 것 같습니다.이 기능이 왜 내장되어 있지 않은지 궁금합니다. 아마도 그것은 ... 어쨌든, 당신의 독서의 즐거움을 위해, 나는 계속해서 여기에 나의 해결책을 줄 것입니다. 이 코드는 몇 개의 블로그에서 찾은 예제를 기반으로하지만 더 이상 링크를 찾을 수 없습니다. 그래서 저는 제 자신에 대한 모든 공로를 인정해야합니다 ...
내가 작성한 모듈에는 해시 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
이 게시물에서 사용할 수있는 다른 솔루션을 시도한 후 내 솔루션은 다음과 같습니다. 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/http1.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.
이 답변이 누군가를 정말로 도울 수 있기를 바랍니다. :)
추신 나는 이것을 루비 2.3.1에서만 테스트했습니다.
RestClient :: Payload :: Multipart에서 create_file_field를 덮어 쓸 때까지 restclient가 작동하지 않았습니다.
이는 생성 된 '다중 / 폼 데이터 내용 - 처리 " 가되어야 각 부분에서 '형태 데이터 콘텐츠 처리를 ' .
http://www.ietf.org/rfc/rfc2388.txt
필요한 경우 내 포크가 있습니다. git@github.com : kcrawford / rest-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
가능한 솔루션의 긴 목록에 추가 할 nick sieger의 multipart-post 도 있습니다.
나는 같은 문제가 있었다 (jboss 웹 서버에 게시해야 함). Curb은 코드에서 세션 변수를 사용할 때 Ruby가 충돌 (우분투 8.10의 경우 Ruby 1.8.7)하는 것을 제외하고는 잘 작동합니다.
나머지 클라이언트 문서를 파헤 치고 멀티 파트 지원 표시를 찾을 수 없습니다. 위의 나머지 클라이언트 예제를 시도했지만 jboss는 http 게시물이 다중 부분이 아니라고 말했습니다.
multipart-post gem은 Rails 4 Net :: HTTP와 잘 작동하며 다른 특별한 gem은 없습니다.
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
참고URL : https://stackoverflow.com/questions/184178/ruby-how-to-post-a-file-via-http-as-multipart-form-data
'Programming' 카테고리의 다른 글
| 두 좌표 사이의 거리를 계산하는 기능 (0) | 2020.08.11 |
|---|---|
| cURL을 사용하여 기본 HTTP 인증을 올바르게 정의하는 방법은 무엇입니까? (0) | 2020.08.11 |
| Bash의 $ PATH 변수에서 경로를 제거하는 가장 우아한 방법은 무엇입니까? (0) | 2020.08.11 |
| 루비에서 클래스와 Klass의 차이점은 무엇입니까? (0) | 2020.08.11 |
| Mac / OS X에서 / var / lib / docker는 어디에 있습니까? (0) | 2020.08.11 |