protocol-http1 0.1.0

Sign up to get free protection for your applications and to get access to all the features.

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: []