utopia 0.12.6 → 1.0.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.
- 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
|