gin 1.0.3 → 1.0.4
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/History.rdoc +13 -1
- data/Manifest.txt +2 -0
- data/README.rdoc +2 -4
- data/lib/gin.rb +3 -2
- data/lib/gin/app.rb +78 -44
- data/lib/gin/constants.rb +47 -0
- data/lib/gin/controller.rb +39 -27
- data/lib/gin/core_ext/cgi.rb +2 -2
- data/lib/gin/core_ext/rack_commonlogger.rb +7 -0
- data/lib/gin/errorable.rb +1 -1
- data/lib/gin/reloadable.rb +33 -23
- data/lib/gin/request.rb +4 -9
- data/lib/gin/response.rb +9 -10
- data/lib/gin/router.rb +5 -2
- data/test/test_app.rb +10 -9
- data/test/test_controller.rb +11 -2
- data/test/test_router.rb +18 -1
- metadata +9 -11
data/History.rdoc
CHANGED
@@ -1,4 +1,16 @@
|
|
1
|
-
=== 1.0.
|
1
|
+
=== 1.0.4 / 2013-03-19
|
2
|
+
|
3
|
+
* Minor Enhancements
|
4
|
+
* Gin no longer relies on Rack::CommonLogger
|
5
|
+
* Allow routes to default with a custom HTTP verb
|
6
|
+
* Gin::Controller#delete_cookie helper method
|
7
|
+
|
8
|
+
* Bugfixes
|
9
|
+
* Fix for reloading of required files
|
10
|
+
* Fix for routing HTTP verbs that don't have routes mounted to them
|
11
|
+
* Better check before extending the CGI class
|
12
|
+
|
13
|
+
=== 1.0.3 / 2013-03-12
|
2
14
|
|
3
15
|
* Minor Enhancements
|
4
16
|
* Routes have priority over static assets
|
data/Manifest.txt
CHANGED
@@ -7,9 +7,11 @@ Rakefile
|
|
7
7
|
lib/gin.rb
|
8
8
|
lib/gin/app.rb
|
9
9
|
lib/gin/config.rb
|
10
|
+
lib/gin/constants.rb
|
10
11
|
lib/gin/controller.rb
|
11
12
|
lib/gin/core_ext/cgi.rb
|
12
13
|
lib/gin/core_ext/gin_class.rb
|
14
|
+
lib/gin/core_ext/rack_commonlogger.rb
|
13
15
|
lib/gin/errorable.rb
|
14
16
|
lib/gin/filterable.rb
|
15
17
|
lib/gin/reloadable.rb
|
data/README.rdoc
CHANGED
@@ -4,10 +4,8 @@
|
|
4
4
|
|
5
5
|
== Description
|
6
6
|
|
7
|
-
Gin is a small web framework built
|
8
|
-
Sinatra
|
9
|
-
request flow and HTTP helper methods with dedicated controller classes
|
10
|
-
that support Rails-like filters.
|
7
|
+
Gin is a small Ruby web framework, built on Rack, which borrows from
|
8
|
+
Sinatra expressiveness, and targets larger applications.
|
11
9
|
|
12
10
|
== Hello World
|
13
11
|
|
data/lib/gin.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
require 'logger'
|
2
1
|
require 'rack'
|
3
2
|
|
4
3
|
class Gin
|
5
|
-
VERSION = '1.0.
|
4
|
+
VERSION = '1.0.4'
|
6
5
|
|
7
6
|
LIB_DIR = File.expand_path("..", __FILE__) #:nodoc:
|
8
7
|
PUBLIC_DIR = File.expand_path("../../public/", __FILE__) #:nodoc:
|
@@ -108,7 +107,9 @@ class Gin
|
|
108
107
|
|
109
108
|
require 'gin/core_ext/cgi'
|
110
109
|
require 'gin/core_ext/gin_class'
|
110
|
+
require 'gin/core_ext/rack_commonlogger'
|
111
111
|
|
112
|
+
require 'gin/constants'
|
112
113
|
require 'gin/app'
|
113
114
|
require 'gin/router'
|
114
115
|
require 'gin/config'
|
data/lib/gin/app.rb
CHANGED
@@ -13,21 +13,10 @@
|
|
13
13
|
|
14
14
|
class Gin::App
|
15
15
|
extend GinClass
|
16
|
+
include Gin::Constants
|
16
17
|
|
17
18
|
class RouterError < Gin::Error; end
|
18
19
|
|
19
|
-
RACK_KEYS = { #:nodoc:
|
20
|
-
:stack => 'gin.stack'.freeze,
|
21
|
-
:http_route => 'gin.http_route'.freeze,
|
22
|
-
:path_params => 'gin.path_query_hash'.freeze,
|
23
|
-
:controller => 'gin.controller'.freeze,
|
24
|
-
:action => 'gin.action'.freeze,
|
25
|
-
:static => 'gin.static'.freeze,
|
26
|
-
:reloaded => 'gin.reloaded'.freeze,
|
27
|
-
:errors => 'gin.errors'.freeze
|
28
|
-
}.freeze
|
29
|
-
|
30
|
-
|
31
20
|
CALLERS_TO_IGNORE = [ # :nodoc:
|
32
21
|
/\/gin(\/(.*?))?\.rb$/, # all gin code
|
33
22
|
/lib\/tilt.*\.rb$/, # all tilt code
|
@@ -76,7 +65,7 @@ class Gin::App
|
|
76
65
|
end
|
77
66
|
|
78
67
|
if @autoreload && (!defined?(Gin::Reloadable) || !include?(Gin::Reloadable))
|
79
|
-
require 'gin/reloadable'
|
68
|
+
Object.send :require, 'gin/reloadable'
|
80
69
|
include Gin::Reloadable
|
81
70
|
end
|
82
71
|
|
@@ -84,6 +73,18 @@ class Gin::App
|
|
84
73
|
end
|
85
74
|
|
86
75
|
|
76
|
+
##
|
77
|
+
# Custom require used for auto-reloading.
|
78
|
+
|
79
|
+
def self.require file
|
80
|
+
if autoreload
|
81
|
+
track_require file
|
82
|
+
else
|
83
|
+
super file
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
87
88
|
##
|
88
89
|
# Mount a Gin::Controller into the App and specify a base path. If controller
|
89
90
|
# mounts at root, use "/" as the base path.
|
@@ -352,7 +353,7 @@ class Gin::App
|
|
352
353
|
|
353
354
|
def self.environment env=nil
|
354
355
|
@environment = env if env
|
355
|
-
@environment ||= ENV['RACK_ENV'] ||
|
356
|
+
@environment ||= ENV['RACK_ENV'] || ENV_DEV
|
356
357
|
end
|
357
358
|
|
358
359
|
|
@@ -360,7 +361,7 @@ class Gin::App
|
|
360
361
|
# Check if running in development mode.
|
361
362
|
|
362
363
|
def self.development?
|
363
|
-
self.environment ==
|
364
|
+
self.environment == ENV_DEV
|
364
365
|
end
|
365
366
|
|
366
367
|
|
@@ -368,7 +369,7 @@ class Gin::App
|
|
368
369
|
# Check if running in test mode.
|
369
370
|
|
370
371
|
def self.test?
|
371
|
-
self.environment ==
|
372
|
+
self.environment == ENV_TEST
|
372
373
|
end
|
373
374
|
|
374
375
|
|
@@ -376,7 +377,7 @@ class Gin::App
|
|
376
377
|
# Check if running in staging mode.
|
377
378
|
|
378
379
|
def self.staging?
|
379
|
-
self.environment ==
|
380
|
+
self.environment == ENV_STAGE
|
380
381
|
end
|
381
382
|
|
382
383
|
|
@@ -384,7 +385,7 @@ class Gin::App
|
|
384
385
|
# Check if running in production mode.
|
385
386
|
|
386
387
|
def self.production?
|
387
|
-
self.environment ==
|
388
|
+
self.environment == ENV_PROD
|
388
389
|
end
|
389
390
|
|
390
391
|
|
@@ -412,12 +413,12 @@ class Gin::App
|
|
412
413
|
def initialize rack_app=nil, logger=nil
|
413
414
|
load_config
|
414
415
|
|
415
|
-
if !rack_app.respond_to?(:call) && rack_app.respond_to?(
|
416
|
+
if !rack_app.respond_to?(:call) && rack_app.respond_to?(:<<) && logger.nil?
|
416
417
|
@rack_app = nil
|
417
418
|
@logger = rack_app
|
418
419
|
else
|
419
420
|
@rack_app = rack_app
|
420
|
-
@logger =
|
421
|
+
@logger = $stdout
|
421
422
|
end
|
422
423
|
|
423
424
|
validate_all_controllers!
|
@@ -434,7 +435,6 @@ class Gin::App
|
|
434
435
|
# If you use this in production, you're gonna have a bad time.
|
435
436
|
|
436
437
|
def reload!
|
437
|
-
return unless autoreload
|
438
438
|
@mutex ||= Mutex.new
|
439
439
|
|
440
440
|
@mutex.synchronize do
|
@@ -443,7 +443,7 @@ class Gin::App
|
|
443
443
|
self.class.namespace
|
444
444
|
|
445
445
|
self.class.erase_dependencies!
|
446
|
-
|
446
|
+
require self.class.source_file
|
447
447
|
@app = self.class.source_class.new @rack_app, @logger
|
448
448
|
end
|
449
449
|
end
|
@@ -474,8 +474,8 @@ class Gin::App
|
|
474
474
|
# Check if autoreload is needed and reload.
|
475
475
|
|
476
476
|
def try_autoreload env
|
477
|
-
return if env[
|
478
|
-
env[
|
477
|
+
return if env[GIN_RELOADED] || !autoreload
|
478
|
+
env[GIN_RELOADED] = true
|
479
479
|
reload!
|
480
480
|
end
|
481
481
|
|
@@ -484,13 +484,14 @@ class Gin::App
|
|
484
484
|
# Call App instance stack without static file lookup or reloading.
|
485
485
|
|
486
486
|
def call! env
|
487
|
-
if env[
|
488
|
-
env
|
489
|
-
dispatch env, env[RACK_KEYS[:controller]], env[RACK_KEYS[:action]]
|
487
|
+
if env[GIN_STACK]
|
488
|
+
dispatch env, env[GIN_CTRL], env[GIN_ACTION]
|
490
489
|
|
491
490
|
else
|
492
|
-
env[
|
493
|
-
|
491
|
+
env[GIN_STACK] = true
|
492
|
+
with_log_request(env) do
|
493
|
+
@stack.call env
|
494
|
+
end
|
494
495
|
end
|
495
496
|
end
|
496
497
|
|
@@ -500,7 +501,9 @@ class Gin::App
|
|
500
501
|
# env filename.
|
501
502
|
|
502
503
|
def call_static env
|
503
|
-
|
504
|
+
with_log_request(env) do
|
505
|
+
error_delegate.exec(self, env){ send_file env[GIN_STATIC] }
|
506
|
+
end
|
504
507
|
end
|
505
508
|
|
506
509
|
|
@@ -509,13 +512,13 @@ class Gin::App
|
|
509
512
|
# variable to the filepath.
|
510
513
|
|
511
514
|
def static! env
|
512
|
-
filepath = %w{GET HEAD}.include?(env[
|
513
|
-
asset(env[
|
515
|
+
filepath = %w{GET HEAD}.include?(env[REQ_METHOD]) &&
|
516
|
+
asset(env[PATH_INFO])
|
514
517
|
|
515
|
-
filepath ? (env[
|
516
|
-
env.delete(
|
518
|
+
filepath ? (env[GIN_STATIC] = filepath) :
|
519
|
+
env.delete(GIN_STATIC)
|
517
520
|
|
518
|
-
!!env[
|
521
|
+
!!env[GIN_STATIC]
|
519
522
|
end
|
520
523
|
|
521
524
|
|
@@ -525,15 +528,15 @@ class Gin::App
|
|
525
528
|
# and gin.http_route env variables.
|
526
529
|
|
527
530
|
def route! env
|
528
|
-
http_route = "#{env[
|
529
|
-
return true if env[
|
531
|
+
http_route = "#{env[REQ_METHOD]} #{env[PATH_INFO]}"
|
532
|
+
return true if env[GIN_ROUTE] == http_route
|
530
533
|
|
531
|
-
env[
|
532
|
-
router.resources_for env[
|
534
|
+
env[GIN_CTRL], env[GIN_ACTION], env[GIN_PATH_PARAMS] =
|
535
|
+
router.resources_for env[REQ_METHOD], env[PATH_INFO]
|
533
536
|
|
534
|
-
env[
|
537
|
+
env[GIN_ROUTE] = http_route
|
535
538
|
|
536
|
-
!!(env[
|
539
|
+
!!(env[GIN_CTRL] && env[GIN_ACTION])
|
537
540
|
end
|
538
541
|
|
539
542
|
|
@@ -560,7 +563,7 @@ class Gin::App
|
|
560
563
|
|
561
564
|
def dispatch env, ctrl, action
|
562
565
|
raise Gin::NotFound,
|
563
|
-
"No route exists for: #{env[
|
566
|
+
"No route exists for: #{env[REQ_METHOD]} #{env[PATH_INFO]}" unless
|
564
567
|
ctrl && action
|
565
568
|
|
566
569
|
ctrl.new(self, env).call_action action
|
@@ -577,8 +580,6 @@ class Gin::App
|
|
577
580
|
delegate = error_delegate
|
578
581
|
|
579
582
|
begin
|
580
|
-
trace = Gin.app_trace(Array(err.backtrace)).join("\n")
|
581
|
-
logger.error("#{err.class.name}: #{err.message}\n#{trace}")
|
582
583
|
delegate.exec(self, env){ handle_error(err) }
|
583
584
|
|
584
585
|
rescue ::Exception => err
|
@@ -591,6 +592,39 @@ class Gin::App
|
|
591
592
|
private
|
592
593
|
|
593
594
|
|
595
|
+
LOG_FORMAT = %{%s - %s [%s] "%s %s%s" %s %d %s %0.4f %s\n}.freeze #:nodoc:
|
596
|
+
TIME_FORMAT = "%d/%b/%Y %H:%M:%S".freeze #:nodoc:
|
597
|
+
|
598
|
+
def log_request env, resp
|
599
|
+
now = Time.now
|
600
|
+
time = now - env[GIN_TIMESTAMP] if env[GIN_TIMESTAMP]
|
601
|
+
|
602
|
+
ctrl, action = env[GIN_CTRL], env[GIN_ACTION]
|
603
|
+
target = "#{ctrl}##{action}" if ctrl && action
|
604
|
+
|
605
|
+
@logger << ( LOG_FORMAT % [
|
606
|
+
env[FWD_FOR] || env[REMOTE_ADDR] || "-",
|
607
|
+
env[REMOTE_USER] || "-",
|
608
|
+
now.strftime(TIME_FORMAT),
|
609
|
+
env[REQ_METHOD],
|
610
|
+
env[PATH_INFO],
|
611
|
+
env[QUERY_STRING].to_s.empty? ? "" : "?#{env[QUERY_STRING]}",
|
612
|
+
env[HTTP_VERSION] || "HTTP/1.1",
|
613
|
+
resp[0],
|
614
|
+
resp[1][CNT_LENGTH] || "-",
|
615
|
+
time || "-",
|
616
|
+
target || "-" ] )
|
617
|
+
end
|
618
|
+
|
619
|
+
|
620
|
+
def with_log_request env
|
621
|
+
env[GIN_TIMESTAMP] ||= Time.now
|
622
|
+
resp = yield
|
623
|
+
log_request env, resp
|
624
|
+
resp
|
625
|
+
end
|
626
|
+
|
627
|
+
|
594
628
|
def build_app builder
|
595
629
|
setup_sessions builder
|
596
630
|
setup_protection builder
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Gin::Constants
|
2
|
+
EPOCH = Time.at(0)
|
3
|
+
|
4
|
+
# Rack env constants
|
5
|
+
FWD_FOR = 'HTTP_X_FORWARDED_FOR'.freeze
|
6
|
+
FWD_HOST = 'HTTP_X_FORWARDED_HOST'.freeze
|
7
|
+
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
8
|
+
REMOTE_USER = 'REMOTE_USER'.freeze
|
9
|
+
HTTP_VERSION = 'HTTP_VERSION'.freeze
|
10
|
+
REQ_METHOD = 'REQUEST_METHOD'.freeze
|
11
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
12
|
+
QUERY_STRING = 'QUERY_STRING'.freeze
|
13
|
+
IF_MATCH = 'HTTP_IF_MATCH'.freeze
|
14
|
+
IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze
|
15
|
+
IF_MOD_SINCE = 'HTTP_IF_MODIFIED_SINCE'.freeze
|
16
|
+
IF_UNMOD_SINCE = 'HTTP_IF_UNMODIFIED_SINCE'.freeze
|
17
|
+
|
18
|
+
ASYNC_CALLBACK = 'async.callback'.freeze
|
19
|
+
|
20
|
+
# Rack response header constants
|
21
|
+
ETAG = 'ETag'.freeze
|
22
|
+
CNT_LENGTH = 'Content-Length'.freeze
|
23
|
+
CNT_TYPE = 'Content-Type'.freeze
|
24
|
+
CNT_DISPOSITION = 'Content-Disposition'.freeze
|
25
|
+
LOCATION = 'Location'.freeze
|
26
|
+
LAST_MOD = 'Last-Modified'.freeze
|
27
|
+
CACHE_CTRL = 'Cache-Control'.freeze
|
28
|
+
EXPIRES = 'Expires'.freeze
|
29
|
+
PRAGMA = 'Pragma'.freeze
|
30
|
+
|
31
|
+
# Gin env constants
|
32
|
+
GIN_STACK = 'gin.stack'.freeze
|
33
|
+
GIN_ROUTE = 'gin.http_route'.freeze
|
34
|
+
GIN_PATH_PARAMS = 'gin.path_query_hash'.freeze
|
35
|
+
GIN_CTRL = 'gin.controller'.freeze
|
36
|
+
GIN_ACTION = 'gin.action'.freeze
|
37
|
+
GIN_STATIC = 'gin.static'.freeze
|
38
|
+
GIN_RELOADED = 'gin.reloaded'.freeze
|
39
|
+
GIN_ERRORS = 'gin.errors'.freeze
|
40
|
+
GIN_TIMESTAMP = 'gin.timestamp'.freeze
|
41
|
+
|
42
|
+
# Environment names
|
43
|
+
ENV_DEV = "development".freeze
|
44
|
+
ENV_TEST = "test".freeze
|
45
|
+
ENV_STAGE = "staging".freeze
|
46
|
+
ENV_PROD = "production".freeze
|
47
|
+
end
|
data/lib/gin/controller.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
class Gin::Controller
|
2
2
|
extend GinClass
|
3
|
+
include Gin::Constants
|
3
4
|
include Gin::Filterable
|
4
5
|
include Gin::Errorable
|
5
6
|
|
6
7
|
|
7
8
|
error Gin::NotFound, Gin::BadRequest, ::Exception do |err|
|
9
|
+
trace = Gin.app_trace(Array(err.backtrace)).join(" \n")
|
10
|
+
trace = " " << trace << "\n\n" unless trace.empty?
|
11
|
+
logger << "[ERROR] #{err.class.name}: #{err.message}\n#{trace}"
|
12
|
+
|
8
13
|
status( err.respond_to?(:http_status) ? err.http_status : 500 )
|
9
14
|
@response.headers.clear
|
10
15
|
content_type :html
|
@@ -71,8 +76,7 @@ class Gin::Controller
|
|
71
76
|
def call_action action #:nodoc:
|
72
77
|
invoke{ dispatch action }
|
73
78
|
invoke{ handle_status(@response.status) }
|
74
|
-
content_type self.class.content_type unless
|
75
|
-
@response[Gin::Response::H_CTYPE]
|
79
|
+
content_type self.class.content_type unless @response[CNT_TYPE]
|
76
80
|
@response.finish
|
77
81
|
end
|
78
82
|
|
@@ -107,7 +111,7 @@ class Gin::Controller
|
|
107
111
|
# Get or set the HTTP response Content-Type header.
|
108
112
|
|
109
113
|
def content_type type=nil, params={}
|
110
|
-
return @response[
|
114
|
+
return @response[CNT_TYPE] unless type
|
111
115
|
|
112
116
|
default = params.delete(:default)
|
113
117
|
mime_type = mime_type(type) || default
|
@@ -127,7 +131,7 @@ class Gin::Controller
|
|
127
131
|
end.join(', ')
|
128
132
|
end
|
129
133
|
|
130
|
-
@response[
|
134
|
+
@response[CNT_TYPE] = mime_type
|
131
135
|
end
|
132
136
|
|
133
137
|
|
@@ -175,15 +179,15 @@ class Gin::Controller
|
|
175
179
|
|
176
180
|
value = '"%s"' % value
|
177
181
|
value = 'W/' + value if kind == :weak
|
178
|
-
@response[
|
182
|
+
@response[ETAG] = value
|
179
183
|
|
180
184
|
if (200..299).include?(status) || status == 304
|
181
|
-
if etag_matches? @env[
|
185
|
+
if etag_matches? @env[IF_NONE_MATCH], new_resource
|
182
186
|
halt(@request.safe? ? 304 : 412)
|
183
187
|
end
|
184
188
|
|
185
|
-
if @env[
|
186
|
-
halt 412 unless etag_matches? @env[
|
189
|
+
if @env[IF_MATCH]
|
190
|
+
halt 412 unless etag_matches? @env[IF_MATCH], new_resource
|
187
191
|
end
|
188
192
|
end
|
189
193
|
end
|
@@ -191,7 +195,7 @@ class Gin::Controller
|
|
191
195
|
|
192
196
|
def etag_matches? list, new_resource=@request.post? #:nodoc:
|
193
197
|
return !new_resource if list == '*'
|
194
|
-
list.to_s.split(/\s*,\s*/).include? response[
|
198
|
+
list.to_s.split(/\s*,\s*/).include? response[ETAG]
|
195
199
|
end
|
196
200
|
|
197
201
|
|
@@ -216,7 +220,7 @@ class Gin::Controller
|
|
216
220
|
# end
|
217
221
|
|
218
222
|
def stream keep_open=false, &block
|
219
|
-
scheduler = env[
|
223
|
+
scheduler = env[ASYNC_CALLBACK] ? EventMachine : Gin::Stream
|
220
224
|
body Gin::Stream.new(scheduler, keep_open){ |out| yield(out) }
|
221
225
|
end
|
222
226
|
|
@@ -270,6 +274,15 @@ class Gin::Controller
|
|
270
274
|
end
|
271
275
|
|
272
276
|
|
277
|
+
##
|
278
|
+
# Delete the response cookie with the given name.
|
279
|
+
# Does not affect request cookies.
|
280
|
+
|
281
|
+
def delete_cookie name
|
282
|
+
@response.delete_cookie name
|
283
|
+
end
|
284
|
+
|
285
|
+
|
273
286
|
##
|
274
287
|
# Build a path to the given controller and action or route name,
|
275
288
|
# with any expected params. If no controller is specified and the
|
@@ -344,13 +357,13 @@ class Gin::Controller
|
|
344
357
|
# redirect to(:show_foo, :id => 123)
|
345
358
|
|
346
359
|
def redirect uri, *args
|
347
|
-
if @env[
|
360
|
+
if @env[HTTP_VERSION] == 'HTTP/1.1' && @env[REQ_METHOD] != 'GET'
|
348
361
|
status 303
|
349
362
|
else
|
350
363
|
status 302
|
351
364
|
end
|
352
365
|
|
353
|
-
@response[
|
366
|
+
@response[LOCATION] = url_to(uri.to_s)
|
354
367
|
halt(*args)
|
355
368
|
end
|
356
369
|
|
@@ -360,7 +373,7 @@ class Gin::Controller
|
|
360
373
|
# Produces a 404 response if no file is found.
|
361
374
|
|
362
375
|
def send_file path, opts={}
|
363
|
-
if opts[:type] || !@response[
|
376
|
+
if opts[:type] || !@response[CNT_TYPE]
|
364
377
|
content_type opts[:type] || File.extname(path),
|
365
378
|
:default => 'application/octet-stream'
|
366
379
|
end
|
@@ -371,14 +384,14 @@ class Gin::Controller
|
|
371
384
|
filename = File.basename(path) if filename.nil?
|
372
385
|
|
373
386
|
if disposition
|
374
|
-
@response[
|
387
|
+
@response[CNT_DISPOSITION] =
|
375
388
|
"%s; filename=\"%s\"" % [disposition, filename]
|
376
389
|
end
|
377
390
|
|
378
391
|
last_modified opts[:last_modified] || File.mtime(path).httpdate
|
379
392
|
halt 200 if @request.head?
|
380
393
|
|
381
|
-
@response[
|
394
|
+
@response[CNT_LENGTH] = File.size?(path).to_s
|
382
395
|
halt 200, File.open(path, "rb")
|
383
396
|
|
384
397
|
rescue Errno::ENOENT
|
@@ -398,20 +411,20 @@ class Gin::Controller
|
|
398
411
|
time = Time.parse(time) if String === time
|
399
412
|
time = time.to_time if time.respond_to?(:to_time)
|
400
413
|
|
401
|
-
@response[
|
402
|
-
return if @env[
|
414
|
+
@response[LAST_MOD] = time.httpdate
|
415
|
+
return if @env[IF_NONE_MATCH]
|
403
416
|
|
404
|
-
if status == 200 && @env[
|
417
|
+
if status == 200 && @env[IF_MOD_SINCE]
|
405
418
|
# compare based on seconds since epoch
|
406
|
-
since = Time.httpdate(@env[
|
419
|
+
since = Time.httpdate(@env[IF_MOD_SINCE]).to_i
|
407
420
|
halt 304 if since >= time.to_i
|
408
421
|
end
|
409
422
|
|
410
|
-
if @env[
|
423
|
+
if @env[IF_UNMOD_SINCE] &&
|
411
424
|
((200..299).include?(status) || status == 412)
|
412
425
|
|
413
426
|
# compare based on seconds since epoch
|
414
|
-
since = Time.httpdate(@env[
|
427
|
+
since = Time.httpdate(@env[IF_UNMOD_SINCE]).to_i
|
415
428
|
halt 412 if since < time.to_i
|
416
429
|
end
|
417
430
|
rescue ArgumentError
|
@@ -442,7 +455,7 @@ class Gin::Controller
|
|
442
455
|
values << [key, value].join('=')
|
443
456
|
end
|
444
457
|
|
445
|
-
@response[
|
458
|
+
@response[CACHE_CTRL] = values.join(', ') if values.any?
|
446
459
|
end
|
447
460
|
|
448
461
|
|
@@ -470,7 +483,7 @@ class Gin::Controller
|
|
470
483
|
values.last.merge!(:max_age => max_age) unless values.last[:max_age]
|
471
484
|
cache_control(*values)
|
472
485
|
|
473
|
-
@response[
|
486
|
+
@response[EXPIRES] = time.httpdate
|
474
487
|
end
|
475
488
|
|
476
489
|
|
@@ -479,9 +492,8 @@ class Gin::Controller
|
|
479
492
|
# not to cache the response.
|
480
493
|
|
481
494
|
def expire_cache_control
|
482
|
-
@response[
|
483
|
-
expires
|
484
|
-
:no_cache, :no_store, :must_revalidate, max_age: 0
|
495
|
+
@response[PRAGMA] = 'no-cache'
|
496
|
+
expires EPOCH, :no_cache, :no_store, :must_revalidate, max_age: 0
|
485
497
|
end
|
486
498
|
|
487
499
|
|
@@ -579,7 +591,7 @@ class Gin::Controller
|
|
579
591
|
private
|
580
592
|
|
581
593
|
|
582
|
-
DEV_ERROR_HTML = File.read(File.join(Gin::PUBLIC_DIR, "error.html")) #:nodoc:
|
594
|
+
DEV_ERROR_HTML = File.read(File.join(Gin::PUBLIC_DIR, "error.html")).freeze #:nodoc:
|
583
595
|
|
584
596
|
BAD_REQ_MSG = "Expected param `%s'" #:nodoc:
|
585
597
|
|
data/lib/gin/core_ext/cgi.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'cgi'
|
2
2
|
|
3
|
-
unless
|
3
|
+
unless CGI.escapeHTML("'") == "'"
|
4
4
|
|
5
5
|
class CGI #:nodoc:
|
6
6
|
class << self
|
@@ -8,7 +8,7 @@ class CGI #:nodoc:
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def self.escapeHTML str
|
11
|
-
__escapeHTML(str).gsub
|
11
|
+
__escapeHTML(str).gsub("'", "'")
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
data/lib/gin/errorable.rb
CHANGED
@@ -89,7 +89,7 @@ module Gin::Errorable
|
|
89
89
|
# Re-raises the error if no handler is found.
|
90
90
|
|
91
91
|
def handle_error err
|
92
|
-
(@env[Gin::
|
92
|
+
(@env[Gin::Constants::GIN_ERRORS] ||= []) << err
|
93
93
|
status(err.http_status) if err.respond_to?(:http_status)
|
94
94
|
status(500) unless (400..599).include? status
|
95
95
|
|
data/lib/gin/reloadable.rb
CHANGED
@@ -13,7 +13,7 @@ module Gin::Reloadable #:nodoc:
|
|
13
13
|
|
14
14
|
|
15
15
|
def erase_dependencies!
|
16
|
-
reloadables.each do |key, (
|
16
|
+
reloadables.each do |key, (_, files, consts)|
|
17
17
|
erase! files, consts
|
18
18
|
end
|
19
19
|
true
|
@@ -21,26 +21,45 @@ module Gin::Reloadable #:nodoc:
|
|
21
21
|
|
22
22
|
|
23
23
|
def erase! files, consts, parent=nil
|
24
|
-
parent ||= Object
|
24
|
+
parent ||= ::Object
|
25
25
|
files.each{|f| $LOADED_FEATURES.delete f }
|
26
26
|
clear_constants parent, consts
|
27
27
|
end
|
28
28
|
|
29
29
|
|
30
|
-
def clear_constants
|
31
|
-
names
|
30
|
+
def clear_constants root, names=nil
|
31
|
+
each_constant(root, names) do |name, const, parent|
|
32
|
+
if Module === const || !gin_constants[const.object_id]
|
33
|
+
parent.send(:remove_const, name) rescue nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
32
37
|
|
38
|
+
|
39
|
+
def gin_constants
|
40
|
+
return @gin_constants if @gin_constants
|
41
|
+
@gin_constants = {Gin.object_id => ::Gin}
|
42
|
+
|
43
|
+
each_constant(Gin) do |name, const, _|
|
44
|
+
@gin_constants[const.object_id] = const
|
45
|
+
end
|
46
|
+
|
47
|
+
@gin_constants
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def each_constant parent, names=nil, &block
|
52
|
+
names ||= parent.constants
|
33
53
|
names.each do |name|
|
34
54
|
const = parent.const_get(name)
|
35
55
|
next unless const
|
36
56
|
|
37
|
-
if
|
38
|
-
next unless parent == Object || const.name =~ /(^|::)#{parent.name}::/
|
39
|
-
|
40
|
-
parent.send(:remove_const, name)
|
41
|
-
else
|
42
|
-
parent.send(:remove_const, name) rescue nil
|
57
|
+
if ::Module === const
|
58
|
+
next unless parent == ::Object || const.name =~ /(^|::)#{parent.name}::/
|
59
|
+
each_constant(const, &block)
|
43
60
|
end
|
61
|
+
|
62
|
+
block.call name, const, parent
|
44
63
|
end
|
45
64
|
end
|
46
65
|
|
@@ -55,36 +74,27 @@ module Gin::Reloadable #:nodoc:
|
|
55
74
|
|
56
75
|
|
57
76
|
def track_require file
|
58
|
-
old_consts = Object.constants
|
77
|
+
old_consts = ::Object.constants
|
59
78
|
old_features = $LOADED_FEATURES.dup
|
60
79
|
|
61
80
|
filepath = Gin.find_loadpath file
|
62
81
|
|
63
82
|
if !reloadables[filepath]
|
64
|
-
success = Object.send(:require, file)
|
83
|
+
success = ::Object.send(:require, file)
|
65
84
|
|
66
85
|
else reloadables[filepath]
|
67
86
|
without_warnings{
|
68
|
-
success = Object.send(:require, file)
|
87
|
+
success = ::Object.send(:require, file)
|
69
88
|
}
|
70
89
|
end
|
71
90
|
|
72
91
|
reloadables[filepath] = [
|
73
92
|
file,
|
74
93
|
$LOADED_FEATURES - old_features,
|
75
|
-
Object.constants - old_consts
|
94
|
+
::Object.constants - old_consts
|
76
95
|
] if success
|
77
96
|
|
78
97
|
success
|
79
98
|
end
|
80
|
-
|
81
|
-
|
82
|
-
def require file
|
83
|
-
if autoreload
|
84
|
-
track_require file
|
85
|
-
else
|
86
|
-
super file
|
87
|
-
end
|
88
|
-
end
|
89
99
|
end
|
90
100
|
end
|
data/lib/gin/request.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
class Gin::Request < Rack::Request
|
2
|
+
include Gin::Constants
|
2
3
|
|
3
4
|
def initialize env
|
4
5
|
super
|
5
|
-
self.params.update env[
|
6
|
-
env[Gin::App::RACK_KEYS[:path_params]]
|
6
|
+
self.params.update env[GIN_PATH_PARAMS] if env[GIN_PATH_PARAMS]
|
7
7
|
end
|
8
8
|
|
9
9
|
|
10
10
|
def forwarded?
|
11
|
-
@env.include?
|
11
|
+
@env.include? FWD_HOST
|
12
12
|
end
|
13
13
|
|
14
14
|
|
@@ -28,12 +28,7 @@ class Gin::Request < Rack::Request
|
|
28
28
|
|
29
29
|
|
30
30
|
def params
|
31
|
-
|
32
|
-
super
|
33
|
-
@params = process_params @params
|
34
|
-
end
|
35
|
-
|
36
|
-
@params
|
31
|
+
@params ||= process_params(super) || {}
|
37
32
|
end
|
38
33
|
|
39
34
|
|
data/lib/gin/response.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
class Gin::Response < Rack::Response
|
2
|
+
include Gin::Constants
|
2
3
|
|
3
4
|
NO_HEADER_STATUSES = [100, 101, 204, 205, 304].freeze #:nodoc:
|
4
|
-
H_CTYPE = "Content-Type".freeze #:nodoc:
|
5
|
-
H_CLENGTH = "Content-Length".freeze #:nodoc:
|
6
5
|
|
7
6
|
attr_accessor :status
|
8
7
|
attr_reader :body
|
@@ -16,11 +15,11 @@ class Gin::Response < Rack::Response
|
|
16
15
|
|
17
16
|
def finish
|
18
17
|
body_out = body
|
19
|
-
header[
|
18
|
+
header[CNT_TYPE] ||= 'text/html;charset=UTF-8'
|
20
19
|
|
21
20
|
if NO_HEADER_STATUSES.include?(status.to_i)
|
22
|
-
header.delete
|
23
|
-
header.delete
|
21
|
+
header.delete CNT_TYPE
|
22
|
+
header.delete CNT_LENGTH
|
24
23
|
|
25
24
|
if status.to_i > 200
|
26
25
|
close
|
@@ -37,14 +36,14 @@ class Gin::Response < Rack::Response
|
|
37
36
|
private
|
38
37
|
|
39
38
|
def update_content_length
|
40
|
-
if header[
|
39
|
+
if header[CNT_TYPE] && !header[CNT_LENGTH]
|
41
40
|
case body
|
42
41
|
when Array
|
43
|
-
header[
|
44
|
-
|
45
|
-
|
42
|
+
header[CNT_LENGTH] = body.inject(0) do |l, p|
|
43
|
+
l + Rack::Utils.bytesize(p)
|
44
|
+
end.to_s
|
46
45
|
when File
|
47
|
-
header[
|
46
|
+
header[CNT_LENGTH] = body.size.to_s
|
48
47
|
end
|
49
48
|
end
|
50
49
|
end
|
data/lib/gin/router.rb
CHANGED
@@ -33,10 +33,12 @@ class Gin::Router
|
|
33
33
|
|
34
34
|
|
35
35
|
# Create restful routes if they aren't taken already.
|
36
|
-
def defaults
|
36
|
+
def defaults default_verb=nil
|
37
|
+
default_verb = (default_verb || 'get').to_s.downcase
|
38
|
+
|
37
39
|
(@ctrl.actions - @actions).each do |action|
|
38
40
|
verb, path = DEFAULT_ACTION_MAP[action]
|
39
|
-
verb, path = [
|
41
|
+
verb, path = [default_verb, "/#{action}"] if verb.nil?
|
40
42
|
|
41
43
|
add(verb, action, path) unless verb.nil? ||
|
42
44
|
@routes.any?{|(r,n,(c,a,p))| r == make_route(verb, path)[0] }
|
@@ -194,6 +196,7 @@ class Gin::Router
|
|
194
196
|
def resources_for http_verb, path
|
195
197
|
param_vals = []
|
196
198
|
curr_node = @routes_tree[http_verb.to_s.downcase]
|
199
|
+
return unless curr_node
|
197
200
|
|
198
201
|
path.scan(%r{/([^/]+|$)}) do |(key)|
|
199
202
|
next if key.empty?
|
data/test/test_app.rb
CHANGED
@@ -87,8 +87,8 @@ class AppTest < Test::Unit::TestCase
|
|
87
87
|
FooApp.instance_variable_set("@autoreload", nil)
|
88
88
|
|
89
89
|
@error_io = StringIO.new
|
90
|
-
@app = FooApp.new
|
91
|
-
@rapp = FooApp.new lambda{|env| [200,{'Content-Type'=>'text/html'},["HI"]]}
|
90
|
+
@app = FooApp.new @error_io
|
91
|
+
@rapp = FooApp.new lambda{|env| [200,{'Content-Type'=>'text/html'},["HI"]]}, @error_io
|
92
92
|
end
|
93
93
|
|
94
94
|
|
@@ -242,7 +242,7 @@ class AppTest < Test::Unit::TestCase
|
|
242
242
|
assert_equal [FooMiddleware, :foo, :bar], FooApp.middleware[0]
|
243
243
|
assert !FooMiddleware.called?
|
244
244
|
|
245
|
-
myapp = FooApp.new
|
245
|
+
myapp = FooApp.new @error_io
|
246
246
|
myapp.call({'rack.input' => "", 'PATH_INFO' => '/foo', 'REQUEST_METHOD' => 'GET'})
|
247
247
|
assert FooMiddleware.called?
|
248
248
|
|
@@ -254,7 +254,7 @@ class AppTest < Test::Unit::TestCase
|
|
254
254
|
|
255
255
|
def test_call_reload
|
256
256
|
FooApp.autoreload true
|
257
|
-
myapp = FooApp.new
|
257
|
+
myapp = FooApp.new @error_io
|
258
258
|
|
259
259
|
assert !myapp.reloaded?
|
260
260
|
myapp.call 'rack.input' => "", 'PATH_INFO' => '/foo', 'REQUEST_METHOD' => 'GET'
|
@@ -277,7 +277,7 @@ class AppTest < Test::Unit::TestCase
|
|
277
277
|
env = {'rack.input' => "", 'PATH_INFO' => '/bad', 'REQUEST_METHOD' => 'GET'}
|
278
278
|
expected = [200, {'Content-Length'=>"5"}, "AHOY!"]
|
279
279
|
myapp = lambda{|env| expected }
|
280
|
-
@app = FooApp.new myapp
|
280
|
+
@app = FooApp.new myapp, @error_io
|
281
281
|
|
282
282
|
resp = @app.call env
|
283
283
|
assert_equal expected, resp
|
@@ -330,7 +330,7 @@ class AppTest < Test::Unit::TestCase
|
|
330
330
|
assert_equal 'text/html;charset=UTF-8', resp[1]['Content-Type']
|
331
331
|
assert_equal @app.asset("404.html"), resp[2].path
|
332
332
|
|
333
|
-
msg = "ERROR
|
333
|
+
msg = "[ERROR] Gin::NotFound: No route exists for: GET /foo"
|
334
334
|
@error_io.rewind
|
335
335
|
assert @error_io.read.include?(msg)
|
336
336
|
end
|
@@ -344,7 +344,7 @@ class AppTest < Test::Unit::TestCase
|
|
344
344
|
assert_equal 500, resp[0]
|
345
345
|
assert_equal @app.asset("500.html"), resp[2].path
|
346
346
|
@error_io.rewind
|
347
|
-
assert @error_io.read.
|
347
|
+
assert @error_io.read.include?("[ERROR] RuntimeError: Something bad happened\n")
|
348
348
|
end
|
349
349
|
|
350
350
|
|
@@ -530,8 +530,9 @@ class AppTest < Test::Unit::TestCase
|
|
530
530
|
|
531
531
|
|
532
532
|
def test_init
|
533
|
-
|
534
|
-
|
533
|
+
@app = FooApp.new
|
534
|
+
assert_equal $stdout, @app.logger, "logger should default to $stdout"
|
535
|
+
assert_equal $stdout, @rapp.logger, "logger should default to $stdout"
|
535
536
|
assert_nil @app.rack_app, "Rack application should be nil by default"
|
536
537
|
assert Proc === @rapp.rack_app, "Rack application should be a Proc"
|
537
538
|
assert Gin::Router === @app.router, "Should have a Gin::Router"
|
data/test/test_controller.rb
CHANGED
@@ -69,7 +69,7 @@ class ControllerTest < Test::Unit::TestCase
|
|
69
69
|
def setup
|
70
70
|
MockApp.instance_variable_set("@environment", nil)
|
71
71
|
MockApp.instance_variable_set("@asset_host", nil)
|
72
|
-
@app = MockApp.new
|
72
|
+
@app = MockApp.new StringIO.new
|
73
73
|
@ctrl = BarController.new(@app, rack_env)
|
74
74
|
end
|
75
75
|
|
@@ -230,7 +230,7 @@ class ControllerTest < Test::Unit::TestCase
|
|
230
230
|
assert_equal 'no-cache', @ctrl.response['Pragma']
|
231
231
|
assert_equal 'no-cache, no-store, must-revalidate, max-age=0',
|
232
232
|
@ctrl.response['Cache-Control']
|
233
|
-
assert_equal
|
233
|
+
assert_equal Gin::Constants::EPOCH.httpdate,
|
234
234
|
@ctrl.response['Expires']
|
235
235
|
end
|
236
236
|
|
@@ -249,6 +249,15 @@ class ControllerTest < Test::Unit::TestCase
|
|
249
249
|
end
|
250
250
|
|
251
251
|
|
252
|
+
def test_delete_cookie
|
253
|
+
test_set_cookie
|
254
|
+
|
255
|
+
@ctrl.delete_cookie "test"
|
256
|
+
assert_equal "test=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000",
|
257
|
+
@ctrl.response['Set-Cookie']
|
258
|
+
end
|
259
|
+
|
260
|
+
|
252
261
|
def test_set_session
|
253
262
|
@ctrl.session['test'] = 'user@example.com'
|
254
263
|
assert_equal({"test"=>"user@example.com"}, @ctrl.env['rack.session'])
|
data/test/test_router.rb
CHANGED
@@ -113,7 +113,7 @@ class RouterTest < Test::Unit::TestCase
|
|
113
113
|
end
|
114
114
|
|
115
115
|
|
116
|
-
def
|
116
|
+
def test_add_all_routes_as_defaults
|
117
117
|
@router.add MyCtrl, "/" do
|
118
118
|
get :show, "/:id"
|
119
119
|
defaults
|
@@ -124,6 +124,23 @@ class RouterTest < Test::Unit::TestCase
|
|
124
124
|
end
|
125
125
|
|
126
126
|
|
127
|
+
def test_add_all_with_default_verb
|
128
|
+
@router.add MyCtrl, "/" do
|
129
|
+
get :show, "/:id"
|
130
|
+
defaults :post
|
131
|
+
end
|
132
|
+
|
133
|
+
assert_equal [MyCtrl, :index, {}],
|
134
|
+
@router.resources_for("GET", "/")
|
135
|
+
|
136
|
+
assert_equal [MyCtrl, :show, {'id' => '123'}],
|
137
|
+
@router.resources_for("GET", "/123")
|
138
|
+
|
139
|
+
assert_equal [MyCtrl, :unmounted_action, {}],
|
140
|
+
@router.resources_for("POST", "/unmounted_action")
|
141
|
+
end
|
142
|
+
|
143
|
+
|
127
144
|
def test_add_all
|
128
145
|
@router.add MyCtrl, "/"
|
129
146
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -75,13 +75,9 @@ dependencies:
|
|
75
75
|
- - ~>
|
76
76
|
- !ruby/object:Gem::Version
|
77
77
|
version: '3.5'
|
78
|
-
description: ! 'Gin is a small web framework built
|
78
|
+
description: ! 'Gin is a small Ruby web framework, built on Rack, which borrows from
|
79
79
|
|
80
|
-
Sinatra
|
81
|
-
|
82
|
-
request flow and HTTP helper methods with dedicated controller classes
|
83
|
-
|
84
|
-
that support Rails-like filters.'
|
80
|
+
Sinatra expressiveness, and targets larger applications.'
|
85
81
|
email:
|
86
82
|
- yaksnrainbows@gmail.com
|
87
83
|
executables: []
|
@@ -100,9 +96,11 @@ files:
|
|
100
96
|
- lib/gin.rb
|
101
97
|
- lib/gin/app.rb
|
102
98
|
- lib/gin/config.rb
|
99
|
+
- lib/gin/constants.rb
|
103
100
|
- lib/gin/controller.rb
|
104
101
|
- lib/gin/core_ext/cgi.rb
|
105
102
|
- lib/gin/core_ext/gin_class.rb
|
103
|
+
- lib/gin/core_ext/rack_commonlogger.rb
|
106
104
|
- lib/gin/errorable.rb
|
107
105
|
- lib/gin/filterable.rb
|
108
106
|
- lib/gin/reloadable.rb
|
@@ -150,7 +148,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
150
148
|
version: '0'
|
151
149
|
segments:
|
152
150
|
- 0
|
153
|
-
hash:
|
151
|
+
hash: -1880592358909000516
|
154
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
153
|
none: false
|
156
154
|
requirements:
|
@@ -162,8 +160,8 @@ rubyforge_project: gin
|
|
162
160
|
rubygems_version: 1.8.24
|
163
161
|
signing_key:
|
164
162
|
specification_version: 3
|
165
|
-
summary: Gin is a small web framework built
|
166
|
-
|
163
|
+
summary: Gin is a small Ruby web framework, built on Rack, which borrows from Sinatra
|
164
|
+
expressiveness, and targets larger applications.
|
167
165
|
test_files:
|
168
166
|
- test/test_app.rb
|
169
167
|
- test/test_config.rb
|