Rack compatible HTTP router for Ruby.
2.3.1 - 2025-12-17
- Require Rack version 2.2.16 or higher, which is the earliest version of Rack 2.x that will run on Ruby 4.0 as well as our currently supported Ruby versions (3.2, 3.3, 3.4). (@cllns in 2b15582)
-
Set correct Rack
"SCRIPT_NAME"value for mounts under dynamic prefixes. (@timriley in #294).For example, given a mount under
scope '/stations/:station_id', the "SCRIPT_NAME" will be e.g."/stations/42"rather than"/stations/:station_id". -
For a mount with a prefix, allow the Rack
"PATH_INFO"value to remain an empty string when a request is made for that exact prefix only. (@timriley in #295)Given the following:
mount ->(env) { [200, {}, [env["PATH_INFO"]] }, at: "/settings"
When a request is made to
"/settings", the"SCRIPT_NAME"will be"/settings"and"PATH_INFO"will be"". This ensures that the Rack environment can be used to reconstruct the same path as used for the original request.To ensure a mounted router instance can still route to its root, treat
""the same as"/"for the purposes of matching routes.
-
Allow scopes to be given a custom name prefix, with
as:. This prefix is given to any named routes within the scope. (@timriley in #286) -
Allow route names given to
as:to specify their own prefix, which goes before any name prefixes added by the scopes the route is nested within. To specify a prefix, provide an array toas:. (@timriley in #286)scope "backend" do scope "admin", as: :secret do get "/cats/new", to: ->(*) { [200, {}, ["OK"]] }, as: [:new, :cat] end end router.path(:new_backend_secret_cat) # => "/backend/admin/cats/new
-
As part of Rack v2/v3 compatibility, improve coordination between
Hanami::Middleware::BodyParserandHanami::Routerregarding access of the request body. (@timriley in #287)BodyParserwill now makeenv["rack.input"]rewindable, and rewind the body after accessing it, ensuring downstream users can still read"rack.input"if needed.Routerwill only attempt to make"rack.input"rewindable if it hasn’t already been made so. -
Change
Hanami::Middleware::BodyParser::Parserto refer tomedia_typesinstead ofmime_types. (@timriley in #289)A body parser subclass should now look like this:
class CustomParser < Hanami::Middleware::BodyParser::Parser def self.media_types = ["application/custom"] def parse(body) body # Your parsing logic here end end
-
Allow
Hanami::Middleware::BodyParserto be initialized with a single body parser, instead of requiring an array. (@timriley in #288)Hanami::Middleware::BodyParser.new(app, MyCustomParser)
- [Kyle Plump] Improve runtime performance by building mustermann matchers when routes are first built. This corrects a performance regression introduced with the fixes in v2.2.0. (#279)
- Drop support for Ruby 3.1
- [Kyle Plump, Tim Riley] Support Rack 3 in addition to Rack 2 (#277)
- [Sven Schwyn] Convert -, +, ~, and . to underscore in URL helper names (#280)
- [inouire] Allow redirects to absolute URLs:
redirect "/redirect", to: "https://hanamirb.org/"(#282)
- [Damian C. Rossney, Kyle Plump] Scopes with a dynamic segment (e.g.
scope ":locale" do) and containing arootroute will no longer match the root route for requests with a trailing slash. This makes the behavior consistent with scopes using static strings. (#273)
- [Damian C. Rossney, Kyle Plump] Support paths with different variable names in same path location (#273)
- Drop support for Ruby 3.0
- [Pat Allan] Fix PATH_INFO and SCRIPT_NAME for Rack apps mounted at the root (keep the leading slash in PATH_INFO, and set SCRIPT_NAME to a blank string)
- [Pat Allan] Process glob routes and mounted apps together, so that the routes can be handled in the user-specified order (previously, a root-mounted app would handle routes even if matching globs were declared earlier)
- [Pat Allan] Pass keyword args through to middleware
- [Tim Riley] Accept
not_allowed_proc:argument when initializingHanami::Router. This allows customisation of thenot_allowedbehavior like fornot_found(#259)
- [Luca Guidi] Official support for Ruby 3.2
- [Armin, Luca Guidi] Introduce
Hanami::Middleware::BodyParser::FormParserto parse multipart file upload
- [Luca Guidi] Return HTTP response header
Allowwhen returning405HTTP status
- [Luca Guidi] Don't parse request body when Body Parser already parsed it
- [Luca Guidi] During routes inspection, ensure to print path prefixes for nested named routes
- [Benjamin Klotz]
Hanami::Middleware::BodyParser::Parser#parse(abstract method) to raiseNoMethodErrorinstead ofNotImplementedError
- [Peter Solnica]
Hanami::Middleware::BodyParsercan be initialized with one or more formats and additional custom mime types per format (Hanami::Middleware::BodyParser.new(app, [:json, :xml])orHanami::Middleware::BodyParser.new(app, [json: "application/json+scim"])) (#230)
- [Luca Guidi] [Internal] Ensure
Hanami::Middleware::Errorclass is available where it is needed [#225]
- [Marc Busqué] Introduced
Hanami::Router::Formatter::CSVfor CSV inspection of the routes
- [Marc Busqué] Routes inspection: Don't print empty line after the definition of a
getroute - [Marc Busqué] Routes inspection: Print
<controller>.<action>instead of(proc) - [Marc Busqué] Routes inspection: Print
(block)instead ofNilClasswhen inspecting a route block
- [Luca Guidi] Official support for MRI 3.1
- [Luca Guidi] Parse non-GET request body and make it available in Rack env under the
router.paramskey. For JSON requests, please useHanami:::Middleware::JsonParser
- [Luca Guidi] Drop support for Ruby: MRI 2.6, and 2.7.
- [Luca Guidi] Introduced
Hanami::Router#to_inspectwhich returns a string blob with all the routes formatted for human readability
- [Luca Guidi] Official support for MRI 3.0
- [Luca Guidi] Introduced
Hanami::Middleware::BodyParser::Parseras superclass for body parsers - [Paweł Świątkowski] Added
not_found:option toHanami::Router#initializeto customize HTTP 404 status
- [Luca Guidi]
Hanami::Router#initializedo not yield block if not given - [Luca Guidi] Ensure to not accidentally cache response headers for HTTP 404 and 405
- [Luca Guidi] Ensure scoped root to not be added as trailing slash
- [Luca Guidi] Block syntax. Routes definition accept a block which returning value is the body of the Rack response.
- [Luca Guidi] Added
resolver:option toHanami::Router#initializeto provide your own strategy to load endpoints.
- [Luca Guidi] Removed
Hanami::Router#resourceand#resources. - [Luca Guidi] Removed loading of routes endpoints.
- [Luca Guidi] Removed
inflector:fromHanami::Router#initialize - [Luca Guidi] Removed
scheme:,host:,port:fromHanami::Router#initialize, usebase_url:instead.
- [Luca Guidi] Introduce
Hanami::Router#scopeto support single routing tier for Hanami - [Semyon Pupkov] Added
inflector:option forHanami::Router#initializebased ondry-inflector
- [Luca Guidi] Drop support for Ruby: MRI 2.3, and 2.4.
- [Luca Guidi] Renamed
Hanami::Router#namespace=>#prefix - [Gustavo Caso] Remove body cleanup for
HEADrequests - [Semyon Pupkov] Remove the ability to force SSL (
force_ssl:option forHanami::Router#initialize) - [Gustavo Caso] Remove router body parsers (
parsers:option forHanami::Router#initialize) - [Luca Guidi] Globbed path requires named capture (was
get "/files/*", now isget "/files/*names") - [Luca Guidi] Router is frozen after initialization
- [Luca Guidi] All the code base respects the frozen string pragma
- [Luca Guidi]
Hanami::Router#initializerequiresconfiguration:option if routes endpoints areHanami::Actionsubclasses
- [Luca Guidi] Official support for Ruby: MRI 2.7
- [Luca Guidi] Support
rack2.1
- [Luca Guidi] Official support for Ruby: MRI 2.6
- [Luca Guidi] Support
bundler2.0+
- [Tim Riley] Skip attempting to parse unknown types in
Hanami::Middleware::BodyParser
- [Luca Guidi] Official support for JRuby 9.2.0.0
- [Gustavo Caso] Introduce
Hanami::Middleware::BodyParserRack middleware to parse payload of non-GET HTTP requests.
- [Alfonso Uceda] Deprecate
Hanami::Router.new(force_ssl: true). Use webserver (eg. Nginx), Rack middleware (eg.rack-ssl), or another strategy to force HTTPS connection. - [Gustavo Caso] Deprecate
Hanami::Router.new(body_parsers: [:json]). UseHanami::Middleware::BodyParserinstead.
- [Luca Guidi] Official support for Ruby: MRI 2.5
- [malin-as] Ensure
Hanami::Routerto properly respond tounlink
- [Sergey Fedorov] Allow Rack applications to be mounted inside a namespace. (
namespace "api" { mount V1::App, at: "/v1" })
- [Luca Guidi] Introduce new introspection methods (
#redirect?and#redirection_path) for recognized routes (seeHanami::Router#recognize)
- [Luca Guidi] Ensure
Hanami::Router#redirectto be compatible with#recognize
- [Valentyn Ostakh] Deep symbolize params from parsed body
- [Luca Guidi]
Hanami::Router#recognizemust return a non-routeable object when the endpoint cannot be resolved
- [Luca Guidi] Official support for Ruby: MRI 2.4
- [Jakub Pavlík] Added
:asoption for RESTful resources (eg.resources :psi, controller: 'dogs', as: 'dogs')
- [Pascal Betz] Make compatible with Rack 2.0 only
- [Luca Guidi] Ensure JSON body parser to not eval untrusted input
- [Kyle Chong] Referenced params from body parses in Rack env via
router.parsed_body
- [Luca Guidi & Lucas Hosseini] Ensure params from routes take precedence over params from body parsing
- [Luca Guidi] Ensure inspector to respect path prefix of mouted apps
- [Luca Guidi] Official support for Ruby: MRI 2.3+ and JRuby 9.1.5.0+
- [Sean Collins] Introduced
Hanami::Router#root. Example:root to: 'home#index', equivalent toget '/', to: 'home#index', as: :root. - [Nicola Racco] Allow to mount Rack applications at a specific host. Example:
mount Blog, host: 'blog', which will be hit forGET http://blog.example.com - [Luca Guidi] Support
multi_jsongem as backend for JSON body parser. Ifmulti_jsonis present in the gem bundle, it will be used, otherwise it will fallback to Ruby'sJSON. - [Luca Guidi] Introduced
Hanami::Routing::RecognizedRoute#pathin order to allow a better introspection
- [Andrew De Ponte] Make routes inspection to work when non-Hanami apps are mounted
- [Andrew De Ponte] Ensure to set the right
SCRIPT_NAMEin Rack env for mounted Hanami apps - [Luca Guidi] Fix
NoMethodErrorwhenHanami::Router#recognizeis invoked with a Rack env or a route name or a path that can't be recognized
– [Luca Guidi] Drop support for Ruby 2.0 and 2.1. Official support for JRuby 9.0.5.0+
- [Anton Davydov] Fix double leading slash for Capybara's
current_path
- [Luca Guidi] Fix body parsers for non Hash requests
- [Luca Guidi] Renamed the project
- [Anton Davydov] Print stacked lines for routes inspection
- [Luca Guidi] Added
Lotus::Router#recognizeas a testing facility. Examplerouter.recognize('/') # => associated route - [Luca Guidi] Added
Lotus::Router.definein order to wrap routes definitions inconfig/routes.rbwhenLotus::Routeris used outside of Lotus projects - [David Strauß] Make
Lotus::Routing::Parsing::JsonParsercompatible withapplication/vnd.api+jsonMIME Type - [Alfonso Uceda Pompa] Improved exception messages for
Lotus::Router#pathand#url
- [Alfonso Uceda Pompa] Ensure
Lotus::Router#pathand#urlto generate correct URL for mounted applications - [Vladislav Zarakovsky] Ensure Force SSL mode to respect Rack SPEC
- [Alfonso Uceda Pompa] A failure for body parsers raises a
Lotus::Routing::Parsing::BodyParsingErrorexception - [Karim Tarek] Introduced
Lotus::Router::Errorand let all the framework exceptions to inherit from it.
- [Luca Guidi] Official support for JRuby 9k+
- [Alfonso Uceda Pompa] Ensure mounted applications to not repeat their prefix (eg
/admin/admin) - [Thiago Felippe] Ensure router inspector properly prints routes with repeated entries (eg
/admin/dashboard/admin)
- [Alfonso Uceda Pompa] Force SSL (eg
Lotus::Router.new(force_ssl: true). - [Alfonso Uceda Pompa] Allow router to accept a
:prefixoption, in order to generate prefixed routes.
- [Alfonso Uceda Pompa] Nested RESTful resource(s)
- [Alfonso Uceda Pompa] RESTful resource(s) have a correct pluralization/singularization for variables and named routes (eg.
/books/:idis now:bookinstead of:books)
- [Alfonso Uceda Pompa] Lotus::Action compat: invoke
.callif defined, otherwise fall back to#call.
- [Luca Guidi & Alfonso Uceda Pompa] Introduced routes inspector for CLI
- [Luca Guidi & Janko Marohnić] Introduced body parser for JSON
- [Luca Guidi] Introduced request body parsers: they parse body and turn into params.
- [Fred Wu] Introduced Router#define
- [Luca Guidi] Fix for member/collection actions in RESTful resource(s): allow to take actions with a leading slash.
- [Janko Marohnić] Fix for nested namespaces and RESTful resource(s) under namespace. They were generating wrong route names.
- [Luca Guidi] Made InvalidRouteException to inherit from StandardError so it can be catched from anonymous
rescueclause - [Luca Guidi] Fix RESTful resource(s) to respect :only/:except options
- [Luca Guidi] Aligned naming conventions with Lotus::Controller: no more BooksController::Index. Use Books::Index instead.
- [Luca Guidi] Removed
:prefixoption for routes. Use#namespaceblocks instead. - [Janko Marohnić] Make 301 the default redirect status
- [Luca Guidi] Introduced Lotus::Router#mount
- [Luca Guidi] Let specify a pattern for Lotus::Routing::EndpointResolver
- [Luca Guidi] Make Lotus::Routing::Endpoint::EndpointNotFound to inherit from StandardError, instead of Exception. This make it compatible with Rack::ShowExceptions.
- [Luca Guidi] Official support for Ruby 2.1
- [Luca Guidi] Added support for OPTIONS HTTP verb
- [Luca Guidi] Added Lotus::Routing::EndpointNotFound when a lazy endpoint can't be found
- [Luca Guidi] Make action separator customizable via Lotus::Router options.
- [Luca Guidi] Catch http_router exceptions and re-raise them with names under Lotus::Routing. This helps to have a stable public API.
- [Luca Guidi] Lotus::Routing::Resource::CollectionAction use configurable controller and action name separator over the hardcoded value
- [Luca Guidi] Implemented Lotus::Routing::Namespace#resource
- [Luca Guidi] Lotus::Routing::EndpointResolver now accepts options to inject namespace and suffix
- [Luca Guidi] Allow resolver and route class to be injected via options
- [Luca Guidi] Return 404 for not found and 405 for unacceptable HTTP method
- [Luca Guidi] Allow non-finished Rack responses to be used
- [Luca Guidi] Implemented lazy loading for endpoints
- [Luca Guidi] Implemented Lotus::Router.new to take a block and define routes
- [Luca Guidi] Add support for resource
- [Luca Guidi] Support for resource's member and collection
- [Luca Guidi] Add support for namespaces
- [Luca Guidi] Added support for RESTful resources
- [Luca Guidi] Add support for POST, DELETE, PUT, PATCH, TRACE
- [Luca Guidi] Routes constraints
- [Luca Guidi] Named urls
- [Luca Guidi] Added support for Procs as endpoints
- [Luca Guidi] Implemented redirect
- [Luca Guidi] Basic routing