protocol-http1 0.10.3 → 0.13.1

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6860775fdc28f8e8d173ff80296cefa5c5e1505e70ce264876959fb453beab5a
4
- data.tar.gz: 9985d69a3696bc98c2e01b78d29282220c9b90b968d0030d60ed95b26a3bab14
3
+ metadata.gz: 4e7551e3bc57f35d161475c5c35c8a9a897fa032f3bbc523a6187a033e9e4539
4
+ data.tar.gz: dc11c6473a5e88d61f709ece47c6c36b1c87edc8d3f316d649288039cd0491a0
5
5
  SHA512:
6
- metadata.gz: 33093bb74d0b00b2a3dfded4432c66276ff27d6fe4e1137b4f0b867b51ca5aa1add02512b3f9427b2520be1617bf3e2c039ed497c192a686cc237bb4a65f690f
7
- data.tar.gz: c845bf16f06d9798553a6241ffd3fae1692584bf6ff276437e1ab7828773200716b86be3b15a5808956ac74389852a2772c81b29fdbb6599a6da1bfe1a8bb9ee
6
+ metadata.gz: 6c077a0cd4de36989268ec7c4258d702f23469346a6cc66492b8fbd19a79796d9924f479cbc9fbe5c34efc1c642dc14b8b826e38589aa39d775244251a1acad8
7
+ data.tar.gz: 873cb095fe327997ffaa175b54379b91f88802ee5a4de8beedb7238c55794dda1627b0a605149968d53aca7f8023aad164574f371c33665672d9afa0f4157dc9
@@ -26,11 +26,15 @@ module Protocol
26
26
  module HTTP1
27
27
  module Body
28
28
  class Chunked < HTTP::Body::Readable
29
- # TODO maybe this should take a stream rather than a connection?
30
- def initialize(stream)
29
+ TRAILERS = 'trailers'
30
+ CRLF = "\r\n"
31
+
32
+ def initialize(stream, headers)
31
33
  @stream = stream
32
34
  @finished = false
33
35
 
36
+ @headers = headers
37
+
34
38
  @length = 0
35
39
  @count = 0
36
40
  end
@@ -49,6 +53,7 @@ module Protocol
49
53
  super
50
54
  end
51
55
 
56
+ # Follows the procedure outlined in https://tools.ietf.org/html/rfc7230#section-4.1.3
52
57
  def read
53
58
  return nil if @finished
54
59
 
@@ -56,13 +61,17 @@ module Protocol
56
61
 
57
62
  if length == 0
58
63
  @finished = true
59
- read_line
64
+
65
+ read_trailers
60
66
 
61
67
  return nil
62
68
  end
63
69
 
64
- chunk = @stream.read(length)
65
- read_line # Consume the trailing CRLF
70
+ # Read trailing CRLF:
71
+ chunk = @stream.read(length + 2)
72
+
73
+ # ...and chomp it off:
74
+ chunk.chomp!(CRLF)
66
75
 
67
76
  @length += length
68
77
  @count += 1
@@ -77,7 +86,20 @@ module Protocol
77
86
  private
78
87
 
79
88
  def read_line
80
- @stream.gets(chomp: true)
89
+ @stream.gets(CRLF, chomp: true)
90
+ end
91
+
92
+ def read_trailers
93
+ while line = read_line
94
+ # Empty line indicates end of headers:
95
+ break if line.empty?
96
+
97
+ if match = line.match(HEADER)
98
+ @headers.add(match[1], match[2])
99
+ else
100
+ raise BadHeader, "Could not parse header: #{line.dump}"
101
+ end
102
+ end
81
103
  end
82
104
  end
83
105
  end
@@ -281,14 +281,17 @@ module Protocol
281
281
 
282
282
  def write_fixed_length_body(body, length, head)
283
283
  @stream.write("content-length: #{length}\r\n\r\n")
284
- @stream.flush
285
284
 
286
285
  if head
286
+ @stream.flush
287
+
287
288
  body.close
288
289
 
289
290
  return
290
291
  end
291
292
 
293
+ @stream.flush unless body.ready?
294
+
292
295
  chunk_length = 0
293
296
  body.each do |chunk|
294
297
  chunk_length += chunk.bytesize
@@ -307,26 +310,37 @@ module Protocol
307
310
  end
308
311
  end
309
312
 
310
- def write_chunked_body(body, head)
313
+ def write_chunked_body(body, head, trailers = nil)
311
314
  @stream.write("transfer-encoding: chunked\r\n\r\n")
312
- @stream.flush
313
315
 
314
316
  if head
317
+ @stream.flush
318
+
315
319
  body.close
316
320
 
317
321
  return
318
322
  end
319
323
 
324
+ @stream.flush unless body.ready?
325
+
320
326
  body.each do |chunk|
321
327
  next if chunk.size == 0
322
328
 
323
329
  @stream.write("#{chunk.bytesize.to_s(16).upcase}\r\n")
