7

圧縮されたリクエスト ボディを自動的に解凍する Rack ミドルウェアを作成しました。コードは問題なく動作しているようですが、Rails アプリにプラグインすると、ActionController::ParamsParser から「無効な JSON」というエラーが表示されます。

デバッグ メカニズムとして、圧縮されたコンテンツと解凍されたコンテンツの両方をファイルに書き込み (コードが正しく機能していることを確認するため)、元の JSON ドキュメントを (クライアントが圧縮する前に) 受け取ります。

投稿しているデータJSON データであり、解凍されたコンテンツはhttp://jsonlint.comから有効な JSON として検出されます。

私が間違っていることはありますか?

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

コンソールから取得しているエラーは次のとおりです。

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'

最後に、このミドルウェアを ActionController::Failsafe の後に挿入します。

編集:切り捨ての問題ではないようです

さらに掘り下げた後、切り捨ての問題ではないようです。ログは単に出力を切り取っているだけなので、切り捨ての問題のように見えます。

この時点で、JSON が無効である理由がわかりません。手動でエスケープする必要がありますか?

4

1 に答える 1

17

私は決してルビーの専門家ではありません。また、結果を検証するためにこの問題を再現しようとはしていません。しかし、ラックとアクションパックのコードを掘り下げた後、何かがあるかもしれません。

「rack.input」のドキュメントには、「入力ストリームは、生の HTTP POST データを含む IO のようなオブジェクトです」と記載されています。

だから、あなたはそれを正しく使っているようです。

ただし、actionpack は本文から JSON を解析しようとし (コンテンツ タイプが JSON として指定されている場合)、次のように本文を取得します。

when :json
    body = request.raw_post

ここで、「request」は actionpack 独自の Request クラスであり、「raw_post」は次のように定義されています。

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

「Request.body」は次のとおりです。

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

それはすべてうまくいきます(ただし、誰が最初に値をキャッシュするかを理解するのは混乱します:))。投稿データの読み取り方法に問題があるようです。

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

したがって、問題は、「rack.input」を変更しても「CONTENT_LENGTH」を更新しないため、圧縮されたコンテンツが解凍されたコンテンツよりも明らかに短いため、アクションパックがデータを切り捨てていることだと思います。

ミドルウェア コードで「CONTENT_LENGTH」を更新してみて、問題が解決するかどうかを確認してください。

于 2010-10-19T17:05:51.520 に答える