sinarey 1.0.5 → 1.0.6

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