2010-10-18 8 views
7

Escribí una pieza de Rack Middleware para descomprimir automáticamente cuerpos comprimidos de solicitud. Parece que el código funciona bien, pero cuando lo conecto a mi aplicación Rails, obtengo un error "JSON no válido" de ActionController :: ParamsParser.Rack de rack.input variable se trunca?

Como mecanismo de depuración, estoy escribiendo tanto el contenido comprimido como el contenido descomprimido en un archivo (para asegurarme de que el código funciona correctamente) y recibo mi documento JSON original (antes de que el cliente lo comprima)

Los datos que publico son datos JSON, y el contenido descomprimido se detecta como JSON válido desde http://jsonlint.com.

¿Alguna idea de lo que estoy haciendo mal?

class CompressedRequests 
    def initialize(app) 
    @app = app 
    end 

    def call(env) 
    input = env['rack.input'].read 

    #output the zipped data we received 
    File.open('/Users/ben/Desktop/data.gz', 'w+') do |f| 
     f.write input 
    end 

    if env['REQUEST_METHOD'] =~ /(POST|PUT)/ 
     if env.keys.include? 'HTTP_CONTENT_ENCODING' 
     new_input = decode(input, env['HTTP_CONTENT_ENCODING']) 
     env['rack.input'] = StringIO.new(new_input) 

     #output our decoded data (for debugging) 
     File.open('/Users/ben/Desktop/data.txt', 'w+') do |f| 
      f.write env['rack.input'].read 
     end 

     env.delete('HTTP_CONTENT_ENCODING') 
     end 
    end 

    env['rack.input'].rewind 
    status, headers, response = @app.call(env) 
    return [status, headers, response] 
    end 

    def decode(input, content_encoding) 
    if content_encoding == 'gzip' 
     Zlib::GzipReader.new(input).read 
    elsif content_encoding == 'deflate' 
     Zlib::Inflate.new.inflate new input 
    else 
     input 
    end 
    end 
end 

Aquí está el error que estoy recibiendo desde la consola:

