async-http 0.10.0 → 0.12.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c74bbff1b6216d57e8d60e5a54700f9461cef5aff2436db4abbf07c300a1af09
4
- data.tar.gz: 1354a61e69f69706688f112fef4c958af285bec43172f00d7993bff7fcb9dc7b
3
+ metadata.gz: 9f76d14d623f95c9f57dc60a00e2200e05214149eeb2625ff632d05492981183
4
+ data.tar.gz: e9b9191121ca54563cae6090a28725bf00029eec28e914972f6e36d24b67b082
5
5
  SHA512:
6
- metadata.gz: d62959c37bc323c82d57ca4e745ef4d2a715f3ec0e580e0d4a1e5cbdb65766f6b4c671efab4f8e85fb0fabe6a5cac99be1de1aa2cc92c30bc39e5e51f9f40d51
7
- data.tar.gz: 75b83edd5bd767e638ebeacbd3446e317b24b49eba17628af95e32e78690cc0b26c8c998ed1aaed7248c8e796c7e77fd43a8c80f391fcc5876a8cd84533bdd1d
6
+ metadata.gz: bcdeb04bb6666ed1ccbd04cf40ffa01b37e2c8e1899c109fdb654e9bb8eb5b88a150ecd1ddbd53500eff5915e505e2e54bff6f68566a4f15324a5d6cecfa1179
7
+ data.tar.gz: 287f239b0e75d139c1e93f701365e7062d7e90a6c21912dd4b9d27b5d8c1211a12efcb349c0cf32de15ce04bee890a98efaf0d90d9e125415ec2eb1d6991d255
@@ -11,6 +11,7 @@ addons:
11
11
 
12
12
  before_script:
13
13
  - gem update --system
14
+ - gem install bundler
14
15
 
15
16
  matrix:
16
17
  include:
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
16
16
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
17
  spec.require_paths = ["lib"]
18
18
 
19
- spec.add_dependency("async", "~> 1.4")
19
+ spec.add_dependency("async", "~> 1.5")
20
20
  spec.add_dependency("async-io", "~> 1.6")
21
21
 
22
22
  spec.add_dependency("http-2", "~> 0.8")
@@ -0,0 +1,139 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'async/queue'
22
+
23
+ module Async
24
+ module HTTP
25
+ class Body < Async::Queue
26
+ def initialize
27
+ super
28
+
29
+ @closed = false
30
+ end
31
+
32
+ def closed?
33
+ @closed
34
+ end
35
+
36
+ def each
37
+ return if @closed
38
+
39
+ while chunk = self.dequeue
40
+ yield chunk
41
+ end
42
+
43
+ @closed = true
44
+ end
45
+
46
+ def read
47
+ buffer = Async::IO::BinaryString.new
48
+
49
+ self.each do |chunk|
50
+ buffer << chunk
51
+ end
52
+
53
+ return buffer
54
+ end
55
+
56
+ alias join read
57
+
58
+ def write(chunk)
59
+ self.enqueue(chunk)
60
+ end
61
+
62
+ def close
63
+ self.enqueue(nil)
64
+ end
65
+ end
66
+
67
+ class BufferedBody
68
+ def initialize(body)
69
+ @chunks = []
70
+
71
+ body.each do |chunk|
72
+ @chunks << chunk
73
+ end
74
+ end
75
+
76
+ def each(&block)
77
+ @chunks.each(&block)
78
+ end
79
+
80
+ def read
81
+ @buffer ||= @chunks.join
82
+ end
83
+
84
+ alias join read
85
+
86
+ def closed?
87
+ true
88
+ end
89
+
90
+ module Reader
91
+ def read
92
+ self.body ? self.body.read : nil
93
+ end
94
+
95
+ def close
96
+ return if self.body.nil? or self.body.closed?
97
+
98
+ unless self.body.is_a? BufferedBody
99
+ self.body = BufferedBody.new(self.body)
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ class FixedBody
106
+ CHUNK_LENGTH = 1024*1024
107
+
108
+ def initialize(length, stream)
109
+ @length = length
110
+ @remaining = length
111
+ @stream = stream
112
+ end
113
+
114
+ def closed?
115
+ @remaining == 0
116
+ end
117
+
118
+ def each
119
+ while @remaining > 0
120
+ if chunk = @stream.read(CHUNK_LENGTH)
121
+ @remaining -= chunk.bytesize
122
+
123
+ yield chunk
124
+ end
125
+ end
126
+ end
127
+
128
+ def read
129
+ buffer = @stream.read(@remaining)
130
+
131
+ @remaining = 0
132
+
133
+ return buffer
134
+ end
135
+
136
+ alias join read
137
+ end
138
+ end
139
+ end
@@ -57,14 +57,23 @@ module Async
57
57
  VERBS = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']
