protocol-http1 0.1.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.

Potentially problematic release.


This version of protocol-http1 might be problematic. Click here for more details.

@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4a739005ea471dd8601ea3844e71a6437981bcd5e4cb3e7d5c85f7eb699d55a8
4
+ data.tar.gz: 31c5dc038dceaa79552fb76bb56667f85e7e61ac568f06c564d426a9f6606784
5
+ SHA512:
6
+ metadata.gz: 7aebf8675b398d210cc4fb457d498ea4a682112a93f4d298ced3ed24709dd3d1532eaa6ba56ceeaeb1c521545b23a48f557ba8616aec2f6d3fb2a57937e5ccb7
7
+ data.tar.gz: fcac06a0408e7bafede84eb1ab675bddfec3cc17605690fdc6f6111dc626412a65ff0f0dbefcd908d1c6b14068c4ac2adc03d0f9c717fb8ef9fef2b7f5d62f0e
@@ -0,0 +1,6 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = tab
5
+ indent_size = 2
6
+
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --warnings
3
+ --require spec_helper
@@ -0,0 +1,19 @@
1
+ language: ruby
2
+ dist: xenial
3
+ cache: bundler
4
+
5
+ matrix:
6
+ include:
7
+ - rvm: 2.4
8
+ - rvm: 2.5
9
+ - rvm: 2.6
10
+ - rvm: 2.6
11
+ env: COVERAGE=PartialSummary,Coveralls
12
+ - rvm: truffleruby
13
+ - rvm: jruby-head
14
+ env: JRUBY_OPTS="--debug -X+O"
15
+ - rvm: ruby-head
16
+ allow_failures:
17
+ - rvm: truffleruby
18
+ - rvm: ruby-head
19
+ - rvm: jruby-head
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in protocol-http1.gemspec
4
+ gemspec
@@ -0,0 +1,92 @@
1
+ # Protocol::HTTP1
2
+
3
+ Provides a low-level implementation of the HTTP/1 protocol.
4
+
5
+ [![Build Status](https://secure.travis-ci.com/socketry/protocol-http1.svg)](http://travis-ci.com/socketry/protocol-http1)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'protocol-http1'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install protocol-http1
22
+
23
+ ## Usage
24
+
25
+ Here is a basic HTTP/1.1 client:
26
+
27
+ ```ruby
28
+ require 'async'
29
+ require 'async/io/stream'
30
+ require 'async/http/url_endpoint'
31
+ require 'protocol/http1/connection'
32
+
33
+ Async.run do
34
+ endpoint = Async::HTTP::URLEndpoint.parse("https://www.google.com/search?q=kittens", alpn_protocols: ["http/1.1"])
35
+
36
+ peer = endpoint.connect
37
+
38
+ puts "Connected to #{peer} #{peer.remote_address.inspect}"
39
+
40
+ # IO Buffering...
41
+ stream = Async::IO::Stream.new(peer)
42
+ client = Protocol::HTTP1::Connection.new(stream)
43
+
44
+ def client.read_line
45
+ @stream.read_until(Protocol::HTTP1::Connection::CRLF) or raise EOFError
46
+ end
47
+
48
+ puts "Writing request..."
49
+ client.write_request("www.google.com", "GET", "/search?q=kittens", "HTTP/1.1", [["Accept", "*/*"]])
50
+ client.write_body(nil)
51
+
52
+ puts "Reading response..."
53
+ response = client.read_response("GET")
54
+
55
+ puts "Got response: #{response.inspect}"
56
+
57
+ puts "Closing client..."
58
+ client.close
59
+ end
60
+ ```
61
+
62
+ ## Contributing
63
+
64
+ 1. Fork it
65
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
66
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
67
+ 4. Push to the branch (`git push origin my-new-feature`)
68
+ 5. Create new Pull Request
69
+
70
+ ## License
71
+
72
+ Released under the MIT license.
73
+
74
+ Copyright, 2019, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
75
+
76
+ Permission is hereby granted, free of charge, to any person obtaining a copy
77
+ of this software and associated documentation files (the "Software"), to deal
78
+ in the Software without restriction, including without limitation the rights
79
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
80
+ copies of the Software, and to permit persons to whom the Software is
81
+ furnished to do so, subject to the following conditions:
82
+
83
+ The above copyright notice and this permission notice shall be included in
84
+ all copies or substantial portions of the Software.
85
+
86
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
87
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
88
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
89
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
90
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
91
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
92
+ THE SOFTWARE.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,38 @@
1
+
2
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
3
+
4
+ require 'async'
5
+ require 'async/io/stream'
6
+ require 'async/http/url_endpoint'
7
+ require 'protocol/http1/connection'
8
+ require 'pry'
9
+
10
+ Async.run do
11
+ endpoint = Async::HTTP::URLEndpoint.parse("https://www.google.com/search?q=kittens", alpn_protocols: ["http/1.1"])
12
+
13
+ peer = endpoint.connect
14
+
15
+ puts "Connected to #{peer} #{peer.remote_address.inspect}"
16
+
17
+ # IO Buffering...
18
+ stream = Async::IO::Stream.new(peer)
19
+ client = Protocol::HTTP1::Connection.new(stream)
20
+
21
+ def client.read_line
22
+ @stream.read_until(Protocol::HTTP1::Connection::CRLF) or raise EOFError
23
+ end
24
+
25
+ puts "Writing request..."
26
+ client.write_request("www.google.com", "GET", "/search?q=kittens", "HTTP/1.1", [["Accept", "*/*"]])
27
+ client.write_body(nil)
28
+
29
+ puts "Reading response..."
30
+ response = client.read_response("GET")
31
+
32
+ puts "Got response: #{response.inspect}"
33
+
34
+ puts "Closing client..."
35
+ client.close
36
+ end
37
+
38
+ puts "Exiting."
@@ -0,0 +1,22 @@
1
+ # Copyright, 2019, 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_relative 'http1/version'
22
+ require_relative 'http1/connection'
@@ -0,0 +1,437 @@
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 'protocol/http/headers'
22
+
23
+ require_relative 'error'
24
+
25
+ module Protocol
26
+ module HTTP1
27
+ CONTENT_LENGTH = 'content-length'.freeze
28
+
29
+ TRANSFER_ENCODING = 'transfer-encoding'.freeze
30
+ CHUNKED = 'chunked'.freeze
31
+
32
+ CONNECTION = 'connection'.freeze
33
+ CLOSE = 'close'.freeze
34
+ KEEP_ALIVE = 'keep-alive'.freeze
35
+
36
+ HOST = 'host'.freeze
37
+ UPGRADE = 'upgrade'.freeze
38
+
39
+ class Connection
40
+ CRLF = "\r\n".freeze
41
+ HTTP10 = "HTTP/1.0".freeze
42
+ HTTP11 = "HTTP/1.1".freeze
43
+
44
+ def initialize(stream, persistent = true)
45
+ @stream = stream
46
+
47
+ @persistent = persistent
48
+ @upgrade = nil
49
+
50
+ @count = 0
51
+ end
52
+
53
+ attr :stream
54
+
55
+ # Whether the connection is persistent.
56
+ attr :persistent
57
+
58
+ # Whether the connection has been upgraded, and to what.
59
+ attr :upgrade
60
+
61
+ # The number of requests processed.
62
+ attr :count
63
+
64
+ def upgrade?(headers)
65
+ if upgrade = headers[UPGRADE]
66
+ return upgrade.first
67
+ end
68
+ end
69
+
70
+ def persistent?(version, headers)
71
+ if version == HTTP10
72
+ if connection = headers[CONNECTION]
73
+ return connection.include?(KEEP_ALIVE)
74
+ else
75
+ return false
76
+ end
77
+ else
78
+ if connection = headers[CONNECTION]
79
+ return !connection.include?(CLOSE)
80
+ else
81
+ return true
82
+ end
83
+ end
84
+ end
85
+
86
+ def upgrade!(protocol)
87
+ @upgrade = protocol
88
+ @persistent = false
89
+
90
+ return @stream
91
+ end
92
+
93
+ # Write the appropriate header for connection persistence.
94
+ def write_connection_header(version)
95
+ if @upgrade
96
+ @stream.write("connection: upgrade\r\nupgrade: #{@upgrade}\r\n")
97
+ else
98
+ if version == HTTP10
99
+ @stream.write("connection: keep-alive\r\n") if @persistent
100
+ else
101
+ @stream.write("connection: close\r\n") unless @persistent
102
+ end
103
+ end
104
+ end
105
+
106
+ # Effectively close the connection and return the underlying IO.
107
+ # @return [IO] the underlying non-blocking IO.
108
+ def hijack!
109
+ @persistent = false
110
+
111
+ @stream.flush
112
+
113
+ return @stream
114
+ end
115
+
116
+ # Close the connection and underlying stream.
117
+ def close
118
+ @stream.close
119
+ end
120
+
121
+ def write_request(authority, method, path, version, headers)
122
+ @stream.write("#{method} #{path} #{version}\r\n")
123
+ @stream.write("host: #{authority}\r\n")
124
+
125
+ write_headers(headers)
126
+ write_connection_header(version)
127
+ end
128
+
129
+ def write_response(version, status, headers, body = nil, head = false)
130
+ @stream.write("#{version} #{status}\r\n")
131
+
132
+ write_headers(headers)
133
+ write_connection_header(version)
134
+ write_body(body, version == HTTP11, head)
135
+ end
136
+
137
+ def write_headers(headers)
138
+ headers.each do |name, value|
139
+ @stream.write("#{name}: #{value}\r\n")
140
+ end
141
+ end
142
+
143
+ def each_line
144
+ while line = read_line
145
+ yield line
146
+ end
147
+ end
148
+
149
+ def read_line
150
+ @stream.gets(CRLF, chomp: true) or raise EOFError
151
+ end
152
+
153
+ def read_request
154
+ method, path, version = read_line.split(/\s+/, 3)
155
+ headers = read_headers
156
+
157
+ @persistent = persistent?(version, headers)
158
+ @upgrade = upgrade?(headers)
159
+
160
+ body = read_request_body(headers)
161
+
162
+ @count += 1
163
+
164
+ return headers.delete(HOST), method, path, version, headers, body
165
+ end
166
+
167
+ def read_response(method)
168
+ version, status, reason = read_line.split(/\s+/, 3)
169
+
170
+ status = Integer(status)
171
+
172
+ headers = read_headers
173
+
174
+ @persistent = persistent?(version, headers)
175
+
176
+ body = read_response_body(method, status, headers)
177
+
178
+ @count += 1
179
+
180
+ return version, status, reason, headers, body
181
+ end
182
+
183
+ def read_headers
184
+ fields = []
185
+
186
+ self.each_line do |line|
187
+ if line =~ /^([a-zA-Z\-\d]+):\s*(.+?)\s*$/
188
+ fields << [$1, $2]
189
+ else
190
+ break
191
+ end
192
+ end
193
+
194
+ return HTTP::Headers.new(fields)
195
+ end
196
+
197
+ def read_chunk
198
+ length = self.read_line.to_i(16)
199
+
200
+ if length == 0
201
+ self.read_line
202
+
203
+ return nil
204
+ end
205
+
206
+ # Read the data:
207
+ chunk = @stream.read(length)
208
+
209
+ # Consume the trailing CRLF:
210
+ @stream.read(2)
211
+
212
+ return chunk
213
+ end
214
+
215
+ def write_upgrade_body
216
+ @stream.write("\r\n")
217
+ @stream.flush
218
+
219
+ return @stream unless block_given?
220
+
221
+ begin
222
+ yield @stream
223
+ rescue
224
+ @stream.close_write
225
+ end
226
+ end
227
+
228
+ def write_empty_body(body)
229
+ @stream.write("content-length: 0\r\n\r\n")
230
+ @stream.flush
231
+
232
+ if body
233
+ body.close if body.respond_to?(:close)
234
+ end
235
+ end
236
+
237
+ def write_fixed_length_body(body, length, head)
238
+ @stream.write("content-length: #{length}\r\n\r\n")
239
+ @stream.flush
240
+
241
+ if head
242
+ body.close if body.respond_to?(:close)
243
+
244
+ return
245
+ end
246
+
247
+ chunk_length = 0
248
+ body.each do |chunk|
249
+ chunk_length += chunk.bytesize
250
+
251
+ if chunk_length > length
252
+ raise ProtocolError, "Trying to write #{chunk_length} bytes, but content length was #{length} bytes!"
253
+ end
254
+
255
+ @stream.write(chunk)
256
+ end
257
+
258
+ @stream.flush
259
+
260
+ if chunk_length != length
261
+ raise ProtocolError, "Wrote #{chunk_length} bytes, but content length was #{length} bytes!"
262
+ end
263
+ end
264
+
265
+ def write_chunked_body(body, head)
266
+ @stream.write("transfer-encoding: chunked\r\n\r\n")
267
+ @stream.flush
268
+
269
+ if head
270
+ body.close if body.respond_to?(:close)
271
+
272
+ return
273
+ end
274
+
275
+ body.each do |chunk|
276
+ next if chunk.size == 0
277
+
278
+ @stream.write("#{chunk.bytesize.to_s(16).upcase}\r\n")
279
+ @stream.write(chunk)
280
+ @stream.write(CRLF)
281
+ @stream.flush
282
+ end
283
+
284
+ @stream.write("0\r\n\r\n")
285
+ @stream.flush
286
+ end
287
+
288
+ def write_body_and_close(body, head)
289
+ # We can't be persistent because we don't know the data length:
290
+ @persistent = false
291
+ @stream.write("\r\n")
292
+ @stream.flush
293
+
294
+ if head
295
+ body.close if body.respond_to?(:close)
296
+ else
297
+ body.each do |chunk|
298
+ @stream.write(chunk)
299
+ @stream.flush
300
+ end
301
+ end
302
+
303
+ @stream.close_write
304
+ end
305
+
306
+ def write_body(body, chunked = true, head = false)
307
+ if body.nil? or body.empty?
308
+ write_empty_body(body)
309
+ elsif body.respond_to?(:call)
310
+ write_upgrade_body(&body)
311
+ elsif length = body.length
312
+ write_fixed_length_body(body, length, head)
313
+ elsif @persistent and chunked
314
+ # We specifically ensure that non-persistent connections do not use chunked response, so that hijacking works as expected.
315
+ write_chunked_body(body, head)
316
+ else
317
+ write_body_and_close(body, head)
318
+ end
319
+
320
+ @stream.flush
321
+ end
322
+
323
+ def read_chunked_body
324
+ buffer = String.new.b
325
+
326
+ while chunk = read_chunk
327
+ buffer << chunk
328
+ chunk.clear
329
+ end
330
+
331
+ return buffer
332
+ end
333
+
334
+ def read_fixed_body(length)
335
+ @stream.read(length)
336
+ end
337
+
338
+ def read_tunnel_body
339
+ read_remainder_body
340
+ end
341
+
342
+ def read_remainder_body
343
+ @stream.read
344
+ end
345
+
346
+ HEAD = "HEAD".freeze
347
+ CONNECT = "CONNECT".freeze
348
+
349
+ def read_response_body(method, status, headers)
350
+ # RFC 7230 3.3.3
351
+ # 1. Any response to a HEAD request and any response with a 1xx
352
+ # (Informational), 204 (No Content), or 304 (Not Modified) status
353
+ # code is always terminated by the first empty line after the
354
+ # header fields, regardless of the header fields present in the
355
+ # message, and thus cannot contain a message body.
356
+ if method == "HEAD" or (status >= 100 and status < 200) or status == 204 or status == 304
357
+ return nil
358
+ end
359
+
360
+ # 2. Any 2xx (Successful) response to a CONNECT request implies that
361
+ # the connection will become a tunnel immediately after the empty
362
+ # line that concludes the header fields. A client MUST ignore any
363
+ # Content-Length or Transfer-Encoding header fields received in
364
+ # such a message.
365
+ if method == "CONNECT" and status == 200
366
+ return read_tunnel_body
367
+ end
368
+
369
+ return read_body(headers, true)
370
+ end
371
+
372
+ def read_request_body(headers)
373
+ # 6. If this is a request message and none of the above are true, then
374
+ # the message body length is zero (no message body is present).
375
+ return read_body(headers)
376
+ end
377
+
378
+ def read_body(headers, remainder = false)
379
+ # 3. If a Transfer-Encoding header field is present and the chunked
380
+ # transfer coding (Section 4.1) is the final encoding, the message
381
+ # body length is determined by reading and decoding the chunked
382
+ # data until the transfer coding indicates the data is complete.
383
+ if transfer_encoding = headers.delete(TRANSFER_ENCODING)
384
+ # If a message is received with both a Transfer-Encoding and a
385
+ # Content-Length header field, the Transfer-Encoding overrides the
386
+ # Content-Length. Such a message might indicate an attempt to
387
+ # perform request smuggling (Section 9.5) or response splitting
388
+ # (Section 9.4) and ought to be handled as an error. A sender MUST
389
+ # remove the received Content-Length field prior to forwarding such
390
+ # a message downstream.
391
+ if headers[CONTENT_LENGTH]
392
+ raise BadRequest, "Message contains both transfer encoding and content length!"
393
+ end
394
+
395
+ if transfer_encoding.last == CHUNKED
396
+ return read_chunked_body
397
+ else
398
+ # If a Transfer-Encoding header field is present in a response and
399
+ # the chunked transfer coding is not the final encoding, the
400
+ # message body length is determined by reading the connection until
401
+ # it is closed by the server. If a Transfer-Encoding header field
402
+ # is present in a request and the chunked transfer coding is not
403
+ # the final encoding, the message body length cannot be determined
404
+ # reliably; the server MUST respond with the 400 (Bad Request)
405
+ # status code and then close the connection.
406
+ return read_remainder_body
407
+ end
408
+ end
409
+
410
+ # 5. If a valid Content-Length header field is present without
411
+ # Transfer-Encoding, its decimal value defines the expected message
412
+ # body length in octets. If the sender closes the connection or
413
+ # the recipient times out before the indicated number of octets are
414
+ # received, the recipient MUST consider the message to be
415
+ # incomplete and close the connection.
416
+ if content_length = headers.delete(CONTENT_LENGTH)
417
+ length = Integer(content_length)
418
+ if length > 0
419
+ return read_fixed_body(length)
420
+ elsif length == 0
421
+ return nil
422
+ else
423
+ raise BadRequest, "Invalid content length: #{content_length}"
424
+ end
425
+ end
426
+
427
+ if remainder
428
+ # 7. Otherwise, this is a response message without a declared message
429
+ # body length, so the message body length is determined by the
430
+ # number of octets received prior to the server closing the
431
+ # connection.
432
+ return read_remainder_body
433
+ end
434
+ end
435
+ end
436
+ end
437
+ end
@@ -0,0 +1,31 @@
1
+ # Copyright, 2019, 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 'protocol/http/error'
22
+
23
+ module Protocol
24
+ module HTTP1
25
+ class BadRequest < HTTP::BadRequest
26
+ end
27
+
28
+ class ProtocolError < HTTP::ProtocolError
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ module Protocol
2
+ module HTTP1
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,29 @@
1
+
2
+ require_relative 'lib/protocol/http1/version'
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "protocol-http1"
6
+ spec.version = Protocol::HTTP1::VERSION
7
+ spec.authors = ["Samuel Williams"]
8
+ spec.email = ["samuel.williams@oriontransfer.co.nz"]
9
+
10
+ spec.summary = "A low level implementation of the HTTP/1 protocol."
11
+ spec.homepage = "https://github.com/socketry/protocol-http1"
12
+ spec.license = "MIT"
13
+
14
+ # Specify which files should be added to the gem when it is released.
15
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
16
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
17
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ end
19
+
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency "protocol-http"
24
+
25
+ spec.add_development_dependency "covered"
26
+ spec.add_development_dependency "bundler", "~> 1.17"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "rspec", "~> 3.0"
29
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: protocol-http1
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: protocol-http
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: covered
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.17'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.17'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description:
84
+ email:
85
+ - samuel.williams@oriontransfer.co.nz
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".editorconfig"
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".travis.yml"
94
+ - Gemfile
95
+ - README.md
96
+ - Rakefile
97
+ - examples/http1/request.rb
98
+ - lib/protocol/http1.rb
99
+ - lib/protocol/http1/connection.rb
100
+ - lib/protocol/http1/error.rb
101
+ - lib/protocol/http1/version.rb
102
+ - protocol-http1.gemspec
103
+ homepage: https://github.com/socketry/protocol-http1
104
+ licenses:
105
+ - MIT
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubygems_version: 3.0.2
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: A low level implementation of the HTTP/1 protocol.
126
+ test_files: []