rack-contrib 1.4.0 → 2.5.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.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +27 -13
  3. data/lib/rack/contrib/access.rb +8 -6
  4. data/lib/rack/contrib/backstage.rb +5 -3
  5. data/lib/rack/contrib/bounce_favicon.rb +3 -1
  6. data/lib/rack/contrib/callbacks.rb +2 -0
  7. data/lib/rack/contrib/common_cookies.rb +19 -11
  8. data/lib/rack/contrib/config.rb +3 -15
  9. data/lib/rack/contrib/cookies.rb +2 -0
  10. data/lib/rack/contrib/csshttprequest.rb +12 -6
  11. data/lib/rack/contrib/deflect.rb +35 -33
  12. data/lib/rack/contrib/enforce_valid_encoding.rb +3 -1
  13. data/lib/rack/contrib/evil.rb +2 -0
  14. data/lib/rack/contrib/expectation_cascade.rb +5 -3
  15. data/lib/rack/contrib/garbagecollector.rb +2 -0
  16. data/lib/rack/contrib/host_meta.rb +3 -1
  17. data/lib/rack/contrib/json_body_parser.rb +94 -0
  18. data/lib/rack/contrib/jsonp.rb +21 -17
  19. data/lib/rack/contrib/lazy_conditional_get.rb +13 -4
  20. data/lib/rack/contrib/lighttpd_script_name_fix.rb +2 -0
  21. data/lib/rack/contrib/locale.rb +74 -22
  22. data/lib/rack/contrib/mailexceptions.rb +3 -1
  23. data/lib/rack/contrib/nested_params.rb +6 -115
  24. data/lib/rack/contrib/not_found.rb +22 -7
  25. data/lib/rack/contrib/post_body_content_type_parser.rb +54 -4
  26. data/lib/rack/contrib/printout.rb +3 -1
  27. data/lib/rack/contrib/proctitle.rb +2 -0
  28. data/lib/rack/contrib/profiler.rb +44 -17
  29. data/lib/rack/contrib/relative_redirect.rb +11 -5
  30. data/lib/rack/contrib/response_cache.rb +23 -11
  31. data/lib/rack/contrib/response_headers.rb +8 -2
  32. data/lib/rack/contrib/route_exceptions.rb +2 -0
  33. data/lib/rack/contrib/runtime.rb +3 -30
  34. data/lib/rack/contrib/signals.rb +6 -0
  35. data/lib/rack/contrib/simple_endpoint.rb +3 -1
  36. data/lib/rack/contrib/static_cache.rb +20 -9
  37. data/lib/rack/contrib/time_zone.rb +2 -0
  38. data/lib/rack/contrib/try_static.rb +2 -0
  39. data/lib/rack/contrib/version.rb +5 -0
  40. data/lib/rack/contrib.rb +10 -4
  41. metadata +14 -189
  42. data/lib/rack/contrib/accept_format.rb +0 -66
  43. data/lib/rack/contrib/sendfile.rb +0 -140
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
 
3
5
  # A Rack middleware for providing JSON-P support.
@@ -7,8 +9,7 @@ module Rack
7
9
  class JSONP
8
10
  include Rack::Utils
9
11
 
10
- VALID_JS_VAR = /[a-zA-Z_$][\w$]*/
11
- VALID_CALLBACK = /\A#{VALID_JS_VAR}(?:\.?#{VALID_JS_VAR})*\z/
12
+ VALID_CALLBACK = /\A[a-zA-Z_$](?:\.?[\w$])*\z/
12
13
 
13
14
  # These hold the Unicode characters \u2028 and \u2029.
14
15
  #
@@ -22,6 +23,9 @@ module Rack
22
23
  # "\342\200\251" # => "\u2029"
23
24
  U2028, U2029 = ("\u2028" == 'u2028') ? ["\342\200\250", "\342\200\251"] : ["\u2028", "\u2029"]
24
25
 
26
+ HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
27
+ private_constant :HEADERS_KLASS
28
+
25
29
  def initialize(app)
26
30
  @app = app
27
31
  end
