sinatra 3.0.5 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AUTHORS.md +14 -8
- data/CHANGELOG.md +75 -2
- data/CONTRIBUTING.md +11 -11
- data/Gemfile +22 -20
- data/MAINTENANCE.md +2 -2
- data/README.md +137 -129
- data/Rakefile +25 -22
- data/SECURITY.md +1 -1
- data/VERSION +1 -1
- data/examples/chat.rb +20 -9
- data/examples/lifecycle_events.rb +20 -0
- data/examples/stream.ru +0 -2
- data/lib/sinatra/base.rb +154 -46
- data/lib/sinatra/indifferent_hash.rb +10 -8
- data/lib/sinatra/show_exceptions.rb +2 -2
- data/lib/sinatra/version.rb +1 -1
- data/sinatra.gemspec +4 -5
- metadata +37 -32
- data/examples/rainbows.conf +0 -3
- data/examples/rainbows.rb +0 -22
data/lib/sinatra/base.rb
CHANGED
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
# external dependencies
|
|
4
4
|
require 'rack'
|
|
5
|
+
begin
|
|
6
|
+
require 'rackup'
|
|
7
|
+
rescue LoadError
|
|
8
|
+
end
|
|
5
9
|
require 'tilt'
|
|
6
10
|
require 'rack/protection'
|
|
11
|
+
require 'rack/session'
|
|
7
12
|
require 'mustermann'
|
|
8
13
|
require 'mustermann/sinatra'
|
|
9
14
|
require 'mustermann/regular'
|
|
@@ -19,7 +24,7 @@ require 'sinatra/version'
|
|
|
19
24
|
|
|
20
25
|
module Sinatra
|
|
21
26
|
# The request object. See Rack::Request for more info:
|
|
22
|
-
#
|
|
27
|
+
# https://rubydoc.info/github/rack/rack/main/Rack/Request
|
|
23
28
|
class Request < Rack::Request
|
|
24
29
|
HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/.freeze
|
|
25
30
|
HEADER_VALUE_WITH_PARAMS = %r{(?:(?:\w+|\*)/(?:\w+(?:\.|-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*}.freeze
|
|
@@ -158,8 +163,8 @@ module Sinatra
|
|
|
158
163
|
|
|
159
164
|
# The response object. See Rack::Response and Rack::Response::Helpers for
|
|
160
165
|
# more info:
|
|
161
|
-
#
|
|
162
|
-
#
|
|
166
|
+
# https://rubydoc.info/github/rack/rack/main/Rack/Response
|
|
167
|
+
# https://rubydoc.info/github/rack/rack/main/Rack/Response/Helpers
|
|
163
168
|
class Response < Rack::Response
|
|
164
169
|
DROP_BODY_RESPONSES = [204, 304].freeze
|
|
165
170
|
|
|
@@ -176,8 +181,8 @@ module Sinatra
|
|
|
176
181
|
result = body
|
|
177
182
|
|
|
178
183
|
if drop_content_info?
|
|
179
|
-
headers.delete '
|
|
180
|
-
headers.delete '
|
|
184
|
+
headers.delete 'content-length'
|
|
185
|
+
headers.delete 'content-type'
|
|
181
186
|
end
|
|
182
187
|
|
|
183
188
|
if drop_body?
|
|
@@ -186,9 +191,9 @@ module Sinatra
|
|
|
186
191
|
end
|
|
187
192
|
|
|
188
193
|
if calculate_content_length?
|
|
189
|
-
# if some other code has already set
|
|
194
|
+
# if some other code has already set content-length, don't muck with it
|
|
190
195
|
# currently, this would be the static file-handler
|
|
191
|
-
headers['
|
|
196
|
+
headers['content-length'] = body.map(&:bytesize).reduce(0, :+).to_s
|
|
192
197
|
end
|
|
193
198
|
|
|
194
199
|
[status, headers, result]
|
|
@@ -197,7 +202,7 @@ module Sinatra
|
|
|
197
202
|
private
|
|
198
203
|
|
|
199
204
|
def calculate_content_length?
|
|
200
|
-
headers['
|
|
205
|
+
headers['content-type'] && !headers['content-length'] && (Array === body)
|
|
201
206
|
end
|
|
202
207
|
|
|
203
208
|
def drop_content_info?
|
|
@@ -209,7 +214,7 @@ module Sinatra
|
|
|
209
214
|
end
|
|
210
215
|
end
|
|
211
216
|
|
|
212
|
-
# Some Rack handlers
|
|
217
|
+
# Some Rack handlers implement an extended body object protocol, however,
|
|
213
218
|
# some middleware (namely Rack::Lint) will break it by not mirroring the methods in question.
|
|
214
219
|
# This middleware will detect an extended body object and will make sure it reaches the
|
|
215
220
|
# handler directly. We do this here, so our middleware and middleware set up by the app will
|
|
@@ -289,10 +294,8 @@ module Sinatra
|
|
|
289
294
|
def block.each; yield(call) end
|
|
290
295
|
response.body = block
|
|
291
296
|
elsif value
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
unless request.head? || value.is_a?(Rack::File::Iterator) || value.is_a?(Stream)
|
|
295
|
-
headers.delete 'Content-Length'
|
|
297
|
+
unless request.head? || value.is_a?(Rack::Files::Iterator) || value.is_a?(Stream)
|
|
298
|
+
headers.delete 'content-length'
|
|
296
299
|
end
|
|
297
300
|
response.body = value
|
|
298
301
|
else
|
|
@@ -302,7 +305,10 @@ module Sinatra
|
|
|
302
305
|
|
|
303
306
|
# Halt processing and redirect to the URI provided.
|
|
304
307
|
def redirect(uri, *args)
|
|
305
|
-
|
|
308
|
+
# SERVER_PROTOCOL is required in Rack 3, fall back to HTTP_VERSION
|
|
309
|
+
# for servers not updated for Rack 3 (like Puma 5)
|
|
310
|
+
http_version = env['SERVER_PROTOCOL'] || env['HTTP_VERSION']
|
|
311
|
+
if (http_version == 'HTTP/1.1') && (env['REQUEST_METHOD'] != 'GET')
|
|
306
312
|
status 303
|
|
307
313
|
else
|
|
308
314
|
status 302
|
|
@@ -317,7 +323,7 @@ module Sinatra
|
|
|
317
323
|
# Generates the absolute URI for a given path in the app.
|
|
318
324
|
# Takes Rack routers and reverse proxies into account.
|
|
319
325
|
def uri(addr = nil, absolute = true, add_script_name = true)
|
|
320
|
-
return addr if addr =~ /\A[a-z][a-z0-9+.\-]*:/i
|
|
326
|
+
return addr if addr.to_s =~ /\A[a-z][a-z0-9+.\-]*:/i
|
|
321
327
|
|
|
322
328
|
uri = [host = String.new]
|
|
323
329
|
if absolute
|
|
@@ -372,10 +378,10 @@ module Sinatra
|
|
|
372
378
|
Base.mime_type(type)
|
|
373
379
|
end
|
|
374
380
|
|
|
375
|
-
# Set the
|
|
381
|
+
# Set the content-type of the response body given a media type or file
|
|
376
382
|
# extension.
|
|
377
383
|
def content_type(type = nil, params = {})
|
|
378
|
-
return response['
|
|
384
|
+
return response['content-type'] unless type
|
|
379
385
|
|
|
380
386
|
default = params.delete :default
|
|
381
387
|
mime_type = mime_type(type) || default
|
|
@@ -393,7 +399,7 @@ module Sinatra
|
|
|
393
399
|
"#{key}=#{val}"
|
|
394
400
|
end.join(', ')
|
|
395
401
|
end
|
|
396
|
-
response['
|
|
402
|
+
response['content-type'] = mime_type
|
|
397
403
|
end
|
|
398
404
|
|
|
399
405
|
# https://html.spec.whatwg.org/#multipart-form-data
|
|
@@ -412,12 +418,12 @@ module Sinatra
|
|
|
412
418
|
params = format('; filename="%s"', File.basename(filename).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE))
|
|
413
419
|
response['Content-Disposition'] << params
|
|
414
420
|
ext = File.extname(filename)
|
|
415
|
-
content_type(ext) unless response['
|
|
421
|
+
content_type(ext) unless response['content-type'] || ext.empty?
|
|
416
422
|
end
|
|
417
423
|
|
|
418
424
|
# Use the contents of the file at +path+ as the response body.
|
|
419
425
|
def send_file(path, opts = {})
|
|
420
|
-
if opts[:type] || !response['
|
|
426
|
+
if opts[:type] || !response['content-type']
|
|
421
427
|
content_type opts[:type] || File.extname(path), default: 'application/octet-stream'
|
|
422
428
|
end
|
|
423
429
|
|
|
@@ -429,11 +435,11 @@ module Sinatra
|
|
|
429
435
|
|
|
430
436
|
last_modified opts[:last_modified] if opts[:last_modified]
|
|
431
437
|
|
|
432
|
-
file = Rack::
|
|
438
|
+
file = Rack::Files.new(File.dirname(settings.app_file))
|
|
433
439
|
result = file.serving(request, path)
|
|
434
440
|
|
|
435
441
|
result[1].each { |k, v| headers[k] ||= v }
|
|
436
|
-
headers['
|
|
442
|
+
headers['content-length'] = result[1]['content-length']
|
|
437
443
|
opts[:status] &&= Integer(opts[:status])
|
|
438
444
|
halt (opts[:status] || result[0]), result[2]
|
|
439
445
|
rescue Errno::ENOENT
|
|
@@ -474,8 +480,9 @@ module Sinatra
|
|
|
474
480
|
@back.call(self)
|
|
475
481
|
rescue Exception => e
|
|
476
482
|
@scheduler.schedule { raise e }
|
|
483
|
+
ensure
|
|
484
|
+
close unless @keep_open
|
|
477
485
|
end
|
|
478
|
-
close unless @keep_open
|
|
479
486
|
end
|
|
480
487
|
end
|
|
481
488
|
|
|
@@ -501,12 +508,20 @@ module Sinatra
|
|
|
501
508
|
# the response body have not yet been generated.
|
|
502
509
|
#
|
|
503
510
|
# The close parameter specifies whether Stream#close should be called
|
|
504
|
-
# after the block has been executed.
|
|
505
|
-
# servers like Rainbows.
|
|
511
|
+
# after the block has been executed.
|
|
506
512
|
def stream(keep_open = false)
|
|
507
513
|
scheduler = env['async.callback'] ? EventMachine : Stream
|
|
508
514
|
current = @params.dup
|
|
509
|
-
|
|
515
|
+
stream = if scheduler == Stream && keep_open
|
|
516
|
+
Stream.new(scheduler, false) do |out|
|
|
517
|
+
until out.closed?
|
|
518
|
+
with_params(current) { yield(out) }
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
else
|
|
522
|
+
Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
|
|
523
|
+
end
|
|
524
|
+
body stream
|
|
510
525
|
end
|
|
511
526
|
|
|
512
527
|
# Specify response freshness policy for HTTP caches (Cache-Control header).
|
|
@@ -716,7 +731,7 @@ module Sinatra
|
|
|
716
731
|
# Possible options are:
|
|
717
732
|
# :content_type The content type to use, same arguments as content_type.
|
|
718
733
|
# :layout If set to something falsy, no layout is rendered, otherwise
|
|
719
|
-
# the specified layout is used
|
|
734
|
+
# the specified layout is used (Ignored for `sass`)
|
|
720
735
|
# :layout_engine Engine to use for rendering the layout.
|
|
721
736
|
# :locals A hash with local variables that should be available
|
|
722
737
|
# in the template
|
|
@@ -742,6 +757,20 @@ module Sinatra
|
|
|
742
757
|
render(:haml, template, options, locals, &block)
|
|
743
758
|
end
|
|
744
759
|
|
|
760
|
+
def sass(template, options = {}, locals = {})
|
|
761
|
+
options[:default_content_type] = :css
|
|
762
|
+
options[:exclude_outvar] = true
|
|
763
|
+
options[:layout] = nil
|
|
764
|
+
render :sass, template, options, locals
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
def scss(template, options = {}, locals = {})
|
|
768
|
+
options[:default_content_type] = :css
|
|
769
|
+
options[:exclude_outvar] = true
|
|
770
|
+
options[:layout] = nil
|
|
771
|
+
render :scss, template, options, locals
|
|
772
|
+
end
|
|
773
|
+
|
|
745
774
|
def builder(template = nil, options = {}, locals = {}, &block)
|
|
746
775
|
options[:default_content_type] = :xml
|
|
747
776
|
render_ruby(:builder, template, options, locals, &block)
|
|
@@ -852,7 +881,11 @@ module Sinatra
|
|
|
852
881
|
catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
|
|
853
882
|
end
|
|
854
883
|
|
|
855
|
-
|
|
884
|
+
if content_type
|
|
885
|
+
# sass-embedded returns a frozen string
|
|
886
|
+
output = +output
|
|
887
|
+
output.extend(ContentTyped).content_type = content_type
|
|
888
|
+
end
|
|
856
889
|
output
|
|
857
890
|
end
|
|
858
891
|
|
|
@@ -904,6 +937,35 @@ module Sinatra
|
|
|
904
937
|
end
|
|
905
938
|
end
|
|
906
939
|
|
|
940
|
+
# Extremely simple template cache implementation.
|
|
941
|
+
# * Not thread-safe.
|
|
942
|
+
# * Size is unbounded.
|
|
943
|
+
# * Keys are not copied defensively, and should not be modified after
|
|
944
|
+
# being passed to #fetch. More specifically, the values returned by
|
|
945
|
+
# key#hash and key#eql? should not change.
|
|
946
|
+
#
|
|
947
|
+
# Implementation copied from Tilt::Cache.
|
|
948
|
+
class TemplateCache
|
|
949
|
+
def initialize
|
|
950
|
+
@cache = {}
|
|
951
|
+
end
|
|
952
|
+
|
|
953
|
+
# Caches a value for key, or returns the previously cached value.
|
|
954
|
+
# If a value has been previously cached for key then it is
|
|
955
|
+
# returned. Otherwise, block is yielded to and its return value
|
|
956
|
+
# which may be nil, is cached under key and returned.
|
|
957
|
+
def fetch(*key)
|
|
958
|
+
@cache.fetch(key) do
|
|
959
|
+
@cache[key] = yield
|
|
960
|
+
end
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
# Clears the cache.
|
|
964
|
+
def clear
|
|
965
|
+
@cache = {}
|
|
966
|
+
end
|
|
967
|
+
end
|
|
968
|
+
|
|
907
969
|
# Base class for all Sinatra applications and middleware.
|
|
908
970
|
class Base
|
|
909
971
|
include Rack::Utils
|
|
@@ -918,7 +980,7 @@ module Sinatra
|
|
|
918
980
|
def initialize(app = nil, **_kwargs)
|
|
919
981
|
super()
|
|
920
982
|
@app = app
|
|
921
|
-
@template_cache =
|
|
983
|
+
@template_cache = TemplateCache.new
|
|
922
984
|
@pinned_response = nil # whether a before! filter pinned the content-type
|
|
923
985
|
yield self if block_given?
|
|
924
986
|
end
|
|
@@ -939,7 +1001,7 @@ module Sinatra
|
|
|
939
1001
|
invoke { dispatch! }
|
|
940
1002
|
invoke { error_block!(response.status) } unless @env['sinatra.error']
|
|
941
1003
|
|
|
942
|
-
unless @response['
|
|
1004
|
+
unless @response['content-type']
|
|
943
1005
|
if Array === body && body[0].respond_to?(:content_type)
|
|
944
1006
|
content_type body[0].content_type
|
|
945
1007
|
elsif (default = settings.default_content_type)
|
|
@@ -1002,7 +1064,7 @@ module Sinatra
|
|
|
1002
1064
|
routes = base.routes[@request.request_method]
|
|
1003
1065
|
|
|
1004
1066
|
routes&.each do |pattern, conditions, block|
|
|
1005
|
-
response.delete_header('
|
|
1067
|
+
response.delete_header('content-type') unless @pinned_response
|
|
1006
1068
|
|
|
1007
1069
|
returned_pass_block = process_route(pattern, conditions) do |*args|
|
|
1008
1070
|
env['sinatra.route'] = "#{@request.request_method} #{pattern}"
|
|
@@ -1123,7 +1185,7 @@ module Sinatra
|
|
|
1123
1185
|
invoke do
|
|
1124
1186
|
static! if settings.static? && (request.get? || request.head?)
|
|
1125
1187
|
filter! :before do
|
|
1126
|
-
@pinned_response = !response['
|
|
1188
|
+
@pinned_response = !response['content-type'].nil?
|
|
1127
1189
|
end
|
|
1128
1190
|
route!
|
|
1129
1191
|
end
|
|
@@ -1204,7 +1266,19 @@ module Sinatra
|
|
|
1204
1266
|
end
|
|
1205
1267
|
|
|
1206
1268
|
def dump_errors!(boom)
|
|
1207
|
-
|
|
1269
|
+
if boom.respond_to?(:detailed_message)
|
|
1270
|
+
msg = boom.detailed_message(highlight: false)
|
|
1271
|
+
if msg =~ /\A(.*?)(?: \(#{ Regexp.quote(boom.class.to_s) }\))?\n/
|
|
1272
|
+
msg = $1
|
|
1273
|
+
additional_msg = $'.lines(chomp: true)
|
|
1274
|
+
else
|
|
1275
|
+
additional_msg = []
|
|
1276
|
+
end
|
|
1277
|
+
else
|
|
1278
|
+
msg = boom.message
|
|
1279
|
+
additional_msg = []
|
|
1280
|
+
end
|
|
1281
|
+
msg = ["#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{boom.class} - #{msg}:", *additional_msg, *boom.backtrace].join("\n\t")
|
|
1208
1282
|
@env['rack.errors'].puts(msg)
|
|
1209
1283
|
end
|
|
1210
1284
|
|
|
@@ -1213,6 +1287,7 @@ module Sinatra
|
|
|
1213
1287
|
%r{/sinatra(/(base|main|show_exceptions))?\.rb$}, # all sinatra code
|
|
1214
1288
|
%r{lib/tilt.*\.rb$}, # all tilt code
|
|
1215
1289
|
/^\(.*\)$/, # generated code
|
|
1290
|
+
/\/bundled_gems.rb$/, # ruby >= 3.3 with bundler >= 2.5
|
|
1216
1291
|
%r{rubygems/(custom|core_ext/kernel)_require\.rb$}, # rubygems require hacks
|
|
1217
1292
|
/active_support/, # active_support require hacks
|
|
1218
1293
|
%r{bundler(/(?:runtime|inline))?\.rb}, # bundler require hacks
|
|
@@ -1220,7 +1295,7 @@ module Sinatra
|
|
|
1220
1295
|
%r{zeitwerk/kernel\.rb} # Zeitwerk kernel#require decorator
|
|
1221
1296
|
].freeze
|
|
1222
1297
|
|
|
1223
|
-
attr_reader :routes, :filters, :templates, :errors
|
|
1298
|
+
attr_reader :routes, :filters, :templates, :errors, :on_start_callback, :on_stop_callback
|
|
1224
1299
|
|
|
1225
1300
|
def callers_to_ignore
|
|
1226
1301
|
CALLERS_TO_IGNORE
|
|
@@ -1391,7 +1466,13 @@ module Sinatra
|
|
|
1391
1466
|
# mime_types :js # => ['application/javascript', 'text/javascript']
|
|
1392
1467
|
def mime_types(type)
|
|
1393
1468
|
type = mime_type type
|
|
1394
|
-
type =~ %r{^application/(xml|javascript)$}
|
|
1469
|
+
if type =~ %r{^application/(xml|javascript)$}
|
|
1470
|
+
[type, "text/#{$1}"]
|
|
1471
|
+
elsif type =~ %r{^text/(xml|javascript)$}
|
|
1472
|
+
[type, "application/#{$1}"]
|
|
1473
|
+
else
|
|
1474
|
+
[type]
|
|
1475
|
+
end
|
|
1395
1476
|
end
|
|
1396
1477
|
|
|
1397
1478
|
# Define a before filter; runs before all requests within the same
|
|
@@ -1413,6 +1494,14 @@ module Sinatra
|
|
|
1413
1494
|
filters[type] << compile!(type, path, block, **options)
|
|
1414
1495
|
end
|
|
1415
1496
|
|
|
1497
|
+
def on_start(&on_start_callback)
|
|
1498
|
+
@on_start_callback = on_start_callback
|
|
1499
|
+
end
|
|
1500
|
+
|
|
1501
|
+
def on_stop(&on_stop_callback)
|
|
1502
|
+
@on_stop_callback = on_stop_callback
|
|
1503
|
+
end
|
|
1504
|
+
|
|
1416
1505
|
# Add a route condition. The route is considered non-matching when the
|
|
1417
1506
|
# block returns false.
|
|
1418
1507
|
def condition(name = "#{caller.first[/`.*'/]} condition", &block)
|
|
@@ -1502,18 +1591,37 @@ module Sinatra
|
|
|
1502
1591
|
warn '== Sinatra has ended his set (crowd applauds)' unless suppress_messages?
|
|
1503
1592
|
set :running_server, nil
|
|
1504
1593
|
set :handler_name, nil
|
|
1594
|
+
|
|
1595
|
+
on_stop_callback.call unless on_stop_callback.nil?
|
|
1505
1596
|
end
|
|
1506
1597
|
|
|
1507
1598
|
alias stop! quit!
|
|
1508
1599
|
|
|
1509
1600
|
# Run the Sinatra app as a self-hosted server using
|
|
1510
|
-
# Puma, Falcon,
|
|
1601
|
+
# Puma, Falcon, or WEBrick (in that order). If given a block, will call
|
|
1511
1602
|
# with the constructed handler once we have taken the stage.
|
|
1512
1603
|
def run!(options = {}, &block)
|
|
1604
|
+
unless defined?(Rackup::Handler)
|
|
1605
|
+
rackup_warning = <<~MISSING_RACKUP
|
|
1606
|
+
Sinatra could not start, the "rackup" gem was not found!
|
|
1607
|
+
|
|
1608
|
+
Add it to your bundle with:
|
|
1609
|
+
|
|
1610
|
+
bundle add rackup
|
|
1611
|
+
|
|
1612
|
+
or install it with:
|
|
1613
|
+
|
|
1614
|
+
gem install rackup
|
|
1615
|
+
|
|
1616
|
+
MISSING_RACKUP
|
|
1617
|
+
warn rackup_warning
|
|
1618
|
+
exit 1
|
|
1619
|
+
end
|
|
1620
|
+
|
|
1513
1621
|
return if running?
|
|
1514
1622
|
|
|
1515
1623
|
set options
|
|
1516
|
-
handler =
|
|
1624
|
+
handler = Rackup::Handler.pick(server)
|
|
1517
1625
|
handler_name = handler.name.gsub(/.*::/, '')
|
|
1518
1626
|
server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
|
|
1519
1627
|
server_settings.merge!(Port: port, Host: bind)
|
|
@@ -1589,7 +1697,7 @@ module Sinatra
|
|
|
1589
1697
|
set :running_server, server
|
|
1590
1698
|
set :handler_name, handler_name
|
|
1591
1699
|
server.threaded = settings.threaded if server.respond_to? :threaded=
|
|
1592
|
-
|
|
1700
|
+
on_start_callback.call unless on_start_callback.nil?
|
|
1593
1701
|
yield server if block_given?
|
|
1594
1702
|
end
|
|
1595
1703
|
end
|
|
@@ -1645,7 +1753,7 @@ module Sinatra
|
|
|
1645
1753
|
types.map! { |t| mime_types(t) }
|
|
1646
1754
|
types.flatten!
|
|
1647
1755
|
condition do
|
|
1648
|
-
response_content_type = response['
|
|
1756
|
+
response_content_type = response['content-type']
|
|
1649
1757
|
preferred_type = request.preferred_type(types)
|
|
1650
1758
|
|
|
1651
1759
|
if response_content_type
|
|
@@ -1822,7 +1930,7 @@ module Sinatra
|
|
|
1822
1930
|
set :dump_errors, proc { !test? }
|
|
1823
1931
|
set :show_exceptions, proc { development? }
|
|
1824
1932
|
set :sessions, false
|
|
1825
|
-
set :session_store, Rack::
|
|
1933
|
+
set :session_store, Rack::Session::Cookie
|
|
1826
1934
|
set :logging, false
|
|
1827
1935
|
set :protection, true
|
|
1828
1936
|
set :method_override, false
|
|
@@ -1838,8 +1946,9 @@ module Sinatra
|
|
|
1838
1946
|
begin
|
|
1839
1947
|
require 'securerandom'
|
|
1840
1948
|
set :session_secret, SecureRandom.hex(64)
|
|
1841
|
-
rescue LoadError, NotImplementedError
|
|
1949
|
+
rescue LoadError, NotImplementedError, RuntimeError
|
|
1842
1950
|
# SecureRandom raises a NotImplementedError if no random device is available
|
|
1951
|
+
# RuntimeError raised due to broken openssl backend: https://bugs.ruby-lang.org/issues/19230
|
|
1843
1952
|
set :session_secret, format('%064x', Kernel.rand((2**256) - 1))
|
|
1844
1953
|
end
|
|
1845
1954
|
|
|
@@ -1859,11 +1968,10 @@ module Sinatra
|
|
|
1859
1968
|
|
|
1860
1969
|
ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
|
|
1861
1970
|
|
|
1862
|
-
server.unshift 'puma'
|
|
1863
|
-
server.unshift 'falcon' if ruby_engine != 'jruby'
|
|
1864
|
-
server.unshift 'mongrel' if ruby_engine.nil?
|
|
1865
1971
|
server.unshift 'thin' if ruby_engine != 'jruby'
|
|
1972
|
+
server.unshift 'falcon' if ruby_engine != 'jruby'
|
|
1866
1973
|
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
|
1974
|
+
server.unshift 'puma'
|
|
1867
1975
|
|
|
1868
1976
|
set :absolute_redirects, true
|
|
1869
1977
|
set :prefixed_redirects, false
|
|
@@ -1928,7 +2036,7 @@ module Sinatra
|
|
|
1928
2036
|
</head>
|
|
1929
2037
|
<body>
|
|
1930
2038
|
<h2>Sinatra doesn’t know this ditty.</h2>
|
|
1931
|
-
<img src='#{
|
|
2039
|
+
<img src='#{request.script_name}/__sinatra__/404.png'>
|
|
1932
2040
|
<div id="c">
|
|
1933
2041
|
Try this:
|
|
1934
2042
|
<pre>#{Rack::Utils.escape_html(code)}</pre>
|
|
@@ -1980,7 +2088,7 @@ module Sinatra
|
|
|
1980
2088
|
delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
|
|
1981
2089
|
:template, :layout, :before, :after, :error, :not_found, :configure,
|
|
1982
2090
|
:set, :mime_type, :enable, :disable, :use, :development?, :test?,
|
|
1983
|
-
:production?, :helpers, :settings, :register
|
|
2091
|
+
:production?, :helpers, :settings, :register, :on_start, :on_stop
|
|
1984
2092
|
|
|
1985
2093
|
class << self
|
|
1986
2094
|
attr_accessor :target
|
|
@@ -21,7 +21,8 @@ module Sinatra
|
|
|
21
21
|
# writing interface (calling e.g. <tt>[]=</tt>, <tt>merge</tt>). This mapping
|
|
22
22
|
# belongs to the public interface. For example, given:
|
|
23
23
|
#
|
|
24
|
-
# hash = Sinatra::IndifferentHash.new
|
|
24
|
+
# hash = Sinatra::IndifferentHash.new
|
|
25
|
+
# hash[:a] = 1
|
|
25
26
|
#
|
|
26
27
|
# You are guaranteed that the key is returned as a string:
|
|
27
28
|
#
|
|
@@ -29,7 +30,8 @@ module Sinatra
|
|
|
29
30
|
#
|
|
30
31
|
# Technically other types of keys are accepted:
|
|
31
32
|
#
|
|
32
|
-
# hash = Sinatra::IndifferentHash
|
|
33
|
+
# hash = Sinatra::IndifferentHash
|
|
34
|
+
# hash[:a] = 1
|
|
33
35
|
# hash[0] = 0
|
|
34
36
|
# hash # => { "a"=>1, 0=>0 }
|
|
35
37
|
#
|
|
@@ -41,12 +43,6 @@ module Sinatra
|
|
|
41
43
|
new.merge!(Hash[*args])
|
|
42
44
|
end
|
|
43
45
|
|
|
44
|
-
def initialize(*args)
|
|
45
|
-
args.map!(&method(:convert_value))
|
|
46
|
-
|
|
47
|
-
super(*args)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
46
|
def default(*args)
|
|
51
47
|
args.map!(&method(:convert_key))
|
|
52
48
|
|
|
@@ -186,6 +182,12 @@ module Sinatra
|
|
|
186
182
|
dup.tap(&:compact!)
|
|
187
183
|
end
|
|
188
184
|
|
|
185
|
+
def except(*keys)
|
|
186
|
+
keys.map!(&method(:convert_key))
|
|
187
|
+
|
|
188
|
+
super(*keys)
|
|
189
|
+
end if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.0")
|
|
190
|
+
|
|
189
191
|
private
|
|
190
192
|
|
|
191
193
|
def convert_key(key)
|
data/lib/sinatra/version.rb
CHANGED
data/sinatra.gemspec
CHANGED
|
@@ -36,19 +36,18 @@ RubyGems 2.0 or newer is required to protect against public gem pushes. You can
|
|
|
36
36
|
|
|
37
37
|
s.metadata = {
|
|
38
38
|
'source_code_uri' => 'https://github.com/sinatra/sinatra',
|
|
39
|
-
'changelog_uri' => 'https://github.com/sinatra/sinatra/blob/
|
|
39
|
+
'changelog_uri' => 'https://github.com/sinatra/sinatra/blob/main/CHANGELOG.md',
|
|
40
40
|
'homepage_uri' => 'http://sinatrarb.com/',
|
|
41
41
|
'bug_tracker_uri' => 'https://github.com/sinatra/sinatra/issues',
|
|
42
42
|
'mailing_list_uri' => 'http://groups.google.com/group/sinatrarb',
|
|
43
43
|
'documentation_uri' => 'https://www.rubydoc.info/gems/sinatra'
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
s.required_ruby_version = '>= 2.
|
|
46
|
+
s.required_ruby_version = '>= 2.7.8'
|
|
47
47
|
|
|
48
48
|
s.add_dependency 'mustermann', '~> 3.0'
|
|
49
|
-
s.add_dependency 'rack', '
|
|
49
|
+
s.add_dependency 'rack', '>= 3.0.0', '< 4'
|
|
50
|
+
s.add_dependency 'rack-session', '>= 2.0.0', '< 3'
|
|
50
51
|
s.add_dependency 'rack-protection', version
|
|
51
52
|
s.add_dependency 'tilt', '~> 2.0'
|
|
52
|
-
|
|
53
|
-
s.add_development_dependency 'rack-test', '~> 2'
|
|
54
53
|
end
|