58
58
 
59
59
  VERBS.each do |verb|
60
- define_method(verb.downcase) do |*args|
61
- self.request(verb, *args)
60
+ define_method(verb.downcase) do |*args, &block|
61
+ self.request(verb, *args, &block)
62
62
  end
63
63
  end
64
64
 
65
- def request(*args)
65
+ def request(*args, &block)
66
66
  @connections.acquire do |connection|
67
- connection.send_request(@authority, *args)
67
+ response = connection.send_request(@authority, *args)
68
+
69
+ begin
70
+ yield response if block_given?
71
+ ensure
72
+ # This forces the stream to complete reading.
73
+ response.close
74
+ end
75
+
76
+ return response
68
77
  end
69
78
  end
70
79
 
@@ -20,9 +20,6 @@
20
20
 
21
21
  require_relative 'http11'
22
22
 
23
- require_relative 'request'
24
- require_relative 'response'
25
-
26
23
  module Async
27
24
  module HTTP
28
25
  module Protocol
@@ -35,7 +32,7 @@ module Async
35
32
  end
36
33
 
37
34
  def keep_alive?(headers)
38
- headers[:connection] == KEEP_ALIVE
35
+ headers['connection'] == KEEP_ALIVE
39
36
  end
40
37
 
41
38
  # Server loop.
@@ -64,7 +61,7 @@ module Async
64
61
  end
65
62
 
66
63
  def read_body(headers)
67
- if content_length = headers[:content_length]
64
+ if content_length = headers['content-length']
68
65
  return @stream.read(Integer(content_length))
69
66
  # elsif !keep_alive?(headers)
70
67
  # return @stream.read
@@ -20,18 +20,14 @@
20
20
 
21
21
  require 'async/io/protocol/line'
22
22
 
23
- require_relative 'request'
24
- require_relative 'response'
25
- require_relative '../headers'
23
+ require_relative '../request'
24
+ require_relative '../response'
26
25
 
27
26
  module Async
28
27
  module HTTP
29
28
  module Protocol
30
29
  # Implements basic HTTP/1.1 request/response.
31
30
  class HTTP11 < Async::IO::Protocol::Line
32
- CONTENT_LENGTH = Headers['Content-Length']
33
- TRANSFER_ENCODING = Headers['Transfer-Encoding']
34
-
35
31
  CRLF = "\r\n".freeze
36
32
 
37
33
  def initialize(stream)
@@ -64,7 +60,7 @@ module Async
64
60
  end
65
61
 
66
62
  def keep_alive?(headers)
67
- headers[:connection] != CLOSE
63
+ headers['connection'] != CLOSE
68
64
  end
69
65
 
70
66
  # Server loop.
@@ -125,7 +121,7 @@ module Async
125
121
  headers = read_headers
126
122
  body = read_body(headers)
127
123
 
128
- return headers.delete(:host), method, path, version, headers, body
124
+ return headers.delete('host'), method, path, version, headers, body
129
125
  end
130
126
 
131
127
  def write_response(version, status, headers, body)
@@ -146,11 +142,11 @@ module Async
146
142
  end
147
143
  end
148
144
 
149
- def read_headers(headers = Headers.new)
145
+ def read_headers(headers = {})
150
146
  # Parsing headers:
151
147
  each_line do |line|
152
148
  if line =~ /^([a-zA-Z\-]+):\s*(.+?)\s*$/
153
- headers[$1] = $2
149
+ headers[$1.downcase] = $2
154
150
  else
155
151
  break
156
152
  end
@@ -169,6 +165,7 @@ module Async
169
165
  @stream.write("#{chunk.bytesize.to_s(16).upcase}\r\n")
170
166
  @stream.write(chunk)
171
167
  @stream.write(CRLF)
168
+ @stream.flush
172
169
  end