@@ -41,8 +45,8 @@ module Rack
41
45
  return status, headers, response
42
46
  end
43
47
 
44
- headers = HeaderHash.new(headers)
45
-
48
+ headers = HEADERS_KLASS.new.merge(headers)
49
+
46
50
  if is_json?(headers) && has_callback?(request)
47
51
  callback = request.params['callback']
48
52
  return bad_request unless valid_callback?(callback)
@@ -51,32 +55,32 @@ module Rack
51
55
 
52
56
  # No longer json, its javascript!
53
57
  headers['Content-Type'] = headers['Content-Type'].gsub('json', 'javascript')
54
-
58
+
55
59
  # Set new Content-Length, if it was set before we mutated the response body
56
60
  if headers['Content-Length']
57
- length = response.to_ary.inject(0) { |len, part| len + bytesize(part) }
61
+ length = response.map(&:bytesize).reduce(0, :+)
58
62
  headers['Content-Length'] = length.to_s
59
63
  end
60
64
  end
61
65
 
62
66
  [status, headers, response]
63
67
  end
64
-
68
+
65
69
  private
66
-
70
+
67
71
  def is_json?(headers)
68
72
  headers.key?('Content-Type') && headers['Content-Type'].include?('application/json')
69
73
  end
70
-
74
+
71
75
  def has_callback?(request)
72
76
  request.params.include?('callback') and not request.params['callback'].to_s.empty?
73
77
  end
74
78
 
75
79
  # See:
76
80
  # http://stackoverflow.com/questions/1661197/valid-characters-for-javascript-variable-names
77
- #
81
+ #
78
82
  # NOTE: Supports dots (.) since callbacks are often in objects:
79
- #
83
+ #
80
84
  def valid_callback?(callback)
81
85
  callback =~ VALID_CALLBACK
82
86
  end
@@ -88,8 +92,8 @@ module Rack
88
92
  # method of combining all of the data into a single string makes sense
89
93
  # since JSON is returned as a full string.
90
94
  #
91
- def pad(callback, response, body = "")
92
- response.each do |s|
95
+ def pad(callback, response)
96
+ body = response.to_enum.map do |s|
93
97
  # U+2028 and U+2029 are allowed inside strings in JSON (as all literal
94
98
  # Unicode characters) but JavaScript defines them as newline
95
99
  # seperators. Because no literal newlines are allowed in a string, this
@@ -97,9 +101,9 @@ module Rack
97
101
  # replacing them with the escaped version. This should be safe because
98
102
  # according to the JSON spec, these characters are *only* valid inside
99
103
  # a string and should therefore not be present any other places.
100
- body << s.to_s.gsub(U2028, '\u2028').gsub(U2029, '\u2029')
101
- end
102
-
104
+ s.gsub(U2028, '\u2028').gsub(U2029, '\u2029')
105
+ end.join
106
+
103
107
  # https://github.com/rack/rack-contrib/issues/46
104
108
  response.close if response.respond_to?(:close)
105
109
 
@@ -107,7 +111,7 @@ module Rack
107
111
  end
108
112
 
109
113
  def bad_request(body = "Bad Request")
110
- [ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.size.to_s }, [body] ]
114
+ [ 400, { 'content-type' => 'text/plain', 'content-length' => body.bytesize.to_s }, [body] ]
111
115
  end
112
116
 
113
117
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
 
3
5
  ##
@@ -43,6 +45,10 @@ module Rack
43
45
  # know for sure that it does not modify the cached content, you can set the
44
46
  # `Rack-Lazy-Conditional-Get` on response to `skip`. This will not update the
45
47
  # global modification date.
48
+ #
49
+ # NOTE: This will not work properly in a multi-threaded environment with
50
+ # default cache object. A provided cache object should ensure thread-safety
51
+ # of the `get`/`set`/`[]`/`[]=` methods.
46
52
 
47
53
  class LazyConditionalGet
48
54
 
@@ -68,11 +74,14 @@ module Rack
68
74
 
69
75
  def call env
70
76
  if reading? env and fresh? env
