gin 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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