kiss 1.1 → 1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/bin/kiss +151 -34
- data/data/scaffold.tgz +0 -0
- data/lib/kiss.rb +389 -742
- data/lib/kiss/accessors/controller.rb +47 -0
- data/lib/kiss/accessors/request.rb +106 -0
- data/lib/kiss/accessors/template.rb +23 -0
- data/lib/kiss/action.rb +502 -132
- data/lib/kiss/bench.rb +14 -5
- data/lib/kiss/debug.rb +14 -6
- data/lib/kiss/exception_report.rb +22 -299
- data/lib/kiss/ext/core.rb +700 -0
- data/lib/kiss/ext/rack.rb +33 -0
- data/lib/kiss/ext/sequel_database.rb +47 -0
- data/lib/kiss/ext/sequel_mysql_dataset.rb +23 -0
- data/lib/kiss/form.rb +404 -179
- data/lib/kiss/form/field.rb +183 -307
- data/lib/kiss/form/field_types.rb +239 -0
- data/lib/kiss/format.rb +88 -70
- data/lib/kiss/html/exception_report.css +222 -0
- data/lib/kiss/html/exception_report.html +210 -0
- data/lib/kiss/iterator.rb +14 -12
- data/lib/kiss/login.rb +8 -8
- data/lib/kiss/mailer.rb +68 -66
- data/lib/kiss/model.rb +323 -36
- data/lib/kiss/rack/bench.rb +16 -8
- data/lib/kiss/rack/email_errors.rb +25 -15
- data/lib/kiss/rack/errors_ok.rb +2 -2
- data/lib/kiss/rack/facebook.rb +6 -6
- data/lib/kiss/rack/file_not_found.rb +10 -8
- data/lib/kiss/rack/log_exceptions.rb +3 -3
- data/lib/kiss/rack/recorder.rb +2 -2
- data/lib/kiss/rack/show_debug.rb +2 -2
- data/lib/kiss/rack/show_exceptions.rb +2 -2
- data/lib/kiss/request.rb +435 -0
- data/lib/kiss/sequel_session.rb +15 -14
- data/lib/kiss/static_file.rb +20 -13
- data/lib/kiss/template.rb +327 -0
- metadata +60 -25
- data/lib/kiss/controller_accessors.rb +0 -81
- data/lib/kiss/hacks.rb +0 -188
- data/lib/kiss/sequel_mysql.rb +0 -25
- data/lib/kiss/template_methods.rb +0 -167
data/lib/kiss/rack/bench.rb
CHANGED
@@ -7,15 +7,15 @@ module Rack
|
|
7
7
|
# Rack::Bench shows total request duration for any request.
|
8
8
|
class Bench
|
9
9
|
def initialize(app)
|
10
|
-
@
|
10
|
+
@_app = app
|
11
11
|
end
|
12
12
|
|
13
13
|
def call(env)
|
14
14
|
start_time = Time.now
|
15
|
-
code, headers, body = @
|
15
|
+
code, headers, body = @_app.call(env)
|
16
16
|
end_time = Time.now
|
17
17
|
|
18
|
-
|
18
|
+
html = <<-EOT
|
19
19
|
<style>
|
20
20
|
.kiss_bench {
|
21
21
|
text-align: left;
|
@@ -35,17 +35,25 @@ module Rack
|
|
35
35
|
color: #930;
|
36
36
|
text-decoration: underline;
|
37
37
|
}
|
38
|
+
.kiss_bench small {
|
39
|
+
font-family: arial, sans-serif;
|
40
|
+
float: right;
|
41
|
+
margin-left: 8px;
|
42
|
+
color: #a60;
|
43
|
+
text-align: right;
|
44
|
+
white-space: nowrap;
|
45
|
+
}
|
38
46
|
</style>
|
39
47
|
<div class="kiss_bench">
|
40
|
-
<
|
41
|
-
<
|
48
|
+
<small>kiss bench</small>
|
49
|
+
<tt><b>TOTAL request duration: #{sprintf("%0.3f", end_time.to_f - start_time.to_f)} s</b></tt>
|
42
50
|
</div>
|
43
51
|
EOT
|
44
52
|
|
45
|
-
body
|
46
|
-
headers['Content-Length'] =
|
53
|
+
body = body.prepend_html(html, 'body')
|
54
|
+
headers['Content-Length'] = body.content_length.to_s
|
47
55
|
|
48
|
-
[ code, headers,
|
56
|
+
[ code, headers, body ]
|
49
57
|
end
|
50
58
|
end
|
51
59
|
end
|
@@ -1,17 +1,27 @@
|
|
1
|
+
# DEPRECATED
|
2
|
+
# This module is now deprecated as slated to be removed in Kiss 1.6.
|
3
|
+
|
1
4
|
module Rack
|
2
5
|
# Rack::EmailErrors sends error responses (code 5xx) to email addresses as
|
3
6
|
# specified in the Rack::Builder config.
|
4
7
|
class EmailErrors
|
5
|
-
def initialize(app,
|
6
|
-
@
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
def initialize(app, *args)
|
9
|
+
@_app = app
|
10
|
+
|
11
|
+
if (options = args.first).is_a?(Hash)
|
12
|
+
@_agent = options[:agent]
|
13
|
+
@_subject = options[:subject]
|
14
|
+
@_from = options[:from]
|
15
|
+
@_to = options[:to]
|
16
|
+
else
|
17
|
+
@_agent, @_subject, @_from, *@_to = *args
|
18
|
+
end
|
19
|
+
|
20
|
+
@_agent = '/usr/sbin/sendmail -t' if @_agent == :sendmail
|
11
21
|
end
|
12
22
|
|
13
23
|
def call(env)
|
14
|
-
code, headers, body = @
|
24
|
+
code, headers, body = @_app.call(env)
|
15
25
|
|
16
26
|
if code >= 500 && code < 600
|
17
27
|
begin # rescue any errors in message composition and sending
|
@@ -20,9 +30,9 @@ module Rack
|
|
20
30
|
|
21
31
|
message = <<-EOT
|
22
32
|
Content-type: text/html
|
23
|
-
From: #{@
|
24
|
-
To: #{@
|
25
|
-
Subject: #{@
|
33
|
+
From: #{@_from}
|
34
|
+
To: #{@_to.join(', ')}
|
35
|
+
Subject: #{@_subject} - #{error_type}#{ error_message ? ": #{error_message}" : ''}
|
26
36
|
|
27
37
|
EOT
|
28
38
|
|
@@ -30,15 +40,15 @@ EOT
|
|
30
40
|
message += part
|
31
41
|
end
|
32
42
|
|
33
|
-
if @
|
34
|
-
IO.popen(@
|
43
|
+
if @_agent.is_a?(String)
|
44
|
+
IO.popen(@_agent, "w") do |pipe|
|
35
45
|
pipe.puts(message)
|
36
46
|
end
|
37
47
|
else
|
38
48
|
require 'net/smtp' unless defined?(Net::SMTP)
|
39
|
-
smtp = @
|
49
|
+
smtp = @_agent.is_a?(Net::SMTP) ? @_agent : Net::SMTP.new('localhost')
|
40
50
|
smtp.start do |smtp|
|
41
|
-
smtp.send_message(message, @
|
51
|
+
smtp.send_message(message, @_from, *@_to)
|
42
52
|
end
|
43
53
|
end
|
44
54
|
rescue
|
@@ -55,7 +65,7 @@ EOT
|
|
55
65
|
</body>
|
56
66
|
</html>
|
57
67
|
EOT
|
58
|
-
headers['Content-Length'] = body.
|
68
|
+
headers['Content-Length'] = body.content_length.to_s
|
59
69
|
end
|
60
70
|
|
61
71
|
[ code, headers, body ]
|
data/lib/kiss/rack/errors_ok.rb
CHANGED
@@ -3,11 +3,11 @@ module Rack
|
|
3
3
|
# and HTML entities that are invalid as FBML responses.
|
4
4
|
class ErrorsOK
|
5
5
|
def initialize(app)
|
6
|
-
@
|
6
|
+
@_app = app
|
7
7
|
end
|
8
8
|
|
9
9
|
def call(env)
|
10
|
-
code, headers, body = @
|
10
|
+
code, headers, body = @_app.call(env)
|
11
11
|
|
12
12
|
if code >= 500 && code < 600
|
13
13
|
code = 200
|
data/lib/kiss/rack/facebook.rb
CHANGED
@@ -3,11 +3,11 @@ module Rack
|
|
3
3
|
# and HTML entities that are invalid as FBML responses.
|
4
4
|
class Facebook
|
5
5
|
def initialize(app)
|
6
|
-
@
|
6
|
+
@_app = app
|
7
7
|
end
|
8
8
|
|
9
9
|
def call(env)
|
10
|
-
code, headers, body = @
|
10
|
+
code, headers, body = @_app.call(env)
|
11
11
|
|
12
12
|
if code >= 500 && code < 600
|
13
13
|
code = 200
|
@@ -17,11 +17,11 @@ module Rack
|
|
17
17
|
body.each {|p| contents += p }
|
18
18
|
|
19
19
|
contents.gsub!(/txmt:\/\//, 'http://textmate.local/')
|
20
|
-
contents.gsub!('<body>','<div class="body">')
|
21
|
-
contents.gsub!('</body>','</div>')
|
22
|
-
contents.gsub!('<wbr/>','')
|
20
|
+
contents.gsub!('<body>', '<div class="body">')
|
21
|
+
contents.gsub!('</body>', '</div>')
|
22
|
+
contents.gsub!('<wbr/>', '')
|
23
23
|
|
24
|
-
headers['Content-Length'] = contents.
|
24
|
+
headers['Content-Length'] = contents.content_length.to_s
|
25
25
|
|
26
26
|
[ code, headers, contents ]
|
27
27
|
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module Rack
|
2
|
-
# Rack::FileNotFound rescues
|
2
|
+
# Rack::FileNotFound rescues file-not-found exceptions
|
3
3
|
# (raised when action template files are not found) and returns an
|
4
4
|
# HTTP 404 error response.
|
5
5
|
class FileNotFound
|
6
|
-
def initialize(app,
|
7
|
-
@
|
8
|
-
|
6
|
+
def initialize(app, options = {})
|
7
|
+
@_app = app
|
8
|
+
|
9
|
+
path = options[:path]
|
10
|
+
@_body = path ? (
|
9
11
|
::File.file?(path) ?
|
10
12
|
::File.read(path) :
|
11
13
|
template('could not find specified FileNotFound error document')
|
@@ -13,12 +15,12 @@ module Rack
|
|
13
15
|
end
|
14
16
|
|
15
17
|
def call(env)
|
16
|
-
code, headers, body = @
|
17
|
-
rescue Kiss::
|
18
|
+
code, headers, body = @_app.call(env)
|
19
|
+
rescue Kiss::FileNotFoundError => e
|
18
20
|
[ 404, {
|
19
21
|
"Content-Type" => "text/html",
|
20
|
-
"Content-Length" => @
|
21
|
-
}, @
|
22
|
+
"Content-Length" => @_body.content_length.to_s
|
23
|
+
}, @_body ]
|
22
24
|
end
|
23
25
|
|
24
26
|
def template(error = nil)
|
@@ -3,12 +3,12 @@ module Rack
|
|
3
3
|
# Functionality moved to Kiss#initialize (lib/kiss.rb).
|
4
4
|
|
5
5
|
class LogExceptions
|
6
|
-
def initialize(app,path)
|
7
|
-
@
|
6
|
+
def initialize(app, path)
|
7
|
+
@_app = app
|
8
8
|
end
|
9
9
|
|
10
10
|
def call(env)
|
11
|
-
@
|
11
|
+
@_app.call(env)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
data/lib/kiss/rack/recorder.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Rack
|
2
2
|
class Recorder
|
3
3
|
def initialize(app)
|
4
|
-
@
|
4
|
+
@_app = app
|
5
5
|
|
6
6
|
@@filepath ||= begin
|
7
7
|
puts "Yo. Starting app..."
|
@@ -11,7 +11,7 @@ module Rack
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def call(env)
|
14
|
-
code, headers, body = @
|
14
|
+
code, headers, body = @_app.call(env)
|
15
15
|
|
16
16
|
puts "OK, that was fun."
|
17
17
|
puts "Would save some request and response data here."
|
data/lib/kiss/rack/show_debug.rb
CHANGED
data/lib/kiss/request.rb
ADDED
@@ -0,0 +1,435 @@
|
|
1
|
+
class Kiss
|
2
|
+
class Request
|
3
|
+
|
4
|
+
_attr_accessor :controller
|
5
|
+
|
6
|
+
# Data pertaining to the current request.
|
7
|
+
_attr_reader :env, :protocol, :host, :request, :response,
|
8
|
+
:exception_cache, :exception_email_sent, :path
|
9
|
+
|
10
|
+
# Processes and responds to a request.
|
11
|
+
# Returns array of response code, headers, and body.
|
12
|
+
def initialize(env, controller, passed_config = {})
|
13
|
+
@_controller = controller
|
14
|
+
@_config = passed_config
|
15
|
+
|
16
|
+
@_files_cached_this_request = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
@_env = env
|
21
|
+
|
22
|
+
if @_controller.rack_file && (
|
23
|
+
(env["PATH_INFO"] == '/favicon.ico') ||
|
24
|
+
(env["PATH_INFO"].sub!(/\A#{@_controller.asset_uri}/, ''))
|
25
|
+
)
|
26
|
+
return @_controller.rack_file.call(env)
|
27
|
+
elsif env["PATH_INFO"] =~ /favicon\.\w{3}\Z/
|
28
|
+
return [404, {'Content-type' => 'text/html'}, 'File not found']
|
29
|
+
end
|
30
|
+
|
31
|
+
@_request = Rack::Request.new(env)
|
32
|
+
|
33
|
+
@_protocol, @_app_host = (@_request.server.split(/\:\/\//, 2) rescue ['', ''])
|
34
|
+
@_app_host = @_config[:app_host] if @_config[:app_host]
|
35
|
+
@_app_uri = @_config[:app_uri] || @_request.script_name || ''
|
36
|
+
|
37
|
+
@_host ||= @_request.host rescue ''
|
38
|
+
|
39
|
+
# unfreeze path
|
40
|
+
@_path = "#{@_request.path_info}" || '/'
|
41
|
+
|
42
|
+
# remove extra path noise +[..][R]+GET...
|
43
|
+
@_path.sub!(/\++(\[.*?\]\+*)*\[R\].*/, '')
|
44
|
+
|
45
|
+
@_params = @_request.params
|
46
|
+
@_query_string = @_request.query_string
|
47
|
+
|
48
|
+
# catch and report exceptions in this block
|
49
|
+
status_code, headers, body = handle_request(@_path, @_params)
|
50
|
+
|
51
|
+
if body.respond_to?(:prepend_html)
|
52
|
+
unless @_debug_messages.empty?
|
53
|
+
extend Kiss::Debug
|
54
|
+
body = prepend_debug(body)
|
55
|
+
|
56
|
+
headers['Content-Type'] = 'text/html'
|
57
|
+
headers['Content-Length'] = body.content_length.to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
unless @_benchmarks.empty?
|
61
|
+
stop_benchmark
|
62
|
+
extend Kiss::Bench
|
63
|
+
body = prepend_benchmarks(body)
|
64
|
+
headers['Content-Type'] = 'text/html'
|
65
|
+
headers['Content-Length'] = body.content_length.to_s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
return_database if @_db
|
70
|
+
|
71
|
+
[status_code, headers, body]
|
72
|
+
end
|
73
|
+
|
74
|
+
def path_with_query_string
|
75
|
+
@_path + (@_query_string.empty? ? '' : '?' + @_query_string)
|
76
|
+
end
|
77
|
+
|
78
|
+
def handle_request(path, params = {})
|
79
|
+
begin
|
80
|
+
@_exception_cache = {}
|
81
|
+
@_debug_messages = []
|
82
|
+
@_benchmarks = []
|
83
|
+
@_response = Rack::Response.new
|
84
|
+
|
85
|
+
catch :kiss_request_done do
|
86
|
+
action = invoke_action(path, params)
|
87
|
+
extension = action.extension
|
88
|
+
options = action.output_options
|
89
|
+
|
90
|
+
if content_type = options[:content_type] || (extension ? Kiss.mime_type(extension) : nil)
|
91
|
+
@_response['Content-Type'] = "#{content_type}; #{options[:document_encoding] || 'utf-8'}"
|
92
|
+
end
|
93
|
+
|
94
|
+
send_response(action.output, options)
|
95
|
+
end
|
96
|
+
|
97
|
+
finalize_session if @_session
|
98
|
+
@_response.finish
|
99
|
+
rescue StandardError, LoadError, SyntaxError => e
|
100
|
+
handle_exception(e)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def lookup_exception_handler(e)
|
105
|
+
klass = e.class
|
106
|
+
while klass
|
107
|
+
break if controller.exception_handlers[klass]
|
108
|
+
klass = klass.superclass
|
109
|
+
end
|
110
|
+
|
111
|
+
klass && controller.exception_handlers[klass]
|
112
|
+
end
|
113
|
+
|
114
|
+
def load_exception_handler(exception_handler); end
|
115
|
+
|
116
|
+
def handle_exception(e)
|
117
|
+
@_exception_messages = []
|
118
|
+
|
119
|
+
report = Kiss::ExceptionReport.generate(e, @_env, @_exception_cache, @_db ? @_db.last_query : nil)
|
120
|
+
exception_message = e.message.sub(/\n.*/m, '')
|
121
|
+
|
122
|
+
if @_controller.exception_log_file
|
123
|
+
@_controller.exception_log_file.print(report + "\n--- End of exception report --- \n\n")
|
124
|
+
end
|
125
|
+
|
126
|
+
status_code = 500
|
127
|
+
body = report
|
128
|
+
|
129
|
+
result = [status_code, {
|
130
|
+
"Content-Type" => "text/html",
|
131
|
+
"Content-Length" => body.content_length.to_s,
|
132
|
+
"X-Kiss-Error-Type" => e.class.name,
|
133
|
+
"X-Kiss-Error-Message" => exception_message
|
134
|
+
}, body]
|
135
|
+
|
136
|
+
send_email = true
|
137
|
+
|
138
|
+
unless @_loading_exception_handler
|
139
|
+
@_loading_exception_handler = true
|
140
|
+
begin
|
141
|
+
exception_handler = lookup_exception_handler(e)
|
142
|
+
if exception_handler
|
143
|
+
send_email = exception_handler[:send_email]
|
144
|
+
new_result = load_exception_handler(exception_handler)
|
145
|
+
if exception_handler[:action]
|
146
|
+
result = handle_request(exception_handler[:action])
|
147
|
+
result[0] = exception_handler[:status_code] || 500
|
148
|
+
end
|
149
|
+
end
|
150
|
+
rescue StandardError, LoadError, SyntaxError => e
|
151
|
+
result = handle_exception(e)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
if send_email && @_config.email_errors
|
156
|
+
email_message = <<-EOT
|
157
|
+
Content-type: text/html
|
158
|
+
From: #{@_config.email_errors.from}
|
159
|
+
To: #{@_config.email_errors.to.is_a?(String) ? @_config.email_errors.to : @_config.email_errors.to.join(', ')}
|
160
|
+
Subject: #{@_config.email_errors.app} - #{e.class.name}#{ exception_message ? ": #{exception_message}" : ''}
|
161
|
+
|
162
|
+
#{report}
|
163
|
+
EOT
|
164
|
+
Kiss::Mailer.send( @_config.email_errors.merge(:message => email_message) )
|
165
|
+
@_exception_email_sent = true
|
166
|
+
end
|
167
|
+
|
168
|
+
result
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
##### ACTION METHODS #####
|
173
|
+
|
174
|
+
def invoke_action(path, params = {}, render_options = {})
|
175
|
+
action = get_action(path, params)
|
176
|
+
|
177
|
+
catch :kiss_action_done do
|
178
|
+
action.authenticate if action.class.authentication_required
|
179
|
+
|
180
|
+
action.before_call
|
181
|
+
action.call
|
182
|
+
action.render(render_options)
|
183
|
+
end
|
184
|
+
action.after_call
|
185
|
+
action
|
186
|
+
end
|
187
|
+
|
188
|
+
# Parses request URI to determine action path and arguments, then
|
189
|
+
# instantiates action class to create action handler.
|
190
|
+
def get_action(path, params)
|
191
|
+
@@action_class ||= Kiss::Action.get_root_class(@_controller, @_controller.action_dir)
|
192
|
+
|
193
|
+
# return action handler (instance of action class)
|
194
|
+
@@action_class.get_subclass_from_path(path, self, params)
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
##### FILE METHODS #####
|
199
|
+
|
200
|
+
# If file has already been cached in handling the current request, retrieve from cache
|
201
|
+
# and do not check filesystem for updates. Else cache file via controller's file_cache.
|
202
|
+
def file_cache(path, *args, &block)
|
203
|
+
return @_controller.file_cache[path] if @_files_cached_this_request[path]
|
204
|
+
@_files_cached_this_request[path] = true
|
205
|
+
|
206
|
+
@_controller.file_cache(path, *args, &block)
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
##### DATABASE METHODS #####
|
211
|
+
|
212
|
+
# Acquires and returns a database connection object from the connection pool.
|
213
|
+
#
|
214
|
+
# Tip: `db' is a shorthand alias for `database'.
|
215
|
+
def database
|
216
|
+
@_db ||= begin
|
217
|
+
db = @_controller.database
|
218
|
+
check_evolution_number(db)
|
219
|
+
db.kiss_request = self
|
220
|
+
db
|
221
|
+
end
|
222
|
+
end
|
223
|
+
alias_method :db, :database
|
224
|
+
|
225
|
+
def return_database
|
226
|
+
@_db.kiss_request = nil
|
227
|
+
@_controller.return_database(@_db)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Kiss Model cache, used to invoke and store Kiss database models.
|
231
|
+
#
|
232
|
+
# Example:
|
233
|
+
# models[:users] == database model for `users' table
|
234
|
+
#
|
235
|
+
# Tip: `dbm' (stands for `database models') is a shorthand alias for `models'.
|
236
|
+
def models
|
237
|
+
# make sure we have a database connection
|
238
|
+
# create new model cache unless exists already
|
239
|
+
db.kiss_model_cache
|
240
|
+
end
|
241
|
+
alias_method :dbm, :models
|
242
|
+
|
243
|
+
# Check whether there exists a file in evolution_dir whose number is greater than app's
|
244
|
+
# current evolution number. If so, raise an error to indicate need to apply new evolutions.
|
245
|
+
def check_evolution_number(db)
|
246
|
+
db_version = db.evolution_number
|
247
|
+
if @_controller.directory_exists?(@_controller.evolution_dir) && @_controller.evolution_file(db_version+1)
|
248
|
+
raise <<-EOT
|
249
|
+
database evolution number #{db_version} < last evolution file number #{@_controller.last_evolution_file_number}
|
250
|
+
apply evolutions or set database evolution number
|
251
|
+
EOT
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
##### SESSION METHODS #####
|
257
|
+
|
258
|
+
# Retrieves or generates session data object, based on session ID from cookie value.
|
259
|
+
def session
|
260
|
+
@_session ||= begin
|
261
|
+
@_controller.session_class ? begin
|
262
|
+
@_controller.session_setup ||= begin
|
263
|
+
# setup session storage
|
264
|
+
@_controller.session_class.setup_storage(self)
|
265
|
+
true
|
266
|
+
end
|
267
|
+
|
268
|
+
session = @_controller.session_class.persist(self, @_request.cookies[@_controller.cookie_name])
|
269
|
+
@_session_fingerprint = Marshal.dump(session.data).hash
|
270
|
+
|
271
|
+
cookie_vars = {
|
272
|
+
:value => session.values[:session_id],
|
273
|
+
:path => @_config[:cookie_path] || @_app_uri,
|
274
|
+
:domain => @_config[:cookie_domain] || @_request.host
|
275
|
+
}
|
276
|
+
cookie_vars[:expires] = Time.now + @_config[:cookie_lifespan] if @_config[:cookie_lifespan]
|
277
|
+
|
278
|
+
# set_cookie here or at render time
|
279
|
+
@_response.set_cookie @_controller.cookie_name, cookie_vars
|
280
|
+
|
281
|
+
session
|
282
|
+
end : {}
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Saves session to session store, if session data has changed since load.
|
287
|
+
def finalize_session
|
288
|
+
@_session.save if @_session_fingerprint != Marshal.dump(@_session.data).hash
|
289
|
+
end
|
290
|
+
|
291
|
+
# Returns a Kiss::Login object containing data from session.login.
|
292
|
+
def login
|
293
|
+
@_login ||= Kiss::Login.new(session)
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
##### OUTPUT METHODS #####
|
298
|
+
|
299
|
+
# Outputs a Kiss::StaticFile object as response to Rack.
|
300
|
+
# Used to return static files efficiently.
|
301
|
+
def send_file(path, options = {})
|
302
|
+
@_response = Kiss::StaticFile.new(path, options)
|
303
|
+
|
304
|
+
throw :kiss_request_done
|
305
|
+
end
|
306
|
+
|
307
|
+
# Prepares Rack::Response object to return application response to Rack.
|
308
|
+
def send_response(output = '', options = {})
|
309
|
+
@_response['Content-Length'] = output.content_length.to_s
|
310
|
+
@_response['Content-Type'] = options[:content_type] if options[:content_type]
|
311
|
+
if options[:filename]
|
312
|
+
@_response['Content-Disposition'] = "#{options[:disposition] || 'inline'}; filename=#{options[:filename]}"
|
313
|
+
elsif options[:disposition]
|
314
|
+
@_response['Content-Disposition'] = options[:disposition]
|
315
|
+
end
|
316
|
+
@_response.body = output
|
317
|
+
|
318
|
+
throw :kiss_request_done
|
319
|
+
end
|
320
|
+
|
321
|
+
# Sends HTTP 302 response to redirect client browser agent to specified URL.
|
322
|
+
def redirect_url(url)
|
323
|
+
@_response.status = 302
|
324
|
+
@_response['Location'] = url
|
325
|
+
@_response.body = ''
|
326
|
+
|
327
|
+
throw :kiss_request_done
|
328
|
+
end
|
329
|
+
|
330
|
+
# Redirects to specified action path, which may also include arguments.
|
331
|
+
def redirect_action(action, options = {})
|
332
|
+
redirect_url( app_url(options) + action + (options[:params] ?
|
333
|
+
'?' + options[:params].keys.map do |k|
|
334
|
+
"#{k.to_s.url_escape}=#{options[:params][k].to_s.url_escape}"
|
335
|
+
end.join('&') : '')
|
336
|
+
)
|
337
|
+
end
|
338
|
+
|
339
|
+
|
340
|
+
##### DEBUG/BENCH OUTPUT #####
|
341
|
+
|
342
|
+
# Adds debug message to inspect object. Debug messages will be shown at top of
|
343
|
+
# application response body.
|
344
|
+
def debug(object, context = Kernel.caller[0])
|
345
|
+
@_debug_messages.push( [object.inspect.gsub(/ /, ' '), context] )
|
346
|
+
object
|
347
|
+
end
|
348
|
+
alias_method :trace, :debug
|
349
|
+
|
350
|
+
# Starts a new benchmark timer, with optional label. Benchmark results will be shown
|
351
|
+
# at top of application response body.
|
352
|
+
def start_benchmark(label = nil, context = Kernel.caller[0])
|
353
|
+
stop_benchmark(context)
|
354
|
+
@_benchmarks.push(
|
355
|
+
:label => label,
|
356
|
+
:start_time => Time.now,
|
357
|
+
:start_context => context
|
358
|
+
)
|
359
|
+
end
|
360
|
+
alias_method :bench, :start_benchmark
|
361
|
+
|
362
|
+
# Stops last benchmark timer, if still running.
|
363
|
+
def stop_benchmark(end_context = nil)
|
364
|
+
if @_benchmarks[-1] && !@_benchmarks[-1][:end_time]
|
365
|
+
@_benchmarks[-1][:end_time] = Time.now
|
366
|
+
@_benchmarks[-1][:end_context] = end_context
|
367
|
+
end
|
368
|
+
end
|
369
|
+
alias_method :bench_stop, :stop_benchmark
|
370
|
+
|
371
|
+
##### OTHER METHODS #####
|
372
|
+
|
373
|
+
# Returns URL/URI of app root (corresponding to top level of action_dir).
|
374
|
+
def app_url(options = {})
|
375
|
+
@_controller.app_url({
|
376
|
+
:protocol => @_protocol,
|
377
|
+
:host => @_app_host,
|
378
|
+
:uri => @_app_uri
|
379
|
+
}.merge(options))
|
380
|
+
end
|
381
|
+
|
382
|
+
# Returns URL/URI of app's static assets (asset_host or public_uri).
|
383
|
+
def pub_url(options = {})
|
384
|
+
if options.empty?
|
385
|
+
@_pub ||= (@_controller.asset_host ? @_protocol + '://' + @_controller.asset_host : '') +
|
386
|
+
(options[:uri] || @_controller.asset_uri || '')
|
387
|
+
else
|
388
|
+
(options[:protocol] || @_protocol) + '://' + (options[:host] || @_controller.asset_host) +
|
389
|
+
(options[:uri] || @_controller.asset_uri || '')
|
390
|
+
end
|
391
|
+
end
|
392
|
+
alias_method :asset_url, :pub_url
|
393
|
+
|
394
|
+
def query_string_with_params(params = {})
|
395
|
+
params = @_request.GET.merge(params)
|
396
|
+
params.keys.map do |key|
|
397
|
+
"#{key.to_s.url_escape}=#{params[key].to_s.url_escape}"
|
398
|
+
end.join('&')
|
399
|
+
end
|
400
|
+
|
401
|
+
def url_with_params(params = {})
|
402
|
+
"#{app_url}#{@_path}?#{query_string_with_params(params)}"
|
403
|
+
end
|
404
|
+
|
405
|
+
# Adds data to be displayed in "Cache" section of Kiss exception reports.
|
406
|
+
def exception_cache(data = nil)
|
407
|
+
@_exception_cache.merge!(data) if data
|
408
|
+
@_exception_cache
|
409
|
+
end
|
410
|
+
alias_method :set_exception_cache, :exception_cache
|
411
|
+
|
412
|
+
# Returns new Kiss::Mailer object using specified options.
|
413
|
+
def new_email(options = {})
|
414
|
+
controller.new_email({
|
415
|
+
:request => self
|
416
|
+
}.merge(options))
|
417
|
+
end
|
418
|
+
|
419
|
+
def send_email(options = {})
|
420
|
+
new_email(options).send
|
421
|
+
end
|
422
|
+
|
423
|
+
def cookies
|
424
|
+
request.cookies
|
425
|
+
end
|
426
|
+
|
427
|
+
def set_cookie(*args)
|
428
|
+
response.set_cookie(*args)
|
429
|
+
end
|
430
|
+
|
431
|
+
def path_info
|
432
|
+
@_request.env["PATH_INFO"]
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|