324
330
  @stream.write(chunk)
325
331
  @stream.write(CRLF)
326
- @stream.flush
332
+
333
+ @stream.flush unless body.ready?
334
+ end
335
+
336
+ if trailers
337
+ @stream.write("0\r\n")
338
+ write_headers(trailers)
339
+ @stream.write("\r\n")
340
+ else
341
+ @stream.write("0\r\n\r\n")
327
342
  end
328
343
 
329
- @stream.write("0\r\n\r\n")
330
344
  @stream.flush
331
345
  end
332
346
 
@@ -335,31 +349,36 @@ module Protocol
335
349
  @persistent = false
336
350
 
337
351
  @stream.write("\r\n")
338
- @stream.flush
352
+ @stream.flush unless body.ready?
339
353
 
340
354
  if head
341
355
  body.close
342
356
  else
343
357
  body.each do |chunk|
344
358
  @stream.write(chunk)
345
- @stream.flush
359
+
360
+ @stream.flush unless body.ready?
346
361
  end
347
362
  end
348
363
 
349
364
  @stream.close_write
350
365
  end
351
366
 
352
- def write_body(version, body, head = false)
353
- if body.nil? or body.empty?
367
+ def write_body(version, body, head = false, trailers = nil)
368
+ if body.nil?
354
369
  write_connection_header(version)
355
370
  write_empty_body(body)
356
- elsif length = body.length
371
+ elsif length = body.length and (VERSION != HTTP11 && trailers.nil?)
357
372
  write_connection_header(version)
358
373
  write_fixed_length_body(body, length, head)
374
+ elsif body.empty?
375
+ # Even thought this code is the same as the first clause `body.nil?`, HEAD responses have an empty body but still carry a content length. `write_fixed_length_body` takes care of this appropriately.
376
+ write_connection_header(version)
377
+ write_empty_body(body)
359
378
  elsif @persistent and version == HTTP11
360
379
  write_connection_header(version)
361
380
  # We specifically ensure that non-persistent connections do not use chunked response, so that hijacking works as expected.
362
- write_chunked_body(body, head)
381
+ write_chunked_body(body, head, trailers)
363
382
  else
364
383
  @persistent = false
365
384
  write_connection_header(version)
@@ -367,8 +386,8 @@ module Protocol
367
386
  end
368
387
  end
369
388
 
370
- def read_chunked_body
371
- Body::Chunked.new(@stream)
389
+ def read_chunked_body(headers)
390
+ Body::Chunked.new(@stream, headers)
372
391
  end
373
392
 
374
393
  def read_fixed_body(length)
@@ -466,7 +485,7 @@ module Protocol
466
485
  end
467
486
 
468
487
  if transfer_encoding.last == CHUNKED
469
- return read_chunked_body
488
+ return read_chunked_body(headers)
470
489
  else
471
490
  # If a Transfer-Encoding header field is present in a response and
472
491
  # the chunked transfer coding is not the final encoding, the
@@ -479,7 +498,7 @@ module Protocol
479
498
  return read_remainder_body
480
499
  end
481
500
  end
482
-
501
+
483
502
  # 5. If a valid Content-Length header field is present without
484
503
  # Transfer-Encoding, its decimal value defines the expected message
485
504
  # body length in octets. If the sender closes the connection or
@@ -22,6 +22,6 @@
22
22
 
23
23
  module Protocol
24
24
  module HTTP1
25
- VERSION = "0.10.3"
25
+ VERSION = "0.13.1"
26
26
  end
27
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-http1
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.3
4
+ version: 0.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-21 00:00:00.000000000 Z
11
+ date: 2020-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: protocol-http
@@ -16,16 +16,16 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.15'
19
+ version: '0.19'
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: '0.15'
26
+ version: '0.19'
27
27
  - !ruby/object:Gem::Dependency
28
- name: covered
28
+ name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: bundler
42
+ name: covered
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
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
55
  - !ruby/object:Gem::Dependency
70
56
  name: rspec
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -81,7 +67,7 @@ dependencies:
81
67
  - !ruby/object:Gem::Version
82
68
  version: '3.0'
83
69
  - !ruby/object:Gem::Dependency
84
- name: rspec-memory
70
+ name: rspec-files
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
73
  - - "~>"
@@ -95,7 +81,7 @@ dependencies:
95
81
  - !ruby/object:Gem::Version
96
82
  version: '1.0'
97
83
  - !ruby/object:Gem::Dependency
98
- name: rspec-files
84
+ name: rspec-memory
99
85
  requirement: !ruby/object:Gem::Requirement
100
86
  requirements:
101
87
  - - "~>"
@@ -108,21 +94,12 @@ dependencies:
108
94
  - - "~>"
109
95
  - !ruby/object:Gem::Version
110
96
  version: '1.0'
111
- description:
97
+ description:
112
98
  email:
