0.0
No commit activity in last 3 years
No release in over 3 years
Rack Middleware for parsing request body without touching or mixing in request.params.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

>= 0
 Project Readme

Rack::BodyParser

Rack middleware for parsing incoming request body per content type.

Inspiration

Rack::BodyParser is heavily inspired by Rack::Parser, but behaves a bit different. See Key Features.

Key Features; Differences to Rack::Parser

  1. Rack::BodyParser only parses the request body.

  2. Rack::BodyParser does not touch or replace the original params or request.params, and does not mix params and body payload. Instead the parsed result is stored in a separate key parsed_body in Rack's env.

  3. (optional) patching of Rack::Request with support for custom getter method Parsed result available as request.parsed_body with support for custom request.#{your_key_here} per parser. Enable with :patch_request => true.

  4. (optional) access to headers/env. If your parser respond_to? :env=, this setter method will be invoked with Rack's env just before running call on your parser.

Batteries not included

Rack::BodyParser does not contain any parsers out of the box.

Installation

Available through RubyGems:

gem install rack-bodyparser

Usage

Define your :parsers per media-type, which is the content-type without media type parameters; e.g., when content-type is text/plain;charset=utf-8, the media-type is text/plain.

Parsers configuration accepts a String or Regexp as media-type key, and anything that respond_to? 'call' as parser.

Sinatra example:

# app.rb

use Rack::BodyParser, :parsers => { 
  'application/json' => proc { |data| JSON.parse data },
  'application/xml'  => proc { |data| XML.parse data },
  /msgpack/          => proc { |data| Msgpack.parse data }
}

post '/' do
  puts env['parsed_body']
end

Error Handling

Rack::BodyParser has one default error handler that can be overridden by setting the 'default' handler. As with parsers, you can use a String or Regexp as media-type key and anything that respond_to? 'call' as the error handler. The error handler must accept the two parameters Error and type (the media type).

use Rack::Parser, :handlers => {
  'default' => proc { |e, type| 
    [400, {}, ['[Rack::BodyParser] Failed to parse %s: %s' % [type, e.to_s]]] 
  },
  'application/xml' => proc { [400, {}, 'Error: XML unsupported'] }
}

Note: the error handler rescues exceptions that descend from StandardError. See http://www.mikeperham.com/2012/03/03/the-perils-of-rescue-exception/

Logging

Rack::BodyParser will try to warn if a logger is present.

Patch Rack::Request

Setting up Rack::BodyParser with :patch_request => true will add a parsed_body getter method to Rack::Request. Parsers can also provide a :rack_request_key to define a custom key per parser:

# gem 'jsonapi_parser'
require 'json/api' # JSONAPI document parser/validator
module Rack::BodyParser::JSONAPI
  module Parser
    # This defines the getter key for Rack::Request
    def self.rack_request_key; :document; end

    def self.call(body)
      JSON::API.parse(JSON.parse(body))
    end
  end

  module Error
    def self.call(e, type)
      payload = {
        errors: {
          title: 'Failed to parse body as JSONAPI document',
          detail: e.message
        }
      }.to_json
      [422, {}, [payload]]
    end
  end
end

use Rack::BodyParser, :patch_request => true,
  :parsers  => { 
    'application/vnd.api+json' => Rack::BodyParser::JSONAPI::Parser
  },
  :handlers => {
    'application/vnd.api+json' => Rack::BodyParser::JSONAPI::Error
  }

post '/' do
  # These all output the same
  puts env['parsed_body']
  puts request.parsed_body
  puts request.document
end

Request headers/environment

Need headers or other data from Rack's env in your parser? Set up your parser with a setter method, respond_to? :env=, and it will be invoked just before running the main call method.

module ThisParserNeedsHeaders
  def self.env=(env)
    @env = env
  end

  def self.call(body)
    # @env available here for your parsing pleasure.
  end
end

use Rack::BodyParser, :parsers => { 
  'plain/text' => ThisParserNeedsHeaders 
}

Copyright

Copyright © 2016, 2017 Aaron Heesakkers.

See MIT-LICENSE for details.