merb 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/README +22 -4
  2. data/Rakefile +15 -3
  3. data/TODO +2 -3
  4. data/bin/merb +61 -36
  5. data/examples/sample_app/dist/app/controllers/files.rb +31 -0
  6. data/examples/sample_app/dist/app/controllers/posts.rb +26 -2
  7. data/examples/sample_app/dist/app/controllers/test.rb +7 -1
  8. data/examples/sample_app/dist/app/views/files/progress.jerb +3 -0
  9. data/examples/sample_app/dist/app/views/files/start.herb +62 -0
  10. data/examples/sample_app/dist/app/views/files/upload.herb +6 -0
  11. data/examples/sample_app/dist/app/views/layout/{application.rhtml → application.herb} +2 -3
  12. data/examples/sample_app/dist/app/views/layout/{foo.rhtml → foo.herb} +0 -0
  13. data/examples/sample_app/dist/app/views/posts/{_comments.rhtml → _comments.herb} +0 -0
  14. data/examples/sample_app/dist/app/views/posts/comment.jerb +1 -0
  15. data/examples/sample_app/dist/app/views/posts/{list.rhtml → list.herb} +0 -0
  16. data/examples/sample_app/dist/app/views/posts/{new.rhtml → new.herb} +0 -0
  17. data/examples/sample_app/dist/app/views/posts/{show.rhtml → show.herb} +0 -0
  18. data/examples/sample_app/dist/app/views/posts/xml_test.xerb +3 -0
  19. data/examples/sample_app/dist/app/views/test/{foo.rhtml → foo.herb} +0 -0
  20. data/examples/sample_app/dist/app/views/test/{hello.rhtml → hello.herb} +0 -0
  21. data/examples/sample_app/dist/app/views/test/json.jerb +1 -0
  22. data/examples/sample_app/dist/conf/merb.yml +11 -0
  23. data/examples/sample_app/dist/conf/merb_init.rb +1 -1
  24. data/examples/sample_app/dist/conf/mup.conf +11 -0
  25. data/examples/sample_app/dist/public/javascripts/mup.js +113 -0
  26. data/examples/sample_app/script/merb_stop +7 -3
  27. data/examples/sample_app/script/startdrb +8 -0
  28. data/lib/merb.rb +37 -2
  29. data/lib/merb/merb_class_extensions.rb +21 -22
  30. data/lib/merb/merb_controller.rb +101 -33
  31. data/lib/merb/merb_handler.rb +26 -25
  32. data/lib/merb/merb_router.rb +1 -1
  33. data/lib/merb/merb_utils.rb +35 -37
  34. data/lib/merb/mixins/basic_authentication_mixin.rb +39 -0
  35. data/lib/merb/mixins/controller_mixin.rb +119 -115
  36. data/lib/merb/mixins/javascript_mixin.rb +63 -0
  37. data/lib/merb/mixins/render_mixin.rb +85 -69
  38. data/lib/merb/mixins/responder_mixin.rb +38 -0
  39. data/lib/merb/session/merb_drb_server.rb +107 -0
  40. data/lib/merb/session/merb_drb_session.rb +71 -0
  41. data/lib/merb/session/merb_session.rb +1 -0
  42. data/lib/merb/vendor/paginator/README.txt +84 -0
  43. data/lib/merb/vendor/paginator/paginator.rb +121 -0
  44. data/lib/mutex_hotfix.rb +34 -0
  45. metadata +41 -63
  46. data/doc/rdoc/classes/ControllerMixin.html +0 -676
  47. data/doc/rdoc/classes/Hash.html +0 -148
  48. data/doc/rdoc/classes/Merb.html +0 -140
  49. data/doc/rdoc/classes/Merb/Controller.html +0 -338
  50. data/doc/rdoc/classes/Merb/RouteMatcher.html +0 -388
  51. data/doc/rdoc/classes/Merb/Server.html +0 -148
  52. data/doc/rdoc/classes/Merb/Session.html +0 -201
  53. data/doc/rdoc/classes/Merb/SessionMixin.html +0 -199
  54. data/doc/rdoc/classes/MerbControllerError.html +0 -111
  55. data/doc/rdoc/classes/MerbHandler.html +0 -430
  56. data/doc/rdoc/classes/MerbHash.html +0 -469
  57. data/doc/rdoc/classes/MerbHash/Mutex.html +0 -198
  58. data/doc/rdoc/classes/Noroutefound.html +0 -153
  59. data/doc/rdoc/classes/Object.html +0 -149
  60. data/doc/rdoc/classes/RenderMixin.html +0 -362
  61. data/doc/rdoc/classes/String.html +0 -212
  62. data/doc/rdoc/classes/Symbol.html +0 -179
  63. data/doc/rdoc/created.rid +0 -1
  64. data/doc/rdoc/files/LICENSE.html +0 -129
  65. data/doc/rdoc/files/README.html +0 -417
  66. data/doc/rdoc/files/TODO.html +0 -151
  67. data/doc/rdoc/files/lib/merb/merb_class_extensions_rb.html +0 -101
  68. data/doc/rdoc/files/lib/merb/merb_controller_rb.html +0 -101
  69. data/doc/rdoc/files/lib/merb/merb_handler_rb.html +0 -101
  70. data/doc/rdoc/files/lib/merb/merb_router_rb.html +0 -101
  71. data/doc/rdoc/files/lib/merb/merb_utils_rb.html +0 -108
  72. data/doc/rdoc/files/lib/merb/mixins/controller_mixin_rb.html +0 -101
  73. data/doc/rdoc/files/lib/merb/mixins/render_mixin_rb.html +0 -101
  74. data/doc/rdoc/files/lib/merb/session/merb_session_rb.html +0 -101
  75. data/doc/rdoc/files/lib/merb_rb.html +0 -140
  76. data/doc/rdoc/files/lib/merb_tasks_rb.html +0 -101
  77. data/doc/rdoc/fr_class_index.html +0 -43
  78. data/doc/rdoc/fr_file_index.html +0 -40
  79. data/doc/rdoc/fr_method_index.html +0 -104
  80. data/doc/rdoc/index.html +0 -24
  81. data/doc/rdoc/rdoc-style.css +0 -208
  82. data/examples/sample_app/dist/app/controllers/upload.rb +0 -29
  83. data/examples/sample_app/dist/app/views/posts/comment.merbjs +0 -1
  84. data/examples/sample_app/dist/app/views/upload/start.rhtml +0 -15
  85. data/examples/sample_app/dist/app/views/upload/upload.rhtml +0 -4
  86. data/examples/sample_app/dist/public/files/README +0 -35
  87. data/examples/sample_app/dist/public/files/setup.rb +0 -1346
  88. data/examples/sample_app/log/merb.log +0 -778
