开发者

Override Rack method

My setup: Rails 3.0.9, Ruby 1.9.2

Due to a bug in Rack 1.2.3, I'm attempting to override Rack::Utils::Multipart.parse_multipart by creating a new file

rack_parse_multipart.rb

module Rack
  module Utils
    module Multipart
      def self.parse_multipart(env)
        ...my changes...           
      end
    end
 end    
end

Now I just need to figure out where I require this file, can someone point me in the right direction? Thanks in advance f开发者_开发技巧or your help.


For others having problems with this Rack 1.2.3 bug, there is a nice copy-paste solution here https://github.com/rack/rack/issues/186

goes in config/initializers

# -*- encoding: binary -*-
require 'rack/utils'
module Rack
  module Utils
    module Multipart
      def self.parse_multipart(env)
        unless env['CONTENT_TYPE'] =~
            %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
          nil
        else
          boundary = "--#{$1}"

          params = {}
          buf = ""
          content_length = env['CONTENT_LENGTH'].to_i
          input = env['rack.input']
          input.rewind

          boundary_size = Utils.bytesize(boundary) + EOL.size
          bufsize = 16384

          content_length -= boundary_size

          read_buffer = ''

          status = input.read(boundary_size, read_buffer)
          raise EOFError, "bad content body"  unless status == boundary + EOL

          rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n

          loop {
            head = nil
            body = ''
            filename = content_type = name = nil

            until head && buf =~ rx
              if !head && i = buf.index(EOL+EOL)
                head = buf.slice!(0, i+2) # First \r\n
                buf.slice!(0, 2)          # Second \r\n

                token = /[^\s()<>,;:\\"\/\[\]?=]+/
                condisp = /Content-Disposition:\s*#{token}\s*/i
                dispparm = /;\s*(#{token})=("(?:\\"|[^"])*"|#{token})*/

                rfc2183 = /^#{condisp}(#{dispparm})+$/i
                broken_quoted = /^#{condisp}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{token}=)/i
                broken_unquoted = /^#{condisp}.*;\sfilename=(#{token})/i

                if head =~ rfc2183
                  filename = Hash[head.scan(dispparm)]['filename']
                  filename = $1 if filename and filename =~ /^"(.*)"$/
                elsif head =~ broken_quoted
                  filename = $1
                elsif head =~ broken_unquoted
                  filename = $1
                end

                if filename && filename !~ /\\[^\\"]/
                  filename = Utils.unescape(filename).gsub(/\\(.)/, '\1')
                end

                content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
                name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]

                if filename
                  body = Tempfile.new("RackMultipart")
                  body.binmode  if body.respond_to?(:binmode)
                end

                next
              end

              # Save the read body part.
              if head && (boundary_size+4 < buf.size)
                body << buf.slice!(0, buf.size - (boundary_size+4))
              end

              c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
              raise EOFError, "bad content body"  if c.nil? || c.empty?
              buf << c
              content_length -= c.size
            end

            # Save the rest.
            if i = buf.index(rx)
              body << buf.slice!(0, i)
              buf.slice!(0, boundary_size+2)

              content_length = -1  if $1 == "--"
            end

            if filename == ""
              # filename is blank which means no file has been selected
              data = nil
            elsif filename
              body.rewind

              # Take the basename of the upload's original filename.
              # This handles the full Windows paths given by Internet Explorer
              # (and perhaps other broken user agents) without affecting
              # those which give the lone filename.
              filename = filename.split(/[\/\\]/).last

              data = {:filename => filename, :type => content_type,
                      :name => name, :tempfile => body, :head => head}
            # elsif !filename && content_type
            #   body.rewind
            # 
            #   # Generic multipart cases, not coming from a form
            #   data = {:type => content_type,
            #           :name => name, :tempfile => body, :head => head}
            else
              data = body
            end

            Utils.normalize_params(params, name, data) unless data.nil?

            # break if we're at the end of a buffer, but not if it is the end of a field
            break if (buf.empty? && $1 != EOL) || content_length == -1
          }

          input.rewind

          params
        end
      end
    end
  end
end


Don't do it like this, your file should be like this:

Rack::Utils::UploadedFile.class_eval do 

  def self.parse_multipart( env ) 
    # add your code here
  end

end

This file can be placed in a initializer file on your initializers folder.

The difference between doing the way you did and the way I'm showing is that when you're using module/class you might break the Rails autoload mechanism, as Rails could think you're defining the class (and not load the original class by itself) and the original class would never be loaded.

Whenever you're doing monkey patching like this make sure you use the class_eval solution so that Rails is forced to load the original class first and then runs your code.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