sinatra 2.2.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|