@@ -25,6 +25,8 @@ class MerbHandler < Mongrel::HttpHandler
25
25
  # and your controller can go on processing other requests.
26
26
  def process(request, response)
27
27
 
28
+ start = Time.now
29
+
28
30
  if response.socket.closed?
29
31
  return
30
32
  end
@@ -41,11 +43,10 @@ class MerbHandler < Mongrel::HttpHandler
41
43
  if get_or_head and @files.can_serve(path_info)
42
44
  # File exists as-is so serve it up
43
45
  MERB_LOGGER.info("Serving static file: #{path_info}")
44
-
45
46
  @files.process(request,response)
46
47
  elsif get_or_head and @files.can_serve(page_cached)
47
48
  # Possible cached page, serve it up
48
- MERB_LOGGER.info("Serving static file: #{path_info}")
49
+ MERB_LOGGER.info("Serving static file: #{page_cached}")
49
50
  request.params[Mongrel::Const::PATH_INFO] = page_cached
50
51
  @files.process(request,response)
51
52
  else
@@ -53,14 +54,22 @@ class MerbHandler < Mongrel::HttpHandler
53
54
  # This handles parsing the query string and post/file upload
54
55
  # params and is outside of the synchronize call so that
55
56
  # multiple file uploads can be done at once.
57
+ controller = nil
56
58
  controller, action = handle(request)
57
- MERB_LOGGER.info("Routing to controller: #{controller.class} action: #{action}")
58
- output = nil
59
- # synchronize here because this is where ActiveRecord or your db
60
- # calls will be run in your controller methods.
61
- @guard.synchronize {
62
- output = controller.dispatch(action)
63
- }
59
+ MERB_LOGGER.info("Routing to controller: #{controller.class} action: #{action}\nParsing HTTP Input took: #{Time.now - start} seconds")
60
+
61
+ # special case allows the progress action of a Files controller
62
+ # to be handled without locking since no db access is required.
63
+ # but we do need to synchronize whenever you use ActiveRecord
64
+ # in your controllers.
65
+ if (action == 'progress') && (Files === controller)
66
+ puts 'skip mutex'
67
+ controller.dispatch(action)
68
+ else
69
+ @guard.synchronize {
70
+ controller.dispatch(action)
71
+ }
72
+ end
64
73
  rescue Exception => e