71
- return [304, {'Last-Modified' => env['HTTP_IF_MODIFIED_SINCE']}, []]
77
+ return [304, {'last-modified' => env['HTTP_IF_MODIFIED_SINCE']}, []]
72
78
  end
79
+
73
80
  status, headers, body = @app.call env
81
+ headers = Rack.release < "3" ? Utils::HeaderHash.new(headers) : headers
82
+
74
83
  update_cache unless (reading?(env) or skipping?(headers))
75
- headers['Last-Modified'] = cached_value if stampable? headers
84
+ headers['last-modified'] = cached_value if stampable? headers
76
85
  [status, headers, body]
77
86
  end
78
87
 
@@ -87,11 +96,11 @@ module Rack
87
96
  end
88
97
 
89
98
  def skipping? headers
90
- headers['Rack-Lazy-Conditional-Get'] == 'skip'
99
+ headers['rack-lazy-conditional-get'] == 'skip'
91
100
  end
92
101
 
93
102
  def stampable? headers
94
- !headers.has_key?('Last-Modified') and headers['Rack-Lazy-Conditional-Get'] == 'yes'
103
+ !headers.has_key?('last-modified') and headers['rack-lazy-conditional-get'] == 'yes'
95
104
  end
96
105
 
97
106
  def update_cache
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Lighttpd sets the wrong SCRIPT_NAME and PATH_INFO if you mount your
3
5
  # FastCGI app at "/". This middleware fixes this issue.
@@ -1,42 +1,94 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'i18n'
2
4
 
3
5
  module Rack
4
6
  class Locale
7
+ HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
8
+ private_constant :HEADERS_KLASS
9
+
5
10
  def initialize(app)
6
11
  @app = app
7
12
  end
8
13
 
9
14
  def call(env)
10
- old_locale = I18n.locale
11
-
12
- begin
13
- locale = accept_locale(env) || I18n.default_locale
14
- locale = env['rack.locale'] = I18n.locale = locale.to_s
15
- status, headers, body = @app.call(env)
16
- headers['Content-Language'] = locale unless headers['Content-Language']
17
- [status, headers, body]
18
- ensure
19
- I18n.locale = old_locale
15
+ locale_to_restore = I18n.locale
16
+
17
+ locale = user_preferred_locale(env["HTTP_ACCEPT_LANGUAGE"])
18
+ locale ||= I18n.default_locale
19
+
20
+ env['rack.locale'] = I18n.locale = locale.to_s
21
+ status, headers, body = @app.call(env)
22
+ headers = HEADERS_KLASS.new.merge(headers)
23
+
24
+ unless headers['Content-Language']
25
+ headers['Content-Language'] = locale.to_s
20
26
  end
27
+
28
+ [status, headers, body]
29
+ ensure
30
+ I18n.locale = locale_to_restore
21
31
  end
22
32
 
23
33
  private
24
34
 
25
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
26
- def accept_locale(env)
27
- accept_langs = env["HTTP_ACCEPT_LANGUAGE"]
28
- return if accept_langs.nil?
35
+ # Accept-Language header is covered mainly by RFC 7231
36
+ # https://tools.ietf.org/html/rfc7231
37
+ #
38
+ # Related sections:
39
+ #
40
+ # * https://tools.ietf.org/html/rfc7231#section-5.3.1
41
+ # * https://tools.ietf.org/html/rfc7231#section-5.3.5
42
+ # * https://tools.ietf.org/html/rfc4647#section-3.4
43
+ #
44
+ # There is an obsolete RFC 2616 (https://tools.ietf.org/html/rfc2616)
45
+ #
46
+ # Edge cases:
47
+ #
48
+ # * Value can be a comma separated list with optional whitespaces:
49
+ # Accept-Language: da, en-gb;q=0.8, en;q=0.7
50
+ #
51
+ # * Quality value can contain optional whitespaces as well:
52
+ # Accept-Language: ru-UA, ru; q=0.8, uk; q=0.6, en-US; q=0.4, en; q=0.2
53
+ #
54
+ # * Quality prefix 'q=' can be in upper case (Q=)
55
+ #
56
+ # * Ignore case when match locale with I18n available locales
57
+ #
58
+ def user_preferred_locale(header)
59
+ return if header.nil?
29
60
 
