Ruby: Как отправить файл через HTTP как multipart/form-data?

Исключение нулевого указателя - это индикатор того, что вы используете объект, не инициализируя его.

Например, ниже - класс ученика, который будет использовать его в нашем коде.

public class Student {

    private int id;

    public int getId() {
        return this.id;
    }

    public setId(int newId) {
        this.id = newId;
    }
}

Приведенный ниже код дает вам исключение с нулевым указателем.

public class School {

    Student obj_Student;

    public School() {
        try {
            obj_Student.getId();
        }
        catch(Exception e) {
            System.out.println("Null Pointer ");
        }
    }
}

Поскольку вы используете Obj_Student, но вы забыли инициализировать его, как в правильном коде, показанном ниже:

public class School {

    Student obj_Student;

    public School() {
        try {
            obj_Student = new Student();
            obj_Student.setId(12);
            obj_Student.getId();
        }
        catch(Exception e) {
            System.out.println("Null Pointer ");
        }
    }
}
107
задан kch 4 December 2009 в 21:07
поделиться

9 ответов

Мне нравится 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 запустят Вас.

96
ответ дан Matt 24 November 2019 в 03:40
поделиться

curb похож на отличное решение, но в случае, если оно не удовлетворяет Ваши потребности, Вы можете делать это с Net::HTTP. Многослойное сообщение формы является просто тщательно-отформатированной-строкой с некоторыми дополнительными заголовками. Это походит на каждого Ruby программиста, который должен сделать, многослойные сообщения заканчивают тем, что писали свою собственную небольшую библиотеку для него, которая заставляет меня задаться вопросом, почему эта функциональность не встроена. Возможно, это... Так или иначе, для Вашего удовольствия чтения, я буду идти вперед и давать свое решение здесь. Этот код базируется прочь примеров, которые я нашел на нескольких блогах, но я сожалею, что не могу больше находить ссылки. Таким образом, я предполагаю, что просто должен взять весь кредит на меня...

модуль я записал для этого, содержит один общедоступный класс, для генерации данных формы и заголовков из хеша 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
29
ответ дан Cody Brimhall 24 November 2019 в 03:40
поделиться

Вот мое решение после попытки других, доступных на этом сообщении, я использую его для загрузки фотографии на 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
17
ответ дан Alex 24 November 2019 в 03:40
поделиться

Хорошо, вот простой пример с помощью ограничения.

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]
7
ответ дан kch 24 November 2019 в 03:40
поделиться

Хорошо решение с 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
1
ответ дан 24 November 2019 в 03:40
поделиться

У меня была та же проблема (должен отправить на jboss веб-сервер). Ограничение хорошо работает для меня, за исключением того, что оно заставило рубин отказывать (рубиновые 1.8.7 на человечности 8.10), когда я использую переменные сеанса в коде.

я рою в документы клиента отдыха, не мог найти признак многослойной поддержки. Я попробовал примеры клиента отдыха выше, но jboss сказал, что сообщение http не является многослойным.

0
ответ дан 24 November 2019 в 03:40
поделиться

также есть многоотраслевой пост Ника Сигера , чтобы добавить к длинному списку возможных решений.

1
ответ дан 24 November 2019 в 03:40
поделиться

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

3
ответ дан 24 November 2019 в 03:40
поделиться

Я не могу сказать достаточно хороших слов о библиотеке многостраничных сообщений Ника Зигера.

Он добавляет поддержку многокомпонентной публикации непосредственно в 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|
36
ответ дан 24 November 2019 в 03:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: