gin 0.0.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  require 'autotest/restart'
4
4
 
5
- # Autotest.add_hook :initialize do |at|
5
+ Autotest.add_hook :initialize do |at|
6
6
  # at.extra_files << "../some/external/dependency.rb"
7
7
  #
8
8
  # at.libs << ":../some/external"
9
9
  #
10
- # at.add_exception 'vendor'
10
+ at.add_exception 'test/app'
11
11
  #
12
12
  # at.add_mapping(/dependency.rb/) do |f, _|
13
13
  # at.files_matching(/test_.*rb$/)
@@ -16,7 +16,7 @@ require 'autotest/restart'
16
16
  # %w(TestA TestB).each do |klass|
17
17
  # at.extra_class_map[klass] = "test/test_misc.rb"
18
18
  # end
19
- # end
19
+ end
20
20
 
21
21
  # Autotest.add_hook :run_command do |at|
22
22
  # system "rake build"
@@ -0,0 +1,7 @@
1
+ .DS_Store
2
+ pkg/
3
+ TODO.rdoc
4
+ *.swo
5
+ *.swp
6
+ .*.swo
7
+ .*.swp
@@ -1,6 +1,3 @@
1
- === 0.0.0 / 2013-02-13
2
-
3
- * 1 major enhancement
4
-
5
- * Birthday!
6
-
1
+ === 1.0.0 / 2013-03-07
2
+
3
+ * Birthday!
@@ -1,8 +1,42 @@
1
1
  .autotest
2
+ .gitignore
2
3
  History.rdoc
3
4
  Manifest.txt
4
5
  README.rdoc
5
6
  Rakefile
6
- bin/gin
7
7
  lib/gin.rb
8
- test/gin_test.rb
8
+ lib/gin/app.rb
9
+ lib/gin/config.rb
10
+ lib/gin/controller.rb
11
+ lib/gin/core_ext/cgi.rb
12
+ lib/gin/core_ext/gin_class.rb
13
+ lib/gin/errorable.rb
14
+ lib/gin/filterable.rb
15
+ lib/gin/reloadable.rb
16
+ lib/gin/request.rb
17
+ lib/gin/response.rb
18
+ lib/gin/router.rb
19
+ lib/gin/stream.rb
20
+ public/400.html
21
+ public/404.html
22
+ public/500.html
23
+ public/error.html
24
+ public/favicon.ico
25
+ public/gin.css
26
+ public/gin_sm.png
27
+ test/app/app_foo.rb
28
+ test/app/controllers/app_controller.rb
29
+ test/app/controllers/foo_controller.rb
30
+ test/mock_config/backend.yml
31
+ test/mock_config/memcache.yml
32
+ test/mock_config/not_a_config.txt
33
+ test/test_app.rb
34
+ test/test_config.rb
35
+ test/test_controller.rb
36
+ test/test_errorable.rb
37
+ test/test_filterable.rb
38
+ test/test_gin.rb
39
+ test/test_helper.rb
40
+ test/test_request.rb
41
+ test/test_response.rb
42
+ test/test_router.rb
@@ -1,29 +1,39 @@
1
- = gin
1
+ = Gin
2
2
 
3
- * https://github.com/yaks/gin
3
+ * http://yaks.me/gin
4
4
 
5
- == DESCRIPTION:
5
+ == Description
6
6
 
7
- Something fun coming soon.
7
+ Gin is a small web framework built from the redistillation of
8
+ Sinatra and Rails idioms. Specifically, it uses much of Sinatra's
9
+ request flow and HTTP helper methods with dedicated controller classes
10
+ that support Rails-like filters.
8
11
 
9
- == REQUIREMENTS:
12
+ == Hello World
10
13
 
11
- * rack
14
+ # config.ru
15
+ require 'gin'
12
16
 
13
- == INSTALL:
17
+ class HelloWorldCtrl < Gin::Controller
18
+ def index; "Hello World!"; end
19
+ end
14
20
 
15
- * gem install gin
21
+ class MyApp < Gin::App
22
+ mount HelloWorldCtrl, "/"
23
+ end
16
24
 
17
- == DEVELOPERS:
25
+ run MyApp.new
18
26
 
19
- After checking out the source, run:
27
+ == Requirements
20
28
 
21
- $ rake newb
29
+ * rack
30
+ * rack-protection
22
31
 
23
- This task will install any missing dependencies, run the tests/specs,
24
- and generate the RDoc.
32
+ == Install
33
+
34
+ * gem install gin
25
35
 
26
- == LICENSE:
36
+ == License
27
37
 
28
38
  (The MIT License)
29
39
 
data/Rakefile CHANGED
@@ -3,21 +3,14 @@
3
3
  require 'rubygems'
4
4
  require 'hoe'
5
5
 
6
- # Hoe.plugin :compiler
7
- # Hoe.plugin :gem_prelude_sucks
8
- # Hoe.plugin :inline
9
- # Hoe.plugin :isolate
10
- # Hoe.plugin :racc
11
- # Hoe.plugin :rcov
12
- # Hoe.plugin :rubyforge
13
-
14
6
  Hoe.spec 'gin' do
15
7
  developer('Jeremie Castagna', 'yaksnrainbows@gmail.com')
16
8
  self.readme_file = "README.rdoc"
17
9
  self.history_file = "History.rdoc"
18
10
  self.extra_rdoc_files = FileList['*.rdoc']
19
11
 
20
- self.extra_deps << ['rack', '>=1.5.2']
12
+ self.extra_deps << ['rack', '~>1.1']
13
+ self.extra_deps << ['rack-protection', '~>1.0']
21
14
  end
22
15
 
23
16
  # vim: syntax=ruby
data/lib/gin.rb CHANGED
@@ -1,3 +1,124 @@
1
+ require 'logger'
2
+
3
+ require 'rack'
4
+ require 'rack-protection'
5
+
6
+
1
7
  class Gin
2
- VERSION = '0.0.0'
8
+ VERSION = '1.0.0'
9
+
10
+ LIB_DIR = File.expand_path("..", __FILE__) #:nodoc:
11
+ PUBLIC_DIR = File.expand_path("../../public/", __FILE__) #:nodoc:
12
+
13
+ class Error < StandardError; end
14
+
15
+ class BadRequest < ArgumentError
16
+ def http_status; 400; end
17
+ end
18
+
19
+ class NotFound < NameError
20
+ def http_status; 404; end
21
+ end
22
+
23
+
24
+ ##
25
+ # Change string to underscored version.
26
+
27
+ def self.underscore str
28
+ str = str.dup
29
+ str.gsub!('::', '/')
30
+ str.gsub!(/([A-Z]+?)([A-Z][a-z])/, '\1_\2')
31
+ str.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
32
+ str.downcase
33
+ end
34
+
35
+
36
+ ##
37
+ # Create a URI query from a Hash.
38
+
39
+ def self.build_query value, prefix=nil
40
+ case value
41
+ when Array
42
+ raise ArgumentError, "no prefix given" if prefix.nil?
43
+ value.map { |v|
44
+ build_query(v, "#{prefix}[]")
45
+ }.join("&")
46
+
47
+ when Hash
48
+ value.map { |k, v|
49
+ build_query(v, prefix ?
50
+ "#{prefix}[#{CGI.escape(k.to_s)}]" : CGI.escape(k.to_s))
51
+ }.join("&")
52
+
53
+ when String, Integer, Float, TrueClass, FalseClass
54
+ raise ArgumentError, "value must be a Hash" if prefix.nil?
55
+ "#{prefix}=#{CGI.escape(value.to_s)}"
56
+
57
+ else
58
+ prefix
59
+ end
60
+ end
61
+
62
+
63
+ ##
64
+ # Returns the full path to the file given based on the load paths.
65
+
66
+ def self.find_loadpath file
67
+ name = file.dup
68
+ name << ".rb" unless name[-3..-1] == ".rb"
69
+
70
+ return name if name[0] == ?/ && File.file?(name)
71
+
72
+ filepath = nil
73
+
74
+ dir = $:.find do |path|
75
+ filepath = File.expand_path(name, path)
76
+ File.file? filepath
77
+ end
78
+
79
+ dir && filepath
80
+ end
81
+
82
+
83
+ ##
84
+ # Get a namespaced constant.
85
+
86
+ def self.const_find str_or_ary, parent=Object
87
+ const = nil
88
+ names = Array === str_or_ary ? str_or_ary : str_or_ary.split("::")
89
+ names.each do |name|
90
+ const = parent.const_get(name)
91
+ parent = const
92
+ end
93
+
94
+ const
95
+ end
96
+
97
+
98
+ APP_START_MATCH = %r{/gin/app\.rb:\d+:in `dispatch'} #:nodoc:
99
+
100
+ ##
101
+ # Get the application backtrace only.
102
+ # Removes gem and Gin paths from the trace.
103
+
104
+ def self.app_trace trace
105
+ trace = trace.dup
106
+ trace.pop until trace.last.nil? || trace.last =~ APP_START_MATCH
107
+ trace.pop while trace.last && trace.last.start_with?(LIB_DIR)
108
+ trace
109
+ end
110
+
111
+
112
+ require 'gin/core_ext/cgi'
113
+ require 'gin/core_ext/gin_class'
114
+
115
+ require 'gin/app'
116
+ require 'gin/router'
117
+ require 'gin/config'
118
+ require 'gin/request'
119
+ require 'gin/response'
120
+ require 'gin/stream'
121
+ require 'gin/errorable'
122
+ require 'gin/filterable'
123
+ require 'gin/controller'
3
124
  end
@@ -0,0 +1,595 @@
1
+ ##
2
+ # The Gin::App is the entry point for Rack, for all Gin Applications.
3
+ # This class MUST be subclassed and initialized.
4
+ # # my_app.rb
5
+ # class MyApp < Gin::App
6
+ # require 'my_controller'
7
+ # mount MyController, "/"
8
+ # end
9
+ #
10
+ # # config.ru
11
+ # require 'my_app'
12
+ # run MyApp.new
13
+
14
+ class Gin::App
15
+ extend GinClass
16
+
17
+ class RouterError < Gin::Error; end
18
+
19
+ RACK_KEYS = { #:nodoc:
20
+ :stack => 'gin.stack'.freeze,
21
+ :path_params => 'gin.path_query_hash'.freeze,
22
+ :reloaded => 'gin.reloaded'.freeze,
23
+ :errors => 'gin.errors'.freeze
24
+ }.freeze
25
+
26
+
27
+ CALLERS_TO_IGNORE = [ # :nodoc:
28
+ /\/gin(\/(.*?))?\.rb$/, # all gin code
29
+ /lib\/tilt.*\.rb$/, # all tilt code
30
+ /^\(.*\)$/, # generated code
31
+ /rubygems\/custom_require\.rb$/, # rubygems require hacks
32
+ /active_support/, # active_support require hacks
33
+ /bundler(\/runtime)?\.rb/, # bundler require hacks
34
+ /<internal:/, # internal in ruby >= 1.9.2
35
+ /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
36
+ ]
37
+
38
+
39
+ def self.inherited subclass #:nodoc:
40
+ caller_line = caller.find{|line| !CALLERS_TO_IGNORE.any?{|m| line =~ m} }
41
+ filepath = File.expand_path(caller_line.split(/:\d+:in `/).first)
42
+ dir = File.dirname(filepath)
43
+ subclass.root_dir dir
44
+ subclass.instance_variable_set("@source_file", filepath)
45
+ subclass.instance_variable_set("@source_class", subclass.to_s)
46
+ end
47
+
48
+
49
+ ##
50
+ # Create a new intance of the app and call it.
51
+
52
+ def self.call env
53
+ @instance ||= self.new
54
+ @instance.call env
55
+ end
56
+
57
+
58
+ ##
59
+ # Enable or disable auto-app reloading.
60
+ # On by default in development mode.
61
+ #
62
+ # In order for an app to be reloadable, the libs and controllers must be
63
+ # required from the Gin::App class context, or use MyApp.require("lib").
64
+ #
65
+ # Reloading is not supported for applications defined in the config.ru file.
66
+
67
+ def self.autoreload val=nil
68
+ @autoreload = val unless val.nil?
69
+
70
+ if @autoreload.nil?
71
+ @autoreload = File.extname(source_file) != ".ru" && development?
72
+ end
73
+
74
+ if @autoreload && (!defined?(Gin::Reloadable) || !include?(Gin::Reloadable))
75
+ require 'gin/reloadable'
76
+ include Gin::Reloadable
77
+ end
78
+
79
+ @autoreload
80
+ end
81
+
82
+
83
+ ##
84
+ # Mount a Gin::Controller into the App and specify a base path. If controller
85
+ # mounts at root, use "/" as the base path.
86
+ # mount UserController, "/user" do
87
+ # get :index, "/"
88
+ # get :show, "/:id"
89
+ # post :create, "/"
90
+ # get :stats # mounts to "/stats" by default
91
+ # any :logged_in # any HTTP verb will trigger this action
92
+ # end
93
+ #
94
+ # Controllers with non-mounted actions will throw a warning at boot time.
95
+ #
96
+ # Restful routes are automatically mounted when no block is given:
97
+ #
98
+ # mount UserController
99
+ # # restfully mounted to /user
100
+ # # non-canonical actions are mounted to /user/<action_name>
101
+ #
102
+ # Mount blocks also support routing whatever actions are left to their restful
103
+ # defaults:
104
+ #
105
+ # mount UserController do
106
+ # get :foo, "/"
107
+ # defaults
108
+ # end
109
+ #
110
+ # All Gin::Controller methods are considered actions and will be mounted in
111
+ # restful mode. For helper methods, include a module into your controller.
112
+
113
+ def self.mount ctrl, base_path=nil, &block
114
+ router.add ctrl, base_path, &block
115
+ end
116
+
117
+
118
+ ##
119
+ # Returns the source file of the current app.
120
+
121
+ def self.source_file
122
+ @source_file
123
+ end
124
+
125
+
126
+ def self.namespace #:nodoc:
127
+ # Parent namespace of the App class. Used for reloading purposes.
128
+ Gin.const_find(@source_class.split("::")[0..-2]) if @source_class
129
+ end
130
+
131
+
132
+ def self.source_class #:nodoc:
133
+ # Lookup the class from its name. Used for reloading purposes.
134
+ Gin.const_find(@source_class) if @source_class
135
+ end
136
+
137
+
138
+ ##
139
+ # Get or set the root directory of the application.
140
+ # Defaults to the app file's directory.
141
+
142
+ def self.root_dir dir=nil
143
+ @root_dir = dir if dir
144
+ @root_dir
145
+ end
146
+
147
+
148
+ ##
149
+ # Get or set the path to the config directory.
150
+ # Defaults to root_dir + "config"
151
+ #
152
+ # Configs are expected to be YAML files following this pattern:
153
+ # default: &default
154
+ # key: value
155
+ #
156
+ # development: *default
157
+ # other_key: value
158
+ #
159
+ # production: *default
160
+ # ...
161
+ #
162
+ # Configs will be named according to the filename, and only the config for
163
+ # the current environment will be accessible.
164
+
165
+ def self.config_dir dir=nil
166
+ @config_dir = dir if dir
167
+ @config_dir ||= File.join(root_dir, "config")
168
+ end
169
+
170
+
171
+ ##
172
+ # Access the config for your application, loaded from the config_dir.
173
+ # # config/memcache.yml
174
+ # default: &default
175
+ # host: example.com
176
+ # connections: 1
177
+ # development: *default
178
+ # host: localhost
179
+ #
180
+ # # access from App class or instance
181
+ # config.memcache['host']
182
+
183
+ def self.config
184
+ @config ||= Gin::Config.new environment, config_dir
185
+ end
186
+
187
+
188
+ ##
189
+ # Loads all configs from the config_dir.
190
+
191
+ def self.load_config
192
+ return unless File.directory?(config_dir)
193
+ config.dir = config_dir
194
+ config.load!
195
+ end
196
+
197
+
198
+ ##
199
+ # Get or set the path to the public directory.
200
+ # Defaults to root_dir + "public"
201
+
202
+ def self.public_dir dir=nil
203
+ @public_dir = dir if dir
204
+ @public_dir ||= File.join(root_dir, "public")
205
+ end
206
+
207
+
208
+ ##
209
+ # Get or set the CDN asset host (and path).
210
+ # If block is given, evaluates the block on every read.
211
+
212
+ def self.asset_host host=nil, &block
213
+ @asset_host = host if host
214
+ @asset_host = block if block_given?
215
+ host = @asset_host.respond_to?(:call) ? @asset_host.call : @asset_host
216
+ end
217
+
218
+
219
+ ##
220
+ # Returns the asset host for a given asset name. This is useful when assigning
221
+ # a block for the asset_host. The asset_name argument is passed to the block.
222
+
223
+ def self.asset_host_for asset_name
224
+ @asset_host.respond_to?(:call) ? @asset_host.call(asset_name) : @asset_host
225
+ end
226
+
227
+
228
+ ##
229
+ # Returns the first 8 bytes of the asset file's md5.
230
+ # File path is assumed relative to the public_dir.
231
+
232
+ def self.asset_version path
233
+ path = File.expand_path(File.join(public_dir, path))
234
+ return unless File.file?(path)
235
+
236
+ @asset_versions ||= {}
237
+ @asset_versions[path] ||= md5(path)
238
+ end
239
+
240
+
241
+ MD5 = RUBY_PLATFORM =~ /darwin/ ? 'md5 -q' : 'md5sum' #:nodoc:
242
+
243
+ def self.md5 path #:nodoc:
244
+ `#{MD5} #{path}`[0...8]
245
+ end
246
+
247
+
248
+ ##
249
+ # Define a Gin::Controller as a catch-all error rendering controller.
250
+ # This can be a dedicated controller, or a parent controller
251
+ # such as AppController. Defaults to Gin::Controller.
252
+ #
253
+ # The error delegate should handle the following errors
254
+ # for creating custom pages for Gin errors:
255
+ # Gin::NotFound, Gin::BadRequest, ::Exception
256
+
257
+ def self.error_delegate ctrl=nil
258
+ @error_delegate = ctrl if ctrl
259
+ @error_delegate ||= Gin::Controller
260
+ end
261
+
262
+
263
+ ##
264
+ # Router instance that handles mapping Rack-env -> Controller#action.
265
+
266
+ def self.router
267
+ @router ||= Gin::Router.new
268
+ end
269
+
270
+
271
+ ##
272
+ # Lookup or register a mime type in Rack's mime registry.
273
+
274
+ def self.mime_type type, value=nil
275
+ return type if type.nil? || type.to_s.include?('/')
276
+ type = ".#{type}" unless type.to_s[0] == ?.
277
+ return Rack::Mime.mime_type(type, nil) unless value
278
+ Rack::Mime::MIME_TYPES[type] = value
279
+ end
280
+
281
+
282
+ ##
283
+ # Provides all mime types matching type, including deprecated types:
284
+ # mime_types :html # => ['text/html']
285
+ # mime_types :js # => ['application/javascript', 'text/javascript']
286
+
287
+ def self.mime_types type
288
+ type = mime_type type
289
+ type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type]
290
+ end
291
+
292
+
293
+ ##
294
+ # Add middleware internally to the app.
295
+ # Middleware statuses and Exceptions will NOT be
296
+ # handled by the error_delegate.
297
+
298
+ def self.use middleware, *args, &block
299
+ ary = [middleware, *args]
300
+ ary << block if block_given?
301
+ self.middleware << ary
302
+ end
303
+
304
+
305
+ ##
306
+ # List of internal app middleware.
307
+
308
+ def self.middleware
309
+ @middleware ||= []
310
+ end
311
+
312
+
313
+ ##
314
+ # Use rack sessions or not. Supports assigning
315
+ # hash for options. Defaults to true.
316
+
317
+ def self.sessions opts=nil
318
+ @session = opts unless opts.nil?
319
+ @session = true if @session.nil?
320
+ @session
321
+ end
322
+
323
+
324
+ ##
325
+ # Get or set the session secret String.
326
+ # Defaults to a new random value on boot.
327
+
328
+ def self.session_secret val=nil
329
+ @session_secret = val if val
330
+ @session_secret ||= "%064x" % Kernel.rand(2**256-1)
331
+ end
332
+
333
+
334
+ ##
335
+ # Use rack-protection or not. Supports assigning
336
+ # hash for options. Defaults to true.
337
+
338
+ def self.protection opts=nil
339
+ @protection = opts unless opts.nil?
340
+ @protection = true if @protection.nil?
341
+ @protection
342
+ end
343
+
344
+
345
+ ##
346
+ # Get or set the current environment name,
347
+ # by default ENV ['RACK_ENV'], or "development".
348
+
349
+ def self.environment env=nil
350
+ @environment = env if env
351
+ @environment ||= ENV['RACK_ENV'] || "development"
352
+ end
353
+
354
+
355
+ ##
356
+ # Check if running in development mode.
357
+
358
+ def self.development?
359
+ self.environment == "development"
360
+ end
361
+
362
+
363
+ ##
364
+ # Check if running in test mode.
365
+
366
+ def self.test?
367
+ self.environment == "test"
368
+ end
369
+
370
+
371
+ ##
372
+ # Check if running in staging mode.
373
+
374
+ def self.staging?
375
+ self.environment == "staging"
376
+ end
377
+
378
+
379
+ ##
380
+ # Check if running in production mode.
381
+
382
+ def self.production?
383
+ self.environment == "production"
384
+ end
385
+
386
+
387
+ class_proxy :protection, :sessions, :session_secret, :middleware, :autoreload
388
+ class_proxy :error_delegate, :router
389
+ class_proxy :root_dir, :public_dir
390
+ class_proxy :mime_type, :asset_host_for, :asset_host, :asset_version
391
+ class_proxy :environment, :development?, :test?, :staging?, :production?
392
+ class_proxy :load_config, :config, :config_dir
393
+
394
+ # Application logger. Defaults to log to $stdout.
395
+ attr_accessor :logger
396
+
397
+ # App to fallback on if Gin::App is used as middleware and no route is found.
398
+ attr_reader :rack_app
399
+
400
+ # Internal Rack stack.
401
+ attr_reader :stack
402
+
403
+
404
+ ##
405
+ # Create a new Rack-mountable Gin::App instance, with an optional
406
+ # rack_app and logger.
407
+
408
+ def initialize rack_app=nil, logger=nil
409
+ load_config
410
+
411
+ if !rack_app.respond_to?(:call) && rack_app.respond_to?(:log) && logger.nil?
412
+ @rack_app = nil
413
+ @logger = rack_app
414
+ else
415
+ @rack_app = rack_app
416
+ @logger = Logger.new $stdout
417
+ end
418
+
419
+ validate_all_controllers!
420
+
421
+ @app = self
422
+ @stack = build_app Rack::Builder.new
423
+ end
424
+
425
+
426
+ ##
427
+ # Used for auto reloading the whole app in development mode.
428
+ # Will only reload if Gin::App.autoreload is set to true.
429
+ #
430
+ # If you use this in production, you're gonna have a bad time.
431
+
432
+ def reload!
433
+ return unless autoreload
434
+ self.class.erase! [self.class.source_file],
435
+ [self.class.name.split("::").last],
436
+ self.class.namespace
437
+
438
+ self.class.erase_dependencies!
439
+ Object.send(:require, self.class.source_file)
440
+ @app = self.class.source_class.new @rack_app, @logger
441
+ end
442
+
443
+
444
+ ##
445
+ # Default Rack call method.
446
+
447
+ def call env
448
+ if filename = static?(env)
449
+ return error_delegate.exec(self, env){ send_file filename }
450
+ end
451
+
452
+ if autoreload && !env[RACK_KEYS[:reloaded]]
453
+ env[RACK_KEYS[:reloaded]] = true
454
+ reload!
455
+ @app.call env
456
+
457
+ elsif env[RACK_KEYS[:stack]]
458
+ env.delete RACK_KEYS[:stack]
459
+ @app.call! env
460
+
461
+ else
462
+ env[RACK_KEYS[:stack]] = true
463
+ @stack.call env
464
+ end
465
+ end
466
+
467
+
468
+ ##
469
+ # Call App instance without internal middleware or reloading.
470
+
471
+ def call! env
472
+ ctrl, action, env[RACK_KEYS[:path_params]] =
473
+ router.resources_for env['REQUEST_METHOD'], env['PATH_INFO']
474
+
475
+ dispatch env, ctrl, action
476
+ end
477
+
478
+
479
+ STATIC_PATH_CLEANER = %r{\.+/|/\.+} #:nodoc:
480
+
481
+ ##
482
+ # Check if the request is for a static file.
483
+
484
+ def static? env
485
+ %w{GET HEAD}.include?(env['REQUEST_METHOD']) && asset(env['PATH_INFO'])
486
+ end
487
+
488
+
489
+ ##
490
+ # Check if an asset exists.
491
+ # Returns the full path to the asset if found, otherwise nil.
492
+
493
+ def asset path
494
+ path = path.gsub STATIC_PATH_CLEANER, ""
495
+
496
+ filepath = File.expand_path(File.join(public_dir, path))
497
+ return filepath if File.file? filepath
498
+
499
+ filepath = File.expand_path(File.join(Gin::PUBLIC_DIR, path))
500
+ return filepath if File.file? filepath
501
+ end
502
+
503
+
504
+ ##
505
+ # Dispatch the Rack env to the given controller and action.
506
+
507
+ def dispatch env, ctrl, action
508
+ raise Gin::NotFound,
509
+ "No route exists for: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}" unless
510
+ ctrl && action
511
+
512
+ ctrl.new(self, env).call_action action
513
+
514
+ rescue Gin::NotFound => err
515
+ @rack_app ? @rack_app.call(env) : handle_error(err, env)
516
+
517
+ rescue ::Exception => err
518
+ handle_error(err, env)
519
+ end
520
+
521
+
522
+ ##
523
+ # Handle error with error_delegate if available, otherwise re-raise.
524
+
525
+ def handle_error err, env
526
+ delegate = error_delegate
527
+
528
+ begin
529
+ trace = Gin.app_trace(Array(err.backtrace)).join("\n")
530
+ logger.error("#{err.class.name}: #{err.message}\n#{trace}")
531
+ delegate.exec(self, env){ handle_error(err) }
532
+
533
+ rescue ::Exception => err
534
+ delegate = Gin::Controller and retry unless delegate == Gin::Controller
535
+ raise
536
+ end
537
+ end
538
+
539
+
540
+ private
541
+
542
+
543
+ def build_app builder
544
+ setup_sessions builder
545
+ setup_protection builder
546
+ middleware.each do |args|
547
+ block = args.pop if Proc === args.last
548
+ builder.use(*args, &block)
549
+ end
550
+
551
+ builder.run self
552
+ builder.to_app
553
+ end
554
+
555
+
556
+ def setup_sessions builder
557
+ return unless sessions
558
+ options = {}
559
+ options[:secret] = session_secret if session_secret
560
+ options.merge! sessions.to_hash if sessions.respond_to? :to_hash
561
+ builder.use Rack::Session::Cookie, options
562
+ end
563
+
564
+
565
+ def setup_protection builder
566
+ return unless protection
567
+ options = Hash === protection ? protection.dup : {}
568
+ options[:except] = Array options[:except]
569
+ options[:except] += [:session_hijacking, :remote_token] unless sessions
570
+ options[:reaction] ||= :drop_session
571
+ builder.use Rack::Protection, options
572
+ end
573
+
574
+
575
+ ##
576
+ # Make sure all controller actions have a route, or raise a RouterError.
577
+
578
+ def validate_all_controllers!
579
+ actions = {}
580
+
581
+ router.each_route do |route, ctrl, action|
582
+ (actions[ctrl] ||= []) << action
583
+ end
584
+
585
+ actions.each do |ctrl, actions|
586
+ not_mounted = ctrl.actions - actions
587
+ raise RouterError, "#{ctrl}##{not_mounted[0]} has no route." unless
588
+ not_mounted.empty?
589
+
590
+ extra_mounted = actions - ctrl.actions
591
+ raise RouterError, "#{ctrl}##{extra_mounted[0]} is not a method" unless
592
+ extra_mounted.empty?
593
+ end
594
+ end
595
+ end