173
170
 
174
171
  @stream.write("0\r\n\r\n")
@@ -176,31 +173,56 @@ module Async
176
173
  buffer = String.new
177
174
  body.each{|chunk| buffer << chunk}
178
175
 
179
- @stream.write("Content-Length: #{chunk.bytesize}\r\n\r\n")
180
- @stream.write(chunk)
176
+ @stream.write("Content-Length: #{buffer.bytesize}\r\n\r\n")
177
+ @stream.write(buffer)
181
178
  end
182
179
  end
183
180
 
184
- def read_body(headers)
185
- if headers[:transfer_encoding] == 'chunked'
186
- buffer = Async::IO::BinaryString.new
181
+ class ChunkedBody
182
+ def initialize(protocol)
183
+ @protocol = protocol
184
+ @closed = false
185
+ end
186
+
187
+ def closed?
188
+ @closed
189
+ end
190
+
191
+ def each
192
+ return if @closed
187
193
 
188
194
  while true
189
- size = read_line.to_i(16)
195
+ size = @protocol.read_line.to_i(16)
190
196
 
191
197
  if size == 0
192
- read_line
193
- break
198
+ @closed = true
199
+ @protocol.read_line
200
+
201
+ return
194
202
  end
195
203
 
196
- buffer << @stream.read(size)
204
+ yield @protocol.stream.read(size)
197
205
 
198
- read_line # Consume the trailing CRLF
206
+ @protocol.read_line # Consume the trailing CRLF
207
+ end
208
+ end
209
+
210
+ def read
211
+ buffer = Async::IO::BinaryString.new
212
+
213
+ self.each do |chunk|
214
+ buffer << chunk
199
215
  end
200
216
 
201
217
  return buffer
202
- elsif content_length = headers[:content_length]
203
- return @stream.read(Integer(content_length))
218
+ end
219
+ end
220
+
221
+ def read_body(headers)
222
+ if headers['transfer-encoding'] == 'chunked'
223
+ return ChunkedBody.new(self)
224
+ elsif content_length = headers['content-length']
225
+ return FixedBody.new(Integer(content_length), @stream)
204
226
  end
205
227
  end
206
228
  end
@@ -18,9 +18,8 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require_relative 'request'
22
- require_relative 'response'
23
- require_relative '../headers'
21
+ require_relative '../request'
22
+ require_relative '../response'
24
23
 
25
24
  require 'async/notification'
26
25
 
@@ -46,6 +45,7 @@ module Async
46
45
  AUTHORITY = ':authority'.freeze
47
46
  REASON = ':reason'.freeze
48
47
  STATUS = ':status'.freeze
48
+ VERSION = 'HTTP/2.0'.freeze
49
49
 
50
50
  def initialize(controller, stream)
51
51
  @controller = controller
@@ -56,18 +56,19 @@ module Async
56
56
  @stream.flush
57
57
  end
58
58
 
59
- # @controller.on(:frame_sent) do |frame|
60
- # Async.logger.debug(self) {"Sent frame: #{frame.inspect}"}
61
- # end
62
- #
63
- # @controller.on(:frame_received) do |frame|
64
- # Async.logger.debug(self) {"Received frame: #{frame.inspect}"}
65
- # end
59
+ @controller.on(:frame_sent) do |frame|
60
+ Async.logger.debug(self) {"Sent frame: #{frame.inspect}"}
61
+ end
62
+
63
+ @controller.on(:frame_received) do |frame|
64
+ Async.logger.debug(self) {"Received frame: #{frame.inspect}"}
65
+ end
66
66
 
67
67
  if @controller.is_a? ::HTTP2::Client
68
68
  @controller.send_connection_preface
69
- @reader = read_in_background
70
69
  end
70
+
71
+ @reader = read_in_background
71
72
  end
72
73
 
73
74
  # Multiple requests can be processed at the same time.
@@ -97,15 +98,13 @@ module Async
97
98
  @stream.close
98
99
  end
99
100
 
100
- def receive_requests(&block)
101
+ def receive_requests(task: Task.current, &block)
101
102
  # emits new streams opened by the client
102
103
  @controller.on(:stream) do |stream|
103
104
  request = Request.new
