rackables 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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