rackables 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,6 @@
1
+ Bundles the following Rack middlewares:
2
+
3
+ Rackables::CacheControl
4
+ Rackables::DefaultCharset
5
+ Rackables::PublicExceptionPage
6
+ Rackables::TrailingSlashRedirect
@@ -0,0 +1,22 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << "test"
5
+ t.test_files = FileList['test/test*.rb']
6
+ t.verbose = true
7
+ end
8
+ task :default => :test
9
+
10
+ begin
11
+ require 'jeweler'
12
+ Jeweler::Tasks.new do |gemspec|
13
+ gemspec.name = "rackables"
14
+ gemspec.summary = "Bundle of useful Rack middleware"
15
+ gemspec.description = "Bundles Rack middleware: CacheControl, DefaultCharset, PublicExceptionPage, TrailingSlashRedirect"
16
+ gemspec.email = "gbuesing@gmail.com"
17
+ gemspec.homepage = "http://github.com/gbuesing/rackables"
18
+ gemspec.authors = ["Geoff Buesing"]
19
+ end
20
+ rescue LoadError
21
+ puts "Jeweler not available. Install it with: gem install jeweler"
22
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,6 @@
1
+ module Rackables
2
+ autoload :CacheControl, 'rackables/cache_control'
3
+ autoload :DefaultCharset, 'rackables/default_charset'
4
+ autoload :PublicExceptionPage, 'rackables/public_exception_page'
5
+ autoload :TrailingSlashRedirect, 'rackables/trailing_slash_redirect'
6
+ end
@@ -0,0 +1,21 @@
1
+ module Rackables
2
+ class CacheControl
3
+ # Works well with Varnish
4
+ # TODO: accomodate more options than just public, max-age=
5
+ def initialize(app, value, opts)
6
+ @app = app
7
+ @value = value
8
+ @opts = opts
9
+ end
10
+
11
+ def call(env)
12
+ status, headers, body = @app.call(env)
13
+ if headers['Cache-Control'].nil?
14
+ max_age = @opts[:max_age]
15
+ max_age = max_age.call if max_age.respond_to?(:call)
16
+ headers['Cache-Control'] = "#{@value}, max-age=#{max_age}"
17
+ end
18
+ [status, headers, body]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ module Rackables
2
+ class DefaultCharset
3
+ HAS_CHARSET = /charset=/
4
+
5
+ def initialize(app, value = 'utf-8')
6
+ @app = app
7
+ @value = value
8
+ end
9
+
10
+ def call(env)
11
+ status, headers, body = @app.call(env)
12
+ content_type = headers['Content-Type']
13
+ if content_type && content_type !~ HAS_CHARSET
14
+ headers['Content-Type'] = "#{content_type}; charset=#{@value}"
15
+ end
16
+ [status, headers, body]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,72 @@
1
+ module Rackables
2
+ # Returns a user-friendly exception page
3
+ # Should be included at the very top of the middleware pipeline so that unhandled exceptions from anywhere down the pipeline are rescued
4
+ # In development, you'd want to use Rack::ShowExceptions instead of this (config.ru example):
5
+ #
6
+ # if ENV['RACK_ENV'] == 'development'
7
+ # use Rack::ShowExceptions
8
+ # else
9
+ # use Rackables::PublicExceptionPage
10
+ # end
11
+ #
12
+ # The default HTML included here is a copy of the 500 page included with Rails
13
+ # You can optionally specify your own file, ex:
14
+ #
15
+ # use Rackables::PublicExceptionPage, "public/500.html"
16
+ class PublicExceptionPage
17
+
18
+ def initialize(app, file_path = nil)
19
+ @app = app
20
+ @file_path = file_path
21
+ end
22
+
23
+ def call(env)
24
+ @app.call(env)
25
+ rescue ::Exception
26
+ [500, {'Content-Type' => 'text/html', 'Content-Length' => html.length.to_s}, [html]]
27
+ end
28
+
29
+ private
30
+
31
+ def html
32
+ @html ||= @file_path ? ::File.read(@file_path) : default_html
33
+ end
34
+
35
+ # Exception page from Rails (500.html)
36
+ def default_html
37
+ <<-EOV
38
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
39
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
40
+
41
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
42
+
43
+ <head>
44
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
45
+ <title>We're sorry, but something went wrong (500)</title>
46
+ <style type="text/css">
47
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
48
+ div.dialog {
49
+ width: 25em;
50
+ padding: 0 4em;
51
+ margin: 4em auto 0 auto;
52
+ border: 1px solid #ccc;
53
+ border-right-color: #999;
54
+ border-bottom-color: #999;
55
+ }
56
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
57
+ </style>
58
+ </head>
59
+
60
+ <body>
61
+ <!-- This file lives in public/500.html -->
62
+ <div class="dialog">
63
+ <h1>We're sorry, but something went wrong.</h1>
64
+ <p>We've been notified about this issue and we'll take a look at it shortly.</p>
65
+ </div>
66
+ </body>
67
+ </html>
68
+ EOV
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,20 @@
1
+ module Rackables
2
+ class TrailingSlashRedirect
3
+ HAS_TRAILING_SLASH = %r{^/(.*)/$}
4
+
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ if env['PATH_INFO'] =~ HAS_TRAILING_SLASH
11
+ location = "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}/#{$1}"
12
+ location = "#{location}?#{env['QUERY_STRING']}" if env['QUERY_STRING'].to_s =~ /\S/
13
+ [301, {"Location" => location}, []]
14
+ else
15
+ @app.call(env)
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1 @@
1
+ <h1>Custom 500 page</h1>
@@ -0,0 +1,27 @@
1
+ require 'test/unit'
2
+ require 'rackables'
3
+
4
+ class TestCacheControl < Test::Unit::TestCase
5
+
6
+ def test_sets_cache_control
7
+ app = Proc.new { [200, {}, []]}
8
+ middleware = Rackables::CacheControl.new(app, :public, :max_age => 5)
9
+ status, headers, body = middleware.call({})
10
+ assert_equal 'public, max-age=5', headers['Cache-Control']
11
+ end
12
+
13
+ def test_sets_cache_control_with_value_specified_as_proc
14
+ app = Proc.new { [200, {}, []]}
15
+ middleware = Rackables::CacheControl.new(app, :public, :max_age => Proc.new {5})
16
+ status, headers, body = middleware.call({})
17
+ assert_equal 'public, max-age=5', headers['Cache-Control']
18
+ end
19
+
20
+ def test_respects_existing_cache_control_value
21
+ app = Proc.new { [200, {'Cache-Control' => 'private'}, []]}
22
+ middleware = Rackables::CacheControl.new(app, :public, :max_age => 5)
23
+ status, headers, body = middleware.call({})
24
+ assert_equal 'private', headers['Cache-Control']
25
+ end
26
+
27
+ end
@@ -0,0 +1,27 @@
1
+ require 'test/unit'
2
+ require 'rackables'
3
+
4
+ class TestDefaultCharset < Test::Unit::TestCase
5
+
6
+ def test_adds_utf8_charset_by_default
7
+ app = Proc.new { [200, {'Content-Type' => 'text/html'}, []]}
8
+ middleware = Rackables::DefaultCharset.new(app)
9
+ status, headers, body = middleware.call({})
10
+ assert_equal 'text/html; charset=utf-8', headers['Content-Type']
11
+ end
12
+
13
+ def test_adds_specified_charset
14
+ app = Proc.new { [200, {'Content-Type' => 'text/html'}, []]}
15
+ middleware = Rackables::DefaultCharset.new(app, 'us-ascii')
16
+ status, headers, body = middleware.call({})
17
+ assert_equal 'text/html; charset=us-ascii', headers['Content-Type']
18
+ end
19
+
20
+ def test_does_not_overwrite_existing_charset_value
21
+ app = Proc.new { [200, {'Content-Type' => 'text/html; charset=us-ascii'}, []]}
22
+ middleware = Rackables::DefaultCharset.new(app, 'iso-8859-2')
23
+ status, headers, body = middleware.call({})
24
+ assert_equal 'text/html; charset=us-ascii', headers['Content-Type']
25
+ end
26
+
27
+ end
@@ -0,0 +1,30 @@
1
+ require 'test/unit'
2
+ require 'rackables'
3
+
4
+ class TestPublicExceptionPage < Test::Unit::TestCase
5
+
6
+ def test_returns_downstream_app_response_when_no_exception_raised
7
+ app = Proc.new { [200, {'Content-Type' => 'text/html'}, ['Downstream app']]}
8
+ middleware = Rackables::PublicExceptionPage.new(app)
9
+ status, headers, body = middleware.call({})
10
+ assert_equal 200, status
11
+ assert_equal ['Downstream app'], body
12
+ end
13
+
14
+ def test_returns_500_with_default_exception_page_when_exception_raised_by_downstream_app
15
+ app = Proc.new { raise }
16
+ middleware = Rackables::PublicExceptionPage.new(app)
17
+ status, headers, body = middleware.call({})
18
+ assert_equal 500, status
19
+ assert_match /We\'re sorry, but something went wrong/, body[0]
20
+ end
21
+
22
+ def test_returns_500_with_custom_exception_page_when_file_path_specified
23
+ app = Proc.new { raise }
24
+ middleware = Rackables::PublicExceptionPage.new(app, 'test/fixtures/custom_500.html')
25
+ status, headers, body = middleware.call({})
26
+ assert_equal 500, status
27
+ assert_match /Custom 500 page/, body[0]
28
+ end
29
+
30
+ end
@@ -0,0 +1,39 @@
1
+ require 'test/unit'
2
+ require 'rackables'
3
+
4
+ class TestTrailingSlashRedirect < Test::Unit::TestCase
5
+
6
+ def test_passes_to_downstream_app_when_no_trailing_slash_on_path_info
7
+ app = Proc.new { [200, {'Content-Type' => 'text/html'}, ['Downstream app']]}
8
+ middleware = Rackables::TrailingSlashRedirect.new(app)
9
+ status, headers, body = middleware.call({'PATH_INFO' => '/foo'})
10
+ assert_equal 200, status
11
+ assert_equal ['Downstream app'], body
12
+ assert_nil headers['Location']
13
+ end
14
+
15
+ def test_returns_301_when_trailing_slash_on_path_info
16
+ app = Proc.new { [200, {'Content-Type' => 'text/html'}, ['Downstream app']]}
17
+ middleware = Rackables::TrailingSlashRedirect.new(app)
18
+ status, headers, body = middleware.call({'rack.url_scheme' => 'http', 'HTTP_HOST' => 'bar.com', 'PATH_INFO' => '/foo/'})
19
+ assert_equal 301, status
20
+ assert_equal 'http://bar.com/foo', headers['Location']
21
+ end
22
+
23
+ def test_301_respects_query_string
24
+ app = Proc.new { [200, {'Content-Type' => 'text/html'}, ['Downstream app']]}
25
+ middleware = Rackables::TrailingSlashRedirect.new(app)
26
+ status, headers, body = middleware.call({'rack.url_scheme' => 'http', 'HTTP_HOST' => 'bar.com', 'PATH_INFO' => '/foo/', 'QUERY_STRING' => 'baz=hi'})
27
+ assert_equal 301, status
28
+ assert_equal 'http://bar.com/foo?baz=hi', headers['Location']
29
+ end
30
+
31
+ def test_301_respects_https
32
+ app = Proc.new { [200, {'Content-Type' => 'text/html'}, ['Downstream app']]}
33
+ middleware = Rackables::TrailingSlashRedirect.new(app)
34
+ status, headers, body = middleware.call({'rack.url_scheme' => 'https', 'HTTP_HOST' => 'bar.com', 'PATH_INFO' => '/foo/', 'QUERY_STRING' => 'baz=hi'})
35
+ assert_equal 301, status
36
+ assert_equal 'https://bar.com/foo?baz=hi', headers['Location']
37
+ end
38
+
39
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rackables
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Geoff Buesing
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-12 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: "Bundles Rack middleware: CacheControl, DefaultCharset, PublicExceptionPage, TrailingSlashRedirect"
17
+ email: gbuesing@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - README
26
+ - Rakefile
27
+ - VERSION
28
+ - lib/rackables.rb
29
+ - lib/rackables/cache_control.rb
30
+ - lib/rackables/default_charset.rb
31
+ - lib/rackables/public_exception_page.rb
32
+ - lib/rackables/trailing_slash_redirect.rb
33
+ - test/fixtures/custom_500.html
34
+ - test/test_cache_control.rb
35
+ - test/test_default_charset.rb
36
+ - test/test_public_exception_page.rb
37
+ - test/test_trailing_slash_redirect.rb
38
+ has_rdoc: true
39
+ homepage: http://github.com/gbuesing/rackables
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --charset=UTF-8
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.3.5
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: Bundle of useful Rack middleware
66
+ test_files:
67
+ - test/test_cache_control.rb
68
+ - test/test_default_charset.rb
69
+ - test/test_public_exception_page.rb
70
+ - test/test_trailing_slash_redirect.rb