Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
nicksieger committed Mar 16, 2012
0 parents commit 746beae
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .gitignore
@@ -0,0 +1,17 @@
*.gem
*.rbc
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
2 changes: 2 additions & 0 deletions .rspec
@@ -0,0 +1,2 @@
--color
--format documentation
3 changes: 3 additions & 0 deletions Gemfile
@@ -0,0 +1,3 @@
source "http://rubygems.org"

gemspec
22 changes: 22 additions & 0 deletions LICENSE
@@ -0,0 +1,22 @@
Copyright (c) 2012 Nick Sieger

MIT License

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.
29 changes: 29 additions & 0 deletions README.md
@@ -0,0 +1,29 @@
# Rack::Chain

TODO: Write a gem description

## Installation

Add this line to your application's Gemfile:

gem 'rack-chain'

And then execute:

$ bundle

Or install it yourself as:

$ gem install rack-chain

## Usage

TODO: Write usage instructions here

## Contributing

1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Added some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request
6 changes: 6 additions & 0 deletions Rakefile
@@ -0,0 +1,6 @@
require 'bundler/gem_tasks'

require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new

task :default => :spec
2 changes: 2 additions & 0 deletions lib/rack-chain.rb
@@ -0,0 +1,2 @@
require 'rack-chain/version'
require 'rack/chain'
5 changes: 5 additions & 0 deletions lib/rack-chain/version.rb
@@ -0,0 +1,5 @@
module Rack
class Chain
VERSION = "0.0.1"
end
end
56 changes: 56 additions & 0 deletions lib/rack/chain.rb
@@ -0,0 +1,56 @@
require 'rack'
require 'rack/builder'
require 'fiber'

class Rack::Chain
attr_reader :endpoint
attr_accessor :filters

class Link
def initialize(to)
@to = to
end

def call(env)
Fiber.new do
Fiber.yield @to.call(env)
end.resume
end
end

def initialize(endpoint, filters = [])
@endpoint = endpoint
@filters = filters
end

def call(env)
Link.new(filters.reverse.inject(endpoint) do |endpt,filter|
if filter.respond_to?(:[])
filter[Link.new(endpt)]
else
filter.new(Link.new(endpt))
end
end).call(env)
end

# Include this module in Rack::Builder to make all apps use Rack::Chain.
#
# Alternatively, extend Rack::Builder in config.ru to use
# Rack::Chain for that particular config.ru. Example:
#
# <pre>
# require 'rack/chain'
# extend Rack::Chain::Linker
# use Middleware1
# use Middleware2
# run App
# </pre>
module Linker
def to_app
app = @map ? generate_map(@run, @map) : @run
fail "missing run or map statement" unless app
Rack::Chain.new(app, @use)
end
end
end

20 changes: 20 additions & 0 deletions rack-chain.gemspec
@@ -0,0 +1,20 @@
# -*- encoding: utf-8 -*-
require File.expand_path('../lib/rack-chain/version', __FILE__)

Gem::Specification.new do |gem|
gem.authors = ["Nick Sieger"]
gem.email = ["nick@nicksieger.com"]
gem.description = %q{TODO: Write a gem description}
gem.summary = %q{TODO: Write a gem summary}
gem.homepage = ""

gem.files = `git ls-files`.split($\)
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.name = "rack-chain"
gem.require_paths = ["lib"]
gem.version = Rack::Chain::VERSION

gem.add_runtime_dependency "rack", [">= 1.4.0"]
gem.add_development_dependency "rspec", [">= 2.8.0"]
end
76 changes: 76 additions & 0 deletions spec/rack/chain_spec.rb
@@ -0,0 +1,76 @@
require File.expand_path('../../spec_helper', __FILE__)

describe Rack::Chain do
let(:env) { Hash.new }

let(:app) { app_dummy.new }

let(:chain) { Rack::Chain.new(app) }

let(:filter_names) { %w(Foo Bar Baz) }

let(:filters) { filter_names.map {|x| filter_dummy(x) } }

let(:full_chain) { chain.tap {|c| c.filters += filters } }

it "has an ordered list of filters" do
full_chain.filters.should == filters
end

it "calls each filter in order" do
full_chain.call(env)[2].should == ["App", *filter_names.reverse]
end

context Rack::Chain::Linker, "#to_app" do
let(:builder) do
Rack::Builder.new do
extend Rack::Chain::Linker
end.tap do |builder|
filters.each do |filter|
builder.use filter
end
builder.run app
end
end

it "overrides Rack::Builder#to_app to create a Rack::Chain" do
builder.to_app.should be_instance_of(Rack::Chain)
end

it "calls each filter in order" do
builder.to_app.call(env)[2].should == ["App", *filter_names.reverse]
end
end

context "with a large set of middleware" do
let(:number_of_filters) { 100 }

let(:filter_names) { (1..number_of_filters).map {|n| "Filter#{n}" } }

let(:filters) { filter_names.map {|x| filter_dummy(x) { caller.size } } }

# Skip the call stack size for the app
let(:app) { app_dummy { nil }.new }

it "the call stack size stays constant" do
call_stack_sizes = full_chain.call(env)[2].compact
call_stack_sizes.max.should == call_stack_sizes.min
end

context "but using normal Rack::Builder" do
let(:builder) do
Rack::Builder.new.tap {|builder|
filters.each do |filter|
builder.use filter
end
builder.run app
}
end

it "the call stack size grows with each layer" do
call_stack_sizes = builder.call(env)[2].compact
call_stack_sizes.max.should > number_of_filters
end
end
end
end
60 changes: 60 additions & 0 deletions spec/spec_helper.rb
@@ -0,0 +1,60 @@
require 'bundler/setup'
require 'rspec'
require 'rack/chain'

module Rack::Chain::Fixtures
class FilterDummy
def initialize(app)
@app = app
end

def call(env)
@app.call(env).tap do |result|
result[2] ||= []
result[2] << body if body
end
end

def body
self.class.name.split(/::/)[-1]
end
end

class AppDummy
def call(env)
[200, {"Content-Type" => "text/plain"}, [body]]
end

def body
self.class.name.split(/::/)[-1]
end
end

module Dummies
end

def reset_fixtures
Dummies.class_eval do
constants.each {|c| remove_const(c) }
end
end

def filter_dummy(name = "Foo", &block)
Dummies.const_set(name, Class.new(FilterDummy) do
define_method(:body, &block) if block
end)
end

def app_dummy(name = "App", &block)
Dummies.const_set(name, Class.new(AppDummy) do
define_method(:body, &block) if block
end)
end
end

RSpec.configure do |config|
config.include Rack::Chain::Fixtures
config.after :each do
reset_fixtures
end
end

0 comments on commit 746beae

Please sign in to comment.