65
74
  response.start(500) do |head,out|
66
75
  head["Content-Type"] = "text/html"
@@ -87,20 +96,18 @@ class MerbHandler < Mongrel::HttpHandler
87
96
  end
88
97
  end
89
98
 
90
- controller = nil
91
-
92
99
  if sendfile
93
- MERB_LOGGER.info("X-SENDFILE: #{sendfile}")
100
+ MERB_LOGGER.info("X-SENDFILE: #{sendfile}\nComplete Request took: #{Time.now - start} seconds")
94
101
  # send X-SENDFILE header to mongrel
95
102
  response.send_status(File.size(sendfile))
96
103
  response.send_header
97
104
  response.send_file(sendfile)
98
105
  else
99
- MERB_LOGGER.info("Response status: #{response.status}\n\n")
106
+ MERB_LOGGER.info("Response status: #{response.status}\nComplete Request took: #{Time.now - start} seconds\n\n")
100
107
  # render response from successful controller
101
- response.send_status((output||'').length)
108
+ response.send_status((controller.body||='').length)
102
109
  response.send_header
103
- response.write(output)
110
+ response.write(controller.body)
104
111
  end
105
112
  end
106
113
  end
@@ -114,14 +121,8 @@ class MerbHandler < Mongrel::HttpHandler
114
121
  path = request.params[Mongrel::Const::PATH_INFO].sub(/\/+/, '/')
115
122
  path = path[0..-2] if (path[-1] == ?/)
116
123
  route = Merb::RouteMatcher.new.route_request(path)
117
- if route
118
- MERB_LOGGER.info("No Matching Route!") if route[:controller] == 'Noroutefound'
119
- [ instantiate_controller(route[:controller], request.body, request.params, route),
120
- route[:action] ]
121
- else
122
- MERB_LOGGER.info("No Matching Route!")
123
- ["<html><body>Error: no route matches!</body></html>", nil]
124
- end
124
+ [ instantiate_controller(route[:controller], request.body, request.params, route),
125
+ route[:action] ]
125
126
  end
126
127
 
127
128
  # take a controller class name string and reload or require
@@ -143,8 +144,8 @@ class MerbHandler < Mongrel::HttpHandler
143
144
 
144
145
  # format exception message for browser display
145
146
  def html_exception(e)
146
- "<html><h2>Merb Error!</h2><p>#{ e.message } - (#{ e.class })\n" <<
147
- "#{(e.backtrace or []).join('<br />')}</p></html>"
147
+ "<html><body><h2>Merb Error!</h2><p>#{ e.message } - (#{ e.class })\n" <<
148
+ "#{(e.backtrace or []).join('<br />')}</p></body></html>"
148
149
  end
149
150
 
150
151
  def exception(e)
@@ -59,7 +59,7 @@ module Merb
59
59
 
60
60
  # compile each individual route into a when /.../
61
61
  # component of the case statement. Takes /:sections
62
- # if the route def that start with : and turns them
62
+ # of the route def that start with : and turns them
63
63
  # into placeholders for whatever urls match against
64
64
  # the route in question. Special case for the default
65
65
  # /:controller/:action/:id route.
@@ -4,7 +4,12 @@ class String
4
4
  # :allow_reloading is set to true in the config
5
5
  # file or command line options.
6
6
  def import
7
- Merb::Server.config[:allow_reloading] ? load( self.snake_case + '.rb' ) : require( self.snake_case )
7
+ if Merb::Server.config[:allow_reloading]
8
+ Object.send(:remove_const, self.camel_case.intern) rescue nil
9
+ load(self.snake_case + '.rb')
10
+ else
11
+ require(self.snake_case)
12
+ end
8
13
  end
9
14
 
10
15
  # "FooBar".snake_case #=> "foo_bar"
@@ -20,14 +25,40 @@ class String
20
25
  words.map!{|w| w.downcase.sub(%r/^./){|c| c.upcase}}