30
- languages_and_qvalues = accept_langs.split(",").map { |l|
31
- l += ';q=1.0' unless l =~ /;q=\d+(?:\.\d+)?$/
32
- l.split(';q=')
33
- }
61
+ locales = header.gsub(/\s+/, '').split(",").map do |language_tag|
62
+ locale, quality = language_tag.split(/;q=/i)
63
+ quality = quality ? quality.to_f : 1.0
64
+ [locale, quality]
65
+ end.reject do |(locale, quality)|
66
+ locale == '*' || quality == 0
67
+ end.sort_by do |(_, quality)|
68
+ quality
69
+ end.map(&:first)
34
70
 
35
- lang = languages_and_qvalues.sort_by { |(locale, qvalue)|
36
- qvalue.to_f
37
- }.last.first
71
+ return if locales.empty?
72
+
73
+ if I18n.enforce_available_locales
74
+ locale = locales.reverse.find { |locale| I18n.available_locales.any? { |al| match?(al, locale) } }
75
+ matched_locale = I18n.available_locales.find { |al| match?(al, locale) } if locale
76
+ if !locale && !matched_locale
77
+ matched_locale = locales.reverse.find { |locale| I18n.available_locales.any? { |al| variant_match?(al, locale) } }
78
+ matched_locale = matched_locale[0,2] if matched_locale
79
+ end
80
+ matched_locale
81
+ else
82
+ locales.last
83
+ end
84
+ end
85
+
86
+ def match?(s1, s2)
87
+ s1.to_s.casecmp(s2.to_s) == 0
88
+ end
38
89
 
39
- lang == '*' ? nil : lang
90
+ def variant_match?(s1, s2)
91
+ s1.to_s.casecmp(s2[0,2].to_s) == 0
40
92
  end
41
93
  end
42
94
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'net/smtp'
2
4
  require 'mail'
3
5
  require 'erb'
@@ -90,7 +92,7 @@ module Rack
90
92
  mail.delivery_method :test
91
93
  elsif config[:smtp]
92
94
  smtp = config[:smtp]
93
- # for backward compability, replace the :server key with :address
95
+ # for backward compatibility, replace the :server key with :address
94
96
  address = smtp.delete :server
95
97
  smtp[:address] = address if address
96
98
  mail.delivery_method :smtp, smtp
@@ -1,5 +1,6 @@
1
- require 'cgi'
2
- require 'strscan'
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/utils'
3
4
 
4
5
  module Rack
5
6
  # Rack middleware for parsing POST/PUT body data into nested parameters
@@ -20,124 +21,14 @@ module Rack
20
21
 
21
22
  def call(env)
22
23
  if form_vars = env[FORM_VARS]
23
- env[FORM_HASH] = parse_query_parameters(form_vars)
24
+ Rack::Utils.parse_nested_query(form_vars)
24
25
  elsif env[CONTENT_TYPE] == URL_ENCODED
25
26
  post_body = env[POST_BODY]
26
27
  env[FORM_INPUT] = post_body
27
- env[FORM_HASH] = parse_query_parameters(post_body.read)
28
- post_body.rewind if post_body.respond_to?(:rewind)
28
+ env[FORM_HASH] = Rack::Utils.parse_nested_query(post_body.read)
29
+ post_body.rewind
29
30
  end
30
31
  @app.call(env)
31
32
  end
