homura-runtime 0.3.3 → 0.3.5

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/docs/ARCHITECTURE.md +2 -2
  4. data/lib/homura/runtime/ai.rb +31 -8
  5. data/lib/homura/runtime/async_registry.rb +18 -0
  6. data/lib/homura/runtime/durable_object.rb +43 -1
  7. data/lib/homura/runtime/queue.rb +12 -17
  8. data/lib/homura/runtime/version.rb +1 -1
  9. data/lib/homura/runtime.rb +103 -50
  10. data/vendor/rack/auth/abstract/handler.rb +41 -0
  11. data/vendor/rack/auth/abstract/request.rb +51 -0
  12. data/vendor/rack/auth/basic.rb +58 -0
  13. data/vendor/rack/bad_request.rb +8 -0
  14. data/vendor/rack/body_proxy.rb +63 -0
  15. data/vendor/rack/builder.rb +315 -0
  16. data/vendor/rack/cascade.rb +67 -0
  17. data/vendor/rack/common_logger.rb +94 -0
  18. data/vendor/rack/conditional_get.rb +87 -0
  19. data/vendor/rack/config.rb +22 -0
  20. data/vendor/rack/constants.rb +68 -0
  21. data/vendor/rack/content_length.rb +34 -0
  22. data/vendor/rack/content_type.rb +33 -0
  23. data/vendor/rack/deflater.rb +159 -0
  24. data/vendor/rack/directory.rb +210 -0
  25. data/vendor/rack/etag.rb +71 -0
  26. data/vendor/rack/events.rb +172 -0
  27. data/vendor/rack/files.rb +224 -0
  28. data/vendor/rack/head.rb +25 -0
  29. data/vendor/rack/headers.rb +238 -0
  30. data/vendor/rack/lint.rb +1000 -0
  31. data/vendor/rack/lock.rb +29 -0
  32. data/vendor/rack/media_type.rb +42 -0
  33. data/vendor/rack/method_override.rb +56 -0
  34. data/vendor/rack/mime.rb +694 -0
  35. data/vendor/rack/mock.rb +3 -0
  36. data/vendor/rack/mock_request.rb +161 -0
  37. data/vendor/rack/mock_response.rb +147 -0
  38. data/vendor/rack/multipart/generator.rb +99 -0
  39. data/vendor/rack/multipart/parser.rb +586 -0
  40. data/vendor/rack/multipart/uploaded_file.rb +82 -0
  41. data/vendor/rack/multipart.rb +77 -0
  42. data/vendor/rack/null_logger.rb +48 -0
  43. data/vendor/rack/protection/authenticity_token.rb +256 -0
  44. data/vendor/rack/protection/base.rb +140 -0
  45. data/vendor/rack/protection/content_security_policy.rb +80 -0
  46. data/vendor/rack/protection/cookie_tossing.rb +77 -0
  47. data/vendor/rack/protection/escaped_params.rb +93 -0
  48. data/vendor/rack/protection/form_token.rb +25 -0
  49. data/vendor/rack/protection/frame_options.rb +39 -0
  50. data/vendor/rack/protection/http_origin.rb +43 -0
  51. data/vendor/rack/protection/ip_spoofing.rb +27 -0
  52. data/vendor/rack/protection/json_csrf.rb +60 -0
  53. data/vendor/rack/protection/path_traversal.rb +45 -0
  54. data/vendor/rack/protection/referrer_policy.rb +27 -0
  55. data/vendor/rack/protection/remote_referrer.rb +22 -0
  56. data/vendor/rack/protection/remote_token.rb +24 -0
  57. data/vendor/rack/protection/session_hijacking.rb +37 -0
  58. data/vendor/rack/protection/strict_transport.rb +41 -0
  59. data/vendor/rack/protection/version.rb +7 -0
  60. data/vendor/rack/protection/xss_header.rb +27 -0
  61. data/vendor/rack/protection.rb +58 -0
  62. data/vendor/rack/query_parser.rb +261 -0
  63. data/vendor/rack/recursive.rb +66 -0
  64. data/vendor/rack/reloader.rb +112 -0
  65. data/vendor/rack/request.rb +818 -0
  66. data/vendor/rack/response.rb +403 -0
  67. data/vendor/rack/rewindable_input.rb +116 -0
  68. data/vendor/rack/runtime.rb +35 -0
  69. data/vendor/rack/sendfile.rb +197 -0
  70. data/vendor/rack/session/abstract/id.rb +533 -0
  71. data/vendor/rack/session/constants.rb +13 -0
  72. data/vendor/rack/session/cookie.rb +292 -0
  73. data/vendor/rack/session/encryptor.rb +415 -0
  74. data/vendor/rack/session/pool.rb +76 -0
  75. data/vendor/rack/session/version.rb +10 -0
  76. data/vendor/rack/session.rb +12 -0
  77. data/vendor/rack/show_exceptions.rb +433 -0
  78. data/vendor/rack/show_status.rb +121 -0
  79. data/vendor/rack/static.rb +188 -0
  80. data/vendor/rack/tempfile_reaper.rb +44 -0
  81. data/vendor/rack/urlmap.rb +99 -0
  82. data/vendor/rack/utils.rb +631 -0
  83. data/vendor/rack/version.rb +17 -0
  84. data/vendor/rack.rb +66 -0
  85. metadata +76 -1
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'body_proxy'
4
+ require_relative 'request'
5
+ require_relative 'response'
6
+
7
+ module Rack
8
+ ### This middleware provides hooks to certain places in the request /
9
+ # response lifecycle. This is so that middleware that don't need to filter
10
+ # the response data can safely leave it alone and not have to send messages
11
+ # down the traditional "rack stack".
12
+ #
13
+ # The events are:
14
+ #
15
+ # * on_start(request, response)
16
+ #
17
+ # This event is sent at the start of the request, before the next
18
+ # middleware in the chain is called. This method is called with a request
19
+ # object, and a response object. Right now, the response object is always
20
+ # nil, but in the future it may actually be a real response object.
21
+ #
22
+ # * on_commit(request, response)
23
+ #
24
+ # The response has been committed. The application has returned, but the
25
+ # response has not been sent to the webserver yet. This method is always
26
+ # called with a request object and the response object. The response
27
+ # object is constructed from the rack triple that the application returned.
28
+ # Changes may still be made to the response object at this point.
29
+ #
30
+ # * on_send(request, response)
31
+ #
32
+ # The webserver has started iterating over the response body, or has called
33
+ # the streaming body, and presumably has started sending data over the
34
+ # wire. This method is always called with a request object and the response
35
+ # object. The response object is constructed from the rack triple that the
36
+ # application returned. Changes SHOULD NOT be made to the response object
37
+ # as the webserver has already started sending data. Any mutations will
38
+ # likely result in an exception.
39
+ #
40
+ # * on_finish(request, response)
41
+ #
42
+ # The webserver has closed the response, and all data has been written to
43
+ # the response socket. The request and response object should both be
44
+ # read-only at this point. The body MAY NOT be available on the response
45
+ # object as it may have been flushed to the socket.
46
+ #
47
+ # * on_error(request, response, error)
48
+ #
49
+ # An exception has occurred in the application or an `on_commit` event.
50
+ # This method will get the request, the response (if available) and the
51
+ # exception that was raised.
52
+ #
53
+ # ## Order
54
+ #
55
+ # `on_start` is called on the handlers in the order that they were passed to
56
+ # the constructor. `on_commit`, on_send`, `on_finish`, and `on_error` are
57
+ # called in the reverse order. `on_finish` handlers are called inside an
58
+ # `ensure` block, so they are guaranteed to be called even if something
59
+ # raises an exception. If something raises an exception in a `on_finish`
60
+ # method, then nothing is guaranteed.
61
+
62
+ class Events
63
+ module Abstract
64
+ def on_start(req, res)
65
+ end
66
+
67
+ def on_commit(req, res)
68
+ end
69
+
70
+ def on_send(req, res)
71
+ end
72
+
73
+ def on_finish(req, res)
74
+ end
75
+
76
+ def on_error(req, res, e)
77
+ end
78
+ end
79
+
80
+ class EventedBodyProxy < Rack::BodyProxy # :nodoc:
81
+ attr_reader :request, :response
82
+
83
+ def initialize(body, request, response, handlers, &block)
84
+ super(body, &block)
85
+ @request = request
86
+ @response = response
87
+ @handlers = handlers
88
+ end
89
+
90
+ def each
91
+ @handlers.reverse_each { |handler| handler.on_send request, response }
92
+ super
93
+ end
94
+
95
+ def call(stream)
96
+ @handlers.reverse_each { |handler| handler.on_send request, response }
97
+ super
98
+ end
99
+
100
+ def respond_to?(method_name, include_all = false)
101
+ case method_name
102
+ when :each, :call
103
+ @body.respond_to?(method_name, include_all)
104
+ else
105
+ super
106
+ end
107
+ end
108
+ end
109
+
110
+ class BufferedResponse < Rack::Response::Raw # :nodoc:
111
+ attr_reader :body
112
+
113
+ def initialize(status, headers, body)
114
+ super(status, headers)
115
+ @body = body
116
+ end
117
+
118
+ def to_a; [status, headers, body]; end
119
+ end
120
+
121
+ def initialize(app, handlers)
122
+ @app = app
123
+ @handlers = handlers
124
+ end
125
+
126
+ def call(env)
127
+ request = make_request env
128
+ on_start request, nil
129
+
130
+ begin
131
+ status, headers, body = @app.call request.env
132
+ response = make_response status, headers, body
133
+ on_commit request, response
134
+ rescue StandardError => e
135
+ on_error request, response, e
136
+ on_finish request, response
137
+ raise
138
+ end
139
+
140
+ body = EventedBodyProxy.new(body, request, response, @handlers) do
141
+ on_finish request, response
142
+ end
143
+ [response.status, response.headers, body]
144
+ end
145
+
146
+ private
147
+
148
+ def on_error(request, response, e)
149
+ @handlers.reverse_each { |handler| handler.on_error request, response, e }
150
+ end
151
+
152
+ def on_commit(request, response)
153
+ @handlers.reverse_each { |handler| handler.on_commit request, response }
154
+ end
155
+
156
+ def on_start(request, response)
157
+ @handlers.each { |handler| handler.on_start request, nil }
158
+ end
159
+
160
+ def on_finish(request, response)
161
+ @handlers.reverse_each { |handler| handler.on_finish request, response }
162
+ end
163
+
164
+ def make_request(env)
165
+ Rack::Request.new env
166
+ end
167
+
168
+ def make_response(status, headers, body)
169
+ BufferedResponse.new status, headers, body
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ require_relative 'constants'
6
+ require_relative 'head'
7
+ require_relative 'utils'
8
+ require_relative 'request'
9
+ require_relative 'mime'
10
+
11
+ module Rack
12
+ # Rack::Files serves files below the +root+ directory given, according to the
13
+ # path info of the Rack request.
14
+ # e.g. when Rack::Files.new("/etc") is used, you can access 'passwd' file
15
+ # as http://localhost:9292/passwd
16
+ #
17
+ # Handlers can detect if bodies are a Rack::Files, and use mechanisms
18
+ # like sendfile on the +path+.
19
+ #
20
+ # Be aware that just like the default behavior of most webservers, Rack::Files
21
+ # will follow symbolic links encountered under the root. If a symlink points to
22
+ # a location outside of the root, that target will still be served as part of
23
+ # the response.
24
+
25
+ class Files
26
+ ALLOWED_VERBS = %w[GET HEAD OPTIONS]
27
+ ALLOW_HEADER = ALLOWED_VERBS.join(', ')
28
+ MULTIPART_BOUNDARY = 'AaB03x'
29
+
30
+ attr_reader :root
31
+
32
+ def initialize(root, headers = {}, default_mime = 'text/plain')
33
+ @root = (::File.expand_path(root) if root)
34
+ @headers = headers
35
+ @default_mime = default_mime
36
+ @head = Rack::Head.new(lambda { |env| get env })
37
+ end
38
+
39
+ def call(env)
40
+ # HEAD requests drop the response body, including 4xx error messages.
41
+ @head.call env
42
+ end
43
+
44
+ def get(env)
45
+ request = Rack::Request.new env
46
+ unless ALLOWED_VERBS.include? request.request_method
47
+ return fail(405, "Method Not Allowed", { 'allow' => ALLOW_HEADER })
48
+ end
49
+
50
+ path_info = Utils.unescape_path request.path_info
51
+ return fail(400, "Bad Request") unless Utils.valid_path?(path_info)
52
+
53
+ clean_path_info = Utils.clean_path_info(path_info)
54
+ path = ::File.join(@root, clean_path_info)
55
+
56
+ available = begin
57
+ ::File.file?(path) && ::File.readable?(path)
58
+ rescue SystemCallError
59
+ # Not sure in what conditions this exception can occur, but this
60
+ # is a safe way to handle such an error.
61
+ # :nocov:
62
+ false
63
+ # :nocov:
64
+ end
65
+
66
+ if available
67
+ serving(request, path)
68
+ else
69
+ fail(404, "File not found: #{path_info}")
70
+ end
71
+ end
72
+
73
+ def serving(request, path)
74
+ if request.options?
75
+ return [200, { 'allow' => ALLOW_HEADER, CONTENT_LENGTH => '0' }, []]
76
+ end
77
+ last_modified = ::File.mtime(path).httpdate
78
+ return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified
79
+
80
+ headers = { "last-modified" => last_modified }
81
+ mime_type = mime_type path, @default_mime
82
+ headers[CONTENT_TYPE] = mime_type if mime_type
83
+
84
+ assign_headers(headers, request)
85
+
86
+ status = 200
87
+ size = filesize path
88
+
89
+ ranges = Rack::Utils.get_byte_ranges(request.get_header('HTTP_RANGE'), size)
90
+ if ranges.nil?
91
+ # No ranges:
92
+ ranges = [0..size - 1]
93
+ elsif ranges.empty?
94
+ # Unsatisfiable. Return error, and file size:
95
+ response = fail(416, "Byte range unsatisfiable")
96
+ response[1]["content-range"] = "bytes */#{size}"
97
+ return response
98
+ else
99
+ # Partial content
100
+ partial_content = true
101
+
102
+ if ranges.size == 1
103
+ range = ranges[0]
104
+ headers["content-range"] = "bytes #{range.begin}-#{range.end}/#{size}"
105
+ else
106
+ headers[CONTENT_TYPE] = "multipart/byteranges; boundary=#{MULTIPART_BOUNDARY}"
107
+ end
108
+
109
+ status = 206
110
+ body = BaseIterator.new(path, ranges, mime_type: mime_type, size: size)
111
+ size = body.bytesize
112
+ end
113
+
114
+ headers[CONTENT_LENGTH] = size.to_s
115
+
116
+ if request.head?
117
+ body = []
118
+ elsif !partial_content
119
+ body = Iterator.new(path, ranges, mime_type: mime_type, size: size)
120
+ end
121
+
122
+ [status, headers, body]
123
+ end
124
+
125
+ def assign_headers(headers, request)
126
+ headers.merge!(@headers) if @headers
127
+ end
128
+
129
+ class BaseIterator
130
+ attr_reader :path, :ranges, :options
131
+
132
+ def initialize(path, ranges, options)
133
+ @path = path
134
+ @ranges = ranges
135
+ @options = options
136
+ end
137
+
138
+ def each
139
+ ::File.open(path, "rb") do |file|
140
+ ranges.each do |range|
141
+ yield multipart_heading(range) if multipart?
142
+
143
+ each_range_part(file, range) do |part|
144
+ yield part
145
+ end
146
+ end
147
+
148
+ yield "\r\n--#{MULTIPART_BOUNDARY}--\r\n" if multipart?
149
+ end
150
+ end
151
+
152
+ def bytesize
153
+ size = ranges.inject(0) do |sum, range|
154
+ sum += multipart_heading(range).bytesize if multipart?
155
+ sum += range.size
156
+ end
157
+ size += "\r\n--#{MULTIPART_BOUNDARY}--\r\n".bytesize if multipart?
158
+ size
159
+ end
160
+
161
+ def close; end
162
+
163
+ private
164
+
165
+ def multipart?
166
+ ranges.size > 1
167
+ end
168
+
169
+ def multipart_heading(range)
170
+ <<-EOF
171
+ \r
172
+ --#{MULTIPART_BOUNDARY}\r
173
+ content-type: #{options[:mime_type]}\r
174
+ content-range: bytes #{range.begin}-#{range.end}/#{options[:size]}\r
175
+ \r
176
+ EOF
177
+ end
178
+
179
+ def each_range_part(file, range)
180
+ file.seek(range.begin)
181
+ remaining_len = range.end - range.begin + 1
182
+ while remaining_len > 0
183
+ part = file.read([8192, remaining_len].min)
184
+ break unless part
185
+ remaining_len -= part.length
186
+
187
+ yield part
188
+ end
189
+ end
190
+ end
191
+
192
+ class Iterator < BaseIterator
193
+ alias :to_path :path
194
+ end
195
+
196
+ private
197
+
198
+ def fail(status, body, headers = {})
199
+ body += "\n"
200
+
201
+ [
202
+ status,
203
+ {
204
+ CONTENT_TYPE => "text/plain",
205
+ CONTENT_LENGTH => body.size.to_s,
206
+ "x-cascade" => "pass"
207
+ }.merge!(headers),
208
+ [body]
209
+ ]
210
+ end
211
+
212
+ # The MIME type for the contents of the file located at @path
213
+ def mime_type(path, default_mime)
214
+ Mime.mime_type(::File.extname(path), default_mime)
215
+ end
216
+
217
+ def filesize(path)
218
+ # We check via File::size? whether this file provides size info
219
+ # via stat (e.g. /proc files often don't), otherwise we have to
220
+ # figure it out by reading the whole file into memory.
221
+ ::File.size?(path) || ::File.read(path).bytesize
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'constants'
4
+ require_relative 'body_proxy'
5
+
6
+ module Rack
7
+ # Rack::Head returns an empty body for all HEAD requests. It leaves
8
+ # all other requests unchanged.
9
+ class Head
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ _, _, body = response = @app.call(env)
16
+
17
+ if env[REQUEST_METHOD] == HEAD
18
+ body.close if body.respond_to?(:close)
19
+ response[2] = []
20
+ end
21
+
22
+ response
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ # Rack::Headers is a Hash subclass that downcases all keys. It's designed
5
+ # to be used by rack applications that don't implement the Rack 3 SPEC
6
+ # (by using non-lowercase response header keys), automatically handling
7
+ # the downcasing of keys.
8
+ class Headers < Hash
9
+ KNOWN_HEADERS = {}
10
+ %w(
11
+ Accept-CH
12
+ Accept-Patch
13
+ Accept-Ranges
14
+ Access-Control-Allow-Credentials
15
+ Access-Control-Allow-Headers
16
+ Access-Control-Allow-Methods
17
+ Access-Control-Allow-Origin
18
+ Access-Control-Expose-Headers
19
+ Access-Control-Max-Age
20
+ Age
21
+ Allow
22
+ Alt-Svc
23
+ Cache-Control
24
+ Connection
25
+ Content-Disposition
26
+ Content-Encoding
27
+ Content-Language
28
+ Content-Length
29
+ Content-Location
30
+ Content-MD5
31
+ Content-Range
32
+ Content-Security-Policy
33
+ Content-Security-Policy-Report-Only
34
+ Content-Type
35
+ Date
36
+ Delta-Base
37
+ ETag
38
+ Expect-CT
39
+ Expires
40
+ Feature-Policy
41
+ IM
42
+ Last-Modified
43
+ Link
44
+ Location
45
+ NEL
46
+ P3P
47
+ Permissions-Policy
48
+ Pragma
49
+ Preference-Applied
50
+ Proxy-Authenticate
51
+ Public-Key-Pins
52
+ Referrer-Policy
53
+ Refresh
54
+ Report-To
55
+ Retry-After
56
+ Server
57
+ Set-Cookie
58
+ Status
59
+ Strict-Transport-Security
60
+ Timing-Allow-Origin
61
+ Tk
62
+ Trailer
63
+ Transfer-Encoding
64
+ Upgrade
65
+ Vary
66
+ Via
67
+ WWW-Authenticate
68
+ Warning
69
+ X-Cascade
70
+ X-Content-Duration
71
+ X-Content-Security-Policy
72
+ X-Content-Type-Options
73
+ X-Correlation-ID
74
+ X-Correlation-Id
75
+ X-Download-Options
76
+ X-Frame-Options
77
+ X-Permitted-Cross-Domain-Policies
78
+ X-Powered-By
79
+ X-Redirect-By
80
+ X-Request-ID
81
+ X-Request-Id
82
+ X-Runtime
83
+ X-UA-Compatible
84
+ X-WebKit-CS
85
+ X-XSS-Protection
86
+ ).each do |str|
87
+ downcased = str.downcase.freeze
88
+ KNOWN_HEADERS[str] = KNOWN_HEADERS[downcased] = downcased
89
+ end
90
+
91
+ def self.[](*items)
92
+ if items.length % 2 != 0
93
+ if items.length == 1 && items.first.is_a?(Hash)
94
+ new.merge!(items.first)
95
+ else
96
+ raise ArgumentError, "odd number of arguments for Rack::Headers"
97
+ end
98
+ else
99
+ hash = new
100
+ loop do
101
+ break if items.length == 0
102
+ key = items.shift
103
+ value = items.shift
104
+ hash[key] = value
105
+ end
106
+ hash
107
+ end
108
+ end
109
+
110
+ def [](key)
111
+ super(downcase_key(key))
112
+ end
113
+
114
+ def []=(key, value)
115
+ super(KNOWN_HEADERS[key] || key.downcase.freeze, value)
116
+ end
117
+ alias store []=
118
+
119
+ def assoc(key)
120
+ super(downcase_key(key))
121
+ end
122
+
123
+ def compare_by_identity
124
+ raise TypeError, "Rack::Headers cannot compare by identity, use regular Hash"
125
+ end
126
+
127
+ def delete(key)
128
+ super(downcase_key(key))
129
+ end
130
+
131
+ def dig(key, *a)
132
+ super(downcase_key(key), *a)
133
+ end
134
+
135
+ def fetch(key, *default, &block)
136
+ key = downcase_key(key)
137
+ super
138
+ end
139
+
140
+ def fetch_values(*a)
141
+ super(*a.map!{|key| downcase_key(key)})
142
+ end
143
+
144
+ def has_key?(key)
145
+ super(downcase_key(key))
146
+ end
147
+ alias include? has_key?
148
+ alias key? has_key?
149
+ alias member? has_key?
150
+
151
+ def invert
152
+ hash = self.class.new
153
+ each{|key, value| hash[value] = key}
154
+ hash
155
+ end
156
+
157
+ def merge(hash, &block)
158
+ dup.merge!(hash, &block)
159
+ end
160
+
161
+ def reject(&block)
162
+ hash = dup
163
+ hash.reject!(&block)
164
+ hash
165
+ end
166
+
167
+ def replace(hash)
168
+ clear
169
+ update(hash)
170
+ end
171
+
172
+ def select(&block)
173
+ hash = dup
174
+ hash.select!(&block)
175
+ hash
176
+ end
177
+
178
+ def to_proc
179
+ lambda{|x| self[x]}
180
+ end
181
+
182
+ def transform_values(&block)
183
+ dup.transform_values!(&block)
184
+ end
185
+
186
+ def update(hash, &block)
187
+ hash.each do |key, value|
188
+ self[key] = if block_given? && include?(key)
189
+ block.call(key, self[key], value)
190
+ else
191
+ value
192
+ end
193
+ end
194
+ self
195
+ end
196
+ alias merge! update
197
+
198
+ def values_at(*keys)
199
+ keys.map{|key| self[key]}
200
+ end
201
+
202
+ # :nocov:
203
+ if RUBY_VERSION >= '2.5'
204
+ # :nocov:
205
+ def slice(*a)
206
+ h = self.class.new
207
+ a.each{|k| h[k] = self[k] if has_key?(k)}
208
+ h
209
+ end
210
+
211
+ def transform_keys(&block)
212
+ dup.transform_keys!(&block)
213
+ end
214
+
215
+ def transform_keys!
216
+ hash = self.class.new
217
+ each do |k, v|
218
+ hash[yield k] = v
219
+ end
220
+ replace(hash)
221
+ end
222
+ end
223
+
224
+ # :nocov:
225
+ if RUBY_VERSION >= '3.0'
226
+ # :nocov:
227
+ def except(*a)
228
+ super(*a.map!{|key| downcase_key(key)})
229
+ end
230
+ end
231
+
232
+ private
233
+
234
+ def downcase_key(key)
235
+ key.is_a?(String) ? KNOWN_HEADERS[key] || key.downcase : key
236
+ end
237
+ end
238
+ end