Contents::"2010-05-17T12:46:30Z","background":false},{"longitude":-95.38620785000001,"latitude":29.62815358333334,"estimated_speed":14.04305,"timestamp":"2010-05-17T12:46:36Z","background":false},{"longitude":-95.3862767,"latitude":29.62926725,"estimated_speed":39.87791,"timestamp":"2010-05-17T12:46:42Z","background":false},{"longitude":-95.38655023333334,"latitude":29.63051011666666,"estimated_speed":46.09239,"timestamp":"2010-05-17T12:46:49Z","background":false},{"longitude":-95.38676226666666,"latitude":29.63158775,"estimated_speed":47.34936,"timestamp":"2010-05-17T12:46:55Z","background":false},{"longitude":-95.38675346666666,"latitude":29.63219841666666,"estimated_speed":22.54016,"timestamp":"2010-05-17T12:47:03Z","background":false},{"longitude":-95.38675491666666,"latitude":29.63265714999999,"estimated_speed":14.03642,"timestamp":"2010-05-17T12:47:10Z","background":false},{"longitude":-95.38677551666666,"latitude":29.63358661666667,"estimated_speed":29.29489,"timestamp":"2010-05-17T12:47:17Z","background":false},{"longitude":-95.38679026666662,"latitude":29.63466445,"estimated_speed":38.34926,"timestamp":"2010-05-17T12:47:24Z","background":false},{"longitude":-95.38681656666668,"latitude":29.63590941666666,"estimated_speed":44.82093,"timestamp":"2010-05-17T12:47:31Z","background":false},{"longitude":-95.38683366666667,"latitude":29.63679638333334,"estimated_speed":40.21729,"timestamp":"2010-05-17T12:47:37Z","background":false},{"longitude":-95.38685133333333,"latitude":29.63815714999999,"estimated_speed":44.86543,"timestamp":"2010-05-17T12:47:44Z","background":false},{"longitude":-95.3868655 
/!\ FAILSAFE /!\ Mon Oct 18 18:18:43 -0500 2010 
    Status: 500 Internal Server Error 
    Invalid JSON string 
    /Library/Ruby/Gems/1.8/gems/activesupport-2.3.5/lib/active_support/json/backends/yaml.rb:14:in `decode' 
    /Library/Ruby/Gems/1.8/gems/activesupport-2.3.5/lib/active_support/json/decoding.rb:11:in `__send__' 
    /Library/Ruby/Gems/1.8/gems/activesupport-2.3.5/lib/active_support/json/decoding.rb:11:in `decode' 
    /Library/Ruby/Gems/1.8/gems/actionpack-2.3.5/lib/action_controller/params_parser.rb:42:in `parse_formatted_parameters' 
    /Library/Ruby/Gems/1.8/gems/actionpack-2.3.5/lib/action_controller/params_parser.rb:11:in `call' 
    /Library/Ruby/Gems/1.8/gems/actionpack-2.3.5/lib/action_controller/session/cookie_store.rb:93:in `call' 
    /Library/Ruby/Gems/1.8/gems/actionpack-2.3.5/lib/action_controller/failsafe.rb:26:in `call' 
    /Users/ben/projects/safecell/safecellweb/lib/compressed_requests.rb:36:in `call' 
    /Library/Ruby/Gems/1.8/gems/rack-1.0.1/lib/rack/lock.rb:11:in `call' 
    /Library/Ruby/Gems/1.8/gems/rack-1.0.1/lib/rack/lock.rb:11:in `synchronize' 
    /Library/Ruby/Gems/1.8/gems/rack-1.0.1/lib/rack/lock.rb:11:in `call' 
    /Library/Ruby/Gems/1.8/gems/actionpack-2.3.5/lib/action_controller/dispatcher.rb:114:in `call' 
    /Library/Ruby/Gems/1.8/gems/actionpack-2.3.5/lib/action_controller/reloader.rb:34:in `run' 
    /Library/Ruby/Gems/1.8/gems/actionpack-2.3.5/lib/action_controller/dispatcher.rb:108:in `call' 
    /Library/Ruby/Gems/1.8/gems/rails-2.3.5/lib/rails/rack/static.rb:31:in `call' 
    /Library/Ruby/Gems/1.8/gems/rack-1.0.1/lib/rack/urlmap.rb:46:in `call' 
    /Library/Ruby/Gems/1.8/gems/rack-1.0.1/lib/rack/urlmap.rb:40:in `each' 
    /Library/Ruby/Gems/1.8/gems/rack-1.0.1/lib/rack/urlmap.rb:40:in `call' 
    /Library/Ruby/Gems/1.8/gems/rails-2.3.5/lib/rails/rack/log_tailer.rb:17:in `call' 
    /Library/Ruby/Gems/1.8/gems/rack-1.0.1/lib/rack/content_length.rb:13:in `call' 
    /Library/Ruby/Gems/1.8/gems/rack-1.0.1/lib/rack/handler/webrick.rb:50:in `service' 

Una última pieza de información, estoy insertando este middleware después ActionController :: prueba de fallos.

EDIT: Parece que no es un problema de truncamiento

Después de más de excavación, parece que no es un problema de truncamiento después de todo. Los registros simplemente recortan la salida para que se vea como como un problema de truncamiento.

En este punto, no estoy seguro de por qué el JSON entra como no válido. ¿Debo hacer algún escape manual?

Respuesta

15

No soy un experto en rubí de ninguna manera. Tampoco he intentado reproducir este problema para verificar mis resultados. Pero después de excavar el código del estante y el paquete de acción, podría tener algo.

El documento para "rack.input" indica: "El flujo de entrada es un objeto similar a IO que contiene los datos HTTP POST sin procesar."

Así que estás usando eso correctamente, al parecer.

Sin embargo, actionpack intenta analizar JSON fuera del cuerpo (si no se especifica el tipo de contenido como JSON) y recupera el cuerpo de esta manera:

when :json 
    body = request.raw_post 

donde "solicitud" es propia clase Solicitud de actionpack, y "raw_post" se define así:

def raw_post 
    unless @env.include? 'RAW_POST_DATA' 
    @env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i) 
    body.rewind if body.respond_to?(:rewind) 
    end 
    @env['RAW_POST_DATA'] 
end 

y "request.body" es:

def body 
    if raw_post = @env['RAW_POST_DATA'] 
    raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding) 
    StringIO.new(raw_post) 
    else 
    @env['rack.input'] 
    end 
end 

Todo se ve bien y bien (aunque es confuso descubrir quién guarda primero el valor :)). Parece que el problema está en cómo se leen los datos de la publicación:

@env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i) 

Así que supongo el problema es que, dado que cambia "rack.input", pero no actualiza "CONTENT_LENGTH", es actionpack truncando los datos ya que obviamente el contenido comprimido hubiera sido más corto que el contenido descomprimido.

Intente actualizar "CONTENT_LENGTH" en su código de middleware y vea si eso lo soluciona.

+1

Dude you rock. ¡Eso fue todo! Aquí está el código de trabajo actualizado: http://gist.github.com/635471 –

+1

Cool story bro: D – noodl

+0

Explicación excelente, que cómo debería ser cada uno :) – Pablo

Cuestiones relacionadas