32
-
33
- ## the rest is nabbed from Rails ##
34
-
35
- def parse_query_parameters(query_string)
36
- return {} if query_string.nil? or query_string.empty?
37
-
38
- pairs = query_string.split('&').collect do |chunk|
39
- next if chunk.empty?
40
- key, value = chunk.split('=', 2)
41
- next if key.empty?
42
- value = value.nil? ? nil : CGI.unescape(value)
43
- [ CGI.unescape(key), value ]
44
- end.compact
45
-
46
- UrlEncodedPairParser.new(pairs).result
47
- end
48
-
49
- class UrlEncodedPairParser < StringScanner
50
- attr_reader :top, :parent, :result
51
-
52
- def initialize(pairs = [])
53
- super('')
54
- @result = {}
55
- pairs.each { |key, value| parse(key, value) }
56
- end
57
-
58
- KEY_REGEXP = %r{([^\[\]=&]+)}
59
- BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
60
-
61
- # Parse the query string
62
- def parse(key, value)
63
- self.string = key
64
- @top, @parent = result, nil
65
-
66
- # First scan the bare key
67
- key = scan(KEY_REGEXP) or return
68
- key = post_key_check(key)
69
-
70
- # Then scan as many nestings as present
71
- until eos?
72
- r = scan(BRACKETED_KEY_REGEXP) or return
73
- key = self[1]
74
- key = post_key_check(key)
75
- end
76
-
77
- bind(key, value)
78
- end
79
-
80
- private
81
- # After we see a key, we must look ahead to determine our next action. Cases:
82
- #
83
- # [] follows the key. Then the value must be an array.
84
- # = follows the key. (A value comes next)
85
- # & or the end of string follows the key. Then the key is a flag.
86
- # otherwise, a hash follows the key.
87
- def post_key_check(key)
88
- if scan(/\[\]/) # a[b][] indicates that b is an array
89
- container(key, Array)
90
- nil
91
- elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
92
- container(key, Hash)
93
- nil
94
- else # End of key? We do nothing.
95
- key
96
- end
97
- end
98
-
99
- # Add a container to the stack.
100
- def container(key, klass)
101
- type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
102
- value = bind(key, klass.new)
103
- type_conflict! klass, value unless value.is_a?(klass)
104
- push(value)
105
- end
106
-
107
- # Push a value onto the 'stack', which is actually only the top 2 items.
108
- def push(value)
109
- @parent, @top = @top, value
110
- end
111
-
112
- # Bind a key (which may be nil for items in an array) to the provided value.
113
- def bind(key, value)
114
- if top.is_a? Array
115
- if key
116
- if top[-1].is_a?(Hash) && ! top[-1].key?(key)
117
- top[-1][key] = value
118
- else
119
- top << {key => value}
120
- end
121
- push top.last
122
- return top[key]
123
- else
124
- top << value
125
- return value
126
- end
127
- elsif top.is_a? Hash
128
- key = CGI.unescape(key)
129
- parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
130
- top[key] ||= value
131
- return top[key]
132
- else
133
- raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
134
- end
135
- end
136
-
137
- def type_conflict!(klass, value)
138
- raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
139
- end
140
- end
141
-
142
33
  end
143
34
  end
@@ -1,18 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
- # Rack::NotFound is a default endpoint. Initialize with the path to
3
- # your 404 page.
4
+ # Rack::NotFound is a default endpoint. Optionally initialize with the
5
+ # path to a custom 404 page, to override the standard response body.
6
+ #
7
+ # Examples:
8
+ #
9
+ # Serve default 404 response:
10
+ # run Rack::NotFound.new
11
+ #
12
+ # Serve a custom 404 page:
13
+ # run Rack::NotFound.new('path/to/your/404.html')
4
14
 
5
15
  class NotFound
6
16
  F = ::File
7
17
 
8
- def initialize(path)
9
- file = F.expand_path(path)
10
- @content = F.read(file)
11
- @length = @content.size.to_s
18
+ def initialize(path = nil, content_type = 'text/html')
19
+ if path.nil?
20
+ @content = "Not found\n"
21
+ else
22
+ @content = F.read(path)
23
+ end
24
+ @length = @content.bytesize.to_s
25
+
26
+ @content_type = content_type
12
27
  end
13
28
 
14
29
  def call(env)
15
- [404, {'Content-Type' => 'text/html', 'Content-Length' => @length}, [@content]]
30
+ [404, {'content-type' => @content_type, 'content-length' => @length}, [@content]]
16
31
  end
17
32
  end
