sinarey 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1180 @@
1
+ require 'sinatra/base'
2
+
3
+ module Sinatra
4
+
5
+ class SinareyBase
6
+ include Rack::Utils
7
+ include Helpers
8
+ include Templates
9
+
10
+ URI_INSTANCE = URI.const_defined?(:Parser) ? URI::Parser.new : URI
11
+
12
+ attr_accessor :app, :env, :request, :response, :params
13
+ attr_reader :template_cache
14
+
15
+ def initialize(app = nil)
16
+ super()
17
+ @app = app
18
+ @template_cache = Tilt::Cache.new
19
+ yield self if block_given?
20
+ end
21
+
22
+ # Rack call interface.
23
+ def call(env)
24
+ dup.call!(env)
25
+ end
26
+
27
+ def call!(env) # :nodoc:
28
+ @env = env
29
+ @request = Request.new(env)
30
+ @response = Response.new
31
+ @params = indifferent_params(@request.params)
32
+ template_cache.clear if settings.reload_templates
33
+ force_encoding(@params)
34
+
35
+ route = @request.path_info
36
+ route.chop! if (char=route[-1]) and char=='/' # ignore last '/' char
37
+
38
+ @response['Content-Type'] = nil
39
+ invoke { dispatch! }
40
+ invoke { error_block!(response.status) } unless @env['sinatra.error']
41
+
42
+ unless @response['Content-Type']
43
+ if Array === body and body[0].respond_to? :content_type
44
+ content_type body[0].content_type
45
+ else
46
+ content_type :html
47
+ end
48
+ end
49
+
50
+ @response.finish
51
+ end
52
+
53
+ # Access settings defined with Base.set.
54
+ def self.settings
55
+ self
56
+ end
57
+
58
+ # Access settings defined with Base.set.
59
+ def settings
60
+ self.class.settings
61
+ end
62
+
63
+ def options
64
+ warn "Sinatra::Base#options is deprecated and will be removed, " \
65
+ "use #settings instead."
66
+ settings
67
+ end
68
+
69
+ # Exit the current block, halts any further processing
70
+ # of the request, and returns the specified response.
71
+ def halt(*response)
72
+ response = response.first if response.length == 1
73
+ throw :halt, response
74
+ end
75
+
76
+ # Pass control to the next matching route.
77
+ # If there are no more matching routes, Sinatra will
78
+ # return a 404 response.
79
+ def pass(&block)
80
+ throw :pass, block
81
+ end
82
+
83
+ # Forward the request to the downstream app -- middleware only.
84
+ def forward
85
+ fail "downstream app not set" unless @app.respond_to? :call
86
+ status, headers, body = @app.call env
87
+ @response.status = status
88
+ @response.body = body
89
+ @response.headers.merge! headers
90
+ nil
91
+ end
92
+
93
+ private
94
+
95
+ # Run filters defined on the class and all superclasses.
96
+ def filter!(type, base = settings)
97
+ filter! type, base.superclass if base.superclass.respond_to?(:filters)
98
+ base.filters[type].each { |args| process_route(*args) }
99
+ end
100
+
101
+ # Run routes defined on the class and all superclasses.
102
+ def route!(base = settings, pass_block = nil)
103
+ if router = env['sinarey.router']
104
+ return mount_route!(router)
105
+ end
106
+
107
+ if turbo_route = (turbo_routes = base.turbo_routes[@request.request_method]) && (path_info = turbo_routes[@request.path_info])
108
+ turbo_route.tap do |block_id|
109
+ block = base.blocks[block_id]
110
+ returned_pass_block = process_turbo_route do |*args|
111
+ env['sinatra.route'] = block.instance_variable_get(:@route_name)
112
+ route_eval { block[*args] }
113
+ end
114
+ pass_block = returned_pass_block if returned_pass_block
115
+ end
116
+ elsif routes = base.routes[@request.request_method]
117
+ routes.each do |pattern, keys, conditions, block_id|
118
+ block = base.blocks[block_id]
119
+ returned_pass_block = process_route(pattern, keys, conditions) do |*args|
120
+ env['sinatra.route'] = block.instance_variable_get(:@route_name)
121
+ route_eval { block[*args] }
122
+ end
123
+ # don't wipe out pass_block in superclass
124
+ pass_block = returned_pass_block if returned_pass_block
125
+ end
126
+ end
127
+
128
+ # Run routes defined in superclass.
129
+ if base.superclass.respond_to?(:routes)
130
+ return route!(base.superclass, pass_block)
131
+ end
132
+
133
+ route_eval(&pass_block) if pass_block
134
+ route_missing
135
+ end
136
+
137
+ def mount_route!(options,base = settings)
138
+ type,block_id = options[:type],options[:block_id]
139
+ case type
140
+ when :turbo
141
+ block = base.blocks[block_id]
142
+ returned_pass_block = process_turbo_route do |*args|
143
+ env['sinatra.route'] = block.instance_variable_get(:@route_name)
144
+ route_eval { block[*args] }
145
+ end
146
+ pass_block = returned_pass_block if returned_pass_block
147
+ when :normal
148
+ match,keys,conditions = options[:match],options[:keys],options[:conditions]
149
+ block = base.blocks[block_id]
150
+ returned_pass_block = process_mount_route(match, keys, conditions) do |*args|
151
+ env['sinatra.route'] = block.instance_variable_get(:@route_name)
152
+ route_eval { block[*args] }
153
+ end
154
+ # don't wipe out pass_block in superclass
155
+ pass_block = returned_pass_block if returned_pass_block
156
+ end
157
+
158
+ route_eval(&pass_block) if pass_block
159
+ route_missing
160
+ end
161
+
162
+ # Run a route block and throw :halt with the result.
163
+ def route_eval
164
+ throw :halt, yield
165
+ end
166
+
167
+ # If the current request matches pattern and conditions, fill params
168
+ # with keys and call the given block.
169
+ # Revert params afterwards.
170
+ #
171
+ # Returns pass block.
172
+
173
+ def process_turbo_route(block = nil)
174
+ catch(:pass) do
175
+ block ? block[self, []] : yield(self, [])
176
+ end
177
+ end
178
+
179
+ def process_route(pattern, keys, conditions, block_id = nil, values = [])
180
+ route = @request.path_info
181
+ return unless match = pattern.match(route)
182
+ values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }
183
+
184
+ if values.any?
185
+ original, @params = params, params.merge('splat' => [], 'captures' => values)
186
+ keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
187
+ end
188
+
189
+ catch(:pass) do
190
+ conditions.each { |c| throw :pass if c.bind(self).call == false }
191
+ (block_id && (block = settings.blocks[block_id])) ? block[self, values] : yield(self, values)
192
+ end
193
+ ensure
194
+ @params = original if original
195
+ end
196
+
197
+ def process_mount_route(match, keys, conditions, block_id = nil, values = [])
198
+ values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }
199
+
200
+ if values.any?
201
+ original, @params = params, params.merge('splat' => [], 'captures' => values)
202
+ keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
203
+ end
204
+
205
+ catch(:pass) do
206
+ conditions.each { |c| throw :pass if c.bind(self).call == false }
207
+ (block_id && (block = settings.blocks[block_id])) ? block[self, values] : yield(self, values)
208
+ end
209
+ ensure
210
+ @params = original if original
211
+ end
212
+
213
+ # No matching route was found or all routes passed. The default
214
+ # implementation is to forward the request downstream when running
215
+ # as middleware (@app is non-nil); when no downstream app is set, raise
216
+ # a NotFound exception. Subclasses can override this method to perform
217
+ # custom route miss logic.
218
+ def route_missing
219
+ if @app
220
+ forward
221
+ else
222
+ raise NotFound
223
+ end
224
+ end
225
+
226
+ # Attempt to serve static files from public directory. Throws :halt when
227
+ # a matching file is found, returns nil otherwise.
228
+ def static!
229
+ return if (public_dir = settings.public_folder).nil?
230
+ path = File.expand_path("#{public_dir}#{unescape(request.path_info)}" )
231
+ return unless File.file?(path)
232
+
233
+ env['sinatra.static_file'] = path
234
+ cache_control(*settings.static_cache_control) if settings.static_cache_control?
235
+ send_file path, :disposition => nil
236
+ end
237
+
238
+ # Enable string or symbol key access to the nested params hash.
239
+ def indifferent_params(object)
240
+ case object
241
+ when Hash
242
+ new_hash = indifferent_hash
243
+ object.each { |key, value| new_hash[key] = indifferent_params(value) }
244
+ new_hash
245
+ when Array
246
+ object.map { |item| indifferent_params(item) }
247
+ else
248
+ object
249
+ end
250
+ end
251
+
252
+ # Creates a Hash with indifferent access.
253
+ def indifferent_hash
254
+ Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
255
+ end
256
+
257
+ # Run the block with 'throw :halt' support and apply result to the response.
258
+ def invoke
259
+ res = catch(:halt) { yield }
260
+ res = [res] if Fixnum === res or String === res
261
+ if Array === res and Fixnum === res.first
262
+ res = res.dup
263
+ status(res.shift)
264
+ body(res.pop)
265
+ headers(*res)
266
+ elsif res.respond_to? :each
267
+ body res
268
+ end
269
+ nil # avoid double setting the same response tuple twice
270
+ end
271
+
272
+ # Dispatch a request with error handling.
273
+ def dispatch!
274
+ invoke do
275
+ static! if settings.static? && (request.get? || request.head?)
276
+ filter! :before
277
+ route!
278
+ end
279
+ rescue ::Exception => boom
280
+ invoke { handle_exception!(boom) }
281
+ ensure
282
+ begin
283
+ filter! :after
284
+ rescue ::Exception => boom
285
+ invoke { handle_exception!(boom) } unless @env['sinatra.error']
286
+ end
287
+ end
288
+
289
+ # Error handling during requests.
290
+ def handle_exception!(boom)
291
+ @env['sinatra.error'] = boom
292
+
293
+ if boom.respond_to? :http_status
294
+ status(boom.http_status)
295
+ elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
296
+ status(boom.code)
297
+ else
298
+ status(500)
299
+ end
300
+
301
+ status(500) unless status.between? 400, 599
302
+
303
+ if server_error?
304
+ dump_errors! boom if settings.dump_errors?
305
+ raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
306
+ end
307
+
308
+ if not_found?
309
+ body '<h1>Not Found</h1>'
310
+ end
311
+
312
+ res = error_block!(boom.class, boom) || error_block!(status, boom)
313
+ return res if res or not server_error?
314
+ raise boom if settings.raise_errors? or settings.show_exceptions?
315
+ error_block! Exception, boom
316
+ end
317
+
318
+ # Find an custom error block for the key(s) specified.
319
+ def error_block!(key, *block_params)
320
+ base = settings
321
+ while base.respond_to?(:errors)
322
+ next base = base.superclass unless args_array = base.errors[key]
323
+ args_array.reverse_each do |args|
324
+ first = args == args_array.first
325
+ args += [block_params]
326
+ resp = process_route(*args)
327
+ return resp unless resp.nil? && !first
328
+ end
329
+ end
330
+ return false unless key.respond_to? :superclass and key.superclass < Exception
331
+ error_block!(key.superclass, *block_params)
332
+ end
333
+
334
+ def dump_errors!(boom)
335
+ msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
336
+ @env['rack.errors'].puts(msg)
337
+ end
338
+
339
+ class << self
340
+ CALLERS_TO_IGNORE = [ # :nodoc:
341
+ /\/sinatra(\/(base|main|showexceptions))?\.rb$/, # all sinatra code
342
+ /lib\/tilt.*\.rb$/, # all tilt code
343
+ /^\(.*\)$/, # generated code
344
+ /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks
345
+ /active_support/, # active_support require hacks
346
+ /bundler(\/runtime)?\.rb/, # bundler require hacks
347
+ /<internal:/, # internal in ruby >= 1.9.2
348
+ /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
349
+ ]
350
+
351
+ # contrary to what the comment said previously, rubinius never supported this
352
+ if defined?(RUBY_IGNORE_CALLERS)
353
+ warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
354
+ CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
355
+ end
356
+
357
+ attr_reader :blocks, :turbo_routes, :routes, :filters, :templates, :errors
358
+
359
+ # Removes all routes, filters, middleware and extension hooks from the
360
+ # current class (not routes/filters/... defined by its superclass).
361
+ def reset!
362
+ @conditions = []
363
+ @routes = {}
364
+ @turbo_routes = {}
365
+ @blocks = {}
366
+ @filters = {:before => [], :after => []}
367
+ @errors = {}
368
+ @middleware = []
369
+ @prototype = nil
370
+ @extensions = []
371
+
372
+ if superclass.respond_to?(:templates)
373
+ @templates = Hash.new { |hash,key| superclass.templates[key] }
374
+ else
375
+ @templates = {}
376
+ end
377
+ end
378
+
379
+ # Extension modules registered on this class and all superclasses.
380
+ def extensions
381
+ if superclass.respond_to?(:extensions)
382
+ (@extensions + superclass.extensions).uniq
383
+ else
384
+ @extensions
385
+ end
386
+ end
387
+
388
+ # Middleware used in this class and all superclasses.
389
+ def middleware
390
+ if superclass.respond_to?(:middleware)
391
+ superclass.middleware + @middleware
392
+ else
393
+ @middleware
394
+ end
395
+ end
396
+
397
+ # Sets an option to the given value. If the value is a proc,
398
+ # the proc will be called every time the option is accessed.
399
+ def set(option, value = (not_set = true), ignore_setter = false, &block)
400
+ raise ArgumentError if block and !not_set
401
+ value, not_set = block, false if block
402
+
403
+ if not_set
404
+ raise ArgumentError unless option.respond_to?(:each)
405
+ option.each { |k,v| set(k, v) }
406
+ return self
407
+ end
408
+
409
+ if respond_to?("#{option}=") and not ignore_setter
410
+ return __send__("#{option}=", value)
411
+ end
412
+
413
+ setter = proc { |val| set option, val, true }
414
+ getter = proc { value }
415
+
416
+ case value
417
+ when Proc
418
+ getter = value
419
+ when Symbol, Fixnum, FalseClass, TrueClass, NilClass
420
+ getter = value.inspect
421
+ when Hash
422
+ setter = proc do |val|
423
+ val = value.merge val if Hash === val
424
+ set option, val, true
425
+ end
426
+ end
427
+
428
+ define_singleton("#{option}=", setter) if setter
429
+ define_singleton(option, getter) if getter
430
+ define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
431
+ self
432
+ end
433
+
434
+ # Same as calling `set :option, true` for each of the given options.
435
+ def enable(*opts)
436
+ opts.each { |key| set(key, true) }
437
+ end
438
+
439
+ # Same as calling `set :option, false` for each of the given options.
440
+ def disable(*opts)
441
+ opts.each { |key| set(key, false) }
442
+ end
443
+
444
+ # Define a custom error handler. Optionally takes either an Exception
445
+ # class, or an HTTP status code to specify which errors should be
446
+ # handled.
447
+ def error(*codes, &block)
448
+ args = compile! "ERROR", //, block
449
+ codes = codes.map { |c| Array(c) }.flatten
450
+ codes << Exception if codes.empty?
451
+ codes.each { |c| (@errors[c] ||= []) << args }
452
+ end
453
+
454
+ # Sugar for `error(404) { ... }`
455
+ def not_found(&block)
456
+ error(404, &block)
457
+ error(Sinatra::NotFound, &block)
458
+ end
459
+
460
+ # Define a named template. The block must return the template source.
461
+ def template(name, &block)
462
+ filename, line = caller_locations.first
463
+ templates[name] = [block, filename, line.to_i]
464
+ end
465
+
466
+ # Define the layout template. The block must return the template source.
467
+ def layout(name = :layout, &block)
468
+ template name, &block
469
+ end
470
+
471
+ # Load embedded templates from the file; uses the caller's __FILE__
472
+ # when no file is specified.
473
+ def inline_templates=(file = nil)
474
+ file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file
475
+
476
+ begin
477
+ io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
478
+ app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2)
479
+ rescue Errno::ENOENT
480
+ app, data = nil
481
+ end
482
+
483
+ if data
484
+ if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
485
+ encoding = $2
486
+ else
487
+ encoding = settings.default_encoding
488
+ end
489
+ lines = app.count("\n") + 1
490
+ template = nil
491
+ force_encoding data, encoding
492
+ data.each_line do |line|
493
+ lines += 1
494
+ if line =~ /^@@\s*(.*\S)\s*$/
495
+ template = force_encoding('', encoding)
496
+ templates[$1.to_sym] = [template, file, lines]
497
+ elsif template
498
+ template << line
499
+ end
500
+ end
501
+ end
502
+ end
503
+
504
+ # Lookup or register a mime type in Rack's mime registry.
505
+ def mime_type(type, value = nil)
506
+ return type if type.nil?
507
+ return type.to_s if type.to_s.include?('/')
508
+ type = ".#{type}" unless type.to_s[0] == ?.
509
+ return Rack::Mime.mime_type(type, nil) unless value
510
+ Rack::Mime::MIME_TYPES[type] = value
511
+ end
512
+
513
+ # provides all mime types matching type, including deprecated types:
514
+ # mime_types :html # => ['text/html']
515
+ # mime_types :js # => ['application/javascript', 'text/javascript']
516
+ def mime_types(type)
517
+ type = mime_type type
518
+ type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type]
519
+ end
520
+
521
+ # Define a before filter; runs before all requests within the same
522
+ # context as route handlers and may access/modify the request and
523
+ # response.
524
+ def before(path = nil, options = {}, &block)
525
+ add_filter(:before, path, options, &block)
526
+ end
527
+
528
+ # Define an after filter; runs after all requests within the same
529
+ # context as route handlers and may access/modify the request and
530
+ # response.
531
+ def after(path = nil, options = {}, &block)
532
+ add_filter(:after, path, options, &block)
533
+ end
534
+
535
+ # add a filter
536
+ def add_filter(type, path = nil, options = {}, &block)
537
+ path, options = //, path if path.respond_to?(:each_pair)
538
+ filters[type] << compile!(type, path || //, block, options)
539
+ end
540
+
541
+ # Add a route condition. The route is considered non-matching when the
542
+ # block returns false.
543
+ def condition(name = "#{caller.first[/`.*'/]} condition", &block)
544
+ @conditions << generate_method(name, &block)
545
+ end
546
+
547
+ def public=(value)
548
+ warn ":public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead"
549
+ set(:public_folder, value)
550
+ end
551
+
552
+ def public_dir=(value)
553
+ self.public_folder = value
554
+ end
555
+
556
+ def public_dir
557
+ public_folder
558
+ end
559
+
560
+ # Defining a `GET` handler also automatically defines
561
+ # a `HEAD` handler.
562
+ def get(path, opts = {}, &block)
563
+ conditions = @conditions.dup
564
+ route('GET', path, opts, &block)
565
+
566
+ @conditions = conditions
567
+ route('HEAD', path, opts, &block)
568
+ end
569
+
570
+ def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end
571
+ def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end
572
+ def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end
573
+ def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end
574
+ def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end
575
+ def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end
576
+ def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end
577
+ def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end
578
+
579
+ # Makes the methods defined in the block and in the Modules given
580
+ # in `extensions` available to the handlers and templates
581
+ def helpers(*extensions, &block)
582
+ class_eval(&block) if block_given?
583
+ include(*extensions) if extensions.any?
584
+ end
585
+
586
+ # Register an extension. Alternatively take a block from which an
587
+ # extension will be created and registered on the fly.
588
+ def register(*extensions, &block)
589
+ extensions << Module.new(&block) if block_given?
590
+ @extensions += extensions
591
+ extensions.each do |extension|
592
+ extend extension
593
+ extension.registered(self) if extension.respond_to?(:registered)
594
+ end
595
+ end
596
+
597
+ def development?; environment == :development end
598
+ def production?; environment == :production end
599
+ def test?; environment == :test end
600
+
601
+ # Set configuration options for Sinatra and/or the app.
602
+ # Allows scoping of settings for certain environments.
603
+ def configure(*envs)
604
+ yield self if envs.empty? || envs.include?(environment.to_sym)
605
+ end
606
+
607
+ # Use the specified Rack middleware
608
+ def use(middleware, *args, &block)
609
+ @prototype = nil
610
+ @middleware << [middleware, args, block]
611
+ end
612
+
613
+ # Stop the self-hosted server if running.
614
+ def quit!
615
+ return unless running?
616
+ # Use Thin's hard #stop! if available, otherwise just #stop.
617
+ running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
618
+ $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
619
+ set :running_server, nil
620
+ set :handler_name, nil
621
+ end
622
+
623
+ alias_method :stop!, :quit!
624
+
625
+ # Run the Sinatra app as a self-hosted server using
626
+ # Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call
627
+ # with the constructed handler once we have taken the stage.
628
+ def run!(options = {}, &block)
629
+ return if running?
630
+ set options
631
+ handler = detect_rack_handler
632
+ handler_name = handler.name.gsub(/.*::/, '')
633
+ server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
634
+ server_settings.merge!(:Port => port, :Host => bind)
635
+
636
+ begin
637
+ start_server(handler, server_settings, handler_name, &block)
638
+ rescue Errno::EADDRINUSE
639
+ $stderr.puts "== Someone is already performing on port #{port}!"
640
+ raise
641
+ ensure
642
+ quit!
643
+ end
644
+ end
645
+
646
+ alias_method :start!, :run!
647
+
648
+ # Check whether the self-hosted server is running or not.
649
+ def running?
650
+ running_server?
651
+ end
652
+
653
+ # The prototype instance used to process requests.
654
+ def prototype
655
+ @prototype ||= new
656
+ end
657
+
658
+ # Create a new instance without middleware in front of it.
659
+ alias new! new unless method_defined? :new!
660
+
661
+ # Create a new instance of the class fronted by its middleware
662
+ # pipeline. The object is guaranteed to respond to #call but may not be
663
+ # an instance of the class new was called on.
664
+ def new(*args, &bk)
665
+ instance = new!(*args, &bk)
666
+ Wrapper.new(build(instance).to_app, instance)
667
+ end
668
+
669
+ # Creates a Rack::Builder instance with all the middleware set up and
670
+ # the given +app+ as end point.
671
+ def build(app)
672
+ builder = Rack::Builder.new
673
+ setup_default_middleware builder
674
+ setup_middleware builder
675
+ builder.run app
676
+ builder
677
+ end
678
+
679
+ def call(env)
680
+ synchronize { prototype.call(env) }
681
+ end
682
+
683
+ # Like Kernel#caller but excluding certain magic entries and without
684
+ # line / method information; the resulting array contains filenames only.
685
+ def caller_files
686
+ cleaned_caller(1).flatten
687
+ end
688
+
689
+ # Like caller_files, but containing Arrays rather than strings with the
690
+ # first element being the file, and the second being the line.
691
+ def caller_locations
692
+ cleaned_caller 2
693
+ end
694
+
695
+ private
696
+
697
+ # Starts the server by running the Rack Handler.
698
+ def start_server(handler, server_settings, handler_name)
699
+ handler.run(self, server_settings) do |server|
700
+ unless handler_name =~ /cgi/i
701
+ $stderr.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
702
+ "on #{port} for #{environment} with backup from #{handler_name}"
703
+ end
704
+
705
+ setup_traps
706
+ set :running_server, server
707
+ set :handler_name, handler_name
708
+ server.threaded = settings.threaded if server.respond_to? :threaded=
709
+
710
+ yield server if block_given?
711
+ end
712
+ end
713
+
714
+ def setup_traps
715
+ if traps?
716
+ at_exit { quit! }
717
+
718
+ [:INT, :TERM].each do |signal|
719
+ old_handler = trap(signal) do
720
+ quit!
721
+ old_handler.call if old_handler.respond_to?(:call)
722
+ end
723
+ end
724
+
725
+ set :traps, false
726
+ end
727
+ end
728
+
729
+ # Dynamically defines a method on settings.
730
+ def define_singleton(name, content = Proc.new)
731
+ # replace with call to singleton_class once we're 1.9 only
732
+ (class << self; self; end).class_eval do
733
+ undef_method(name) if method_defined? name
734
+ String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
735
+ end
736
+ end
737
+
738
+ # Condition for matching host name. Parameter might be String or Regexp.
739
+ def host_name(pattern)
740
+ condition { pattern === request.host }
741
+ end
742
+
743
+ # Condition for matching user agent. Parameter should be Regexp.
744
+ # Will set params[:agent].
745
+ def user_agent(pattern)
746
+ condition do
747
+ if request.user_agent.to_s =~ pattern
748
+ @params[:agent] = $~[1..-1]
749
+ true
750
+ else
751
+ false
752
+ end
753
+ end
754
+ end
755
+ alias_method :agent, :user_agent
756
+
757
+ # Condition for matching mimetypes. Accepts file extensions.
758
+ def provides(*types)
759
+ types.map! { |t| mime_types(t) }
760
+ types.flatten!
761
+ condition do
762
+ if type = response['Content-Type']
763
+ types.include? type or types.include? type[/^[^;]+/]
764
+ elsif type = request.preferred_type(types)
765
+ params = (type.respond_to?(:params) ? type.params : {})
766
+ content_type(type, params)
767
+ true
768
+ else
769
+ false
770
+ end
771
+ end
772
+ end
773
+
774
+ def route(verb, path, options = {}, &block)
775
+ if path.class == String
776
+ return if path.empty?
777
+ path.chop! if (char=path[-1]) and char=='/'
778
+ end
779
+
780
+ # Because of self.options.host
781
+ host_name(options.delete(:host)) if options.key?(:host)
782
+
783
+ if /^[a-zA-Z0-9\-\/_]*$/ === path
784
+ turbo_signature = turbo_compile!(verb, path, block, options)
785
+ @turbo_routes[verb] ||= {}
786
+ @turbo_routes[verb][path] = turbo_signature
787
+ else
788
+ signature = compile!(verb, path, block, options)
789
+ (@routes[verb] ||= []) << signature
790
+ end
791
+ invoke_hook(:route_added, verb, path, block)
792
+ signature
793
+ end
794
+
795
+ def invoke_hook(name, *args)
796
+ extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
797
+ end
798
+
799
+ def generate_method(method_name, &block)
800
+ define_method(method_name, &block)
801
+ method = instance_method method_name
802
+ remove_method method_name
803
+ method
804
+ end
805
+
806
+ def turbo_compile!(verb, path, block, options = {})
807
+ method_name = "#{verb} #{path}"
808
+ unbound_method = generate_method(method_name, &block)
809
+ wrapper = block.arity != 0 ?
810
+ proc { |a,p| unbound_method.bind(a).call(*p) } :
811
+ proc { |a,p| unbound_method.bind(a).call }
812
+ wrapper.instance_variable_set(:@route_name, method_name)
813
+ @blocks ||= {}
814
+ block_id = @blocks.size + 1
815
+ @blocks[block_id] = wrapper
816
+ block_id
817
+ end
818
+
819
+ def compile!(verb, path, block, options = {})
820
+ options.each_pair { |option, args| send(option, *args) }
821
+ method_name = "#{verb} #{path}"
822
+ unbound_method = generate_method(method_name, &block)
823
+ pattern, keys = compile path
824
+ conditions, @conditions = @conditions, []
825
+
826
+ wrapper = block.arity != 0 ?
827
+ proc { |a,p| unbound_method.bind(a).call(*p) } :
828
+ proc { |a,p| unbound_method.bind(a).call }
829
+ wrapper.instance_variable_set(:@route_name, method_name)
830
+ @blocks ||= {}
831
+ block_id = @blocks.size + 1
832
+ @blocks[block_id] = wrapper
833
+ [ pattern, keys, conditions, block_id ]
834
+ end
835
+
836
+ def compile(path)
837
+ if path.respond_to? :to_str
838
+ keys = []
839
+
840
+ # We append a / at the end if there was one.
841
+ # Reason: Splitting does not split off an empty
842
+ # string at the end if the split separator
843
+ # is at the end.
844
+ #
845
+ postfix = '/' if path =~ /\/\z/
846
+
847
+ # Split the path into pieces in between forward slashes.
848
+ #
849
+ segments = path.split('/').map! do |segment|
850
+ ignore = []
851
+
852
+ # Special character handling.
853
+ #
854
+ pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c|
855
+ ignore << escaped(c).join if c.match(/[\.@]/)
856
+ patt = encoded(c)
857
+ patt.gsub(/%[\da-fA-F]{2}/) do |match|
858
+ match.split(//).map! {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join
859
+ end
860
+ end
861
+
862
+ ignore = ignore.uniq.join
863
+
864
+ # Key handling.
865
+ #
866
+ pattern.gsub(/((:\w+)|\*)/) do |match|
867
+ if match == "*"
868
+ keys << 'splat'
869
+ "(.*?)"
870
+ else
871
+ keys << $2[1..-1]
872
+ ignore_pattern = safe_ignore(ignore)
873
+
874
+ ignore_pattern
875
+ end
876
+ end
877
+ end
878
+
879
+ # Special case handling.
880
+ #
881
+ if segment = segments.pop
882
+ if segment.match(/\[\^\\\./)
883
+ parts = segment.rpartition(/\[\^\\\./)
884
+ parts[1] = '[^'
885
+ segments << parts.join
886
+ else
887
+ segments << segment
888
+ end
889
+ end
890
+ [/\A#{segments.join('/')}#{postfix}\z/, keys]
891
+ elsif path.respond_to?(:keys) && path.respond_to?(:match)
892
+ [path, path.keys]
893
+ elsif path.respond_to?(:names) && path.respond_to?(:match)
894
+ [path, path.names]
895
+ elsif path.respond_to? :match
896
+ [path, []]
897
+ else
898
+ raise TypeError, path
899
+ end
900
+ end
901
+
902
+ def encoded(char)
903
+ enc = URI_INSTANCE.escape(char)
904
+ enc = "(?:#{escaped(char, enc).join('|')})" if enc == char
905
+ enc = "(?:#{enc}|#{encoded('+')})" if char == " "
906
+ enc
907
+ end
908
+
909
+ def escaped(char, enc = URI_INSTANCE.escape(char))
910
+ [Regexp.escape(enc), URI_INSTANCE.escape(char, /./)]
911
+ end
912
+
913
+ def safe_ignore(ignore)
914
+ unsafe_ignore = []
915
+ ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
916
+ unsafe_ignore << hex[1..2]
917
+ ''
918
+ end
919
+ unsafe_patterns = unsafe_ignore.map! do |unsafe|
920
+ chars = unsafe.split(//).map! do |char|
921
+ if char =~ /[A-Z]/
922
+ char <<= char.tr('A-Z', 'a-z')
923
+ end
924
+ char
925
+ end
926
+
927
+ "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
928
+ end
929
+ if unsafe_patterns.length > 0
930
+ "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
931
+ else
932
+ "([^#{ignore}/?#]+)"
933
+ end
934
+ end
935
+
936
+ def setup_default_middleware(builder)
937
+ builder.use ExtendedRack
938
+ builder.use ShowExceptions if show_exceptions?
939
+ builder.use Rack::MethodOverride if method_override?
940
+ builder.use Rack::Head
941
+ setup_logging builder
942
+ setup_sessions builder
943
+ setup_protection builder
944
+ end
945
+
946
+ def setup_middleware(builder)
947
+ middleware.each { |c,a,b| builder.use(c, *a, &b) }
948
+ end
949
+
950
+ def setup_logging(builder)
951
+ if logging?
952
+ setup_common_logger(builder)
953
+ setup_custom_logger(builder)
954
+ elsif logging == false
955
+ setup_null_logger(builder)
956
+ end
957
+ end
958
+
959
+ def setup_null_logger(builder)
960
+ builder.use Rack::NullLogger
961
+ end
962
+
963
+ def setup_common_logger(builder)
964
+ builder.use Sinatra::CommonLogger
965
+ end
966
+
967
+ def setup_custom_logger(builder)
968
+ if logging.respond_to? :to_int
969
+ builder.use Rack::Logger, logging
970
+ else
971
+ builder.use Rack::Logger
972
+ end
973
+ end
974
+
975
+ def setup_protection(builder)
976
+ return unless protection?
977
+ options = Hash === protection ? protection.dup : {}
978
+ protect_session = options.fetch(:session) { sessions? }
979
+ options[:except] = Array options[:except]
980
+ options[:except] += [:session_hijacking, :remote_token] unless protect_session
981
+ options[:reaction] ||= :drop_session
982
+ builder.use Rack::Protection, options
983
+ end
984
+
985
+ def setup_sessions(builder)
986
+ return unless sessions?
987
+ options = {}
988
+ options[:secret] = session_secret if session_secret?
989
+ options.merge! sessions.to_hash if sessions.respond_to? :to_hash
990
+ builder.use Rack::Session::Cookie, options
991
+ end
992
+
993
+ def detect_rack_handler
994
+ servers = Array(server)
995
+ servers.each do |server_name|
996
+ begin
997
+ return Rack::Handler.get(server_name.to_s)
998
+ rescue LoadError, NameError
999
+ end
1000
+ end
1001
+ fail "Server handler (#{servers.join(',')}) not found."
1002
+ end
1003
+
1004
+ def inherited(subclass)
1005
+ subclass.reset!
1006
+ subclass.set :app_file, caller_files.first unless subclass.app_file?
1007
+ super
1008
+ end
1009
+
1010
+ @@mutex = Mutex.new
1011
+ def synchronize(&block)
1012
+ if lock?
1013
+ @@mutex.synchronize(&block)
1014
+ else
1015
+ yield
1016
+ end
1017
+ end
1018
+
1019
+ # used for deprecation warnings
1020
+ def warn(message)
1021
+ super message + "\n\tfrom #{cleaned_caller.first.join(':')}"
1022
+ end
1023
+
1024
+ # Like Kernel#caller but excluding certain magic entries
1025
+ def cleaned_caller(keep = 3)
1026
+ caller(1).
1027
+ map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
1028
+ reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
1029
+ end
1030
+ end
1031
+
1032
+ # Fixes encoding issues by
1033
+ # * defaulting to UTF-8
1034
+ # * casting params to Encoding.default_external
1035
+ #
1036
+ # The latter might not be necessary if Rack handles it one day.
1037
+ # Keep an eye on Rack's LH #100.
1038
+ def force_encoding(*args) settings.force_encoding(*args) end
1039
+ if defined? Encoding
1040
+ def self.force_encoding(data, encoding = default_encoding)
1041
+ return if data == settings || data.is_a?(Tempfile)
1042
+ if data.respond_to? :force_encoding
1043
+ data.force_encoding(encoding).encode!
1044
+ elsif data.respond_to? :each_value
1045
+ data.each_value { |v| force_encoding(v, encoding) }
1046
+ elsif data.respond_to? :each
1047
+ data.each { |v| force_encoding(v, encoding) }
1048
+ end
1049
+ data
1050
+ end
1051
+ else
1052
+ def self.force_encoding(data, *) data end
1053
+ end
1054
+
1055
+ reset!
1056
+
1057
+ set :environment, (ENV['RACK_ENV'] || :development).to_sym
1058
+ set :raise_errors, Proc.new { test? }
1059
+ set :dump_errors, Proc.new { !test? }
1060
+ set :show_exceptions, Proc.new { development? }
1061
+ set :sessions, false
1062
+ set :logging, false
1063
+ set :protection, true
1064
+ set :method_override, false
1065
+ set :use_code, false
1066
+ set :default_encoding, "utf-8"
1067
+ set :x_cascade, true
1068
+ set :add_charset, %w[javascript xml xhtml+xml json].map { |t| "application/#{t}" }
1069
+ settings.add_charset << /^text\//
1070
+
1071
+ # explicitly generating a session secret eagerly to play nice with preforking
1072
+ begin
1073
+ require 'securerandom'
1074
+ set :session_secret, SecureRandom.hex(64)
1075
+ rescue LoadError, NotImplementedError
1076
+ # SecureRandom raises a NotImplementedError if no random device is available
1077
+ set :session_secret, "%064x" % Kernel.rand(2**256-1)
1078
+ end
1079
+
1080
+ class << self
1081
+ alias_method :methodoverride?, :method_override?
1082
+ alias_method :methodoverride=, :method_override=
1083
+ end
1084
+
1085
+ set :run, false # start server via at-exit hook?
1086
+ set :running_server, nil
1087
+ set :handler_name, nil
1088
+ set :traps, true
1089
+ set :server, %w[HTTP webrick]
1090
+ set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' }
1091
+ set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
1092
+
1093
+ ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
1094
+
1095
+ if ruby_engine == 'macruby'
1096
+ server.unshift 'control_tower'
1097
+ else
1098
+ server.unshift 'reel'
1099
+ server.unshift 'mongrel' if ruby_engine.nil?
1100
+ server.unshift 'puma' if ruby_engine != 'rbx'
1101
+ server.unshift 'thin' if ruby_engine != 'jruby'
1102
+ server.unshift 'puma' if ruby_engine == 'rbx'
1103
+ server.unshift 'trinidad' if ruby_engine == 'jruby'
1104
+ end
1105
+
1106
+ set :absolute_redirects, true
1107
+ set :prefixed_redirects, false
1108
+ set :empty_path_info, nil
1109
+
1110
+ set :app_file, nil
1111
+ set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
1112
+ set :views, Proc.new { root && File.join(root, 'views') }
1113
+ set :reload_templates, Proc.new { development? }
1114
+ set :lock, false
1115
+ set :threaded, true
1116
+
1117
+ set :public_folder, Proc.new { root && File.join(root, 'public') }
1118
+ set :static, Proc.new { public_folder && File.exist?(public_folder) }
1119
+ set :static_cache_control, false
1120
+
1121
+ error ::Exception do
1122
+ response.status = 500
1123
+ content_type 'text/html'
1124
+ '<h1>Internal Server Error</h1>'
1125
+ end
1126
+
1127
+ configure :development do
1128
+ get '/__sinatra__/:image.png' do
1129
+ filename = File.dirname(__FILE__) + "/images/#{params[:image]}.png"
1130
+ content_type :png
1131
+ send_file filename
1132
+ end
1133
+
1134
+ error NotFound do
1135
+ content_type 'text/html'
1136
+
1137
+ if self.class == Sinatra::Application
1138
+ code = <<-RUBY.gsub(/^ {12}/, '')
1139
+ #{request.request_method.downcase} '#{request.path_info}' do
1140
+ "Hello World"
1141
+ end
1142
+ RUBY
1143
+ else
1144
+ code = <<-RUBY.gsub(/^ {12}/, '')
1145
+ class #{self.class}
1146
+ #{request.request_method.downcase} '#{request.path_info}' do
1147
+ "Hello World"
1148
+ end
1149
+ end
1150
+ RUBY
1151
+
1152
+ file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '')
1153
+ code = "# in #{file}\n#{code}" unless file.empty?
1154
+ end
1155
+
1156
+ (<<-HTML).gsub(/^ {10}/, '')
1157
+ <!DOCTYPE html>
1158
+ <html>
1159
+ <head>
1160
+ <style type="text/css">
1161
+ body { text-align:center;font-family:helvetica,arial;font-size:22px;
1162
+ color:#888;margin:20px}
1163
+ #c {margin:0 auto;width:500px;text-align:left}
1164
+ </style>
1165
+ </head>
1166
+ <body>
1167
+ <h2>Sinatra doesn&rsquo;t know this ditty.</h2>
1168
+ <img src='#{uri "/__sinatra__/404.png"}'>
1169
+ <div id="c">
1170
+ Try this:
1171
+ <pre>#{code}</pre>
1172
+ </div>
1173
+ </body>
1174
+ </html>
1175
+ HTML
1176
+ end
1177
+ end
1178
+ end
1179
+
1180
+ end