sinatra 2.2.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sinatra might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +133 -16
- data/CONTRIBUTING.md +11 -11
- data/Gemfile +48 -62
- data/MAINTENANCE.md +2 -2
- data/README.md +199 -386
- data/Rakefile +66 -75
- data/VERSION +1 -1
- data/examples/chat.rb +25 -12
- data/examples/lifecycle_events.rb +20 -0
- data/examples/rainbows.rb +3 -1
- data/examples/simple.rb +2 -0
- data/examples/stream.ru +2 -1
- data/lib/sinatra/base.rb +420 -338
- data/lib/sinatra/indifferent_hash.rb +25 -33
- data/lib/sinatra/main.rb +18 -16
- data/lib/sinatra/show_exceptions.rb +17 -15
- data/lib/sinatra/version.rb +3 -1
- data/lib/sinatra.rb +2 -0
- data/sinatra.gemspec +38 -33
- metadata +40 -30
- data/README.de.md +0 -3239
- data/README.es.md +0 -3231
- data/README.fr.md +0 -3111
- data/README.hu.md +0 -728
- data/README.ja.md +0 -2844
- data/README.ko.md +0 -2967
- data/README.malayalam.md +0 -3141
- data/README.pt-br.md +0 -3787
- data/README.pt-pt.md +0 -791
- data/README.ru.md +0 -3207
- data/README.zh.md +0 -2934
data/lib/sinatra/base.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
# external dependencies
|
@@ -10,7 +9,6 @@ require 'mustermann/sinatra'
|
|
10
9
|
require 'mustermann/regular'
|
11
10
|
|
12
11
|
# stdlib dependencies
|
13
|
-
require 'thread'
|
14
12
|
require 'time'
|
15
13
|
require 'uri'
|
16
14
|
|
@@ -21,21 +19,22 @@ require 'sinatra/version'
|
|
21
19
|
|
22
20
|
module Sinatra
|
23
21
|
# The request object. See Rack::Request for more info:
|
24
|
-
#
|
22
|
+
# https://rubydoc.info/github/rack/rack/main/Rack/Request
|
25
23
|
class Request < Rack::Request
|
26
|
-
HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s
|
27
|
-
HEADER_VALUE_WITH_PARAMS =
|
24
|
+
HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/.freeze
|
25
|
+
HEADER_VALUE_WITH_PARAMS = %r{(?:(?:\w+|\*)/(?:\w+(?:\.|-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*}.freeze
|
28
26
|
|
29
27
|
# Returns an array of acceptable media types for the response
|
30
28
|
def accept
|
31
|
-
@env['sinatra.accept'] ||=
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
29
|
+
@env['sinatra.accept'] ||= if @env.include?('HTTP_ACCEPT') && (@env['HTTP_ACCEPT'].to_s != '')
|
30
|
+
@env['HTTP_ACCEPT']
|
31
|
+
.to_s
|
32
|
+
.scan(HEADER_VALUE_WITH_PARAMS)
|
33
|
+
.map! { |e| AcceptEntry.new(e) }
|
34
|
+
.sort
|
35
|
+
else
|
36
|
+
[AcceptEntry.new('*/*')]
|
37
|
+
end
|
39
38
|
end
|
40
39
|
|
41
40
|
def accept?(type)
|
@@ -44,8 +43,10 @@ module Sinatra
|
|
44
43
|
|
45
44
|
def preferred_type(*types)
|
46
45
|
return accept.first if types.empty?
|
46
|
+
|
47
47
|
types.flatten!
|
48
48
|
return types.first if accept.empty?
|
49
|
+
|
49
50
|
accept.detect do |accept_header|
|
50
51
|
type = types.detect { |t| MimeTypeEntry.new(t).accepts?(accept_header) }
|
51
52
|
return type if type
|
@@ -55,23 +56,23 @@ module Sinatra
|
|
55
56
|
alias secure? ssl?
|
56
57
|
|
57
58
|
def forwarded?
|
58
|
-
@env.include?
|
59
|
+
@env.include? 'HTTP_X_FORWARDED_HOST'
|
59
60
|
end
|
60
61
|
|
61
62
|
def safe?
|
62
|
-
get?
|
63
|
+
get? || head? || options? || trace?
|
63
64
|
end
|
64
65
|
|
65
66
|
def idempotent?
|
66
|
-
safe?
|
67
|
+
safe? || put? || delete? || link? || unlink?
|
67
68
|
end
|
68
69
|
|
69
70
|
def link?
|
70
|
-
request_method ==
|
71
|
+
request_method == 'LINK'
|
71
72
|
end
|
72
73
|
|
73
74
|
def unlink?
|
74
|
-
request_method ==
|
75
|
+
request_method == 'UNLINK'
|
75
76
|
end
|
76
77
|
|
77
78
|
def params
|
@@ -95,17 +96,17 @@ module Sinatra
|
|
95
96
|
|
96
97
|
@entry = entry
|
97
98
|
@type = entry[/[^;]+/].delete(' ')
|
98
|
-
@params =
|
99
|
+
@params = params.to_h
|
99
100
|
@q = @params.delete('q') { 1.0 }.to_f
|
100
101
|
end
|
101
102
|
|
102
103
|
def <=>(other)
|
103
|
-
other.priority <=>
|
104
|
+
other.priority <=> priority
|
104
105
|
end
|
105
106
|
|
106
107
|
def priority
|
107
108
|
# We sort in descending order; better matches should be higher.
|
108
|
-
[
|
109
|
+
[@q, -@type.count('*'), @params.size]
|
109
110
|
end
|
110
111
|
|
111
112
|
def to_str
|
@@ -117,7 +118,7 @@ module Sinatra
|
|
117
118
|
end
|
118
119
|
|
119
120
|
def respond_to?(*args)
|
120
|
-
super
|
121
|
+
super || to_str.respond_to?(*args)
|
121
122
|
end
|
122
123
|
|
123
124
|
def method_missing(*args, &block)
|
@@ -136,7 +137,7 @@ module Sinatra
|
|
136
137
|
end
|
137
138
|
|
138
139
|
@type = entry[/[^;]+/].delete(' ')
|
139
|
-
@params =
|
140
|
+
@params = params.to_h
|
140
141
|
end
|
141
142
|
|
142
143
|
def accepts?(entry)
|
@@ -150,17 +151,17 @@ module Sinatra
|
|
150
151
|
def matches_params?(params)
|
151
152
|
return true if @params.empty?
|
152
153
|
|
153
|
-
params.all? { |k,v| !@params.
|
154
|
+
params.all? { |k, v| !@params.key?(k) || @params[k] == v }
|
154
155
|
end
|
155
156
|
end
|
156
157
|
end
|
157
158
|
|
158
159
|
# The response object. See Rack::Response and Rack::Response::Helpers for
|
159
160
|
# more info:
|
160
|
-
#
|
161
|
-
#
|
161
|
+
# https://rubydoc.info/github/rack/rack/main/Rack/Response
|
162
|
+
# https://rubydoc.info/github/rack/rack/main/Rack/Response/Helpers
|
162
163
|
class Response < Rack::Response
|
163
|
-
DROP_BODY_RESPONSES = [204, 304]
|
164
|
+
DROP_BODY_RESPONSES = [204, 304].freeze
|
164
165
|
|
165
166
|
def body=(value)
|
166
167
|
value = value.body while Rack::Response === value
|
@@ -175,8 +176,8 @@ module Sinatra
|
|
175
176
|
result = body
|
176
177
|
|
177
178
|
if drop_content_info?
|
178
|
-
headers.delete
|
179
|
-
headers.delete
|
179
|
+
headers.delete 'Content-Length'
|
180
|
+
headers.delete 'Content-Type'
|
180
181
|
end
|
181
182
|
|
182
183
|
if drop_body?
|
@@ -187,7 +188,7 @@ module Sinatra
|
|
187
188
|
if calculate_content_length?
|
188
189
|
# if some other code has already set Content-Length, don't muck with it
|
189
190
|
# currently, this would be the static file-handler
|
190
|
-
headers[
|
191
|
+
headers['Content-Length'] = body.map(&:bytesize).reduce(0, :+).to_s
|
191
192
|
end
|
192
193
|
|
193
194
|
[status, headers, result]
|
@@ -196,11 +197,11 @@ module Sinatra
|
|
196
197
|
private
|
197
198
|
|
198
199
|
def calculate_content_length?
|
199
|
-
headers[
|
200
|
+
headers['Content-Type'] && !headers['Content-Length'] && (Array === body)
|
200
201
|
end
|
201
202
|
|
202
203
|
def drop_content_info?
|
203
|
-
informational?
|
204
|
+
informational? || drop_body?
|
204
205
|
end
|
205
206
|
|
206
207
|
def drop_body?
|
@@ -215,8 +216,10 @@ module Sinatra
|
|
215
216
|
# still be able to run.
|
216
217
|
class ExtendedRack < Struct.new(:app)
|
217
218
|
def call(env)
|
218
|
-
result
|
219
|
-
|
219
|
+
result = app.call(env)
|
220
|
+
callback = env['async.callback']
|
221
|
+
return result unless callback && async?(*result)
|
222
|
+
|
220
223
|
after_response { callback.call result }
|
221
224
|
setup_close(env, *result)
|
222
225
|
throw :async
|
@@ -224,20 +227,23 @@ module Sinatra
|
|
224
227
|
|
225
228
|
private
|
226
229
|
|
227
|
-
def setup_close(env,
|
228
|
-
return unless body.respond_to?
|
230
|
+
def setup_close(env, _status, _headers, body)
|
231
|
+
return unless body.respond_to?(:close) && env.include?('async.close')
|
232
|
+
|
229
233
|
env['async.close'].callback { body.close }
|
230
234
|
env['async.close'].errback { body.close }
|
231
235
|
end
|
232
236
|
|
233
237
|
def after_response(&block)
|
234
|
-
raise NotImplementedError,
|
238
|
+
raise NotImplementedError, 'only supports EventMachine at the moment' unless defined? EventMachine
|
239
|
+
|
235
240
|
EventMachine.next_tick(&block)
|
236
241
|
end
|
237
242
|
|
238
|
-
def async?(status,
|
243
|
+
def async?(status, _headers, body)
|
239
244
|
return true if status == -1
|
240
|
-
|
245
|
+
|
246
|
+
body.respond_to?(:callback) && body.respond_to?(:errback)
|
241
247
|
end
|
242
248
|
end
|
243
249
|
|
@@ -249,7 +255,7 @@ module Sinatra
|
|
249
255
|
end
|
250
256
|
|
251
257
|
superclass.class_eval do
|
252
|
-
|
258
|
+
alias_method :call_without_check, :call unless method_defined? :call_without_check
|
253
259
|
def call(env)
|
254
260
|
env['sinatra.commonlogger'] = true
|
255
261
|
call_without_check(env)
|
@@ -257,11 +263,14 @@ module Sinatra
|
|
257
263
|
end
|
258
264
|
end
|
259
265
|
|
260
|
-
class
|
266
|
+
class Error < StandardError # :nodoc:
|
267
|
+
end
|
268
|
+
|
269
|
+
class BadRequest < Error # :nodoc:
|
261
270
|
def http_status; 400 end
|
262
271
|
end
|
263
272
|
|
264
|
-
class NotFound <
|
273
|
+
class NotFound < Error # :nodoc:
|
265
274
|
def http_status; 404 end
|
266
275
|
end
|
267
276
|
|
@@ -282,7 +291,7 @@ module Sinatra
|
|
282
291
|
elsif value
|
283
292
|
# Rack 2.0 returns a Rack::File::Iterator here instead of
|
284
293
|
# Rack::File as it was in the previous API.
|
285
|
-
unless request.head? || value.is_a?(Rack::
|
294
|
+
unless request.head? || value.is_a?(Rack::Files::Iterator) || value.is_a?(Stream)
|
286
295
|
headers.delete 'Content-Length'
|
287
296
|
end
|
288
297
|
response.body = value
|
@@ -293,7 +302,7 @@ module Sinatra
|
|
293
302
|
|
294
303
|
# Halt processing and redirect to the URI provided.
|
295
304
|
def redirect(uri, *args)
|
296
|
-
if env['HTTP_VERSION'] == 'HTTP/1.1'
|
305
|
+
if (env['HTTP_VERSION'] == 'HTTP/1.1') && (env['REQUEST_METHOD'] != 'GET')
|
297
306
|
status 303
|
298
307
|
else
|
299
308
|
status 302
|
@@ -308,18 +317,19 @@ module Sinatra
|
|
308
317
|
# Generates the absolute URI for a given path in the app.
|
309
318
|
# Takes Rack routers and reverse proxies into account.
|
310
319
|
def uri(addr = nil, absolute = true, add_script_name = true)
|
311
|
-
return addr if addr =~ /\A[a-z][a-z0-9
|
320
|
+
return addr if addr.to_s =~ /\A[a-z][a-z0-9+.\-]*:/i
|
321
|
+
|
312
322
|
uri = [host = String.new]
|
313
323
|
if absolute
|
314
324
|
host << "http#{'s' if request.secure?}://"
|
315
|
-
if request.forwarded?
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
325
|
+
host << if request.forwarded? || (request.port != (request.secure? ? 443 : 80))
|
326
|
+
request.host_with_port
|
327
|
+
else
|
328
|
+
request.host
|
329
|
+
end
|
320
330
|
end
|
321
331
|
uri << request.script_name.to_s if add_script_name
|
322
|
-
uri << (addr
|
332
|
+
uri << (addr || request.path_info).to_s
|
323
333
|
File.join uri
|
324
334
|
end
|
325
335
|
|
@@ -328,7 +338,10 @@ module Sinatra
|
|
328
338
|
|
329
339
|
# Halt processing and return the error status provided.
|
330
340
|
def error(code, body = nil)
|
331
|
-
|
341
|
+
if code.respond_to? :to_str
|
342
|
+
body = code.to_str
|
343
|
+
code = 500
|
344
|
+
end
|
332
345
|
response.body = body unless body.nil?
|
333
346
|
halt code
|
334
347
|
end
|
@@ -363,11 +376,13 @@ module Sinatra
|
|
363
376
|
# extension.
|
364
377
|
def content_type(type = nil, params = {})
|
365
378
|
return response['Content-Type'] unless type
|
379
|
+
|
366
380
|
default = params.delete :default
|
367
381
|
mime_type = mime_type(type) || default
|
368
|
-
|
382
|
+
raise format('Unknown media type: %p', type) if mime_type.nil?
|
383
|
+
|
369
384
|
mime_type = mime_type.dup
|
370
|
-
unless params.include?
|
385
|
+
unless params.include?(:charset) || settings.add_charset.all? { |p| !(p === mime_type) }
|
371
386
|
params[:charset] = params.delete('charset') || settings.default_encoding
|
372
387
|
end
|
373
388
|
params.delete :charset if mime_type.include? 'charset'
|
@@ -381,36 +396,43 @@ module Sinatra
|
|
381
396
|
response['Content-Type'] = mime_type
|
382
397
|
end
|
383
398
|
|
399
|
+
# https://html.spec.whatwg.org/#multipart-form-data
|
400
|
+
MULTIPART_FORM_DATA_REPLACEMENT_TABLE = {
|
401
|
+
'"' => '%22',
|
402
|
+
"\r" => '%0D',
|
403
|
+
"\n" => '%0A'
|
404
|
+
}.freeze
|
405
|
+
|
384
406
|
# Set the Content-Disposition to "attachment" with the specified filename,
|
385
407
|
# instructing the user agents to prompt to save.
|
386
408
|
def attachment(filename = nil, disposition = :attachment)
|
387
409
|
response['Content-Disposition'] = disposition.to_s.dup
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
410
|
+
return unless filename
|
411
|
+
|
412
|
+
params = format('; filename="%s"', File.basename(filename).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE))
|
413
|
+
response['Content-Disposition'] << params
|
414
|
+
ext = File.extname(filename)
|
415
|
+
content_type(ext) unless response['Content-Type'] || ext.empty?
|
394
416
|
end
|
395
417
|
|
396
418
|
# Use the contents of the file at +path+ as the response body.
|
397
419
|
def send_file(path, opts = {})
|
398
|
-
if opts[:type]
|
399
|
-
content_type opts[:type] || File.extname(path), :
|
420
|
+
if opts[:type] || !response['Content-Type']
|
421
|
+
content_type opts[:type] || File.extname(path), default: 'application/octet-stream'
|
400
422
|
end
|
401
423
|
|
402
424
|
disposition = opts[:disposition]
|
403
425
|
filename = opts[:filename]
|
404
|
-
disposition = :attachment if disposition.nil?
|
426
|
+
disposition = :attachment if disposition.nil? && filename
|
405
427
|
filename = path if filename.nil?
|
406
428
|
attachment(filename, disposition) if disposition
|
407
429
|
|
408
430
|
last_modified opts[:last_modified] if opts[:last_modified]
|
409
431
|
|
410
|
-
file = Rack::
|
432
|
+
file = Rack::Files.new(File.dirname(settings.app_file))
|
411
433
|
result = file.serving(request, path)
|
412
434
|
|
413
|
-
result[1].each { |k,v| headers[k] ||= v }
|
435
|
+
result[1].each { |k, v| headers[k] ||= v }
|
414
436
|
headers['Content-Length'] = result[1]['Content-Length']
|
415
437
|
opts[:status] &&= Integer(opts[:status])
|
416
438
|
halt (opts[:status] || result[0]), result[2]
|
@@ -431,12 +453,16 @@ module Sinatra
|
|
431
453
|
def self.defer(*) yield end
|
432
454
|
|
433
455
|
def initialize(scheduler = self.class, keep_open = false, &back)
|
434
|
-
@back
|
435
|
-
@
|
456
|
+
@back = back.to_proc
|
457
|
+
@scheduler = scheduler
|
458
|
+
@keep_open = keep_open
|
459
|
+
@callbacks = []
|
460
|
+
@closed = false
|
436
461
|
end
|
437
462
|
|
438
463
|
def close
|
439
464
|
return if closed?
|
465
|
+
|
440
466
|
@closed = true
|
441
467
|
@scheduler.schedule { @callbacks.each { |c| c.call } }
|
442
468
|
end
|
@@ -448,8 +474,9 @@ module Sinatra
|
|
448
474
|
@back.call(self)
|
449
475
|
rescue Exception => e
|
450
476
|
@scheduler.schedule { raise e }
|
477
|
+
ensure
|
478
|
+
close unless @keep_open
|
451
479
|
end
|
452
|
-
close unless @keep_open
|
453
480
|
end
|
454
481
|
end
|
455
482
|
|
@@ -460,6 +487,7 @@ module Sinatra
|
|
460
487
|
|
461
488
|
def callback(&block)
|
462
489
|
return yield if closed?
|
490
|
+
|
463
491
|
@callbacks << block
|
464
492
|
end
|
465
493
|
|
@@ -479,7 +507,16 @@ module Sinatra
|
|
479
507
|
def stream(keep_open = false)
|
480
508
|
scheduler = env['async.callback'] ? EventMachine : Stream
|
481
509
|
current = @params.dup
|
482
|
-
|
510
|
+
stream = if scheduler == Stream && keep_open
|
511
|
+
Stream.new(scheduler, false) do |out|
|
512
|
+
until out.closed?
|
513
|
+
with_params(current) { yield(out) }
|
514
|
+
end
|
515
|
+
end
|
516
|
+
else
|
517
|
+
Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
|
518
|
+
end
|
519
|
+
body stream
|
483
520
|
end
|
484
521
|
|
485
522
|
# Specify response freshness policy for HTTP caches (Cache-Control header).
|
@@ -493,18 +530,18 @@ module Sinatra
|
|
493
530
|
# See RFC 2616 / 14.9 for more on standard cache control directives:
|
494
531
|
# http://tools.ietf.org/html/rfc2616#section-14.9.1
|
495
532
|
def cache_control(*values)
|
496
|
-
if values.last.
|
533
|
+
if values.last.is_a?(Hash)
|
497
534
|
hash = values.pop
|
498
|
-
hash.reject! { |
|
535
|
+
hash.reject! { |_k, v| v == false }
|
499
536
|
hash.reject! { |k, v| values << k if v == true }
|
500
537
|
else
|
501
538
|
hash = {}
|
502
539
|
end
|
503
540
|
|
504
|
-
values.map! { |value| value.to_s.tr('_','-') }
|
541
|
+
values.map! { |value| value.to_s.tr('_', '-') }
|
505
542
|
hash.each do |key, value|
|
506
543
|
key = key.to_s.tr('_', '-')
|
507
|
-
value = value.to_i if [
|
544
|
+
value = value.to_i if %w[max-age s-maxage].include? key
|
508
545
|
values << "#{key}=#{value}"
|
509
546
|
end
|
510
547
|
|
@@ -521,7 +558,7 @@ module Sinatra
|
|
521
558
|
# => Expires: Mon, 08 Jun 2009 08:50:17 GMT
|
522
559
|
#
|
523
560
|
def expires(amount, *values)
|
524
|
-
values << {} unless values.last.
|
561
|
+
values << {} unless values.last.is_a?(Hash)
|
525
562
|
|
526
563
|
if amount.is_a? Integer
|
527
564
|
time = Time.now + amount.to_i
|
@@ -531,7 +568,7 @@ module Sinatra
|
|
531
568
|
max_age = time - Time.now
|
532
569
|
end
|
533
570
|
|
534
|
-
values.last.merge!(:max_age
|
571
|
+
values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
|
535
572
|
cache_control(*values)
|
536
573
|
|
537
574
|
response['Expires'] = time.httpdate
|
@@ -546,17 +583,18 @@ module Sinatra
|
|
546
583
|
# with a '304 Not Modified' response.
|
547
584
|
def last_modified(time)
|
548
585
|
return unless time
|
586
|
+
|
549
587
|
time = time_for time
|
550
588
|
response['Last-Modified'] = time.httpdate
|
551
589
|
return if env['HTTP_IF_NONE_MATCH']
|
552
590
|
|
553
|
-
if status == 200
|
591
|
+
if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
|
554
592
|
# compare based on seconds since epoch
|
555
593
|
since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
|
556
594
|
halt 304 if since >= time.to_i
|
557
595
|
end
|
558
596
|
|
559
|
-
if (success?
|
597
|
+
if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
|
560
598
|
# compare based on seconds since epoch
|
561
599
|
since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
|
562
600
|
halt 412 if since < time.to_i
|
@@ -564,7 +602,7 @@ module Sinatra
|
|
564
602
|
rescue ArgumentError
|
565
603
|
end
|
566
604
|
|
567
|
-
ETAG_KINDS = [
|
605
|
+
ETAG_KINDS = %i[strong weak].freeze
|
568
606
|
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
569
607
|
# GET matches. The +value+ argument is an identifier that uniquely
|
570
608
|
# identifies the current version of the resource. The +kind+ argument
|
@@ -576,27 +614,31 @@ module Sinatra
|
|
576
614
|
# GET or HEAD, a '304 Not Modified' response is sent.
|
577
615
|
def etag(value, options = {})
|
578
616
|
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
|
579
|
-
options = {:
|
617
|
+
options = { kind: options } unless Hash === options
|
580
618
|
kind = options[:kind] || :strong
|
581
619
|
new_resource = options.fetch(:new_resource) { request.post? }
|
582
620
|
|
583
621
|
unless ETAG_KINDS.include?(kind)
|
584
|
-
raise ArgumentError,
|
622
|
+
raise ArgumentError, ':strong or :weak expected'
|
585
623
|
end
|
586
624
|
|
587
|
-
value = '"%s"'
|
625
|
+
value = format('"%s"', value)
|
588
626
|
value = "W/#{value}" if kind == :weak
|
589
627
|
response['ETag'] = value
|
590
628
|
|
591
|
-
|
592
|
-
if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
|
593
|
-
halt(request.safe? ? 304 : 412)
|
594
|
-
end
|
629
|
+
return unless success? || status == 304
|
595
630
|
|
596
|
-
|
597
|
-
|
598
|
-
end
|
631
|
+
if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource)
|
632
|
+
halt(request.safe? ? 304 : 412)
|
599
633
|
end
|
634
|
+
|
635
|
+
if env['HTTP_IF_MATCH']
|
636
|
+
return if etag_matches?(env['HTTP_IF_MATCH'], new_resource)
|
637
|
+
|
638
|
+
halt 412
|
639
|
+
end
|
640
|
+
|
641
|
+
nil
|
600
642
|
end
|
601
643
|
|
602
644
|
# Sugar for redirect (example: redirect back)
|
@@ -649,8 +691,8 @@ module Sinatra
|
|
649
691
|
else
|
650
692
|
value.to_time
|
651
693
|
end
|
652
|
-
rescue ArgumentError =>
|
653
|
-
raise
|
694
|
+
rescue ArgumentError => e
|
695
|
+
raise e
|
654
696
|
rescue Exception
|
655
697
|
raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
|
656
698
|
end
|
@@ -660,11 +702,13 @@ module Sinatra
|
|
660
702
|
# Helper method checking if a ETag value list includes the current ETag.
|
661
703
|
def etag_matches?(list, new_resource = request.post?)
|
662
704
|
return !new_resource if list == '*'
|
705
|
+
|
663
706
|
list.to_s.split(/\s*,\s*/).include? response['ETag']
|
664
707
|
end
|
665
708
|
|
666
709
|
def with_params(temp_params)
|
667
|
-
original
|
710
|
+
original = @params
|
711
|
+
@params = temp_params
|
668
712
|
yield
|
669
713
|
ensure
|
670
714
|
@params = original if original
|
@@ -682,7 +726,7 @@ module Sinatra
|
|
682
726
|
# Possible options are:
|
683
727
|
# :content_type The content type to use, same arguments as content_type.
|
684
728
|
# :layout If set to something falsy, no layout is rendered, otherwise
|
685
|
-
# the specified layout is used (Ignored for `sass`
|
729
|
+
# the specified layout is used (Ignored for `sass`)
|
686
730
|
# :layout_engine Engine to use for rendering the layout.
|
687
731
|
# :locals A hash with local variables that should be available
|
688
732
|
# in the template
|
@@ -704,36 +748,24 @@ module Sinatra
|
|
704
748
|
render(:erb, template, options, locals, &block)
|
705
749
|
end
|
706
750
|
|
707
|
-
def erubis(template, options = {}, locals = {})
|
708
|
-
warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \
|
709
|
-
"If you have Erubis installed, it will be used automatically."
|
710
|
-
render :erubis, template, options, locals
|
711
|
-
end
|
712
|
-
|
713
751
|
def haml(template, options = {}, locals = {}, &block)
|
714
752
|
render(:haml, template, options, locals, &block)
|
715
753
|
end
|
716
754
|
|
717
755
|
def sass(template, options = {}, locals = {})
|
718
|
-
options
|
756
|
+
options[:default_content_type] = :css
|
757
|
+
options[:exclude_outvar] = true
|
758
|
+
options[:layout] = nil
|
719
759
|
render :sass, template, options, locals
|
720
760
|
end
|
721
761
|
|
722
762
|
def scss(template, options = {}, locals = {})
|
723
|
-
options
|
763
|
+
options[:default_content_type] = :css
|
764
|
+
options[:exclude_outvar] = true
|
765
|
+
options[:layout] = nil
|
724
766
|
render :scss, template, options, locals
|
725
767
|
end
|
726
768
|
|
727
|
-
def less(template, options = {}, locals = {})
|
728
|
-
options.merge! :layout => false, :default_content_type => :css
|
729
|
-
render :less, template, options, locals
|
730
|
-
end
|
731
|
-
|
732
|
-
def stylus(template, options = {}, locals = {})
|
733
|
-
options.merge! :layout => false, :default_content_type => :css
|
734
|
-
render :styl, template, options, locals
|
735
|
-
end
|
736
|
-
|
737
769
|
def builder(template = nil, options = {}, locals = {}, &block)
|
738
770
|
options[:default_content_type] = :xml
|
739
771
|
render_ruby(:builder, template, options, locals, &block)
|
@@ -748,10 +780,6 @@ module Sinatra
|
|
748
780
|
render :markdown, template, options, locals
|
749
781
|
end
|
750
782
|
|
751
|
-
def textile(template, options = {}, locals = {})
|
752
|
-
render :textile, template, options, locals
|
753
|
-
end
|
754
|
-
|
755
783
|
def rdoc(template, options = {}, locals = {})
|
756
784
|
render :rdoc, template, options, locals
|
757
785
|
end
|
@@ -760,19 +788,10 @@ module Sinatra
|
|
760
788
|
render :asciidoc, template, options, locals
|
761
789
|
end
|
762
790
|
|
763
|
-
def radius(template, options = {}, locals = {})
|
764
|
-
render :radius, template, options, locals
|
765
|
-
end
|
766
|
-
|
767
791
|
def markaby(template = nil, options = {}, locals = {}, &block)
|
768
792
|
render_ruby(:mab, template, options, locals, &block)
|
769
793
|
end
|
770
794
|
|
771
|
-
def coffee(template, options = {}, locals = {})
|
772
|
-
options.merge! :layout => false, :default_content_type => :js
|
773
|
-
render :coffee, template, options, locals
|
774
|
-
end
|
775
|
-
|
776
795
|
def nokogiri(template = nil, options = {}, locals = {}, &block)
|
777
796
|
options[:default_content_type] = :xml
|
778
797
|
render_ruby(:nokogiri, template, options, locals, &block)
|
@@ -782,18 +801,6 @@ module Sinatra
|
|
782
801
|
render(:slim, template, options, locals, &block)
|
783
802
|
end
|
784
803
|
|
785
|
-
def creole(template, options = {}, locals = {})
|
786
|
-
render :creole, template, options, locals
|
787
|
-
end
|
788
|
-
|
789
|
-
def mediawiki(template, options = {}, locals = {})
|
790
|
-
render :mediawiki, template, options, locals
|
791
|
-
end
|
792
|
-
|
793
|
-
def wlang(template, options = {}, locals = {}, &block)
|
794
|
-
render(:wlang, template, options, locals, &block)
|
795
|
-
end
|
796
|
-
|
797
804
|
def yajl(template, options = {}, locals = {})
|
798
805
|
options[:default_content_type] = :json
|
799
806
|
render :yajl, template, options, locals
|
@@ -818,24 +825,27 @@ module Sinatra
|
|
818
825
|
|
819
826
|
# logic shared between builder and nokogiri
|
820
827
|
def render_ruby(engine, template, options = {}, locals = {}, &block)
|
821
|
-
|
822
|
-
|
828
|
+
if template.is_a?(Hash)
|
829
|
+
options = template
|
830
|
+
template = nil
|
831
|
+
end
|
832
|
+
template = proc { block } if template.nil?
|
823
833
|
render engine, template, options, locals
|
824
834
|
end
|
825
835
|
|
826
836
|
def render(engine, data, options = {}, locals = {}, &block)
|
827
837
|
# merge app-level options
|
828
838
|
engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
|
829
|
-
options.merge!(engine_options) { |
|
839
|
+
options.merge!(engine_options) { |_key, v1, _v2| v1 }
|
830
840
|
|
831
841
|
# extract generic options
|
832
842
|
locals = options.delete(:locals) || locals || {}
|
833
|
-
views = options.delete(:views) || settings.views ||
|
843
|
+
views = options.delete(:views) || settings.views || './views'
|
834
844
|
layout = options[:layout]
|
835
845
|
layout = false if layout.nil? && options.include?(:layout)
|
836
846
|
eat_errors = layout.nil?
|
837
|
-
layout = engine_options[:layout] if layout.nil?
|
838
|
-
layout = @default_layout if layout.nil?
|
847
|
+
layout = engine_options[:layout] if layout.nil? || (layout == true && engine_options[:layout] != false)
|
848
|
+
layout = @default_layout if layout.nil? || (layout == true)
|
839
849
|
layout_options = options.delete(:layout_options) || {}
|
840
850
|
content_type = options.delete(:default_content_type)
|
841
851
|
content_type = options.delete(:content_type) || content_type
|
@@ -860,12 +870,17 @@ module Sinatra
|
|
860
870
|
|
861
871
|
# render layout
|
862
872
|
if layout
|
863
|
-
|
864
|
-
|
873
|
+
extra_options = { views: views, layout: false, eat_errors: eat_errors, scope: scope }
|
874
|
+
options = options.merge(extra_options).merge!(layout_options)
|
875
|
+
|
865
876
|
catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
|
866
877
|
end
|
867
878
|
|
868
|
-
|
879
|
+
if content_type
|
880
|
+
# sass-embedded returns a frozen string
|
881
|
+
output = +output
|
882
|
+
output.extend(ContentTyped).content_type = content_type
|
883
|
+
end
|
869
884
|
output
|
870
885
|
end
|
871
886
|
|
@@ -886,12 +901,13 @@ module Sinatra
|
|
886
901
|
@preferred_extension = engine.to_s
|
887
902
|
find_template(views, data, template) do |file|
|
888
903
|
path ||= file # keep the initial path rather than the last one
|
889
|
-
|
904
|
+
found = File.exist?(file)
|
905
|
+
if found
|
890
906
|
path = file
|
891
907
|
break
|
892
908
|
end
|
893
909
|
end
|
894
|
-
throw :layout_missing if eat_errors
|
910
|
+
throw :layout_missing if eat_errors && !found
|
895
911
|
template.new(path, 1, options)
|
896
912
|
end
|
897
913
|
end
|
@@ -907,13 +923,44 @@ module Sinatra
|
|
907
923
|
end
|
908
924
|
|
909
925
|
def compile_block_template(template, options, &body)
|
910
|
-
|
911
|
-
path =
|
912
|
-
line =
|
926
|
+
first_location = caller_locations.first
|
927
|
+
path = first_location.path
|
928
|
+
line = first_location.lineno
|
929
|
+
path = options[:path] || path
|
930
|
+
line = options[:line] || line
|
913
931
|
template.new(path, line.to_i, options, &body)
|
914
932
|
end
|
915
933
|
end
|
916
934
|
|
935
|
+
# Extremely simple template cache implementation.
|
936
|
+
# * Not thread-safe.
|
937
|
+
# * Size is unbounded.
|
938
|
+
# * Keys are not copied defensively, and should not be modified after
|
939
|
+
# being passed to #fetch. More specifically, the values returned by
|
940
|
+
# key#hash and key#eql? should not change.
|
941
|
+
#
|
942
|
+
# Implementation copied from Tilt::Cache.
|
943
|
+
class TemplateCache
|
944
|
+
def initialize
|
945
|
+
@cache = {}
|
946
|
+
end
|
947
|
+
|
948
|
+
# Caches a value for key, or returns the previously cached value.
|
949
|
+
# If a value has been previously cached for key then it is
|
950
|
+
# returned. Otherwise, block is yielded to and its return value
|
951
|
+
# which may be nil, is cached under key and returned.
|
952
|
+
def fetch(*key)
|
953
|
+
@cache.fetch(key) do
|
954
|
+
@cache[key] = yield
|
955
|
+
end
|
956
|
+
end
|
957
|
+
|
958
|
+
# Clears the cache.
|
959
|
+
def clear
|
960
|
+
@cache = {}
|
961
|
+
end
|
962
|
+
end
|
963
|
+
|
917
964
|
# Base class for all Sinatra applications and middleware.
|
918
965
|
class Base
|
919
966
|
include Rack::Utils
|
@@ -925,10 +972,10 @@ module Sinatra
|
|
925
972
|
attr_accessor :app, :env, :request, :response, :params
|
926
973
|
attr_reader :template_cache
|
927
974
|
|
928
|
-
def initialize(app = nil, **
|
975
|
+
def initialize(app = nil, **_kwargs)
|
929
976
|
super()
|
930
977
|
@app = app
|
931
|
-
@template_cache =
|
978
|
+
@template_cache = TemplateCache.new
|
932
979
|
@pinned_response = nil # whether a before! filter pinned the content-type
|
933
980
|
yield self if block_given?
|
934
981
|
end
|
@@ -952,7 +999,7 @@ module Sinatra
|
|
952
999
|
unless @response['Content-Type']
|
953
1000
|
if Array === body && body[0].respond_to?(:content_type)
|
954
1001
|
content_type body[0].content_type
|
955
|
-
elsif default = settings.default_content_type
|
1002
|
+
elsif (default = settings.default_content_type)
|
956
1003
|
content_type default
|
957
1004
|
end
|
958
1005
|
end
|
@@ -970,12 +1017,6 @@ module Sinatra
|
|
970
1017
|
self.class.settings
|
971
1018
|
end
|
972
1019
|
|
973
|
-
def options
|
974
|
-
warn "Sinatra::Base#options is deprecated and will be removed, " \
|
975
|
-
"use #settings instead."
|
976
|
-
settings
|
977
|
-
end
|
978
|
-
|
979
1020
|
# Exit the current block, halts any further processing
|
980
1021
|
# of the request, and returns the specified response.
|
981
1022
|
def halt(*response)
|
@@ -992,7 +1033,8 @@ module Sinatra
|
|
992
1033
|
|
993
1034
|
# Forward the request to the downstream app -- middleware only.
|
994
1035
|
def forward
|
995
|
-
|
1036
|
+
raise 'downstream app not set' unless @app.respond_to? :call
|
1037
|
+
|
996
1038
|
status, headers, body = @app.call env
|
997
1039
|
@response.status = status
|
998
1040
|
@response.body = body
|
@@ -1014,18 +1056,18 @@ module Sinatra
|
|
1014
1056
|
|
1015
1057
|
# Run routes defined on the class and all superclasses.
|
1016
1058
|
def route!(base = settings, pass_block = nil)
|
1017
|
-
|
1018
|
-
routes.each do |pattern, conditions, block|
|
1019
|
-
response.delete_header('Content-Type') unless @pinned_response
|
1059
|
+
routes = base.routes[@request.request_method]
|
1020
1060
|
|
1021
|
-
|
1022
|
-
|
1023
|
-
route_eval { block[*args] }
|
1024
|
-
end
|
1061
|
+
routes&.each do |pattern, conditions, block|
|
1062
|
+
response.delete_header('Content-Type') unless @pinned_response
|
1025
1063
|
|
1026
|
-
|
1027
|
-
|
1064
|
+
returned_pass_block = process_route(pattern, conditions) do |*args|
|
1065
|
+
env['sinatra.route'] = "#{@request.request_method} #{pattern}"
|
1066
|
+
route_eval { block[*args] }
|
1028
1067
|
end
|
1068
|
+
|
1069
|
+
# don't wipe out pass_block in superclass
|
1070
|
+
pass_block = returned_pass_block if returned_pass_block
|
1029
1071
|
end
|
1030
1072
|
|
1031
1073
|
# Run routes defined in superclass.
|
@@ -1049,15 +1091,17 @@ module Sinatra
|
|
1049
1091
|
# Returns pass block.
|
1050
1092
|
def process_route(pattern, conditions, block = nil, values = [])
|
1051
1093
|
route = @request.path_info
|
1052
|
-
route = '/' if route.empty?
|
1094
|
+
route = '/' if route.empty? && !settings.empty_path_info?
|
1053
1095
|
route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
|
1054
|
-
return unless params = pattern.params(route)
|
1055
1096
|
|
1056
|
-
params.
|
1097
|
+
params = pattern.params(route)
|
1098
|
+
return unless params
|
1099
|
+
|
1100
|
+
params.delete('ignore') # TODO: better params handling, maybe turn it into "smart" object or detect changes
|
1057
1101
|
force_encoding(params)
|
1058
|
-
@params = @params.merge(params) if params.any?
|
1102
|
+
@params = @params.merge(params) { |_k, v1, v2| v2 || v1 } if params.any?
|
1059
1103
|
|
1060
|
-
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)}
|
1104
|
+
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? { |subpattern| subpattern.is_a?(Mustermann::Regular) })
|
1061
1105
|
if regexp_exists
|
1062
1106
|
captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
|
1063
1107
|
values += captures
|
@@ -1070,7 +1114,7 @@ module Sinatra
|
|
1070
1114
|
conditions.each { |c| throw :pass if c.bind(self).call == false }
|
1071
1115
|
block ? block[self, values] : yield(self, values)
|
1072
1116
|
end
|
1073
|
-
rescue
|
1117
|
+
rescue StandardError
|
1074
1118
|
@env['sinatra.error.params'] = @params
|
1075
1119
|
raise
|
1076
1120
|
ensure
|
@@ -1084,35 +1128,35 @@ module Sinatra
|
|
1084
1128
|
# a NotFound exception. Subclasses can override this method to perform
|
1085
1129
|
# custom route miss logic.
|
1086
1130
|
def route_missing
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
raise NotFound, "#{request.request_method} #{request.path_info}"
|
1091
|
-
end
|
1131
|
+
raise NotFound unless @app
|
1132
|
+
|
1133
|
+
forward
|
1092
1134
|
end
|
1093
1135
|
|
1094
1136
|
# Attempt to serve static files from public directory. Throws :halt when
|
1095
1137
|
# a matching file is found, returns nil otherwise.
|
1096
1138
|
def static!(options = {})
|
1097
1139
|
return if (public_dir = settings.public_folder).nil?
|
1140
|
+
|
1098
1141
|
path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}"
|
1099
1142
|
return unless valid_path?(path)
|
1100
1143
|
|
1101
1144
|
path = File.expand_path(path)
|
1102
|
-
return unless path.start_with?(File.expand_path(public_dir)
|
1145
|
+
return unless path.start_with?("#{File.expand_path(public_dir)}/")
|
1146
|
+
|
1103
1147
|
return unless File.file?(path)
|
1104
1148
|
|
1105
1149
|
env['sinatra.static_file'] = path
|
1106
1150
|
cache_control(*settings.static_cache_control) if settings.static_cache_control?
|
1107
|
-
send_file path, options.merge(:
|
1151
|
+
send_file path, options.merge(disposition: nil)
|
1108
1152
|
end
|
1109
1153
|
|
1110
1154
|
# Run the block with 'throw :halt' support and apply result to the response.
|
1111
|
-
def invoke
|
1112
|
-
res = catch(:halt)
|
1155
|
+
def invoke(&block)
|
1156
|
+
res = catch(:halt, &block)
|
1113
1157
|
|
1114
|
-
res = [res] if Integer === res
|
1115
|
-
if Array === res
|
1158
|
+
res = [res] if (Integer === res) || (String === res)
|
1159
|
+
if (Array === res) && (Integer === res.first)
|
1116
1160
|
res = res.dup
|
1117
1161
|
status(res.shift)
|
1118
1162
|
body(res.pop)
|
@@ -1128,6 +1172,7 @@ module Sinatra
|
|
1128
1172
|
# Avoid passing frozen string in force_encoding
|
1129
1173
|
@params.merge!(@request.params).each do |key, val|
|
1130
1174
|
next unless val.respond_to?(:force_encoding)
|
1175
|
+
|
1131
1176
|
val = val.dup if val.frozen?
|
1132
1177
|
@params[key] = force_encoding(val)
|
1133
1178
|
end
|
@@ -1139,39 +1184,43 @@ module Sinatra
|
|
1139
1184
|
end
|
1140
1185
|
route!
|
1141
1186
|
end
|
1142
|
-
rescue ::Exception =>
|
1143
|
-
invoke { handle_exception!(
|
1187
|
+
rescue ::Exception => e
|
1188
|
+
invoke { handle_exception!(e) }
|
1144
1189
|
ensure
|
1145
1190
|
begin
|
1146
1191
|
filter! :after unless env['sinatra.static_file']
|
1147
|
-
rescue ::Exception =>
|
1148
|
-
invoke { handle_exception!(
|
1192
|
+
rescue ::Exception => e
|
1193
|
+
invoke { handle_exception!(e) } unless @env['sinatra.error']
|
1149
1194
|
end
|
1150
1195
|
end
|
1151
1196
|
|
1152
1197
|
# Error handling during requests.
|
1153
1198
|
def handle_exception!(boom)
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1199
|
+
error_params = @env['sinatra.error.params']
|
1200
|
+
|
1201
|
+
@params = @params.merge(error_params) if error_params
|
1202
|
+
|
1157
1203
|
@env['sinatra.error'] = boom
|
1158
1204
|
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1205
|
+
http_status = if boom.is_a? Sinatra::Error
|
1206
|
+
if boom.respond_to? :http_status
|
1207
|
+
boom.http_status
|
1208
|
+
elsif settings.use_code? && boom.respond_to?(:code)
|
1209
|
+
boom.code
|
1210
|
+
end
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
http_status = 500 unless http_status&.between?(400, 599)
|
1214
|
+
status(http_status)
|
1166
1215
|
|
1167
1216
|
if server_error?
|
1168
1217
|
dump_errors! boom if settings.dump_errors?
|
1169
|
-
raise boom if settings.show_exceptions?
|
1218
|
+
raise boom if settings.show_exceptions? && (settings.show_exceptions != :after_handler)
|
1170
1219
|
elsif not_found?
|
1171
1220
|
headers['X-Cascade'] = 'pass' if settings.x_cascade?
|
1172
1221
|
end
|
1173
1222
|
|
1174
|
-
if res = error_block!(boom.class, boom) || error_block!(status, boom)
|
1223
|
+
if (res = error_block!(boom.class, boom) || error_block!(status, boom))
|
1175
1224
|
return res
|
1176
1225
|
end
|
1177
1226
|
|
@@ -1180,12 +1229,14 @@ module Sinatra
|
|
1180
1229
|
body Rack::Utils.escape_html(boom.message)
|
1181
1230
|
else
|
1182
1231
|
content_type 'text/html'
|
1183
|
-
body
|
1232
|
+
body "<h1>#{not_found? ? 'Not Found' : 'Bad Request'}</h1>"
|
1184
1233
|
end
|
1185
1234
|
end
|
1186
1235
|
|
1187
1236
|
return unless server_error?
|
1188
|
-
|
1237
|
+
|
1238
|
+
raise boom if settings.raise_errors? || settings.show_exceptions?
|
1239
|
+
|
1189
1240
|
error_block! Exception, boom
|
1190
1241
|
end
|
1191
1242
|
|
@@ -1193,7 +1244,10 @@ module Sinatra
|
|
1193
1244
|
def error_block!(key, *block_params)
|
1194
1245
|
base = settings
|
1195
1246
|
while base.respond_to?(:errors)
|
1196
|
-
|
1247
|
+
args_array = base.errors[key]
|
1248
|
+
|
1249
|
+
next base = base.superclass unless args_array
|
1250
|
+
|
1197
1251
|
args_array.reverse_each do |args|
|
1198
1252
|
first = args == args_array.first
|
1199
1253
|
args += [block_params]
|
@@ -1201,51 +1255,50 @@ module Sinatra
|
|
1201
1255
|
return resp unless resp.nil? && !first
|
1202
1256
|
end
|
1203
1257
|
end
|
1204
|
-
return false unless key.respond_to?
|
1258
|
+
return false unless key.respond_to?(:superclass) && (key.superclass < Exception)
|
1259
|
+
|
1205
1260
|
error_block!(key.superclass, *block_params)
|
1206
1261
|
end
|
1207
1262
|
|
1208
1263
|
def dump_errors!(boom)
|
1209
|
-
msg = ["#{Time.now.strftime(
|
1264
|
+
msg = ["#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
|
1210
1265
|
@env['rack.errors'].puts(msg)
|
1211
1266
|
end
|
1212
1267
|
|
1213
1268
|
class << self
|
1214
1269
|
CALLERS_TO_IGNORE = [ # :nodoc:
|
1215
|
-
|
1216
|
-
/
|
1270
|
+
%r{/sinatra(/(base|main|show_exceptions))?\.rb$}, # all sinatra code
|
1271
|
+
%r{lib/tilt.*\.rb$}, # all tilt code
|
1217
1272
|
/^\(.*\)$/, # generated code
|
1218
|
-
/
|
1273
|
+
%r{rubygems/(custom|core_ext/kernel)_require\.rb$}, # rubygems require hacks
|
1219
1274
|
/active_support/, # active_support require hacks
|
1220
|
-
|
1275
|
+
%r{bundler(/(?:runtime|inline))?\.rb}, # bundler require hacks
|
1221
1276
|
/<internal:/, # internal in ruby >= 1.9.2
|
1222
|
-
/
|
1223
|
-
]
|
1277
|
+
%r{zeitwerk/kernel\.rb} # Zeitwerk kernel#require decorator
|
1278
|
+
].freeze
|
1224
1279
|
|
1225
|
-
|
1226
|
-
if defined?(RUBY_IGNORE_CALLERS)
|
1227
|
-
warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
|
1228
|
-
CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
|
1229
|
-
end
|
1280
|
+
attr_reader :routes, :filters, :templates, :errors, :on_start_callback, :on_stop_callback
|
1230
1281
|
|
1231
|
-
|
1282
|
+
def callers_to_ignore
|
1283
|
+
CALLERS_TO_IGNORE
|
1284
|
+
end
|
1232
1285
|
|
1233
1286
|
# Removes all routes, filters, middleware and extension hooks from the
|
1234
1287
|
# current class (not routes/filters/... defined by its superclass).
|
1235
1288
|
def reset!
|
1236
1289
|
@conditions = []
|
1237
1290
|
@routes = {}
|
1238
|
-
@filters = {:
|
1291
|
+
@filters = { before: [], after: [] }
|
1239
1292
|
@errors = {}
|
1240
1293
|
@middleware = []
|
1241
1294
|
@prototype = nil
|
1242
1295
|
@extensions = []
|
1243
1296
|
|
1244
|
-
if superclass.respond_to?(:templates)
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1297
|
+
@templates = if superclass.respond_to?(:templates)
|
1298
|
+
Hash.new { |_hash, key| superclass.templates[key] }
|
1299
|
+
else
|
1300
|
+
{}
|
1301
|
+
end
|
1249
1302
|
end
|
1250
1303
|
|
1251
1304
|
# Extension modules registered on this class and all superclasses.
|
@@ -1269,16 +1322,21 @@ module Sinatra
|
|
1269
1322
|
# Sets an option to the given value. If the value is a proc,
|
1270
1323
|
# the proc will be called every time the option is accessed.
|
1271
1324
|
def set(option, value = (not_set = true), ignore_setter = false, &block)
|
1272
|
-
raise ArgumentError if block
|
1273
|
-
|
1325
|
+
raise ArgumentError if block && !not_set
|
1326
|
+
|
1327
|
+
if block
|
1328
|
+
value = block
|
1329
|
+
not_set = false
|
1330
|
+
end
|
1274
1331
|
|
1275
1332
|
if not_set
|
1276
1333
|
raise ArgumentError unless option.respond_to?(:each)
|
1277
|
-
|
1334
|
+
|
1335
|
+
option.each { |k, v| set(k, v) }
|
1278
1336
|
return self
|
1279
1337
|
end
|
1280
1338
|
|
1281
|
-
if respond_to?("#{option}=")
|
1339
|
+
if respond_to?("#{option}=") && !ignore_setter
|
1282
1340
|
return __send__("#{option}=", value)
|
1283
1341
|
end
|
1284
1342
|
|
@@ -1317,7 +1375,7 @@ module Sinatra
|
|
1317
1375
|
# class, or an HTTP status code to specify which errors should be
|
1318
1376
|
# handled.
|
1319
1377
|
def error(*codes, &block)
|
1320
|
-
args = compile!
|
1378
|
+
args = compile! 'ERROR', /.*/, block
|
1321
1379
|
codes = codes.flat_map(&method(:Array))
|
1322
1380
|
codes << Exception if codes.empty?
|
1323
1381
|
codes << Sinatra::NotFound if codes.include?(404)
|
@@ -1343,7 +1401,7 @@ module Sinatra
|
|
1343
1401
|
# Load embedded templates from the file; uses the caller's __FILE__
|
1344
1402
|
# when no file is specified.
|
1345
1403
|
def inline_templates=(file = nil)
|
1346
|
-
file = (
|
1404
|
+
file = (caller_files.first || File.expand_path($0)) if file.nil? || file == true
|
1347
1405
|
|
1348
1406
|
begin
|
1349
1407
|
io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
|
@@ -1352,23 +1410,24 @@ module Sinatra
|
|
1352
1410
|
app, data = nil
|
1353
1411
|
end
|
1354
1412
|
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1413
|
+
return unless data
|
1414
|
+
|
1415
|
+
encoding = if app && app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
|
1416
|
+
$2
|
1417
|
+
else
|
1418
|
+
settings.default_encoding
|
1419
|
+
end
|
1420
|
+
|
1421
|
+
lines = app.count("\n") + 1
|
1422
|
+
template = nil
|
1423
|
+
force_encoding data, encoding
|
1424
|
+
data.each_line do |line|
|
1425
|
+
lines += 1
|
1426
|
+
if line =~ /^@@\s*(.*\S)\s*$/
|
1427
|
+
template = force_encoding(String.new, encoding)
|
1428
|
+
templates[$1.to_sym] = [template, file, lines]
|
1429
|
+
elsif template
|
1430
|
+
template << line
|
1372
1431
|
end
|
1373
1432
|
end
|
1374
1433
|
end
|
@@ -1377,8 +1436,10 @@ module Sinatra
|
|
1377
1436
|
def mime_type(type, value = nil)
|
1378
1437
|
return type if type.nil?
|
1379
1438
|
return type.to_s if type.to_s.include?('/')
|
1380
|
-
|
1439
|
+
|
1440
|
+
type = ".#{type}" unless type.to_s[0] == '.'
|
1381
1441
|
return Rack::Mime.mime_type(type, nil) unless value
|
1442
|
+
|
1382
1443
|
Rack::Mime::MIME_TYPES[type] = value
|
1383
1444
|
end
|
1384
1445
|
|
@@ -1387,7 +1448,7 @@ module Sinatra
|
|
1387
1448
|
# mime_types :js # => ['application/javascript', 'text/javascript']
|
1388
1449
|
def mime_types(type)
|
1389
1450
|
type = mime_type type
|
1390
|
-
type =~
|
1451
|
+
type =~ %r{^application/(xml|javascript)$} ? [type, "text/#{$1}"] : [type]
|
1391
1452
|
end
|
1392
1453
|
|
1393
1454
|
# Define a before filter; runs before all requests within the same
|
@@ -1409,6 +1470,14 @@ module Sinatra
|
|
1409
1470
|
filters[type] << compile!(type, path, block, **options)
|
1410
1471
|
end
|
1411
1472
|
|
1473
|
+
def on_start(&on_start_callback)
|
1474
|
+
@on_start_callback = on_start_callback
|
1475
|
+
end
|
1476
|
+
|
1477
|
+
def on_stop(&on_stop_callback)
|
1478
|
+
@on_stop_callback = on_stop_callback
|
1479
|
+
end
|
1480
|
+
|
1412
1481
|
# Add a route condition. The route is considered non-matching when the
|
1413
1482
|
# block returns false.
|
1414
1483
|
def condition(name = "#{caller.first[/`.*'/]} condition", &block)
|
@@ -1416,7 +1485,7 @@ module Sinatra
|
|
1416
1485
|
end
|
1417
1486
|
|
1418
1487
|
def public=(value)
|
1419
|
-
|
1488
|
+
warn_for_deprecation ':public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead'
|
1420
1489
|
set(:public_folder, value)
|
1421
1490
|
end
|
1422
1491
|
|
@@ -1438,14 +1507,21 @@ module Sinatra
|
|
1438
1507
|
route('HEAD', path, opts, &block)
|
1439
1508
|
end
|
1440
1509
|
|
1441
|
-
def put(path, opts = {}, &
|
1442
|
-
|
1443
|
-
def
|
1444
|
-
|
1445
|
-
def
|
1446
|
-
|
1447
|
-
def
|
1448
|
-
|
1510
|
+
def put(path, opts = {}, &block) route 'PUT', path, opts, &block end
|
1511
|
+
|
1512
|
+
def post(path, opts = {}, &block) route 'POST', path, opts, &block end
|
1513
|
+
|
1514
|
+
def delete(path, opts = {}, &block) route 'DELETE', path, opts, &block end
|
1515
|
+
|
1516
|
+
def head(path, opts = {}, &block) route 'HEAD', path, opts, &block end
|
1517
|
+
|
1518
|
+
def options(path, opts = {}, &block) route 'OPTIONS', path, opts, &block end
|
1519
|
+
|
1520
|
+
def patch(path, opts = {}, &block) route 'PATCH', path, opts, &block end
|
1521
|
+
|
1522
|
+
def link(path, opts = {}, &block) route 'LINK', path, opts, &block end
|
1523
|
+
|
1524
|
+
def unlink(path, opts = {}, &block) route 'UNLINK', path, opts, &block end
|
1449
1525
|
|
1450
1526
|
# Makes the methods defined in the block and in the Modules given
|
1451
1527
|
# in `extensions` available to the handlers and templates
|
@@ -1485,37 +1561,41 @@ module Sinatra
|
|
1485
1561
|
# Stop the self-hosted server if running.
|
1486
1562
|
def quit!
|
1487
1563
|
return unless running?
|
1564
|
+
|
1488
1565
|
# Use Thin's hard #stop! if available, otherwise just #stop.
|
1489
1566
|
running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
|
1490
|
-
|
1567
|
+
warn '== Sinatra has ended his set (crowd applauds)' unless suppress_messages?
|
1491
1568
|
set :running_server, nil
|
1492
1569
|
set :handler_name, nil
|
1570
|
+
|
1571
|
+
on_stop_callback.call unless on_stop_callback.nil?
|
1493
1572
|
end
|
1494
1573
|
|
1495
|
-
|
1574
|
+
alias stop! quit!
|
1496
1575
|
|
1497
1576
|
# Run the Sinatra app as a self-hosted server using
|
1498
|
-
# Puma,
|
1577
|
+
# Puma, Falcon, or WEBrick (in that order). If given a block, will call
|
1499
1578
|
# with the constructed handler once we have taken the stage.
|
1500
1579
|
def run!(options = {}, &block)
|
1501
1580
|
return if running?
|
1581
|
+
|
1502
1582
|
set options
|
1503
1583
|
handler = Rack::Handler.pick(server)
|
1504
1584
|
handler_name = handler.name.gsub(/.*::/, '')
|
1505
1585
|
server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
|
1506
|
-
server_settings.merge!(:
|
1586
|
+
server_settings.merge!(Port: port, Host: bind)
|
1507
1587
|
|
1508
1588
|
begin
|
1509
1589
|
start_server(handler, server_settings, handler_name, &block)
|
1510
1590
|
rescue Errno::EADDRINUSE
|
1511
|
-
|
1591
|
+
warn "== Someone is already performing on port #{port}!"
|
1512
1592
|
raise
|
1513
1593
|
ensure
|
1514
1594
|
quit!
|
1515
1595
|
end
|
1516
1596
|
end
|
1517
1597
|
|
1518
|
-
|
1598
|
+
alias start! run!
|
1519
1599
|
|
1520
1600
|
# Check whether the self-hosted server is running or not.
|
1521
1601
|
def running?
|
@@ -1533,10 +1613,11 @@ module Sinatra
|
|
1533
1613
|
# Create a new instance of the class fronted by its middleware
|
1534
1614
|
# pipeline. The object is guaranteed to respond to #call but may not be
|
1535
1615
|
# an instance of the class new was called on.
|
1536
|
-
def new(*args,
|
1537
|
-
instance = new!(*args,
|
1616
|
+
def new(*args, &block)
|
1617
|
+
instance = new!(*args, &block)
|
1538
1618
|
Wrapper.new(build(instance).to_app, instance)
|
1539
1619
|
end
|
1620
|
+
ruby2_keywords :new if respond_to?(:ruby2_keywords, true)
|
1540
1621
|
|
1541
1622
|
# Creates a Rack::Builder instance with all the middleware set up and
|
1542
1623
|
# the given +app+ as end point.
|
@@ -1558,12 +1639,6 @@ module Sinatra
|
|
1558
1639
|
cleaned_caller(1).flatten
|
1559
1640
|
end
|
1560
1641
|
|
1561
|
-
# Like caller_files, but containing Arrays rather than strings with the
|
1562
|
-
# first element being the file, and the second being the line.
|
1563
|
-
def caller_locations
|
1564
|
-
cleaned_caller 2
|
1565
|
-
end
|
1566
|
-
|
1567
1642
|
private
|
1568
1643
|
|
1569
1644
|
# Starts the server by running the Rack Handler.
|
@@ -1574,14 +1649,14 @@ module Sinatra
|
|
1574
1649
|
# Run the instance we created:
|
1575
1650
|
handler.run(self, **server_settings) do |server|
|
1576
1651
|
unless suppress_messages?
|
1577
|
-
|
1652
|
+
warn "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
|
1578
1653
|
end
|
1579
1654
|
|
1580
1655
|
setup_traps
|
1581
1656
|
set :running_server, server
|
1582
1657
|
set :handler_name, handler_name
|
1583
1658
|
server.threaded = settings.threaded if server.respond_to? :threaded=
|
1584
|
-
|
1659
|
+
on_start_callback.call unless on_start_callback.nil?
|
1585
1660
|
yield server if block_given?
|
1586
1661
|
end
|
1587
1662
|
end
|
@@ -1591,18 +1666,18 @@ module Sinatra
|
|
1591
1666
|
end
|
1592
1667
|
|
1593
1668
|
def setup_traps
|
1594
|
-
|
1595
|
-
at_exit { quit! }
|
1669
|
+
return unless traps?
|
1596
1670
|
|
1597
|
-
|
1598
|
-
old_handler = trap(signal) do
|
1599
|
-
quit!
|
1600
|
-
old_handler.call if old_handler.respond_to?(:call)
|
1601
|
-
end
|
1602
|
-
end
|
1671
|
+
at_exit { quit! }
|
1603
1672
|
|
1604
|
-
|
1673
|
+
%i[INT TERM].each do |signal|
|
1674
|
+
old_handler = trap(signal) do
|
1675
|
+
quit!
|
1676
|
+
old_handler.call if old_handler.respond_to?(:call)
|
1677
|
+
end
|
1605
1678
|
end
|
1679
|
+
|
1680
|
+
set :traps, false
|
1606
1681
|
end
|
1607
1682
|
|
1608
1683
|
# Dynamically defines a method on settings.
|
@@ -1630,18 +1705,21 @@ module Sinatra
|
|
1630
1705
|
end
|
1631
1706
|
end
|
1632
1707
|
end
|
1633
|
-
|
1708
|
+
alias agent user_agent
|
1634
1709
|
|
1635
1710
|
# Condition for matching mimetypes. Accepts file extensions.
|
1636
1711
|
def provides(*types)
|
1637
1712
|
types.map! { |t| mime_types(t) }
|
1638
1713
|
types.flatten!
|
1639
1714
|
condition do
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1715
|
+
response_content_type = response['Content-Type']
|
1716
|
+
preferred_type = request.preferred_type(types)
|
1717
|
+
|
1718
|
+
if response_content_type
|
1719
|
+
types.include?(response_content_type) || types.include?(response_content_type[/^[^;]+/])
|
1720
|
+
elsif preferred_type
|
1721
|
+
params = (preferred_type.respond_to?(:params) ? preferred_type.params : {})
|
1722
|
+
content_type(preferred_type, params)
|
1645
1723
|
true
|
1646
1724
|
else
|
1647
1725
|
false
|
@@ -1650,7 +1728,7 @@ module Sinatra
|
|
1650
1728
|
end
|
1651
1729
|
|
1652
1730
|
def route(verb, path, options = {}, &block)
|
1653
|
-
enable :empty_path_info if path ==
|
1731
|
+
enable :empty_path_info if path == '' && empty_path_info.nil?
|
1654
1732
|
signature = compile!(verb, path, block, **options)
|
1655
1733
|
(@routes[verb] ||= []) << signature
|
1656
1734
|
invoke_hook(:route_added, verb, path, block)
|
@@ -1679,12 +1757,13 @@ module Sinatra
|
|
1679
1757
|
pattern = compile(path, route_mustermann_opts)
|
1680
1758
|
method_name = "#{verb} #{path}"
|
1681
1759
|
unbound_method = generate_method(method_name, &block)
|
1682
|
-
conditions
|
1683
|
-
|
1684
|
-
|
1685
|
-
proc { |a,
|
1760
|
+
conditions = @conditions
|
1761
|
+
@conditions = []
|
1762
|
+
wrapper = block.arity.zero? ?
|
1763
|
+
proc { |a, _p| unbound_method.bind(a).call } :
|
1764
|
+
proc { |a, p| unbound_method.bind(a).call(*p) }
|
1686
1765
|
|
1687
|
-
[
|
1766
|
+
[pattern, conditions, wrapper]
|
1688
1767
|
end
|
1689
1768
|
|
1690
1769
|
def compile(path, route_mustermann_opts = {})
|
@@ -1702,7 +1781,7 @@ module Sinatra
|
|
1702
1781
|
end
|
1703
1782
|
|
1704
1783
|
def setup_middleware(builder)
|
1705
|
-
middleware.each { |c,a,b| builder.use(c, *a, &b) }
|
1784
|
+
middleware.each { |c, a, b| builder.use(c, *a, &b) }
|
1706
1785
|
end
|
1707
1786
|
|
1708
1787
|
def setup_logging(builder)
|
@@ -1732,9 +1811,10 @@ module Sinatra
|
|
1732
1811
|
|
1733
1812
|
def setup_protection(builder)
|
1734
1813
|
return unless protection?
|
1814
|
+
|
1735
1815
|
options = Hash === protection ? protection.dup : {}
|
1736
1816
|
options = {
|
1737
|
-
img_src:
|
1817
|
+
img_src: "'self' data:",
|
1738
1818
|
font_src: "'self'"
|
1739
1819
|
}.merge options
|
1740
1820
|
|
@@ -1748,6 +1828,7 @@ module Sinatra
|
|
1748
1828
|
|
1749
1829
|
def setup_sessions(builder)
|
1750
1830
|
return unless sessions?
|
1831
|
+
|
1751
1832
|
options = {}
|
1752
1833
|
options[:secret] = session_secret if session_secret?
|
1753
1834
|
options.merge! sessions.to_hash if sessions.respond_to? :to_hash
|
@@ -1770,15 +1851,15 @@ module Sinatra
|
|
1770
1851
|
end
|
1771
1852
|
|
1772
1853
|
# used for deprecation warnings
|
1773
|
-
def
|
1774
|
-
|
1854
|
+
def warn_for_deprecation(message)
|
1855
|
+
warn message + "\n\tfrom #{cleaned_caller.first.join(':')}"
|
1775
1856
|
end
|
1776
1857
|
|
1777
1858
|
# Like Kernel#caller but excluding certain magic entries
|
1778
1859
|
def cleaned_caller(keep = 3)
|
1779
|
-
caller(1)
|
1780
|
-
map!
|
1781
|
-
reject { |file, *_|
|
1860
|
+
caller(1)
|
1861
|
+
.map! { |line| line.split(/:(?=\d|in )/, 3)[0, keep] }
|
1862
|
+
.reject { |file, *_| callers_to_ignore.any? { |pattern| file =~ pattern } }
|
1782
1863
|
end
|
1783
1864
|
end
|
1784
1865
|
|
@@ -1786,6 +1867,7 @@ module Sinatra
|
|
1786
1867
|
# which is UTF-8 by default
|
1787
1868
|
def self.force_encoding(data, encoding = default_encoding)
|
1788
1869
|
return if data == settings || data.is_a?(Tempfile)
|
1870
|
+
|
1789
1871
|
if data.respond_to? :force_encoding
|
1790
1872
|
data.force_encoding(encoding).encode!
|
1791
1873
|
elsif data.respond_to? :each_value
|
@@ -1796,24 +1878,26 @@ module Sinatra
|
|
1796
1878
|
data
|
1797
1879
|
end
|
1798
1880
|
|
1799
|
-
def force_encoding(*args)
|
1881
|
+
def force_encoding(*args)
|
1882
|
+
settings.force_encoding(*args)
|
1883
|
+
end
|
1800
1884
|
|
1801
1885
|
reset!
|
1802
1886
|
|
1803
1887
|
set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
|
1804
|
-
set :raise_errors,
|
1805
|
-
set :dump_errors,
|
1806
|
-
set :show_exceptions,
|
1888
|
+
set :raise_errors, proc { test? }
|
1889
|
+
set :dump_errors, proc { !test? }
|
1890
|
+
set :show_exceptions, proc { development? }
|
1807
1891
|
set :sessions, false
|
1808
|
-
set :session_store, Rack::
|
1892
|
+
set :session_store, Rack::Protection::EncryptedCookie
|
1809
1893
|
set :logging, false
|
1810
1894
|
set :protection, true
|
1811
1895
|
set :method_override, false
|
1812
1896
|
set :use_code, false
|
1813
|
-
set :default_encoding,
|
1897
|
+
set :default_encoding, 'utf-8'
|
1814
1898
|
set :x_cascade, true
|
1815
1899
|
set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
|
1816
|
-
settings.add_charset <<
|
1900
|
+
settings.add_charset << %r{^text/}
|
1817
1901
|
set :mustermann_opts, {}
|
1818
1902
|
set :default_content_type, 'text/html'
|
1819
1903
|
|
@@ -1821,14 +1905,15 @@ module Sinatra
|
|
1821
1905
|
begin
|
1822
1906
|
require 'securerandom'
|
1823
1907
|
set :session_secret, SecureRandom.hex(64)
|
1824
|
-
rescue LoadError, NotImplementedError
|
1908
|
+
rescue LoadError, NotImplementedError, RuntimeError
|
1825
1909
|
# SecureRandom raises a NotImplementedError if no random device is available
|
1826
|
-
|
1910
|
+
# RuntimeError raised due to broken openssl backend: https://bugs.ruby-lang.org/issues/19230
|
1911
|
+
set :session_secret, format('%064x', Kernel.rand((2**256) - 1))
|
1827
1912
|
end
|
1828
1913
|
|
1829
1914
|
class << self
|
1830
|
-
|
1831
|
-
|
1915
|
+
alias methodoverride? method_override?
|
1916
|
+
alias methodoverride= method_override=
|
1832
1917
|
end
|
1833
1918
|
|
1834
1919
|
set :run, false # start server via at-exit hook?
|
@@ -1836,21 +1921,16 @@ module Sinatra
|
|
1836
1921
|
set :handler_name, nil
|
1837
1922
|
set :traps, true
|
1838
1923
|
set :server, %w[HTTP webrick]
|
1839
|
-
set :bind,
|
1924
|
+
set :bind, proc { development? ? 'localhost' : '0.0.0.0' }
|
1840
1925
|
set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
|
1841
1926
|
set :quiet, false
|
1842
1927
|
|
1843
1928
|
ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
|
1844
1929
|
|
1845
|
-
if ruby_engine
|
1846
|
-
|
1847
|
-
|
1848
|
-
|
1849
|
-
server.unshift 'puma'
|
1850
|
-
server.unshift 'mongrel' if ruby_engine.nil?
|
1851
|
-
server.unshift 'thin' if ruby_engine != 'jruby'
|
1852
|
-
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1853
|
-
end
|
1930
|
+
server.unshift 'thin' if ruby_engine != 'jruby'
|
1931
|
+
server.unshift 'falcon' if ruby_engine != 'jruby'
|
1932
|
+
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1933
|
+
server.unshift 'puma'
|
1854
1934
|
|
1855
1935
|
set :absolute_redirects, true
|
1856
1936
|
set :prefixed_redirects, false
|
@@ -1858,14 +1938,14 @@ module Sinatra
|
|
1858
1938
|
set :strict_paths, true
|
1859
1939
|
|
1860
1940
|
set :app_file, nil
|
1861
|
-
set :root,
|
1862
|
-
set :views,
|
1863
|
-
set :reload_templates,
|
1941
|
+
set :root, proc { app_file && File.expand_path(File.dirname(app_file)) }
|
1942
|
+
set :views, proc { root && File.join(root, 'views') }
|
1943
|
+
set :reload_templates, proc { development? }
|
1864
1944
|
set :lock, false
|
1865
1945
|
set :threaded, true
|
1866
1946
|
|
1867
|
-
set :public_folder,
|
1868
|
-
set :static,
|
1947
|
+
set :public_folder, proc { root && File.join(root, 'public') }
|
1948
|
+
set :static, proc { public_folder && File.exist?(public_folder) }
|
1869
1949
|
set :static_cache_control, false
|
1870
1950
|
|
1871
1951
|
error ::Exception do
|
@@ -1884,7 +1964,7 @@ module Sinatra
|
|
1884
1964
|
error NotFound do
|
1885
1965
|
content_type 'text/html'
|
1886
1966
|
|
1887
|
-
if
|
1967
|
+
if instance_of?(Sinatra::Application)
|
1888
1968
|
code = <<-RUBY.gsub(/^ {12}/, '')
|
1889
1969
|
#{request.request_method.downcase} '#{request.path_info}' do
|
1890
1970
|
"Hello World"
|
@@ -1899,11 +1979,11 @@ module Sinatra
|
|
1899
1979
|
end
|
1900
1980
|
RUBY
|
1901
1981
|
|
1902
|
-
file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(
|
1982
|
+
file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(%r{^/}, '')
|
1903
1983
|
code = "# in #{file}\n#{code}" unless file.empty?
|
1904
1984
|
end
|
1905
1985
|
|
1906
|
-
|
1986
|
+
<<-HTML.gsub(/^ {10}/, '')
|
1907
1987
|
<!DOCTYPE html>
|
1908
1988
|
<html>
|
1909
1989
|
<head>
|
@@ -1915,7 +1995,7 @@ module Sinatra
|
|
1915
1995
|
</head>
|
1916
1996
|
<body>
|
1917
1997
|
<h2>Sinatra doesn’t know this ditty.</h2>
|
1918
|
-
<img src='#{uri
|
1998
|
+
<img src='#{uri '/__sinatra__/404.png'}'>
|
1919
1999
|
<div id="c">
|
1920
2000
|
Try this:
|
1921
2001
|
<pre>#{Rack::Utils.escape_html(code)}</pre>
|
@@ -1935,12 +2015,12 @@ module Sinatra
|
|
1935
2015
|
# top-level. Subclassing Sinatra::Base is highly recommended for
|
1936
2016
|
# modular applications.
|
1937
2017
|
class Application < Base
|
1938
|
-
set :logging,
|
2018
|
+
set :logging, proc { !test? }
|
1939
2019
|
set :method_override, true
|
1940
|
-
set :run,
|
2020
|
+
set :run, proc { !test? }
|
1941
2021
|
set :app_file, nil
|
1942
2022
|
|
1943
|
-
def self.register(*extensions, &block)
|
2023
|
+
def self.register(*extensions, &block) # :nodoc:
|
1944
2024
|
added_methods = extensions.flat_map(&:public_instance_methods)
|
1945
2025
|
Delegator.delegate(*added_methods)
|
1946
2026
|
super(*extensions, &block)
|
@@ -1950,11 +2030,12 @@ module Sinatra
|
|
1950
2030
|
# Sinatra delegation mixin. Mixing this module into an object causes all
|
1951
2031
|
# methods to be delegated to the Sinatra::Application class. Used primarily
|
1952
2032
|
# at the top-level.
|
1953
|
-
module Delegator
|
2033
|
+
module Delegator # :nodoc:
|
1954
2034
|
def self.delegate(*methods)
|
1955
2035
|
methods.each do |method_name|
|
1956
2036
|
define_method(method_name) do |*args, &block|
|
1957
2037
|
return super(*args, &block) if respond_to? method_name
|
2038
|
+
|
1958
2039
|
Delegator.target.send(method_name, *args, &block)
|
1959
2040
|
end
|
1960
2041
|
# ensure keyword argument passing is compatible with ruby >= 2.7
|
@@ -1966,7 +2047,7 @@ module Sinatra
|
|
1966
2047
|
delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
|
1967
2048
|
:template, :layout, :before, :after, :error, :not_found, :configure,
|
1968
2049
|
:set, :mime_type, :enable, :disable, :use, :development?, :test?,
|
1969
|
-
:production?, :helpers, :settings, :register
|
2050
|
+
:production?, :helpers, :settings, :register, :on_start, :on_stop
|
1970
2051
|
|
1971
2052
|
class << self
|
1972
2053
|
attr_accessor :target
|
@@ -1977,7 +2058,8 @@ module Sinatra
|
|
1977
2058
|
|
1978
2059
|
class Wrapper
|
1979
2060
|
def initialize(stack, instance)
|
1980
|
-
@stack
|
2061
|
+
@stack = stack
|
2062
|
+
@instance = instance
|
1981
2063
|
end
|
1982
2064
|
|
1983
2065
|
def settings
|