utopia 1.3.2 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/lib/utopia.rb +2 -3
- data/lib/utopia/exceptions.rb +27 -0
- data/lib/utopia/exceptions/handler.rb +99 -0
- data/lib/utopia/exceptions/mailer.rb +138 -0
- data/lib/utopia/redirection.rb +176 -0
- data/lib/utopia/version.rb +1 -1
- data/setup/site/config.ru +16 -12
- data/setup/site/{pages → public}/_static/icon.png +0 -0
- data/setup/site/{pages → public}/_static/site.css +0 -0
- data/setup/site/{pages → public}/_static/utopia-background.svg +0 -0
- data/setup/site/public/_static/utopia.svg +1 -0
- data/setup/site/public/readme.txt +1 -1
- data/spec/utopia/{exception_handler_spec.rb → exceptions/handler_spec.rb} +17 -25
- data/spec/utopia/exceptions/handler_spec.ru +6 -0
- data/spec/utopia/{exception_handler_spec → exceptions/handler_spec}/controller.rb +0 -0
- data/spec/utopia/{redirector_spec.rb → redirection_spec.rb} +27 -5
- data/spec/utopia/redirection_spec.ru +25 -0
- metadata +20 -20
- data/lib/utopia/exception_handler.rb +0 -104
- data/lib/utopia/redirector.rb +0 -164
- data/setup/site/pages/_static/utopia.svg +0 -1
- data/setup/site/pages/errors/controller.rb +0 -9
- data/setup/site/public/_static +0 -1
- data/spec/utopia/exception_handler_spec.ru +0 -6
- data/spec/utopia/redirector_spec.ru +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0fb046791da67c397e853162649620dc2b530d3e
|
4
|
+
data.tar.gz: 390a488937297cec03a267206d4a096d4c74a497
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bd0a3820ebe9422484bb3c1bf048c99b7f7d0e9907eb0cdfccf292ecb6c55e8927cbdeacf87f96b4ad759b7047290f7622639438997d2fa53c96ced3fd24e26
|
7
|
+
data.tar.gz: 9cca58be165173f26f72cee8bde2b7b5b1052018c2a1fff7946476228cc09c63eb48e4d51a85da50893e59894b33c504afede627b5cfeb51dd15c466af3b94fa
|
data/README.md
CHANGED
@@ -79,7 +79,7 @@ Then, Nginx is configured like so:
|
|
79
79
|
Utopia builds on top of Rack with the following middleware:
|
80
80
|
|
81
81
|
- `Utopia::Static`: Serve static files efficiently.
|
82
|
-
- `Utopia::
|
82
|
+
- `Utopia::Redirection`: Redirect URL patterns and status codes.
|
83
83
|
- `Utopia::Localization`: Non-intrusive localization of resources.
|
84
84
|
- `Utopia::Controller`: Dynamic behaviour with recursive execution.
|
85
85
|
- `Utopia::Content`: XML-style template engine with powerful tag behaviours.
|
@@ -89,9 +89,9 @@ Utopia builds on top of Rack with the following middleware:
|
|
89
89
|
|
90
90
|
This middleware serves static files using the `mime-types` library. By default, it works with `Rack::Sendfile` and `Rack::Cache` and supports `ETag` based caching.
|
91
91
|
|
92
|
-
###
|
92
|
+
### Redirection
|
93
93
|
|
94
|
-
A
|
94
|
+
A set of flexible URI rewriting middleware which includes support for string mappings, regular expressions and status codes (e.g. 404 errors).
|
95
95
|
|
96
96
|
### Localization
|
97
97
|
|
data/lib/utopia.rb
CHANGED
@@ -22,10 +22,9 @@ require_relative 'utopia/version'
|
|
22
22
|
|
23
23
|
require_relative 'utopia/content'
|
24
24
|
require_relative 'utopia/controller'
|
25
|
-
require_relative 'utopia/exception_handler'
|
26
25
|
require_relative 'utopia/localization'
|
27
|
-
require_relative 'utopia/
|
28
|
-
require_relative 'utopia/
|
26
|
+
require_relative 'utopia/exceptions'
|
27
|
+
require_relative 'utopia/redirection'
|
29
28
|
require_relative 'utopia/static'
|
30
29
|
|
31
30
|
require_relative 'utopia/tags/deferred'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'exceptions/handler'
|
22
|
+
require_relative 'exceptions/mailer'
|
23
|
+
|
24
|
+
module Utopia
|
25
|
+
module Exceptions
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Utopia
|
22
|
+
module Exceptions
|
23
|
+
# Catches exceptions and performs an internal redirect.
|
24
|
+
class Handler
|
25
|
+
def initialize(app, location = '/errors/exception')
|
26
|
+
@app = app
|
27
|
+
|
28
|
+
@location = location
|
29
|
+
end
|
30
|
+
|
31
|
+
def freeze
|
32
|
+
@location.freeze
|
33
|
+
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
# Generate a very simple fatal error response. This function should be unlikely to fail. Additionally, it generates a lowest common denominator response which should be suitable as a response to any kind of request. Ideally, this response is also not good or useful for any kind of higher level browser or API client, as this is not a normal error path but one that represents broken behaviour.
|
38
|
+
def fatal_error(env, exception)
|
39
|
+
body = StringIO.new
|
40
|
+
|
41
|
+
write_exception_to_stream(body, env, exception)
|
42
|
+
body.rewind
|
43
|
+
|
44
|
+
return [500, {HTTP::CONTENT_TYPE => "text/plain"}, body]
|
45
|
+
end
|
46
|
+
|
47
|
+
def log_exception(env, exception)
|
48
|
+
# An error has occurred, log it:
|
49
|
+
output = env['rack.errors'] || $stderr
|
50
|
+
write_exception_to_stream(output, env, exception, true)
|
51
|
+
end
|
52
|
+
|
53
|
+
def call(env)
|
54
|
+
begin
|
55
|
+
return @app.call(env)
|
56
|
+
rescue Exception => exception
|
57
|
+
log_exception(env, exception)
|
58
|
+
|
59
|
+
# If the error occurred while accessing the error handler, we finish with a fatal error:
|
60
|
+
if env[Rack::PATH_INFO] == @location
|
61
|
+
return fatal_error(env, exception)
|
62
|
+
else
|
63
|
+
begin
|
64
|
+
# We do an internal redirection to the error location:
|
65
|
+
error_request = env.merge(Rack::PATH_INFO => @location, Rack::REQUEST_METHOD => Rack::GET)
|
66
|
+
error_response = @app.call(error_request)
|
67
|
+
|
68
|
+
return [500, error_response[1], error_response[2]]
|
69
|
+
rescue Exception
|
70
|
+
# If redirection fails, we also finish with a fatal error:
|
71
|
+
return fatal_error(env, exception)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private def write_exception_to_stream(stream, env, exception, include_backtrace = false)
|
78
|
+
buffer = []
|
79
|
+
|
80
|
+
buffer << "While requesting resource #{env[Rack::PATH_INFO].inspect}, a fatal error occurred:"
|
81
|
+
|
82
|
+
while exception != nil
|
83
|
+
buffer << "\t#{exception.class.name}: #{exception.to_s}"
|
84
|
+
|
85
|
+
if include_backtrace
|
86
|
+
exception.backtrace.each do |line|
|
87
|
+
buffer << "\t\t#{line}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
exception = exception.cause
|
92
|
+
end
|
93
|
+
|
94
|
+
# We do this in one go so that lines don't get mixed up.
|
95
|
+
stream.puts buffer.join("\n")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'net/smtp'
|
22
|
+
require 'mail'
|
23
|
+
|
24
|
+
module Utopia
|
25
|
+
module Exceptions
|
26
|
+
# Catches all exceptions raised from the app it wraps and sends a useful email with the exception, stacktrace, and contents of the environment.
|
27
|
+
class Mailer
|
28
|
+
# A basic local non-authenticated SMTP server.
|
29
|
+
LOCAL_SMTP = [:smtp, {
|
30
|
+
:address => "localhost",
|
31
|
+
:port => 25,
|
32
|
+
:enable_starttls_auto => false
|
33
|
+
}]
|
34
|
+
|
35
|
+
def initialize(app, config = {})
|
36
|
+
@app = app
|
37
|
+
|
38
|
+
@to = config[:to] || "postmaster"
|
39
|
+
@from = config.fetch(:from) {(ENV['USER'] || 'rack') + "@localhost"}
|
40
|
+
@subject = config[:subject] || '%{exception} [PID %{pid} : %{cwd}]'
|
41
|
+
@delivery_method = config.fetch(:delivery_method, LOCAL_SMTP)
|
42
|
+
|
43
|
+
@dump_environment = config.fetch(:dump_environment, false)
|
44
|
+
end
|
45
|
+
|
46
|
+
def call(env)
|
47
|
+
begin
|
48
|
+
return @app.call(env)
|
49
|
+
rescue => exception
|
50
|
+
send_notification exception, env
|
51
|
+
|
52
|
+
raise
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
REQUEST_KEYS = [:ip, :referrer, :path, :user_agent]
|
59
|
+
|
60
|
+
def generate_body(exception, env)
|
61
|
+
io = StringIO.new
|
62
|
+
|
63
|
+
# Dump out useful rack environment variables:
|
64
|
+
request = Rack::Request.new(env)
|
65
|
+
|
66
|
+
io.puts "#{request.request_method} #{request.url}"
|
67
|
+
|
68
|
+
io.puts
|
69
|
+
|
70
|
+
REQUEST_KEYS.each do |key|
|
71
|
+
value = request.send(key)
|
72
|
+
io.puts "request.#{key}: #{value.inspect}"
|
73
|
+
end
|
74
|
+
|
75
|
+
request.params.each do |key, value|
|
76
|
+
io.puts "request.params.#{key}: #{value.inspect}"
|
77
|
+
end
|
78
|
+
|
79
|
+
io.puts
|
80
|
+
|
81
|
+
io.puts "#{exception.class.name}: #{exception.to_s}"
|
82
|
+
|
83
|
+
if exception.respond_to?(:backtrace)
|
84
|
+
io.puts exception.backtrace
|
85
|
+
else
|
86
|
+
io.puts exception.to_s
|
87
|
+
end
|
88
|
+
|
89
|
+
return io.string
|
90
|
+
end
|
91
|
+
|
92
|
+
def generate_mail(exception, env)
|
93
|
+
attributes = {
|
94
|
+
exception: exception.class.name,
|
95
|
+
pid: $$,
|
96
|
+
cwd: Dir.getwd,
|
97
|
+
}
|
98
|
+
|
99
|
+
mail = Mail.new(
|
100
|
+
:from => @from,
|
101
|
+
:to => @to,
|
102
|
+
:subject => @subject % attributes
|
103
|
+
)
|
104
|
+
|
105
|
+
mail.text_part = Mail::Part.new
|
106
|
+
mail.text_part.body = generate_body(exception, env)
|
107
|
+
|
108
|
+
if body = extract_body(env) and body.size > 0
|
109
|
+
mail.attachments['body.bin'] = body
|
110
|
+
end
|
111
|
+
|
112
|
+
if @dump_environment
|
113
|
+
mail.attachments['environment.yaml'] = YAML::dump(env)
|
114
|
+
end
|
115
|
+
|
116
|
+
return mail
|
117
|
+
end
|
118
|
+
|
119
|
+
def send_notification(exception, env)
|
120
|
+
mail = generate_mail(exception, env)
|
121
|
+
|
122
|
+
mail.delivery_method(*@delivery_method) if @delivery_method
|
123
|
+
|
124
|
+
mail.deliver
|
125
|
+
rescue => mail_exception
|
126
|
+
$stderr.puts mail_exception.to_s
|
127
|
+
$stderr.puts mail_exception.backtrace
|
128
|
+
end
|
129
|
+
|
130
|
+
def extract_body(env)
|
131
|
+
if io = env['rack.input']
|
132
|
+
io.rewind if io.respond_to?(:rewind)
|
133
|
+
io.read
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'middleware'
|
22
|
+
|
23
|
+
module Utopia
|
24
|
+
module Redirection
|
25
|
+
class RequestFailure < StandardError
|
26
|
+
def initialize(resource_path, resource_status, error_path, error_status)
|
27
|
+
@resource_path = resource_path
|
28
|
+
@resource_status = resource_status
|
29
|
+
|
30
|
+
@error_path = error_path
|
31
|
+
@error_status = error_status
|
32
|
+
|
33
|
+
super "Requested resource #{@resource_path} resulted in a #{@resource_status} error. Requested error handler #{@error_path} resulted in a #{@error_status} error."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Errors
|
38
|
+
# Maps an error code to a given string
|
39
|
+
def initialize(app, codes = {})
|
40
|
+
@codes = codes
|
41
|
+
|
42
|
+
@app = app
|
43
|
+
end
|
44
|
+
|
45
|
+
def freeze
|
46
|
+
@codes.freeze
|
47
|
+
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
def call(env)
|
52
|
+
response = @app.call(env)
|
53
|
+
|
54
|
+
if response[0] >= 400 and location = @codes[response[0]]
|
55
|
+
error_request = env.merge(Rack::PATH_INFO => location, Rack::REQUEST_METHOD => Rack::GET)
|
56
|
+
error_response = @app.call(error_request)
|
57
|
+
|
58
|
+
if error_response[0] >= 400
|
59
|
+
raise RequestFailure.new(env[Rack::PATH_INFO], response[0], location, error_response[0])
|
60
|
+
else
|
61
|
+
# Feed the error code back with the error document:
|
62
|
+
error_response[0] = response[0]
|
63
|
+
return error_response
|
64
|
+
end
|
65
|
+
else
|
66
|
+
return response
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class FileNotFound < Errors
|
72
|
+
def initialize(uri = '/errors/file-not-found')
|
73
|
+
super(404 => uri)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# We cache 301 redirects for 24 hours.
|
78
|
+
DEFAULT_MAX_AGE = 3600*24
|
79
|
+
|
80
|
+
class Redirection
|
81
|
+
def initialize(app, status: 307, max_age: DEFAULT_MAX_AGE)
|
82
|
+
@app = app
|
83
|
+
@status = status
|
84
|
+
@max_age = max_age
|
85
|
+
end
|
86
|
+
|
87
|
+
def freeze
|
88
|
+
@status.freeze
|
89
|
+
@max_age.freeze
|
90
|
+
|
91
|
+
super
|
92
|
+
end
|
93
|
+
|
94
|
+
attr :status
|
95
|
+
attr :max_age
|
96
|
+
|
97
|
+
def cache_control
|
98
|
+
# http://jacquesmattheij.com/301-redirects-a-dangerous-one-way-street
|
99
|
+
"max-age=#{self.max_age}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def headers(location)
|
103
|
+
{HTTP::LOCATION => location, HTTP::CACHE_CONTROL => self.cache_control}
|
104
|
+
end
|
105
|
+
|
106
|
+
def redirect(location)
|
107
|
+
return [self.status, self.headers(location), []]
|
108
|
+
end
|
109
|
+
|
110
|
+
def [] path
|
111
|
+
false
|
112
|
+
end
|
113
|
+
|
114
|
+
def call(env)
|
115
|
+
path = env[Rack::PATH_INFO]
|
116
|
+
|
117
|
+
if redirection = self[path]
|
118
|
+
return redirection
|
119
|
+
end
|
120
|
+
|
121
|
+
return @app.call(env)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class DirectoryIndex < Redirection
|
126
|
+
def initialize(app, index = 'index')
|
127
|
+
@app = app
|
128
|
+
@index = 'index'
|
129
|
+
|
130
|
+
super(app)
|
131
|
+
end
|
132
|
+
|
133
|
+
def [] path
|
134
|
+
if path.end_with?('/')
|
135
|
+
return redirect(path + @index)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class Rewrite < Redirection
|
141
|
+
def initialize(app, patterns, status: 301)
|
142
|
+
@patterns = patterns
|
143
|
+
|
144
|
+
super(app, status: status)
|
145
|
+
end
|
146
|
+
|
147
|
+
def [] path
|
148
|
+
if location = @patterns[path]
|
149
|
+
return redirect(location)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class Moved < Redirection
|
155
|
+
def initialize(app, pattern, prefix, status: 301, flatten: false)
|
156
|
+
@app = app
|
157
|
+
|
158
|
+
@pattern = pattern
|
159
|
+
@prefix = prefix
|
160
|
+
@flatten = flatten
|
161
|
+
|
162
|
+
super(app, status: status)
|
163
|
+
end
|
164
|
+
|
165
|
+
def [] path
|
166
|
+
if path.start_with?(@pattern)
|
167
|
+
if @flatten
|
168
|
+
return redirect(@prefix)
|
169
|
+
else
|
170
|
+
return redirect(path.sub(@pattern, @prefix))
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/lib/utopia/version.rb
CHANGED
data/setup/site/config.ru
CHANGED
@@ -14,10 +14,11 @@ require 'utopia'
|
|
14
14
|
require 'rack/cache'
|
15
15
|
|
16
16
|
if RACK_ENV == :production
|
17
|
-
use Utopia::
|
18
|
-
use Utopia::
|
17
|
+
use Utopia::Exceptions::Handler
|
18
|
+
use Utopia::Exceptions::Mailer
|
19
19
|
elsif RACK_ENV == :development
|
20
20
|
use Rack::ShowExceptions
|
21
|
+
use Utopia::Static, root: 'public'
|
21
22
|
end
|
22
23
|
|
23
24
|
use Rack::Sendfile
|
@@ -31,16 +32,13 @@ end
|
|
31
32
|
|
32
33
|
use Rack::ContentLength
|
33
34
|
|
34
|
-
use Utopia::
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
errors: {
|
42
|
-
404 => "/errors/file-not-found"
|
43
|
-
}
|
35
|
+
use Utopia::Redirection::Rewrite,
|
36
|
+
'/' => '/welcome/index'
|
37
|
+
|
38
|
+
use Utopia::Redirection::DirectoryIndex
|
39
|
+
|
40
|
+
use Utopia::Redirection::Errors,
|
41
|
+
404 => '/errors/file-not-found'
|
44
42
|
|
45
43
|
use Utopia::Localization,
|
46
44
|
:default_locale => 'en',
|
@@ -52,6 +50,12 @@ use Utopia::Controller,
|
|
52
50
|
|
53
51
|
use Utopia::Static
|
54
52
|
|
53
|
+
if RACK_ENV != :production
|
54
|
+
# Serve static files from public/ when not running in a production environment:
|
55
|
+
use Utopia::Static, root: 'public'
|
56
|
+
end
|
57
|
+
|
58
|
+
# Serve dynamic content
|
55
59
|
use Utopia::Content,
|
56
60
|
cache_templates: (RACK_ENV == :production),
|
57
61
|
tags: {
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 420 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><rect x="0" y="0" width="420" height="56" style="fill:#f79433;"/><rect x="0" y="56" width="420" height="24" style="fill:#4e8dd8;"/><g><path d="M75.145,70.819c2.37,-3.097 4.173,-6.921 5.111,-11.365c0.91,-4.318 1.498,-9.261 1.498,-14.692l0,-44.762l-62.754,0l0,44.762c0,2.628 0.244,5.333 0.407,8.035c0.168,2.782 0.674,5.515 1.345,8.118c0.68,2.644 1.739,5.173 3.067,7.517c1.363,2.405 3.263,4.526 5.609,6.303c2.319,1.755 5.245,3.163 8.677,4.172c1.617,0.478 3.416,1.093 5.354,1.093l13.856,0c3.071,0 5.797,-1.058 8.131,-2.001c4.042,-1.631 7.305,-4.049 9.699,-7.18Z" style="fill:#fff;fill-rule:nonzero;"/><path d="M151.481,18.701l0,-18.701l-62.754,-0.022l0,18.723l22.246,0l0.02,61.299l17.93,0l-0.02,-61.299l22.578,0Z" style="fill:#fff;fill-rule:nonzero;"/><path d="M229.926,39.999c0,-22.051 -16.979,-39.992 -37.852,-39.992c-20.872,0 -37.851,17.942 -37.851,39.992c0,22.054 16.979,39.994 37.851,39.994c20.873,0 37.852,-17.94 37.852,-39.994Z" style="fill:#fff;fill-rule:nonzero;"/><path d="M269.238,50.909c9.717,0 17.181,-2.066 22.183,-6.395c5.087,-4.399 7.667,-10.942 7.667,-19.575c0,-3.257 -0.393,-5.962 -1.167,-8.476c-0.778,-2.528 -1.883,-4.934 -3.281,-6.814c-1.401,-1.882 -3.098,-3.458 -5.045,-4.703c-1.895,-1.21 -4.003,-2.198 -6.264,-2.943c-2.239,-0.737 -4.64,-1.263 -7.139,-1.56c-2.464,-0.292 -5.016,-0.443 -7.587,-0.443l-29.468,0l0,80l17.93,0l0,-29.091l12.171,0Z" style="fill:#fff;fill-rule:nonzero;"/><rect x="304.879" y="0" width="17.93" height="80" style="fill:#fff;"/><path d="M362.589,0l-29.477,80l75.888,0l-31.247,-80l-15.164,0Z" style="fill:#fff;fill-rule:nonzero;"/></g></g></svg>
|
@@ -1 +1 @@
|
|
1
|
-
This directory is required by Apache/Phusion Passenger.
|
1
|
+
This directory is required by Apache/Phusion Passenger and contains static assets that are typically served using sendfile.
|
@@ -20,33 +20,25 @@
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
|
-
|
24
|
-
require '
|
23
|
+
require_relative '../rack_helper'
|
24
|
+
require 'utopia/exceptions'
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
let(:app) {Rack::Builder.parse_file(File.expand_path('exception_handler_spec.ru', __dir__)).first}
|
26
|
+
RSpec.describe Utopia::Exceptions::Handler do
|
27
|
+
include_context "rack app", File.expand_path("handler_spec.ru", __dir__)
|
28
|
+
|
29
|
+
it "should successfully call the controller method" do
|
30
|
+
# This request will raise an exception, and then redirect to the /exception url which will fail again, and cause a fatal error.
|
31
|
+
get "/blow?fatal=true"
|
34
32
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
expect(last_response.body).to be_include 'fatal error'
|
43
|
-
end
|
33
|
+
expect(last_response.status).to be == 500
|
34
|
+
expect(last_response.headers['Content-Type']).to be == 'text/plain'
|
35
|
+
expect(last_response.body).to be_include 'fatal error'
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should fail with a 500 error" do
|
39
|
+
get "/blow"
|
44
40
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
expect(last_response.status).to be == 500
|
49
|
-
expect(last_response.body).to be_include 'Error Will Robertson'
|
50
|
-
end
|
41
|
+
expect(last_response.status).to be == 500
|
42
|
+
expect(last_response.body).to be_include 'Error Will Robertson'
|
51
43
|
end
|
52
44
|
end
|
File without changes
|
@@ -19,10 +19,18 @@
|
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
21
|
require_relative 'rack_helper'
|
22
|
-
require 'utopia/
|
22
|
+
require 'utopia/redirection'
|
23
23
|
|
24
|
-
RSpec.describe Utopia::
|
25
|
-
include_context "rack app", "
|
24
|
+
RSpec.describe Utopia::Redirection do
|
25
|
+
include_context "rack app", "redirection_spec.ru"
|
26
|
+
|
27
|
+
it "should redirect directory to index" do
|
28
|
+
get "/welcome/"
|
29
|
+
|
30
|
+
expect(last_response.status).to be == 307
|
31
|
+
expect(last_response.headers['Location']).to be == '/welcome/index'
|
32
|
+
expect(last_response.headers['Cache-Control']).to include("max-age=86400")
|
33
|
+
end
|
26
34
|
|
27
35
|
it "should be permanently moved" do
|
28
36
|
get "/a"
|
@@ -36,7 +44,7 @@ RSpec.describe Utopia::Redirector do
|
|
36
44
|
get "/"
|
37
45
|
|
38
46
|
expect(last_response.status).to be == 301
|
39
|
-
expect(last_response.headers['Location']).to be == '/
|
47
|
+
expect(last_response.headers['Location']).to be == '/welcome/index'
|
40
48
|
expect(last_response.headers['Cache-Control']).to include("max-age=86400")
|
41
49
|
end
|
42
50
|
|
@@ -48,6 +56,20 @@ RSpec.describe Utopia::Redirector do
|
|
48
56
|
end
|
49
57
|
|
50
58
|
it "should blow up if internal error redirect also fails" do
|
51
|
-
expect{get "/teapot"}.to raise_error Utopia::
|
59
|
+
expect{get "/teapot"}.to raise_error Utopia::Redirection::RequestFailure
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should redirect deep url to top" do
|
63
|
+
get "/hierarchy/a/b/c/d/e"
|
64
|
+
|
65
|
+
expect(last_response.status).to be == 301
|
66
|
+
expect(last_response.headers['Location']).to be == '/hierarchy'
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should get a weird status" do
|
70
|
+
get "/weird"
|
71
|
+
|
72
|
+
expect(last_response.status).to be == 333
|
73
|
+
expect(last_response.headers['Location']).to be == '/status'
|
52
74
|
end
|
53
75
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
use Utopia::Redirection::Rewrite, "/" => "/welcome/index"
|
3
|
+
|
4
|
+
use Utopia::Redirection::DirectoryIndex
|
5
|
+
|
6
|
+
use Utopia::Redirection::Errors,
|
7
|
+
404 => '/error',
|
8
|
+
418 => '/teapot'
|
9
|
+
|
10
|
+
use Utopia::Redirection::Moved, "/a", "/b"
|
11
|
+
use Utopia::Redirection::Moved, "/hierarchy/", "/hierarchy", flatten: true
|
12
|
+
use Utopia::Redirection::Moved, "/weird", "/status", status: 333
|
13
|
+
|
14
|
+
def error_handler(env)
|
15
|
+
request = Rack::Request.new(env)
|
16
|
+
if request.path_info == "/error"
|
17
|
+
[200, {}, ["File not found :("]]
|
18
|
+
elsif request.path_info == "/teapot"
|
19
|
+
[418, {}, ["I'm a teapot!"]]
|
20
|
+
else
|
21
|
+
[404, {}, []]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
run self.method(:error_handler)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: utopia
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: trenni
|
@@ -199,7 +199,9 @@ files:
|
|
199
199
|
- lib/utopia/controller/respond.rb
|
200
200
|
- lib/utopia/controller/rewrite.rb
|
201
201
|
- lib/utopia/controller/variables.rb
|
202
|
-
- lib/utopia/
|
202
|
+
- lib/utopia/exceptions.rb
|
203
|
+
- lib/utopia/exceptions/handler.rb
|
204
|
+
- lib/utopia/exceptions/mailer.rb
|
203
205
|
- lib/utopia/extensions/array.rb
|
204
206
|
- lib/utopia/extensions/date.rb
|
205
207
|
- lib/utopia/extensions/rack.rb
|
@@ -210,7 +212,7 @@ files:
|
|
210
212
|
- lib/utopia/middleware.rb
|
211
213
|
- lib/utopia/path.rb
|
212
214
|
- lib/utopia/path/matcher.rb
|
213
|
-
- lib/utopia/
|
215
|
+
- lib/utopia/redirection.rb
|
214
216
|
- lib/utopia/session.rb
|
215
217
|
- lib/utopia/session/encrypted_cookie.rb
|
216
218
|
- lib/utopia/session/lazy_hash.rb
|
@@ -234,16 +236,14 @@ files:
|
|
234
236
|
- setup/site/lib/readme.txt
|
235
237
|
- setup/site/pages/_heading.xnode
|
236
238
|
- setup/site/pages/_page.xnode
|
237
|
-
- setup/site/pages/_static/icon.png
|
238
|
-
- setup/site/pages/_static/site.css
|
239
|
-
- setup/site/pages/_static/utopia-background.svg
|
240
|
-
- setup/site/pages/_static/utopia.svg
|
241
|
-
- setup/site/pages/errors/controller.rb
|
242
239
|
- setup/site/pages/errors/exception.xnode
|
243
240
|
- setup/site/pages/errors/file-not-found.xnode
|
244
241
|
- setup/site/pages/links.yaml
|
245
242
|
- setup/site/pages/welcome/index.xnode
|
246
|
-
- setup/site/public/_static
|
243
|
+
- setup/site/public/_static/icon.png
|
244
|
+
- setup/site/public/_static/site.css
|
245
|
+
- setup/site/public/_static/utopia-background.svg
|
246
|
+
- setup/site/public/_static/utopia.svg
|
247
247
|
- setup/site/public/readme.txt
|
248
248
|
- setup/site/tmp/readme.txt
|
249
249
|
- spec/utopia/content/link_spec.rb
|
@@ -291,9 +291,9 @@ files:
|
|
291
291
|
- spec/utopia/controller/rewrite_spec.rb
|
292
292
|
- spec/utopia/controller/sequence_spec.rb
|
293
293
|
- spec/utopia/controller/variables_spec.rb
|
294
|
-
- spec/utopia/
|
295
|
-
- spec/utopia/
|
296
|
-
- spec/utopia/
|
294
|
+
- spec/utopia/exceptions/handler_spec.rb
|
295
|
+
- spec/utopia/exceptions/handler_spec.ru
|
296
|
+
- spec/utopia/exceptions/handler_spec/controller.rb
|
297
297
|
- spec/utopia/extensions_spec.rb
|
298
298
|
- spec/utopia/http/status_spec.rb
|
299
299
|
- spec/utopia/locale_spec.rb
|
@@ -316,8 +316,8 @@ files:
|
|
316
316
|
- spec/utopia/path_spec.rb
|
317
317
|
- spec/utopia/rack_helper.rb
|
318
318
|
- spec/utopia/rack_spec.rb
|
319
|
-
- spec/utopia/
|
320
|
-
- spec/utopia/
|
319
|
+
- spec/utopia/redirection_spec.rb
|
320
|
+
- spec/utopia/redirection_spec.ru
|
321
321
|
- spec/utopia/session_spec.rb
|
322
322
|
- spec/utopia/session_spec.ru
|
323
323
|
- spec/utopia/static_spec.rb
|
@@ -392,9 +392,9 @@ test_files:
|
|
392
392
|
- spec/utopia/controller/rewrite_spec.rb
|
393
393
|
- spec/utopia/controller/sequence_spec.rb
|
394
394
|
- spec/utopia/controller/variables_spec.rb
|
395
|
-
- spec/utopia/
|
396
|
-
- spec/utopia/
|
397
|
-
- spec/utopia/
|
395
|
+
- spec/utopia/exceptions/handler_spec.rb
|
396
|
+
- spec/utopia/exceptions/handler_spec.ru
|
397
|
+
- spec/utopia/exceptions/handler_spec/controller.rb
|
398
398
|
- spec/utopia/extensions_spec.rb
|
399
399
|
- spec/utopia/http/status_spec.rb
|
400
400
|
- spec/utopia/locale_spec.rb
|
@@ -417,8 +417,8 @@ test_files:
|
|
417
417
|
- spec/utopia/path_spec.rb
|
418
418
|
- spec/utopia/rack_helper.rb
|
419
419
|
- spec/utopia/rack_spec.rb
|
420
|
-
- spec/utopia/
|
421
|
-
- spec/utopia/
|
420
|
+
- spec/utopia/redirection_spec.rb
|
421
|
+
- spec/utopia/redirection_spec.ru
|
422
422
|
- spec/utopia/session_spec.rb
|
423
423
|
- spec/utopia/session_spec.ru
|
424
424
|
- spec/utopia/static_spec.rb
|
@@ -1,104 +0,0 @@
|
|
1
|
-
# Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
# of this software and associated documentation files (the "Software"), to deal
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
8
|
-
# furnished to do so, subject to the following conditions:
|
9
|
-
#
|
10
|
-
# The above copyright notice and this permission notice shall be included in
|
11
|
-
# all copies or substantial portions of the Software.
|
12
|
-
#
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
# THE SOFTWARE.
|
20
|
-
|
21
|
-
require_relative 'middleware'
|
22
|
-
|
23
|
-
require 'trenni/strings'
|
24
|
-
|
25
|
-
module Utopia
|
26
|
-
class ExceptionHandler
|
27
|
-
def initialize(app, location)
|
28
|
-
@app = app
|
29
|
-
|
30
|
-
@location = location
|
31
|
-
|
32
|
-
self.freeze
|
33
|
-
end
|
34
|
-
|
35
|
-
def freeze
|
36
|
-
@location.freeze
|
37
|
-
|
38
|
-
super
|
39
|
-
end
|
40
|
-
|
41
|
-
private def write_exception_to_stream(stream, env, exception, include_backtrace = false)
|
42
|
-
buffer = []
|
43
|
-
|
44
|
-
buffer << "While requesting resource #{env[Rack::PATH_INFO].inspect}, a fatal error occurred:"
|
45
|
-
|
46
|
-
while exception != nil
|
47
|
-
buffer << "\t#{exception.class.name}: #{exception.to_s}"
|
48
|
-
|
49
|
-
if include_backtrace
|
50
|
-
exception.backtrace.each do |line|
|
51
|
-
buffer << "\t\t#{line}"
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
exception = exception.cause
|
56
|
-
end
|
57
|
-
|
58
|
-
# We do this in one go so that lines don't get mixed up.
|
59
|
-
stream.puts buffer.join("\n")
|
60
|
-
end
|
61
|
-
|
62
|
-
# Generate a very simple fatal error response. This function should be unlikely to fail. Additionally, it generates a lowest common denominator response which should be suitable as a response to any kind of request. Ideally, this response is also not good or useful for any kind of higher level browser or API client, as this is not a normal error path but one that represents broken behaviour.
|
63
|
-
def fatal_error(env, exception)
|
64
|
-
body = StringIO.new
|
65
|
-
|
66
|
-
write_exception_to_stream(body, env, exception)
|
67
|
-
body.rewind
|
68
|
-
|
69
|
-
return [500, {HTTP::CONTENT_TYPE => "text/plain"}, body]
|
70
|
-
end
|
71
|
-
|
72
|
-
def log_exception(env, exception)
|
73
|
-
# An error has occurred, log it:
|
74
|
-
output = env['rack.errors'] || $stderr
|
75
|
-
write_exception_to_stream(output, env, exception, true)
|
76
|
-
end
|
77
|
-
|
78
|
-
def redirect(env, exception)
|
79
|
-
response = @app.call(env.merge(Rack::PATH_INFO => @location, Rack::REQUEST_METHOD => Rack::GET))
|
80
|
-
|
81
|
-
return [500, response[1], response[2]]
|
82
|
-
end
|
83
|
-
|
84
|
-
def call(env)
|
85
|
-
begin
|
86
|
-
return @app.call(env)
|
87
|
-
rescue Exception => exception
|
88
|
-
log_exception(env, exception)
|
89
|
-
|
90
|
-
# If the error occurred while accessing the error handler, we finish with a fatal error:
|
91
|
-
if env[Rack::PATH_INFO] == @location
|
92
|
-
return fatal_error(env, exception)
|
93
|
-
else
|
94
|
-
# If redirection fails, we also finish with a fatal error:
|
95
|
-
begin
|
96
|
-
return redirect(env, exception)
|
97
|
-
rescue
|
98
|
-
return fatal_error(env, exception)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
data/lib/utopia/redirector.rb
DELETED
@@ -1,164 +0,0 @@
|
|
1
|
-
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
# of this software and associated documentation files (the "Software"), to deal
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
8
|
-
# furnished to do so, subject to the following conditions:
|
9
|
-
#
|
10
|
-
# The above copyright notice and this permission notice shall be included in
|
11
|
-
# all copies or substantial portions of the Software.
|
12
|
-
#
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
# THE SOFTWARE.
|
20
|
-
|
21
|
-
require_relative 'middleware'
|
22
|
-
|
23
|
-
module Utopia
|
24
|
-
class FailedRequestError < StandardError
|
25
|
-
def initialize(resource_path, resource_status, error_path, error_status)
|
26
|
-
@resource_path = resource_path
|
27
|
-
@resource_status = resource_status
|
28
|
-
|
29
|
-
@error_path = error_path
|
30
|
-
@error_status = error_status
|
31
|
-
end
|
32
|
-
|
33
|
-
def to_s
|
34
|
-
"Requested resource #{@resource_path} resulted in a #{@resource_status} error. Requested error handler #{@error_path} resulted in a #{@error_status} error."
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
class Redirector
|
39
|
-
# This redirects directories to the directory + 'index'
|
40
|
-
DIRECTORY_INDEX = [/^(.*)\/$/, lambda{|prefix| [307, {HTTP::LOCATION => "#{prefix}index"}, []]}].freeze
|
41
|
-
|
42
|
-
# Redirects a whole source tree to a destination tree, given by the roots.
|
43
|
-
def self.moved(source_root, destination_root, max_age = 3600*24)
|
44
|
-
return [/^#{Regexp.escape(source_root)}(.*)$/, lambda{|match| destination_root + match[1]}]
|
45
|
-
end
|
46
|
-
|
47
|
-
def self.starts_with(source_root, destination_uri)
|
48
|
-
return [/^#{Regexp.escape(source_root)}/, destination_uri]
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def normalize_strings(strings)
|
54
|
-
normalized = {}
|
55
|
-
|
56
|
-
strings.each_pair do |key, value|
|
57
|
-
if Array === key
|
58
|
-
key.each { |s| normalized[s] = value }
|
59
|
-
else
|
60
|
-
normalized[key] = value
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
return normalized
|
65
|
-
end
|
66
|
-
|
67
|
-
def normalize_patterns(patterns)
|
68
|
-
normalized = []
|
69
|
-
|
70
|
-
patterns.each do |pattern|
|
71
|
-
*keys, uri = *pattern
|
72
|
-
|
73
|
-
keys.each do |key|
|
74
|
-
normalized.push([key, uri])
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
return normalized
|
79
|
-
end
|
80
|
-
|
81
|
-
public
|
82
|
-
|
83
|
-
def initialize(app, **options)
|
84
|
-
@app = app
|
85
|
-
|
86
|
-
@strings = options[:strings] || {}
|
87
|
-
@patterns = options[:patterns] || []
|
88
|
-
|
89
|
-
@patterns.collect! do |rule|
|
90
|
-
if Symbol === rule[0]
|
91
|
-
self.class.send(*rule)
|
92
|
-
else
|
93
|
-
rule
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
@strings = normalize_strings(@strings)
|
98
|
-
@patterns = normalize_patterns(@patterns)
|
99
|
-
|
100
|
-
@errors = options[:errors]
|
101
|
-
|
102
|
-
self.freeze
|
103
|
-
end
|
104
|
-
|
105
|
-
def freeze
|
106
|
-
@strings.freeze
|
107
|
-
@patterns.freeze
|
108
|
-
@errors.freeze
|
109
|
-
|
110
|
-
super
|
111
|
-
end
|
112
|
-
|
113
|
-
def cache_control(max_age)
|
114
|
-
# http://jacquesmattheij.com/301-redirects-a-dangerous-one-way-street
|
115
|
-
"max-age=#{max_age}"
|
116
|
-
end
|
117
|
-
|
118
|
-
# We cache 301 redirects for 24 hours.
|
119
|
-
DEFAULT_MAX_AGE = 3600*24
|
120
|
-
|
121
|
-
def redirect(uri, match_data, status: 301, max_age: DEFAULT_MAX_AGE)
|
122
|
-
cache_control = self.cache_control(max_age)
|
123
|
-
|
124
|
-
if uri.respond_to? :call
|
125
|
-
uri = uri.call(match_data)
|
126
|
-
end
|
127
|
-
|
128
|
-
return [status, {HTTP::LOCATION => uri.to_s, HTTP::CACHE_CONTROL => cache_control}, []]
|
129
|
-
end
|
130
|
-
|
131
|
-
def call(env)
|
132
|
-
base_path = env[Rack::PATH_INFO]
|
133
|
-
|
134
|
-
if uri = @strings[base_path]
|
135
|
-
return redirect(@strings[base_path], base_path)
|
136
|
-
end
|
137
|
-
|
138
|
-
@patterns.each do |pattern, uri|
|
139
|
-
if match_data = pattern.match(base_path)
|
140
|
-
result = redirect(uri, match_data)
|
141
|
-
|
142
|
-
return result if result != nil
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
response = @app.call(env)
|
147
|
-
|
148
|
-
if @errors && response[0] >= 400 && uri = @errors[response[0]]
|
149
|
-
error_request = env.merge(Rack::PATH_INFO => uri, Rack::REQUEST_METHOD => Rack::GET)
|
150
|
-
error_response = @app.call(error_request)
|
151
|
-
|
152
|
-
if error_response[0] >= 400
|
153
|
-
raise FailedRequestError.new(env[Rack::PATH_INFO], response[0], uri, error_response[0])
|
154
|
-
else
|
155
|
-
# Feed the error code back with the error document
|
156
|
-
error_response[0] = response[0]
|
157
|
-
return error_response
|
158
|
-
end
|
159
|
-
else
|
160
|
-
return response
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
@@ -1 +0,0 @@
|
|
1
|
-
../../../../materials/utopia.svg
|
data/setup/site/public/_static
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
../pages/_static
|
@@ -1,26 +0,0 @@
|
|
1
|
-
|
2
|
-
use Utopia::Redirector,
|
3
|
-
patterns: [
|
4
|
-
Utopia::Redirector::DIRECTORY_INDEX,
|
5
|
-
[:moved, "/a", "/b"],
|
6
|
-
],
|
7
|
-
strings: {
|
8
|
-
'/' => '/c',
|
9
|
-
},
|
10
|
-
errors: {
|
11
|
-
404 => "/error",
|
12
|
-
418 => "/teapot",
|
13
|
-
}
|
14
|
-
|
15
|
-
def error_handler(env)
|
16
|
-
request = Rack::Request.new(env)
|
17
|
-
if request.path_info == "/error"
|
18
|
-
[200, {}, ["File not found :("]]
|
19
|
-
elsif request.path_info == "/teapot"
|
20
|
-
[418, {}, ["I'm a teapot!"]]
|
21
|
-
else
|
22
|
-
[404, {}, []]
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
run self.method(:error_handler)
|