104
- request.version = "HTTP/2.0"
105
- request.headers = Headers.new
106
-
107
- # stream.on(:active) { } # fires when stream transitions to open state
108
- # stream.on(:close) { } # stream is closed by client and server
105
+ request.version = VERSION
106
+ request.headers = {}
107
+ request.body = Body.new
109
108
 
110
109
  stream.on(:headers) do |headers|
111
110
  headers.each do |key, value|
@@ -121,17 +120,20 @@ module Async
121
120
  end
122
121
  end
123
122
 
124
- stream.on(:data) do |body|
125
- request.body = body
123
+ stream.on(:data) do |chunk|
124
+ request.body.write(chunk.to_s) unless chunk.empty?
126
125
  end
127
126
 
128
127
  stream.on(:half_close) do
129
128
  response = yield request
130
129
 
130
+ request.body.close
131
+
131
132
  # send response
132
- stream.headers(STATUS => response[0].to_s)
133
+ headers = {STATUS => response[0].to_s}
134
+ headers.update(response[1])
133
135
 
134
- stream.headers(response[1]) unless response[1].empty?
136
+ stream.headers(headers, end_stream: false)
135
137
 
136
138
  response[2].each do |chunk|
137
139
  stream.data(chunk, end_stream: false)
@@ -141,9 +143,7 @@ module Async
141
143
  end
142
144
  end
143
145
 
144
- while data = @stream.io.read(1024)
145
- @controller << data
146
- end
146
+ @reader.wait
147
147
  end
148
148
 
149
149
  RESPONSE_VERSION = 'HTTP/2'.freeze
@@ -158,20 +158,22 @@ module Async
158
158
  AUTHORITY => authority,
159
159
  }.merge(headers)
160
160
 
161
- stream.headers(internal_headers, end_stream: true)
161
+ stream.headers(internal_headers, end_stream: body.nil?)
162
162
 
163
- # if body
164
- # body.each do |chunk|
165
- # stream.data(chunk, end_stream: false)
166
- # end
167
- #
168
- # stream.data("", end_stream: true)
169
- # end
163
+ if body
164
+ body.each do |chunk|
165
+ stream.data(chunk, end_stream: false)
166
+ end
167
+
168
+ stream.data("", end_stream: true)
169
+ end
170
+
171
+ finished = Async::Notification.new
170
172
 
171
173
  response = Response.new
172
174
  response.version = RESPONSE_VERSION
173
- response.headers = Headers.new
174
- response.body = Async::IO::BinaryString.new
175
+ response.headers = {}
176
+ response.body = Body.new
175
177
 
176
178
  stream.on(:headers) do |headers|
177
179
  # Async.logger.debug(self) {"Stream headers: #{headers.inspect}"}
@@ -185,22 +187,22 @@ module Async
185
187
  response.headers[key] = value
186
188
  end
187
189
  end
190
+
191
+ finished.signal
188
192
  end
189
193
 
190
- stream.on(:data) do |body|
191
- # Async.logger.debug(self) {"Stream data: #{body.size} bytes"}
192
- response.body << body
194
+ stream.on(:data) do |chunk|
195
+ Async.logger.debug(self) {"Stream data: #{chunk.inspect}"}
196
+ response.body.write(chunk.to_s) unless chunk.empty?
193
197
  end
194
198
 
195
- finished = Async::Notification.new
196
-
197
199
  stream.on(:half_close) do
198
- # Async.logger.debug(self) {"Stream half-closed."}
200
+ Async.logger.debug(self) {"Stream half-closed."}
199
201
  end
200
202
 
201
203
  stream.on(:close) do
202
- # Async.logger.debug(self) {"Stream closed, sending signal."}
203
- finished.signal
204
+ Async.logger.debug(self) {"Stream closed, sending signal."}
205
+ response.body.close
204
206
  end
205
207
 
206
208
  @stream.flush
@@ -18,14 +18,12 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require_relative 'body'
22
+
21
23
  module Async
22
24
  module HTTP
23
- module Protocol
24
- class Request < Struct.new(:authority, :method, :path, :version, :headers, :body)
25
- def read
26
- @body
27
- end
28
- end
25
+ class Request < Struct.new(:authority, :method, :path, :version, :headers, :body)
26
+ include BufferedBody::Reader
29
27
  end
30
28
  end
31
29
  end
@@ -18,26 +18,28 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require_relative 'body'
22
+
21
23
  module Async
22
24
  module HTTP
23
- module Protocol
24
- class Response < Struct.new(:version, :status, :reason, :headers, :body)
25
- def continue?
26
- status == 100
27
- end
28
-
29
- def success?
30
- status >= 200 && status < 300
31
- end
32
-
33
- def redirection?
34
- status >= 300 && status < 400
35
- end
36
-
37
- def failure?
38
- status >= 400 && status < 600
39
- end
25
+ class Response < Struct.new(:version, :status, :reason, :headers, :body)
26
+ def continue?
27
+ status == 100
28
+ end
29
+
30
+ def success?
31
+ status >= 200 && status < 300
32
+ end
33
+
34
+ def redirection?
35
+ status >= 300 && status < 400
36
+ end
37
+
38
+ def failure?
39
+ status >= 400 && status < 600
40
40
  end
41
+
42
+ include BufferedBody::Reader
41
43
  end
42
44
  end
43
45
  end
@@ -25,9 +25,13 @@ require_relative 'protocol'
25
25
  module Async
26
26
  module HTTP
27
27
  class Server
28
- def initialize(endpoint, protocol_class = Protocol::HTTP1)
28
+ def initialize(endpoint, protocol_class = Protocol::HTTP1, &block)
29
29
  @endpoint = endpoint
30
- @protocol_class = protocol_class
30
+ @protocol_class = protocol_class || endpoint.protocol
31
+
32
+ if block_given?
33
+ self.define_singleton_method(:handle_request, block)
34
+ end
31
35
  end
32
36
 
33
37
  def handle_request(request, peer, address)
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module HTTP
23
- VERSION = "0.10.0"
23
+ VERSION = "0.12.0"
24
24
  end
25
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-28 00:00:00.000000000 Z
11
+ date: 2018-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.4'
19
+ version: '1.5'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.4'
26
+ version: '1.5'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: async-io
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -124,8 +124,8 @@ files:
124
124
  - Rakefile
125
125
  - async-http.gemspec
126
126
  - lib/async/http.rb
127
+ - lib/async/http/body.rb
127
128
  - lib/async/http/client.rb
128
- - lib/async/http/headers.rb
129
129
  - lib/async/http/pool.rb
130
130
  - lib/async/http/protocol.rb
131
131
  - lib/async/http/protocol/http1.rb
@@ -133,8 +133,8 @@ files:
133
133
  - lib/async/http/protocol/http11.rb
134
134
  - lib/async/http/protocol/http2.rb
135
135
  - lib/async/http/protocol/https.rb
136
- - lib/async/http/protocol/request.rb
137
- - lib/async/http/protocol/response.rb
136
+ - lib/async/http/request.rb
137
+ - lib/async/http/response.rb
138
138
  - lib/async/http/server.rb
139
139
  - lib/async/http/url_endpoint.rb
140
140
  - lib/async/http/version.rb
@@ -1,85 +0,0 @@
1
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
20
-
21
- module Async
22
- module HTTP
23
- class Headers
24
- def initialize
25
- @hash = {}
26
- end
27
-
28
- def freeze
29
- return unless frozen?
30
-
31
- @hash.freeze
32
-
33
- super
34
- end
35
-
36
- def inspect
37
- @hash.inspect
38
- end
39
-
40
- def []= key, value
41
- @hash[symbolize(key)] = value
42
- end
43
-
44
- def [] key
45
- @hash[key]
46
- end
47
-
48
- def == other
49
- @hash == other.to_hash
50
- end
51
-
52
- def delete(key)
53
- @hash.delete(key)
54
- end
55
-
56
- def each
57
- return to_enum unless block_given?
58
-
59
- @hash.each do |key, value|
60
- yield stringify(key), value
61
- end
62
- end
63
-
64
- def symbolize(value)
65
- Headers[value]
66
- end
67
-
68
- def stringify(key)
69
- key.to_s.tr('_', '-')
70
- end
71
-
72
- def to_hash
73
- @hash
74
- end
75
-
76
- def to_http_hash
77
- Hash[@hash.map{|key, value| ["HTTP_#{key.to_s.upcase}", value]}]
78
- end
79
-
80
- def self.[] value
81
- value.downcase.tr('-', '_').to_sym
82
- end
83
- end
84
- end
85
- end