1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
|
module Rack
class Parser
POST_BODY = 'rack.input'.freeze
FORM_INPUT = 'rack.request.form_input'.freeze
FORM_HASH = 'rack.request.form_hash'.freeze
PARSER_RESULT = 'rack.parser.result'.freeze
JSON_PARSER = proc { |data| JSON.parse data }
ERROR_HANDLER = proc { |err, type| [400, {}, ['']] }
attr_reader :parsers, :handlers, :logger
def initialize(app, options = {})
@app = app
@parsers = options[:parsers] || { %r{json} => JSON_PARSER }
@handlers = options[:handlers] || {}
@logger = options[:logger]
end
def call(env)
type = Rack::Request.new(env).media_type
parser = match_content_types_for(parsers, type) if type
return @app.call(env) unless parser
# rack.input is optional in Rack 3.1+
return @app.call(env) unless env[POST_BODY]
body = env[POST_BODY].read ; env[POST_BODY].rewind
return @app.call(env) unless body && !body.empty?
begin
parsed = parser.last.call body
env[PARSER_RESULT] = parsed
env.update FORM_HASH => parsed, FORM_INPUT => env[POST_BODY] if parsed.is_a?(Hash)
rescue StandardError => e
warn! e, type
handler = match_content_types_for handlers, type
handler ||= ['default', ERROR_HANDLER]
return handler.last.call(e, type)
end
@app.call env
end
# Private: send a warning out to the logger
#
# error - Exception object
# type - String of the Content-Type
#
def warn!(error, type)
return unless logger
message = "[Rack::Parser] Error on %s : %s" % [type, error.to_s]
logger.warn message
end
# Private: matches content types for the given media type
#
# content_types - An array of the parsers or handlers options
# type - The media type. gathered from the Rack::Request
#
# Returns The match from the parser/handler hash or nil
def match_content_types_for(content_types, type)
content_types.detect do |content_type, _|
content_type.is_a?(Regexp) ? type.match(content_type) : type == content_type
end
end
end
end
|