21
26
  words.join
22
27
  end
28
+
29
+ # Concatenates a path
30
+ def /(o)
31
+ File.join(self, o.to_s)
32
+ end
23
33
 
24
34
  end
25
35
 
36
+
37
+ module Enumerable
38
+ def injecting(s)
39
+ inject(s) do |k, i|
40
+ yield(k, i); k
41
+ end
42
+ end
43
+ end
44
+
45
+ class Numeric
46
+ def to_currency( pre_symbol='$', thousands=',', decimal='.',
47
+ post_symbol=nil )
48
+ "#{pre_symbol}#{
49
+ ( "%.2f" % self ).gsub(
50
+ /(\d)(?=(?:\d{3})+(?:$|\.))/,
51
+ "\\1#{thousands}"
52
+ )
53
+ }#{post_symbol}"
54
+ end
55
+ end
56
+
26
57
  class Symbol
27
58
 
28
59
  # faster Symbol#to_s to speed up routing.
29
60
  def to_s
30
- @str_rep || (@str_rep = id2name.freeze)
61
+ @str_rep ||= id2name.freeze
31
62
  end
32
63
 
33
64
  # ["foo", "bar"].map &:reverse #=> ['oof', 'rab']
@@ -107,8 +138,9 @@ class MerbHash < Hash
107
138
  super(convert_key(key))
108
139
  end
109
140
 
141
+ # allow merbhash.key to work the same as merbhash[key]
110
142
  def method_missing(m,*a)
