utopia 0.12.6 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -2
- data/Gemfile +6 -0
- data/README.md +48 -14
- data/Rakefile +5 -0
- data/bin/utopia +132 -15
- data/lib/utopia.rb +13 -10
- data/lib/utopia/content.rb +140 -0
- data/lib/utopia/content/link.rb +124 -0
- data/lib/utopia/content/links.rb +228 -0
- data/lib/utopia/content/node.rb +387 -0
- data/lib/utopia/content/processor.rb +128 -0
- data/lib/utopia/content/tag.rb +102 -0
- data/lib/utopia/controller.rb +137 -0
- data/lib/utopia/controller/action.rb +112 -0
- data/lib/utopia/controller/base.rb +174 -0
- data/lib/utopia/{middleware/controller → controller}/variables.rb +36 -38
- data/lib/utopia/exception_handler.rb +79 -0
- data/lib/utopia/extensions/array.rb +2 -2
- data/lib/utopia/localization.rb +143 -0
- data/lib/utopia/mail_exceptions.rb +136 -0
- data/lib/utopia/middleware.rb +7 -22
- data/lib/utopia/path.rb +150 -60
- data/lib/utopia/redirector.rb +152 -0
- data/lib/utopia/{extensions/hash.rb → session.rb} +4 -6
- data/lib/utopia/session/encrypted_cookie.rb +46 -48
- data/lib/utopia/{middleware/directory_index.rb → session/lazy_hash.rb} +44 -27
- data/lib/utopia/static.rb +255 -0
- data/lib/utopia/tags/deferred.rb +12 -8
- data/lib/utopia/tags/environment.rb +18 -6
- data/lib/utopia/tags/node.rb +12 -8
- data/lib/utopia/tags/override.rb +12 -12
- data/lib/utopia/version.rb +1 -1
- data/setup/.bowerrc +3 -0
- data/{lib/utopia/setup → setup}/Gemfile +1 -1
- data/setup/Rakefile +4 -0
- data/{lib/utopia/setup → setup}/cache/head/readme.txt +0 -0
- data/{lib/utopia/setup → setup}/cache/meta/readme.txt +0 -0
- data/setup/config.ru +64 -0
- data/{lib/utopia/setup → setup}/lib/readme.txt +0 -0
- data/{lib/utopia/setup → setup}/pages/_heading.xnode +0 -0
- data/{lib/utopia/setup → setup}/pages/_page.xnode +1 -1
- data/{lib/utopia/setup → setup}/pages/_static/icon.png +0 -0
- data/setup/pages/_static/site.css +70 -0
- data/{lib/utopia/setup → setup}/pages/errors/exception.xnode +0 -0
- data/{lib/utopia/setup → setup}/pages/errors/file-not-found.xnode +0 -0
- data/{lib/utopia/setup → setup}/pages/links.yaml +0 -0
- data/setup/pages/welcome/index.xnode +17 -0
- data/{lib/utopia/setup → setup}/public/readme.txt +0 -0
- data/spec/utopia/content/link_spec.rb +108 -0
- data/spec/utopia/content/links/foo/index.xnode +0 -0
- data/spec/utopia/content/links/foo/links.yaml +2 -0
- data/spec/utopia/content/links/foo/test.de.xnode +0 -0
- data/spec/utopia/content/links/foo/test.en.xnode +0 -0
- data/spec/utopia/content/links/links.yaml +9 -0
- data/spec/utopia/content/links/welcome.xnode +0 -0
- data/spec/utopia/content/localized/five/index.en.xnode +0 -0
- data/spec/utopia/content/localized/four/index.en.xnode +0 -0
- data/spec/utopia/content/localized/four/index.zh.xnode +0 -0
- data/spec/utopia/content/localized/four/links.yaml +4 -0
- data/spec/utopia/content/localized/links.yaml +16 -0
- data/spec/utopia/content/localized/one.xnode +0 -0
- data/spec/utopia/content/localized/three/index.xnode +0 -0
- data/spec/utopia/content/localized/two.en.xnode +0 -0
- data/spec/utopia/content/localized/two.zh.xnode +0 -0
- data/spec/utopia/content/node/ordered/first.xnode +0 -0
- data/spec/utopia/content/node/ordered/index.xnode +0 -0
- data/spec/utopia/content/node/ordered/links.yaml +4 -0
- data/spec/utopia/content/node/ordered/second.xnode +0 -0
- data/spec/utopia/content/node/related/foo.en.xnode +0 -0
- data/spec/utopia/content/node/related/foo.ja.xnode +0 -0
- data/spec/utopia/content/node/related/links.yaml +4 -0
- data/spec/utopia/content/node_spec.rb +63 -0
- data/spec/utopia/{middleware/content_spec.rb → content/processor_spec.rb} +34 -23
- data/spec/utopia/content_spec.rb +87 -0
- data/spec/utopia/content_spec.ru +10 -0
- data/spec/utopia/{middleware/controller_spec.rb → controller_spec.rb} +61 -16
- data/spec/utopia/controller_spec.ru +4 -0
- data/spec/utopia/extensions_spec.rb +6 -17
- data/spec/utopia/localization_spec.rb +60 -0
- data/spec/utopia/localization_spec.ru +11 -0
- data/{lib/utopia/tags.rb → spec/utopia/middleware_spec.rb} +8 -14
- data/spec/utopia/{middleware/content_root → pages}/_heading.xnode +0 -0
- data/spec/utopia/pages/content/_show-value.xnode +1 -0
- data/spec/utopia/pages/content/test-partial.xnode +1 -0
- data/spec/utopia/pages/controller/controller.rb +28 -0
- data/spec/utopia/pages/controller/index.xnode +1 -0
- data/spec/utopia/pages/controller/nested/controller.rb +4 -0
- data/spec/utopia/{middleware/content_root → pages}/index.xnode +0 -0
- data/spec/utopia/pages/localized.de.txt +1 -0
- data/spec/utopia/pages/localized.en.txt +1 -0
- data/spec/utopia/pages/localized.jp.txt +1 -0
- data/spec/utopia/pages/node/index.xnode +1 -0
- data/spec/utopia/pages/test.txt +1 -0
- data/spec/utopia/path_spec.rb +109 -0
- data/spec/utopia/rack_spec.rb +2 -0
- data/spec/utopia/session_spec.rb +82 -0
- data/spec/utopia/session_spec.ru +20 -0
- data/spec/utopia/spec_helper.rb +16 -0
- data/{lib/utopia/extensions/string.rb → spec/utopia/static_spec.rb} +24 -15
- data/spec/utopia/static_spec.ru +4 -0
- data/utopia.gemspec +3 -3
- metadata +138 -54
- data/lib/utopia/extensions/regexp.rb +0 -33
- data/lib/utopia/link.rb +0 -288
- data/lib/utopia/middleware/all.rb +0 -33
- data/lib/utopia/middleware/content.rb +0 -157
- data/lib/utopia/middleware/content/node.rb +0 -386
- data/lib/utopia/middleware/content/processor.rb +0 -123
- data/lib/utopia/middleware/controller.rb +0 -130
- data/lib/utopia/middleware/controller/action.rb +0 -121
- data/lib/utopia/middleware/controller/base.rb +0 -184
- data/lib/utopia/middleware/exception_handler.rb +0 -80
- data/lib/utopia/middleware/localization.rb +0 -147
- data/lib/utopia/middleware/localization/name.rb +0 -69
- data/lib/utopia/middleware/mail_exceptions.rb +0 -138
- data/lib/utopia/middleware/redirector.rb +0 -146
- data/lib/utopia/middleware/requester.rb +0 -126
- data/lib/utopia/middleware/static.rb +0 -295
- data/lib/utopia/setup.rb +0 -60
- data/lib/utopia/setup/config.ru +0 -47
- data/lib/utopia/setup/pages/_static/background.png +0 -0
- data/lib/utopia/setup/pages/_static/site.css +0 -48
- data/lib/utopia/setup/pages/welcome/index.xnode +0 -7
- data/lib/utopia/tag.rb +0 -105
- data/lib/utopia/tags/all.rb +0 -34
@@ -1,80 +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 'utopia/middleware'
|
22
|
-
require 'utopia/extensions/string'
|
23
|
-
|
24
|
-
module Utopia
|
25
|
-
module Middleware
|
26
|
-
class ExceptionHandler
|
27
|
-
def initialize(app, location)
|
28
|
-
@app = app
|
29
|
-
|
30
|
-
@location = location
|
31
|
-
end
|
32
|
-
|
33
|
-
def fatal_error(env, ex)
|
34
|
-
body = StringIO.new
|
35
|
-
|
36
|
-
body.puts "<!DOCTYPE html><html><head><title>Fatal Error</title></head><body>"
|
37
|
-
body.puts "<h1>Fatal Error</h1>"
|
38
|
-
body.puts "<p>While requesting resource #{env['PATH_INFO'].to_html}, a fatal error occurred.</p>"
|
39
|
-
body.puts "<blockquote><strong>#{ex.class.name.to_html}</strong>: #{ex.to_s.to_html}</blockquote>"
|
40
|
-
body.puts "<p>There is nothing more we can do to fix the problem at this point.</p>"
|
41
|
-
body.puts "<p>We apologize for the inconvenience.</p>"
|
42
|
-
body.puts "</body></html>"
|
43
|
-
body.rewind
|
44
|
-
|
45
|
-
return [400, {"Content-Type" => "text/html"}, body]
|
46
|
-
end
|
47
|
-
|
48
|
-
def redirect(env, ex)
|
49
|
-
return @app.call(env.merge('PATH_INFO' => @location, 'REQUEST_METHOD' => 'GET'))
|
50
|
-
end
|
51
|
-
|
52
|
-
def call(env)
|
53
|
-
begin
|
54
|
-
return @app.call(env)
|
55
|
-
rescue Exception => ex
|
56
|
-
log = ::Logger.new(env['rack.errors'])
|
57
|
-
|
58
|
-
log.error "Exception #{ex.to_s.dump}!"
|
59
|
-
|
60
|
-
ex.backtrace.each do |bt|
|
61
|
-
log.error bt
|
62
|
-
end
|
63
|
-
|
64
|
-
if env['PATH_INFO'] == @location
|
65
|
-
return fatal_error(env, ex)
|
66
|
-
else
|
67
|
-
|
68
|
-
# If redirection fails
|
69
|
-
begin
|
70
|
-
return redirect(env, ex)
|
71
|
-
rescue
|
72
|
-
return fatal_error(env, ex)
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
@@ -1,147 +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 'utopia/middleware'
|
22
|
-
require 'utopia/middleware/localization/name'
|
23
|
-
|
24
|
-
module Rack
|
25
|
-
class Request
|
26
|
-
def current_locale
|
27
|
-
env["utopia.current_locale"]
|
28
|
-
end
|
29
|
-
|
30
|
-
def all_locales
|
31
|
-
localization.all_locales
|
32
|
-
end
|
33
|
-
|
34
|
-
def localization
|
35
|
-
env["utopia.localization"]
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
module Utopia
|
41
|
-
module Middleware
|
42
|
-
|
43
|
-
class Localization
|
44
|
-
def initialize(app, options = {})
|
45
|
-
@app = app
|
46
|
-
|
47
|
-
@default_locale = options[:default] || "en"
|
48
|
-
@all_locales = options[:all] || ["en"]
|
49
|
-
|
50
|
-
@nonlocalized = options[:nonlocalized] || []
|
51
|
-
end
|
52
|
-
|
53
|
-
def named_locale(resource_name)
|
54
|
-
if resource_name
|
55
|
-
Name.extract_locale(resource_name, @all_locales)
|
56
|
-
else
|
57
|
-
nil
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
attr :all_locales
|
62
|
-
attr :default_locale
|
63
|
-
|
64
|
-
def check_resource(resource_name, resource_locale, env)
|
65
|
-
localized_name = Name.localized(resource_name, resource_locale, @all_locales).join(".")
|
66
|
-
localized_path = Path.create(env["PATH_INFO"]).dirname + localized_name
|
67
|
-
|
68
|
-
localization_probe = env.dup
|
69
|
-
localization_probe["REQUEST_METHOD"] = "HEAD"
|
70
|
-
localization_probe["PATH_INFO"] = localized_path.to_s
|
71
|
-
|
72
|
-
# Find out if a resource exists for the requested localization
|
73
|
-
return [localized_path, @app.call(localization_probe)]
|
74
|
-
end
|
75
|
-
|
76
|
-
def nonlocalized?(env)
|
77
|
-
@nonlocalized.each do |pattern|
|
78
|
-
case pattern
|
79
|
-
when String
|
80
|
-
return true if pattern == env["PATH_INFO"]
|
81
|
-
when Regexp
|
82
|
-
return true if pattern.match(env["PATH_INFO"])
|
83
|
-
when pattern.respond_to?(:call)
|
84
|
-
return true if pattern.call(env)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
return false
|
89
|
-
end
|
90
|
-
|
91
|
-
def call(env)
|
92
|
-
# Check for a non-localized resource.
|
93
|
-
if nonlocalized?(env)
|
94
|
-
return @app.call(env)
|
95
|
-
end
|
96
|
-
|
97
|
-
# Otherwise, we need to check if the resource has been localized based on the request and referer parameters.
|
98
|
-
path = Path.create(env["PATH_INFO"])
|
99
|
-
env["utopia.localization"] = self
|
100
|
-
|
101
|
-
referer_locale = named_locale(env['HTTP_REFERER'])
|
102
|
-
request_locale = named_locale(path.basename)
|
103
|
-
resource_name = Name.nonlocalized(path.basename, @all_locales).join(".")
|
104
|
-
|
105
|
-
response = nil
|
106
|
-
if request_locale
|
107
|
-
env["utopia.current_locale"] = request_locale
|
108
|
-
resource_path, response = check_resource(resource_name, request_locale, env)
|
109
|
-
elsif referer_locale
|
110
|
-
env["utopia.current_locale"] = referer_locale
|
111
|
-
resource_path, response = check_resource(resource_name, referer_locale, env)
|
112
|
-
end
|
113
|
-
|
114
|
-
# If the previous checks failed, i.e. there was no request/referer locale
|
115
|
-
# or the response was 404 (i.e. no localised resource), we check for the
|
116
|
-
# @default_locale
|
117
|
-
if response == nil || response[0] >= 400
|
118
|
-
env["utopia.current_locale"] = @default_locale
|
119
|
-
resource_path, response = check_resource(resource_name, @default_locale, env)
|
120
|
-
end
|
121
|
-
|
122
|
-
# If the response is 2xx, we have a localised resource
|
123
|
-
if response[0] < 300
|
124
|
-
# If the original request was the same as the localized request,
|
125
|
-
if path.basename == resource_path.basename
|
126
|
-
# The resource URI is correct.
|
127
|
-
return @app.call(env)
|
128
|
-
else
|
129
|
-
# Redirect to the correct resource URI.
|
130
|
-
return [307, {"Location" => resource_path.to_s}, []]
|
131
|
-
end
|
132
|
-
elsif response[0] < 400
|
133
|
-
# We have encountered a redirect while accessing the localized resource
|
134
|
-
return response
|
135
|
-
else
|
136
|
-
# A localized resource was not found, return the unlocalised resource path,
|
137
|
-
if path.basename == resource_name
|
138
|
-
return @app.call(env)
|
139
|
-
else
|
140
|
-
return [307, {"Location" => (path.dirname + resource_name).to_s}, []]
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
end
|
147
|
-
end
|
@@ -1,69 +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
|
-
module Utopia
|
22
|
-
module Middleware
|
23
|
-
class Localization
|
24
|
-
|
25
|
-
module Name
|
26
|
-
def self.nonlocalized(resource_name, all_locales)
|
27
|
-
resource_name = resource_name.split(".") unless resource_name.kind_of? Array
|
28
|
-
|
29
|
-
# We either have a file extension or an existing locale
|
30
|
-
if all_locales.include?(resource_name[-1])
|
31
|
-
resource_name.delete_at(-1)
|
32
|
-
elsif all_locales.include?(resource_name[-2])
|
33
|
-
resource_name.delete_at(-2)
|
34
|
-
end
|
35
|
-
|
36
|
-
resource_name
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.extract_locale(resource_name, all_locales)
|
40
|
-
resource_name = resource_name.split(".") unless resource_name.kind_of? Array
|
41
|
-
|
42
|
-
# We either have a file extension or an existing locale
|
43
|
-
if all_locales.include?(resource_name[-1])
|
44
|
-
return resource_name[-1]
|
45
|
-
elsif all_locales.include?(resource_name[-2])
|
46
|
-
return resource_name[-2]
|
47
|
-
end
|
48
|
-
|
49
|
-
return nil
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.localized(resource_name, locale, all_locales)
|
53
|
-
nonlocalized_name = nonlocalized(resource_name, all_locales)
|
54
|
-
|
55
|
-
if locale == nil
|
56
|
-
return nonlocalized_name
|
57
|
-
end
|
58
|
-
|
59
|
-
if nonlocalized_name.size == 1
|
60
|
-
return nonlocalized_name.push(locale)
|
61
|
-
else
|
62
|
-
return nonlocalized_name.insert(-2, locale)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
@@ -1,138 +0,0 @@
|
|
1
|
-
# Copyright, 2013, 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 Middleware
|
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 MailExceptions
|
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
|
@@ -1,146 +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 'utopia/middleware'
|
22
|
-
require 'utopia/extensions/regexp'
|
23
|
-
require 'utopia/extensions/string'
|
24
|
-
|
25
|
-
module Utopia
|
26
|
-
module Middleware
|
27
|
-
|
28
|
-
class FailedRequestError < StandardError
|
29
|
-
def initialize(resource_path, resource_status, error_path, error_status)
|
30
|
-
@resource_path = resource_path
|
31
|
-
@resource_status = resource_status
|
32
|
-
|
33
|
-
@error_path = error_path
|
34
|
-
@error_status = error_status
|
35
|
-
end
|
36
|
-
|
37
|
-
def to_s
|
38
|
-
"Requested resource #{@resource_path} resulted in a #{@resource_status} error. Requested error handler #{@error_path} resulted in a #{@error_status} error."
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
class Redirector
|
43
|
-
# Redirects a whole source tree to a destination tree, given by the roots.
|
44
|
-
def self.moved(source_root, destination_root)
|
45
|
-
return [
|
46
|
-
/^#{Regexp.escape(source_root)}(.*)$/,
|
47
|
-
lambda do |match|
|
48
|
-
[301, {"Location" => (destination_root + match[1]).to_s}, []]
|
49
|
-
end
|
50
|
-
]
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
def normalize_strings(strings)
|
55
|
-
normalized = {}
|
56
|
-
|
57
|
-
strings.each_pair do |key, value|
|
58
|
-
if Array === key
|
59
|
-
key.each { |s| normalized[s] = value }
|
60
|
-
else
|
61
|
-
normalized[key] = value
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
return normalized
|
66
|
-
end
|
67
|
-
|
68
|
-
def normalize_patterns(patterns)
|
69
|
-
normalized = []
|
70
|
-
|
71
|
-
patterns.each do |pattern|
|
72
|
-
uri = pattern.pop
|
73
|
-
|
74
|
-
pattern.each do |key|
|
75
|
-
normalized.push([key, uri])
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
return normalized
|
80
|
-
end
|
81
|
-
|
82
|
-
public
|
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
|
-
end
|
102
|
-
|
103
|
-
def redirect(uri, match_data)
|
104
|
-
if uri.respond_to? :call
|
105
|
-
return uri.call(match_data)
|
106
|
-
else
|
107
|
-
return [301, {"Location" => uri.to_s}, []]
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def call(env)
|
112
|
-
base_path = env['PATH_INFO']
|
113
|
-
|
114
|
-
if uri = @strings[base_path]
|
115
|
-
return redirect(@strings[base_path], base_path)
|
116
|
-
end
|
117
|
-
|
118
|
-
@patterns.each do |pattern, uri|
|
119
|
-
if match_data = pattern.match(base_path)
|
120
|
-
result = redirect(uri, match_data)
|
121
|
-
|
122
|
-
return result if result != nil
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
response = @app.call(env)
|
127
|
-
|
128
|
-
if @errors && response[0] >= 400 && uri = @errors[response[0]]
|
129
|
-
error_request = env.merge("PATH_INFO" => uri, "REQUEST_METHOD" => "GET")
|
130
|
-
error_response = @app.call(error_request)
|
131
|
-
|
132
|
-
if error_response[0] >= 400
|
133
|
-
raise FailedRequestError.new(env['PATH_INFO'], response[0], uri, error_response[0])
|
134
|
-
else
|
135
|
-
# Feed the error code back with the error document
|
136
|
-
error_response[0] = response[0]
|
137
|
-
return error_response
|
138
|
-
end
|
139
|
-
else
|
140
|
-
return response
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
end
|
146
|
-
end
|