Support for prepending/appending middlewares (#1176)
* Extract router_options * Remove unneeded rubocop:disable * Fix Hanami.rack_app to always return rack builder * Add support for prepanding/appending middlewares
This commit is contained in:
parent
c816219bcd
commit
597a37b6fd
|
@ -261,16 +261,13 @@ module Hanami
|
|||
def load_router
|
||||
require_relative "application/router"
|
||||
|
||||
Router.new(
|
||||
routes: load_routes,
|
||||
resolver: router_resolver,
|
||||
**configuration.router.options,
|
||||
) do
|
||||
use Hanami.application[:rack_monitor]
|
||||
config = configuration
|
||||
|
||||
Hanami.application.config.for_each_middleware do |m, *args, &block|
|
||||
use(m, *args, &block)
|
||||
end
|
||||
Router.new(routes: load_routes, resolver: router_resolver, **router_options) do
|
||||
use Hanami.application[:rack_monitor]
|
||||
use config.sessions.middleware if config.sessions.enabled?
|
||||
|
||||
middleware_stack.update(config.middleware_stack)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -288,6 +285,10 @@ module Hanami
|
|||
proc {}
|
||||
end
|
||||
|
||||
def router_options
|
||||
configuration.router.options
|
||||
end
|
||||
|
||||
def router_resolver
|
||||
config.router.resolver.new(slice: self)
|
||||
end
|
||||
|
|
|
@ -8,6 +8,9 @@ module Hanami
|
|||
# Hanami application router
|
||||
# @since 2.0.0
|
||||
class Router < ::Hanami::Router
|
||||
# @api private
|
||||
attr_reader :middleware_stack
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def initialize(routes:, middleware_stack: Routing::Middleware::Stack.new, **kwargs, &blk)
|
||||
|
@ -27,14 +30,14 @@ module Hanami
|
|||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def use(middleware, *args, &blk)
|
||||
@middleware_stack.use(middleware, *args, &blk)
|
||||
def use(...)
|
||||
middleware_stack.use(...)
|
||||
end
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def scope(*args, &blk)
|
||||
@middleware_stack.with(args.first) do
|
||||
middleware_stack.with(args.first) do
|
||||
super
|
||||
end
|
||||
end
|
||||
|
@ -52,9 +55,7 @@ module Hanami
|
|||
# @since 2.0.0
|
||||
# @api private
|
||||
def to_rack_app
|
||||
return self if @middleware_stack.empty?
|
||||
|
||||
@middleware_stack.to_rack_app(self)
|
||||
middleware_stack.to_rack_app(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,6 +20,10 @@ module Hanami
|
|||
ROOT_PREFIX = "/"
|
||||
private_constant :ROOT_PREFIX
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
attr_reader :stack
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def initialize
|
||||
|
@ -29,8 +33,27 @@ module Hanami
|
|||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def use(middleware, *args, &blk)
|
||||
@stack[@prefix].push([middleware, args, blk])
|
||||
def use(middleware, *args, before: nil, after: nil, &blk)
|
||||
item = [middleware, args, blk]
|
||||
|
||||
if before
|
||||
@stack[@prefix].insert((idx = index_of(before)).zero? ? 0 : idx - 1, item)
|
||||
elsif after
|
||||
@stack[@prefix].insert(index_of(after) + 1, item)
|
||||
else
|
||||
@stack[@prefix].push([middleware, args, blk])
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def update(other)
|
||||
other.stack.each do |prefix, items|
|
||||
stack[prefix].concat(items)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# @since 2.0.0
|
||||
|
@ -45,7 +68,7 @@ module Hanami
|
|||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def to_rack_app(app) # rubocop:disable Metrics/MethodLength
|
||||
def to_rack_app(app)
|
||||
s = self
|
||||
|
||||
Rack::Builder.new do
|
||||
|
@ -58,7 +81,7 @@ module Hanami
|
|||
|
||||
run app
|
||||
end
|
||||
end.to_app
|
||||
end
|
||||
end
|
||||
|
||||
# @since 2.0.0
|
||||
|
@ -82,6 +105,13 @@ module Hanami
|
|||
builder.map(prefix, &blk)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @since 2.0.0
|
||||
def index_of(middleware)
|
||||
@stack[@prefix].index { |(m, *)| m.equal?(middleware) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,10 +9,10 @@ require "pathname"
|
|||
|
||||
require_relative "settings/dotenv_store"
|
||||
require_relative "configuration/logger"
|
||||
require_relative "configuration/middleware"
|
||||
require_relative "configuration/router"
|
||||
require_relative "configuration/sessions"
|
||||
require_relative "constants"
|
||||
require_relative "application/routing/middleware/stack"
|
||||
|
||||
module Hanami
|
||||
# Hanami application configuration
|
||||
|
@ -28,7 +28,13 @@ module Hanami
|
|||
attr_reader :env
|
||||
|
||||
attr_reader :actions
|
||||
|
||||
# @api public
|
||||
attr_reader :middleware
|
||||
|
||||
# @api private
|
||||
alias_method :middleware_stack, :middleware
|
||||
|
||||
attr_reader :router
|
||||
attr_reader :views, :assets
|
||||
|
||||
|
@ -57,7 +63,7 @@ module Hanami
|
|||
Actions.new
|
||||
}
|
||||
|
||||
@middleware = Middleware.new
|
||||
@middleware = Application::Routing::Middleware::Stack.new
|
||||
|
||||
@router = Router.new(self)
|
||||
|
||||
|
@ -113,13 +119,6 @@ module Hanami
|
|||
|
||||
setting :base_url, default: "http://0.0.0.0:2300", constructor: -> url { URI(url) }
|
||||
|
||||
def for_each_middleware(&blk)
|
||||
stack = middleware.stack.dup
|
||||
stack += sessions.middleware if sessions.enabled?
|
||||
|
||||
stack.each(&blk)
|
||||
end
|
||||
|
||||
setting :sessions, default: :null, constructor: -> *args { Sessions.new(*args) }
|
||||
|
||||
private
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Hanami
|
||||
class Configuration
|
||||
# Hanami application configured Rack middleware
|
||||
#
|
||||
# @since 2.0.0
|
||||
class Middleware
|
||||
attr_reader :stack
|
||||
|
||||
def initialize
|
||||
@stack = []
|
||||
end
|
||||
|
||||
def use(middleware, *args, &block)
|
||||
stack.push([middleware, *args, block].compact)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -30,7 +30,7 @@ module Hanami
|
|||
end
|
||||
|
||||
def middleware
|
||||
[[storage_middleware, options]]
|
||||
[storage_middleware, options, nil]
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rack/test"
|
||||
|
||||
RSpec.describe "Hanami web app", :application_integration do
|
||||
include Rack::Test::Methods
|
||||
|
||||
let(:app) { Hanami.rack_app }
|
||||
|
||||
around do |example|
|
||||
with_tmp_directory(Dir.mktmpdir, &example)
|
||||
end
|
||||
|
||||
before do
|
||||
module TestApp
|
||||
module Middlewares
|
||||
class Core
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
end
|
||||
|
||||
class Prepare < Core
|
||||
def call(env)
|
||||
env["tested"] = []
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
class AppendOne < Core
|
||||
def call(env)
|
||||
env["tested"] << "one"
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
class AppendTwo < Core
|
||||
def call(env)
|
||||
env["tested"] << "two"
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
specify "Setting middlewares in the config" do
|
||||
write "config/application.rb", <<~RUBY
|
||||
require "hanami"
|
||||
|
||||
module TestApp
|
||||
class Application < Hanami::Application
|
||||
config.logger.stream = File.new("/dev/null", "w")
|
||||
|
||||
config.middleware.use Middlewares::AppendOne
|
||||
config.middleware.use Middlewares::Prepare, before: Middlewares::AppendOne
|
||||
config.middleware.use Middlewares::AppendTwo, after: Middlewares::AppendOne
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
write "config/routes.rb", <<~RUBY
|
||||
require "hanami/router"
|
||||
|
||||
module TestApp
|
||||
class Routes < Hanami::Routes
|
||||
define do
|
||||
slice :main, at: "/" do
|
||||
root to: "home.index"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
write "slices/main/actions/home/index.rb", <<~RUBY
|
||||
require "hanami/action"
|
||||
|
||||
module Main
|
||||
module Actions
|
||||
module Home
|
||||
class Index < Hanami::Action
|
||||
def handle(req, res)
|
||||
res.body = req.env["tested"].join(".")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
require "hanami/boot"
|
||||
|
||||
get "/"
|
||||
|
||||
expect(last_response).to be_successful
|
||||
expect(last_response.body).to eql("one.two")
|
||||
end
|
||||
|
||||
specify "Setting middlewares in the router" do
|
||||
write "config/application.rb", <<~RUBY
|
||||
require "hanami"
|
||||
|
||||
module TestApp
|
||||
class Application < Hanami::Application
|
||||
config.logger.stream = File.new("/dev/null", "w")
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
write "config/routes.rb", <<~RUBY
|
||||
require "hanami/router"
|
||||
|
||||
module TestApp
|
||||
class Routes < Hanami::Routes
|
||||
define do
|
||||
slice :main, at: "/" do
|
||||
use TestApp::Middlewares::AppendOne
|
||||
use TestApp::Middlewares::Prepare, before: TestApp::Middlewares::AppendOne
|
||||
use TestApp::Middlewares::AppendTwo, after: TestApp::Middlewares::AppendOne
|
||||
|
||||
root to: "home.index"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
write "slices/main/actions/home/index.rb", <<~RUBY
|
||||
require "hanami/action"
|
||||
|
||||
module Main
|
||||
module Actions
|
||||
module Home
|
||||
class Index < Hanami::Action
|
||||
def handle(req, res)
|
||||
res.body = req.env["tested"].join(".")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
require "hanami/boot"
|
||||
|
||||
get "/"
|
||||
|
||||
expect(last_response).to be_successful
|
||||
expect(last_response.body).to eql("one.two")
|
||||
end
|
||||
end
|
|
@ -7,7 +7,34 @@ RSpec.describe "Hanami web app", :application_integration do
|
|||
|
||||
let(:app) { Hanami.rack_app }
|
||||
|
||||
specify "has rack monitor preconfigured with default request logging" do
|
||||
specify "Hanami.rack_app returns a rack builder" do
|
||||
with_tmp_directory do
|
||||
write "config/application.rb", <<~RUBY
|
||||
require "hanami"
|
||||
|
||||
module TestApp
|
||||
class Application < Hanami::Application
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
write "config/routes.rb", <<~RUBY
|
||||
module TestApp
|
||||
class Routes < Hanami::Routes
|
||||
define do
|
||||
root to: ->(env) { [200, {}, ["OK"]] }
|
||||
end
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
require "hanami/boot"
|
||||
|
||||
expect(app).to be_instance_of(Rack::Builder)
|
||||
end
|
||||
end
|
||||
|
||||
specify "Has rack monitor preconfigured with default request logging" do
|
||||
dir = Dir.mktmpdir
|
||||
|
||||
with_tmp_directory(dir) do
|
||||
|
|
|
@ -5,6 +5,7 @@ SPEC_ROOT = File.expand_path(__dir__).freeze
|
|||
require_relative "support/coverage" if ENV["COVERAGE"].eql?("true")
|
||||
|
||||
require "hanami"
|
||||
begin; require "byebug"; rescue LoadError; end
|
||||
require "hanami/utils/file_list"
|
||||
require "hanami/devtools/unit"
|
||||
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "hanami/configuration"
|
||||
|
||||
RSpec.describe Hanami::Configuration do
|
||||
subject(:config) { described_class.new(application_name: application_name, env: :development) }
|
||||
let(:application_name) { "MyApp::Application" }
|
||||
|
||||
describe "#middleware" do
|
||||
it "defaults to a stack with no configured middlewares" do
|
||||
expect(config.middleware).to be_kind_of(Hanami::Configuration::Middleware)
|
||||
expect(config.middleware.stack.count).to be(0)
|
||||
end
|
||||
|
||||
describe "#use" do
|
||||
it "adds a middleware object to the configured stack" do
|
||||
middleware = -> * {}
|
||||
|
||||
expect { config.middleware.use middleware }
|
||||
.to change { config.middleware.stack }
|
||||
.to [[middleware]]
|
||||
end
|
||||
|
||||
it "adds a middleware class with options and a block to the configured stack" do
|
||||
klass, opts, block = Class.new, {options: :here}, -> * {}
|
||||
|
||||
expect { config.middleware.use(klass, opts, &block) }
|
||||
.to change { config.middleware.stack }
|
||||
.to [[klass, opts, block]]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#for_each_middleware" do
|
||||
let(:middleware_item_1) { [Class.new, {options: :here}, -> * {}] }
|
||||
let(:middleware_item_2) { [Class.new, {options: :here}, -> * {}] }
|
||||
|
||||
before do
|
||||
config.middleware.use(*middleware_item_1)
|
||||
config.middleware.use(*middleware_item_2)
|
||||
end
|
||||
|
||||
context "sessions not enabled" do
|
||||
it "yields each given middleware" do
|
||||
expect { |b| config.for_each_middleware(&b) }
|
||||
.to yield_successive_args middleware_item_1, middleware_item_2
|
||||
end
|
||||
end
|
||||
|
||||
context "sessions enabled" do
|
||||
before do
|
||||
config.sessions = :cookie, {secret: "xyz"}
|
||||
end
|
||||
|
||||
it "yields each given middleware, with the session middleware added last" do
|
||||
expect { |b| config.for_each_middleware(&b) }
|
||||
.to yield_successive_args(
|
||||
middleware_item_1,
|
||||
middleware_item_2,
|
||||
array_including(an_object_satisfying { |klass| klass.name =~ /^Rack::Session/ })
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue