Skip to content

Commit

Permalink
Merge 12594a0 into be65b4d
Browse files Browse the repository at this point in the history
  • Loading branch information
ccyrille committed Nov 21, 2019
2 parents be65b4d + 12594a0 commit 02dc1dd
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 41 deletions.
22 changes: 16 additions & 6 deletions README.md
Expand Up @@ -43,10 +43,10 @@ class NonThreadSafeMiddleware
@app = app
@state = 0
end

def call(env)
@state += 1

return @app.call(env)
end
end
Expand All @@ -69,16 +69,16 @@ class CacheEverythingForever
@app = app
@cache_all_the_things = Concurrent::Map.new
end

def freeze
return self if frozen?

# Don't freeze @cache_all_the_things
@app.freeze

super
end

def call(env)
# Use the thread-safe `Concurrent::Map` to fetch the value or store it if it doesn't exist already.
@cache_all_the_things.fetch_or_store(env[Rack::PATH_INFO]) do
Expand All @@ -88,6 +88,16 @@ class CacheEverythingForever
end
```

### Can I ignore a specific middleware ?

In some particular cases, we want to be able to ignore a mutable middleware. This can be done in `config.ru` :

```ruby
Rack::Freeze.configure do |config|
config.ignored_middlewares << MutableButFineMiddleware
end
```

## Contributing

1. Fork it
Expand Down
19 changes: 16 additions & 3 deletions lib/rack/freeze.rb
@@ -1,15 +1,15 @@
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Expand All @@ -19,7 +19,20 @@
# THE SOFTWARE.

require_relative 'freeze/version'
require_relative 'freeze/configuration'
require_relative 'freeze/builder'

module Rack
module Freeze
def self.configure
yield(configuration)
end

def self.configuration
@configuration ||= Rack::Freeze::Configuration.new
end
end
end

# Enforce the policy globally.
Rack::Builder.prepend(Rack::Freeze::Builder)
33 changes: 24 additions & 9 deletions lib/rack/freeze/builder.rb
@@ -1,15 +1,15 @@
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Expand All @@ -25,16 +25,31 @@ module Rack
module Freeze
module Builder
def use(klass, *args, &block)
super Freezer.new(klass), *args, &block
if ignored_middleware?(klass)
super(klass, *args, &block)
else
super(Freezer.new(klass), *args, &block)
end
end

def run(app)
super app.freeze

def run(app)
if ignored_middleware?(app.class)
super(app)
else
super(app.freeze)
end
end

def to_app
super.freeze
app = super
ignored_middleware?(app.class) ? app : app.freeze
end

private

def ignored_middleware?(klass)
Rack::Freeze.configuration.ignored_middlewares.include?(klass)
end
end
end
end
31 changes: 31 additions & 0 deletions lib/rack/freeze/configuration.rb
@@ -0,0 +1,31 @@
# Copyright, 2019, by Klaxit <http://www.klaxit.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

module Rack
module Freeze
class Configuration
attr_accessor :ignored_middlewares

def initialize
@ignored_middlewares = []
end
end
end
end
41 changes: 24 additions & 17 deletions spec/rack/freeze/builder.rb
Expand Up @@ -2,48 +2,55 @@
RSpec.shared_examples_for "frozen middleware" do
it "should be entirely frozen" do
current = builder.to_app

expect(current).to_not be_nil

# This can't traverse into Rack::URLMap and I'm not really sure how it should do that.
while current
expect(current).to be_frozen

current = current.respond_to?(:app) ? current.app : nil
end
end

it "should not be frozen if configuration ignores it" do
allow(Rack::Freeze.configuration).to receive(:ignored_middlewares)
.and_return([described_class])

expect(builder.to_app).to_not be_frozen
end
end

RSpec.shared_context "middleware builder" do
let(:env) {Hash.new}

let(:builder) do
Rack::Builder.new.tap do |builder|
builder.use described_class
builder.run lambda { |env| [404, {}, []] }
end
end

let(:app) {builder.to_app}

it "should generate a valid app" do
expect(app).to_not be_nil
end

it_behaves_like "frozen middleware"
end

class FaultyMiddleware
def initialize(app)
@app = app
end

attr :app

# Broken implementation of freeze doesn't call @app.freeze
def freeze
return self if frozen?

super
end
end
Expand All @@ -52,15 +59,15 @@ class GoodMiddleware
def initialize(app)
@app = app
end

attr :app

# Broken implementation of freeze doesn't call @app.freeze
def freeze
return self if frozen?

@app.freeze

super
end
end
Expand All @@ -70,13 +77,13 @@ def initialize(app)
@app = app
@state = 0
end

attr :app
attr :state

def call(env)
@state += 1

return @app.call(env)
end
end
23 changes: 17 additions & 6 deletions spec/rack/freeze/builder_spec.rb
Expand Up @@ -3,7 +3,7 @@

RSpec.describe StatefulMiddleware do
include_context "middleware builder"

it "should fail when invoked" do
expect{app.call(env)}.to raise_error(RuntimeError, /can't modify frozen/)
end
Expand All @@ -18,11 +18,11 @@
Rack::Builder.new do
use FaultyMiddleware
use GoodMiddleware

run proc{}
end
end

it_behaves_like "frozen middleware"
end

Expand All @@ -35,14 +35,25 @@
use GoodMiddleware
run proc{}
end

map '/bar' do
use FaultyMiddleware
use GoodMiddleware
run proc{}
end
end
end

it_behaves_like "frozen middleware"

it "should be entirely frozen" do
current = builder.to_app

expect(current).to_not be_nil

# This can't traverse into Rack::URLMap and I'm not really sure how it should do that.
while current
expect(current).to be_frozen

current = current.respond_to?(:app) ? current.app : nil
end
end
end

0 comments on commit 02dc1dd

Please sign in to comment.