18
33
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'json'
3
5
  rescue LoadError => e
@@ -6,10 +8,51 @@ end
6
8
 
7
9
  module Rack
8
10
 
11
+ # <b>DEPRECATED:</b> <tt>JSONBodyParser</tt> is a drop-in replacement that is faster and more configurable.
12
+ #
9
13
  # A Rack middleware for parsing POST/PUT body data when Content-Type is
10
14
  # not one of the standard supported types, like <tt>application/json</tt>.
11
15
  #
12
- # TODO: Find a better name.
16
+ # === How to use the middleware
17
+ #
18
+ # Example of simple +config.ru+ file:
19
+ #
20
+ # require 'rack'
21
+ # require 'rack/contrib'
22
+ #
23
+ # use ::Rack::PostBodyContentTypeParser
24
+ #
25
+ # app = lambda do |env|
26
+ # request = Rack::Request.new(env)
27
+ # body = "Hello #{request.params['name']}"
28
+ # [200, {'Content-Type' => 'text/plain'}, [body]]
29
+ # end
30
+ #
31
+ # run app
32
+ #
33
+ # Example with passing block argument:
34
+ #
35
+ # use ::Rack::PostBodyContentTypeParser do |body|
36
+ # { 'params' => JSON.parse(body) }
37
+ # end
38
+ #
39
+ # Example with passing proc argument:
40
+ #
41
+ # parser = ->(body) { { 'params' => JSON.parse(body) } }
42
+ # use ::Rack::PostBodyContentTypeParser, &parser
43
+ #
44
+ #
45
+ # === Failed JSON parsing
46
+ #
47
+ # Returns "400 Bad request" response if invalid JSON document was sent:
48
+ #
49
+ # Raw HTTP response:
50
+ #
51
+ # HTTP/1.1 400 Bad Request
52
+ # Content-Type: text/plain
53
+ # Content-Length: 28
54
+ #
55
+ # failed to parse body as JSON
13
56
  #
14
57
  class PostBodyContentTypeParser
15
58
 
@@ -24,17 +67,24 @@ module Rack
24
67
  #
25
68
  APPLICATION_JSON = 'application/json'.freeze
26
69
 
27
- def initialize(app)
70
+ def initialize(app, &block)
71
+ warn "[DEPRECATION] `PostBodyContentTypeParser` is deprecated. Use `JSONBodyParser` as a drop-in replacement."
28
72
  @app = app
73
+ @block = block || Proc.new { |body| JSON.parse(body, :create_additions => false) }
29
74
  end
30
75
 
31
76
  def call(env)
32
77
  if Rack::Request.new(env).media_type == APPLICATION_JSON && (body = env[POST_BODY].read).length != 0
33
- env[POST_BODY].rewind # somebody might try to read this stream
34
- env.update(FORM_HASH => JSON.parse(body, :create_additions => false), FORM_INPUT => env[POST_BODY])
78
+ env[POST_BODY].rewind if env[POST_BODY].respond_to?(:rewind) # somebody might try to read this stream
79
+ env.update(FORM_HASH => @block.call(body), FORM_INPUT => env[POST_BODY])
35
80
  end
36
81
  @app.call(env)
82
+ rescue JSON::ParserError
83
+ bad_request('failed to parse body as JSON')
37
84
  end
38
85
 
86
+ def bad_request(body = 'Bad Request')
87
+ [ 400, { 'content-type' => 'text/plain', 'content-length' => body.bytesize.to_s }, [body] ]
88
+ end
39
89
  end
40
90
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  #prints the environment and request for simple debugging
3
5
  class Printout
@@ -6,7 +8,7 @@ module Rack
6
8
  end
7
9
 
8
10
  def call(env)
9
- # See http://rack.rubyforge.org/doc/SPEC.html for details
11
+ # See https://github.com/rack/rack/blob/main/SPEC.rdoc for details
10
12
  puts "**********\n Environment\n **************"
11
13
  puts env.inspect
12
14
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Middleware to update the process title ($0) with information about the
3
5
  # current request. Based loosely on: