Programming

Ruby : HTTP를 통해 multipart / form-data로 파일을 게시하는 방법은 무엇입니까?

procodes 2020. 8. 11. 21:21
반응형

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. 멀티 파트 양식 게시물은 몇 가지 추가 헤더가있는 신중하게 형식화 된 문자열입니다. 멀티 파트 게시물을 작성해야하는 모든 루비 프로그래머는 결국 자신의 작은 라이브러리를 작성하는 것 같습니다.이 기능이 왜 내장되어 있지 않은지 궁금합니다. 아마도 그것은 ... 어쨌든, 당신의 독서의 즐거움을 위해, 나는 계속해서 여기에 나의 해결책을 줄 것입니다. 이 코드는 몇 개의 블로그에서 찾은 예제를 기반으로하지만 더 이상 링크를 찾을 수 없습니다. 그래서 저는 제 자신에 대한 모든 공로를 인정해야합니다 ...

내가 작성한 모듈에는 해시 StringFile개체 에서 양식 데이터와 헤더를 생성하기위한 하나의 공용 클래스가 포함되어 있습니다. 예를 들어 "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

반응형