111
- m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m]:raise(NoMethodError,"#{m}")
143
+ m.to_s =~ /=$/?self[$`]=a[0]:a==[]?self[m]:raise(NoMethodError,"#{m}")
112
144
  end
113
145
 
114
146
  protected
@@ -120,37 +152,3 @@ class MerbHash < Hash
120
152
  end
121
153
  end
122
154
 
123
- require 'thread'
124
-
125
- # monkey patch Mutex so it does not leak memory.
126
- class Mutex
127
-
128
- def lock
129
- while (Thread.critical = true; @locked)
130
- @waiting.unshift Thread.current
131
- Thread.stop
132
- end
133
- @locked = true
134
- Thread.critical = false
135
- self
136
- end
137
-
138
- def unlock
139
- return unless @locked
140
- Thread.critical = true
141
- @locked = false
142
- begin
143
- t = @waiting.pop
144
- t.wakeup if t
145
- rescue ThreadError
146
- retry
147
- end
148
- Thread.critical = false
149
- begin
150
- t.run if t
151
- rescue ThreadError
152
- end
153
- self
154
- end
155
-
156
- end
@@ -0,0 +1,39 @@
1
+ module Merb
2
+
3
+ module Authentication
4
+ require 'base64'
5
+
6
+ def credentials
7
+ if d = %w{REDIRECT_X_HTTP_AUTHORIZATION
8
+ X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION}.
9
+ inject([]) { |d,h| @env.has_key?(h) ? @env[h].to_s.split : d }
10
+ return Base64.decode64(d[1]).split(':')[0..1] if d[0] == 'Basic'
11
+ end
12
+ end
13
+
14
+ def authenticated?
15
+ username, password = *credentials
16
+ username == Merb::Server.config[:basic_auth][:username] and password == Merb::Server.config[:basic_auth][:password]
17
+ end
18
+
19
+ def authenticate
20
+ if !authenticated?
21
+ throw :halt
22
+ end
23
+ end
24
+
25
+ def self.included(base)
26
+ base.class_eval do
27
+ def filters_halted
28
+ @status = 401
29
+ @headers['Content-type'] = 'text/plain'
30
+ @headers['Status'] = 'Unauthorized'
31
+ @headers['WWW-Authenticate'] = "Basic realm=\"#{Merb::Server.config[:basic_auth][:domain]}\""
32
+ return 'Unauthorized'
33
+ end
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -1,129 +1,133 @@
1
- module ControllerMixin
2
-
3
- # redirect to another url It can be like /foo/bar
4
- # for redirecting within your same app. Or it can
5
- # be a fully qualified url to another site.
6
- def redirect(url)
7
- MERB_LOGGER.info("Redirecting to: #{url}")
8
- @status = 302
9
- @headers.merge!({'Location'=> url})
10
- return ''
11
- end
12
-
13
- # pass in a path to a file and this will set the
14
- # right headers and let mongrel do its thang and
15
- # serve the static file directly.
16
- def send_file(file)
17
- headers['X-SENDFILE'] = file
18
- return
19
- end
1
+ module Merb
2
+ module ControllerMixin
20
3
 
21
- # This uses nginx X-Accel-Redirect header to send
22
- # a file directly from nginx. See the nginx wiki:
23
- # http://wiki.codemongers.com/NginxXSendfile
24
- def nginx_send_file(file)
25
- headers['X-Accel-Redirect'] = file
26
- return
27
- end
28
-
29
- # parses a query string or the payload of a POST
30
- # request into the params hash. So for example:
31
- # /foo?bar=nik&post[title]=heya&post[body]=whatever
32
- # parses into:
33
- # {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever}}
34
- def query_parse(qs, d = '&;')
35
- m = proc {|_,o,n|o.u(n,&m)rescue([*o]<<n)}
36
- (qs||'').split(/[#{d}] */n).inject(MerbHash[]) { |h,p|
37
- k, v=unescape(p).split('=',2)
38
- h.u(k.split(/[\]\[]+/).reverse.
39
- inject(v) { |x,i| MerbHash[i,x] },&m)
40
- }
41
- end
42
-
43
- # does url escaping
44
- def escape(s)
45
- Mongrel::HttpRequest.escape(s)
46
- end
47
-
48
- # does url unescaping
49
- def unescape(s)
50
- Mongrel::HttpRequest.unescape(s)
51
- end
52
-
53
- # escape text for javascript.
54
- def escape_js(javascript)
55
- (javascript || '').gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
56
- end
57
- alias js :escape_js
58
-
59
- def xml_http_request?
60
- not /XMLHttpRequest/i.match(@headers['HTTP_X_REQUESTED_WITH']).nil?
61
- end
62
- alias xhr? :xml_http_request?
63
- alias ajax? :xml_http_request?
64
-
65
- def remote_ip
66
- return @headers['HTTP_CLIENT_IP'] if @headers.include?('HTTP_CLIENT_IP')
67
-
68
- if @headers.include?('HTTP_X_FORWARDED_FOR') then
69
- remote_ips = @headers['HTTP_X_FORWARDED_FOR'].split(',').reject do |ip|
70
- ip =~ /^unknown$|^(127|10|172\.16|192\.168)\./i
71
- end
72
-
73
- return remote_ips.first.strip unless remote_ips.empty?
4
+ # redirect to another url It can be like /foo/bar
5
+ # for redirecting within your same app. Or it can
6
+ # be a fully qualified url to another site.
7
+ def redirect(url)
8
+ MERB_LOGGER.info("Redirecting to: #{url}")
9
+ @status = 302
10
+ headers.merge!({'Location'=> url})
11
+ return ''
74
12
  end
75
-
76
- return @headers['REMOTE_ADDR']
77
- end
78
-
79
- def protocol
80
- @headers['HTTPS'] == 'on' ? 'https://' : 'http://'
81
- end
13
+
14
+ # pass in a path to a file and this will set the
15
+ # right headers and let mongrel do its thang and
16
+ # serve the static file directly.
17
+ def send_file(file)
18
+ headers['X-SENDFILE'] = file
19
+ return
20
+ end
21
+
22
+ # This uses nginx X-Accel-Redirect header to send
23
+ # a file directly from nginx. See the nginx wiki:
24
+ # http://wiki.codemongers.com/NginxXSendfile
25
+ def nginx_send_file(file)
26
+ headers['X-Accel-Redirect'] = file
27
+ return
28
+ end
82
29
 
83
- def ssl?
84
- @headers['HTTPS'] == 'on'
85
- end
30
+ # parses a query string or the payload of a POST
31
+ # request into the params hash. So for example:
32
+ # /foo?bar=nik&post[title]=heya&post[body]=whatever
33
+ # parses into:
34
+ # {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever}}
35
+ def query_parse(qs, d = '&;')
36
+ m = proc {|_,o,n|o.u(n,&m)rescue([*o]<<n)}
37
+ (qs||'').split(/[#{d}] */n).inject(MerbHash[]) { |h,p|
38
+ k, v=unescape(p).split('=',2)
39
+ h.u(k.split(/[\]\[]+/).reverse.
40
+ inject(v) { |x,i| MerbHash[i,x] },&m)
41
+ }
42
+ end
86
43
 
87
- # The request uri.
44
+ def make_token
45
+ require 'digest/md5'
46
+ Digest::MD5.hexdigest("#{inspect}#{Time.now}#{rand}")
47
+ end
88
48
 
89
- def uri
90
- @headers['REQUEST_URI']
91
- end
49
+ # does url escaping
50
+ def escape(s)
51
+ Mongrel::HttpRequest.escape(s)
52
+ end
92
53
 
93
- # The path is the uri without the query string.
54
+ # does url unescaping
55
+ def unescape(s)
56
+ Mongrel::HttpRequest.unescape(s)
57
+ end
94
58
 
95
- def path
96
- uri ? uri.split('?').first : ''
97
- end
59
+ # returns true if the request is an ajax request.
60
+ def xml_http_request?
61
+ not /XMLHttpRequest/i.match(@env['HTTP_X_REQUESTED_WITH']).nil?
62
+ end
63
+ alias xhr? :xml_http_request?
64
+ alias ajax? :xml_http_request?
98
65
 
99
- def path_info
100
- @headers['PATH_INFO']
101
- end
66
+ # returns the remote IP address if it can find it.
67
+ def remote_ip
68
+ return @env['HTTP_CLIENT_IP'] if @env.include?('HTTP_CLIENT_IP')
102
69
 
103
- def port
104
- @headers['SERVER_PORT'].to_i
105
- end
106
-
107
- def host
108
- @headers['HTTP_X_FORWARDED_HOST'] || @headers['HTTP_HOST']
109
- end
70
+ if @env.include?(Mongrel::Const::HTTP_X_FORWARDED_FOR) then
71
+ remote_ips = @env[Mongrel::Const::HTTP_X_FORWARDED_FOR].split(',').reject do |ip|
72
+ ip =~ /^unknown$|^(127|10|172\.16|192\.168)\./i
73
+ end
110
74
 
111
- def subdomains(tld_length = 1)
112
- parts = host.split('.')
113
- parts[0..-(tld_length+2)]
114
- end
75
+ return remote_ips.first.strip unless remote_ips.empty?
76
+ end
115
77
 
116
- def domain(tld_length = 1)
117
- host.split('.').last(1 + tld_length).join('.')
118
- end
78
+ return @env[Mongrel::Const::REMOTE_ADDR]
79
+ end
80
+
81
+ # returns either 'https://' or 'http://' depending on
82
+ # the HTTPS header
83
+ def protocol
84
+ @env['HTTPS'] == 'on' ? 'https://' : 'http://'
85
+ end
86
+
87
+ # returns true if the request is an SSL request
88
+ def ssl?
89
+ @env['HTTPS'] == 'on'
90
+ end
91
+
92
+ # The request uri.
93
+ def uri
94
+ @env['REQUEST_URI']
95
+ end
96
+
97
+ # The path is the uri without the query string.
98
+ def path
99
+ uri ? uri.split('?').first : ''
100
+ end
119
101
 
120
- def method
121
- @headers['REQUEST_METHOD'].downcase.to_sym
122
- end
102
+ def path_info
103
+ @env['PATH_INFO']
104
+ end
105
+
106
+ def port
107
+ @env['SERVER_PORT'].to_i
108
+ end
123
109
 
124
- [:get, :post, :put, :delete, :head].each do |m|
125
- eval %{
126
- def #{m}?; method == :#{m}; end
127
- }
110
+ def host
111
+ @env['HTTP_X_FORWARDED_HOST'] || @env['HTTP_HOST']
112
+ end
113
+
114
+ def subdomains(tld_length = 1)
115
+ parts = host.split('.')
116
+ parts[0..-(tld_length+2)]
117
+ end
118
+
119
+ def domain(tld_length = 1)
120
+ host.split('.').last(1 + tld_length).join('.')
121
+ end
122
+
123
+ def method
124
+ @method ||= @env['REQUEST_METHOD']
125
+ end
126
+
127
+ [:get, :post, :put, :delete, :head].each do |m|
128
+ eval %{
129
+ def #{m}?; method == :#{m}; end
130
+ }
131
+ end
128
132
  end
129
- end
133
+ end