merb 0.0.6 → 0.0.7

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.
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