Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 746beae1dd0fc8eddf2d7a7c0939f795e32344fe 0 parents
@nicksieger authored
17 .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  .rspec
@@ -0,0 +1,2 @@
+--color
+--format documentation
3  Gemfile
@@ -0,0 +1,3 @@
+source "http://rubygems.org"
+
+gemspec
22 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 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 Rakefile
@@ -0,0 +1,6 @@
+require 'bundler/gem_tasks'
+
+require 'rspec/core/rake_task'
+RSpec::Core::RakeTask.new
+
+task :default => :spec
2  lib/rack-chain.rb
@@ -0,0 +1,2 @@
+require 'rack-chain/version'
+require 'rack/chain'
5 lib/rack-chain/version.rb
@@ -0,0 +1,5 @@
+module Rack
+ class Chain
+ VERSION = "0.0.1"
+ end
+end
56 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 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 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 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.
Something went wrong with that request. Please try again.