113
- - samuel.williams@oriontransfer.co.nz
114
99
  executables: []
115
100
  extensions: []
116
101
  extra_rdoc_files: []
117
102
  files:
118
- - ".editorconfig"
119
- - ".gitignore"
120
- - ".rspec"
121
- - ".travis.yml"
122
- - Gemfile
123
- - README.md
124
- - Rakefile
125
- - examples/http1/request.rb
126
103
  - lib/protocol/http1.rb
127
104
  - lib/protocol/http1/body/chunked.rb
128
105
  - lib/protocol/http1/body/fixed.rb
@@ -131,18 +108,17 @@ files:
131
108
  - lib/protocol/http1/error.rb
132
109
  - lib/protocol/http1/reason.rb
133
110
  - lib/protocol/http1/version.rb
134
- - protocol-http1.gemspec
135
111
  homepage: https://github.com/socketry/protocol-http1
136
112
  licenses:
137
113
  - MIT
138
114
  metadata: {}
139
- post_install_message:
115
+ post_install_message:
140
116
  rdoc_options: []
141
117
  require_paths:
142
118
  - lib
143
119
  required_ruby_version: !ruby/object:Gem::Requirement
144
120
  requirements:
145
- - - "~>"
121
+ - - ">="
146
122
  - !ruby/object:Gem::Version
147
123
  version: '2.4'
148
124
  required_rubygems_version: !ruby/object:Gem::Requirement
@@ -151,8 +127,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
127
  - !ruby/object:Gem::Version
152
128
  version: '0'
153
129
  requirements: []
154
- rubygems_version: 3.1.2
155
- signing_key:
130
+ rubygems_version: 3.0.3
131
+ signing_key:
156
132
  specification_version: 4
157
133
  summary: A low level implementation of the HTTP/1 protocol.
158
134
  test_files: []
@@ -1,6 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- indent_style = tab
5
- indent_size = 2
6
-
data/.gitignore DELETED
@@ -1,13 +0,0 @@
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
13
- .covered.db
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --warnings
3
- --require spec_helper
@@ -1,20 +0,0 @@
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.7
11
- - rvm: 2.6
12
- env: COVERAGE=PartialSummary,Coveralls
13
- - rvm: truffleruby
14
- - rvm: jruby-head
15
- env: JRUBY_OPTS="--debug -X+O"
16
- - rvm: ruby-head
17
- allow_failures:
18
- - rvm: truffleruby
19
- - rvm: ruby-head
20
- - rvm: jruby-head
data/Gemfile DELETED
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- # Specify your gem's dependencies in protocol-http1.gemspec
6
- gemspec
data/README.md DELETED
@@ -1,92 +0,0 @@
1
- # Protocol::HTTP1
2
-
3
- Provides a low-level implementation of the HTTP/1 protocol.
4
-
5
- [![Build Status](https://travis-ci.com/socketry/protocol-http1.svg?branch=master)](https://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/endpoint'
31
- require 'protocol/http1/connection'
32
-
33
- Async do
34
- endpoint = Async::HTTP::Endpoint.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.
data/Rakefile DELETED
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- task :default => :spec
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
4
-
5
- require 'async'
6
- require 'async/io/stream'
7
- require 'async/http/endpoint'
8
- require 'protocol/http1/connection'
9
- require 'pry'
10
-
11
- Async do
12
- endpoint = Async::HTTP::Endpoint.parse("https://www.google.com/search?q=kittens", alpn_protocols: ["http/1.1"])
13
-
14
- peer = endpoint.connect
15
-
16
- puts "Connected to #{peer} #{peer.remote_address.inspect}"
17
-
18
- # IO Buffering...
19
- stream = Async::IO::Stream.new(peer)
20
- client = Protocol::HTTP1::Connection.new(stream)
21
-
22
- def client.read_line
23
- @stream.read_until(Protocol::HTTP1::Connection::CRLF) or raise EOFError
24
- end
25
-
26
- puts "Writing request..."
27
- client.write_request("www.google.com", "GET", "/search?q=kittens", "HTTP/1.1", [["Accept", "*/*"]])
28
- client.write_body(nil)
29
-
30
- puts "Reading response..."
31
- response = client.read_response("GET")
32
-
33
- puts "Got response: #{response.inspect}"
34
-
35
- puts "Closing client..."
36
- client.close
37
- end
38
-
39
- puts "Exiting."
@@ -1,31 +0,0 @@
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
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
15
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
- end
17
-
18
- spec.required_ruby_version = "~> 2.4"
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", "~> 0.15"
24
-
25
- spec.add_development_dependency "covered"
26
- spec.add_development_dependency "bundler"
27
- spec.add_development_dependency "rake", "~> 10.0"
28
- spec.add_development_dependency "rspec", "~> 3.0"
29
- spec.add_development_dependency "rspec-memory", "~> 1.0"
30
- spec.add_development_dependency "rspec-files", "~> 1.0"
31
- end