protocol-http1 0.23.0 → 0.24.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/protocol/http1/body/chunked.rb +16 -23
- data/lib/protocol/http1/body/fixed.rb +18 -13
- data/lib/protocol/http1/body/remainder.rb +15 -14
- data/lib/protocol/http1/connection.rb +155 -7
- data/lib/protocol/http1/error.rb +4 -0
- data/lib/protocol/http1/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +2 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0854252b3602c1271818e28024b62e1a4373521052b75ba0aa879a1f77a6e84e'
|
4
|
+
data.tar.gz: 2ad1603fc177c4b8493f304806a1f8baf4dc147b0f1707d463f534fb1e740a62
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 213525d129854f2198cb1be6be3d99a74d7add2e4a8dc57ed72d3bd1854b29535256120dfcbd735f1823d91bb6bf27fee6571d93d8e85f0031a404be99b39e01
|
7
|
+
data.tar.gz: 32f7a63f81f830a77839fa154b69bfa5a8dc316d579cc48e10d2a786e92806960c36821b3f53da67c3b1e90ba29394264ae76c621d7608c62a0d56224dabec1f
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -12,8 +12,8 @@ module Protocol
|
|
12
12
|
class Chunked < HTTP::Body::Readable
|
13
13
|
CRLF = "\r\n"
|
14
14
|
|
15
|
-
def initialize(
|
16
|
-
@
|
15
|
+
def initialize(connection, headers)
|
16
|
+
@connection = connection
|
17
17
|
@finished = false
|
18
18
|
|
19
19
|
@headers = headers
|
@@ -23,16 +23,16 @@ module Protocol
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def empty?
|
26
|
-
@
|
26
|
+
@connection.nil?
|
27
27
|
end
|
28
28
|
|
29
29
|
def discard
|
30
|
-
if
|
31
|
-
@
|
30
|
+
if connection = @connection
|
31
|
+
@connection = nil
|
32
32
|
|
33
33
|
# We only close the connection if we haven't completed reading the entire body:
|
34
34
|
unless @finished
|
35
|
-
|
35
|
+
connection.close_read
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -48,8 +48,8 @@ module Protocol
|
|
48
48
|
# Follows the procedure outlined in https://tools.ietf.org/html/rfc7230#section-4.1.3
|
49
49
|
def read
|
50
50
|
if !@finished
|
51
|
-
if @
|
52
|
-
length, _extensions = read_line.split(";", 2)
|
51
|
+
if @connection
|
52
|
+
length, _extensions = @connection.read_line.split(";", 2)
|
53
53
|
|
54
54
|
unless length =~ VALID_CHUNK_LENGTH
|
55
55
|
raise BadRequest, "Invalid chunk length: #{length.inspect}"
|
@@ -61,15 +61,16 @@ module Protocol
|
|
61
61
|
if length == 0
|
62
62
|
read_trailer
|
63
63
|
|
64
|
-
# The final chunk has been read and the
|
65
|
-
@
|
64
|
+
# The final chunk has been read and the connection is now closed:
|
65
|
+
@connection.receive_end_stream!
|
66
|
+
@connection = nil
|
66
67
|
@finished = true
|
67
68
|
|
68
69
|
return nil
|
69
70
|
end
|
70
71
|
|
71
72
|
# Read trailing CRLF:
|
72
|
-
chunk = @
|
73
|
+
chunk = @connection.read(length + 2)
|
73
74
|
|
74
75
|
if chunk.bytesize == length + 2
|
75
76
|
# ...and chomp it off:
|
@@ -80,13 +81,13 @@ module Protocol
|
|
80
81
|
|
81
82
|
return chunk
|
82
83
|
else
|
83
|
-
# The
|
84
|
+
# The connection has been closed before we have read the requested length:
|
84
85
|
self.discard
|
85
86
|
end
|
86
87
|
end
|
87
88
|
|
88
|
-
# If the
|
89
|
-
raise EOFError, "
|
89
|
+
# If the connection has been closed before we have read the final chunk, raise an error:
|
90
|
+
raise EOFError, "connection closed before expected length was read!"
|
90
91
|
end
|
91
92
|
end
|
92
93
|
|
@@ -96,16 +97,8 @@ module Protocol
|
|
96
97
|
|
97
98
|
private
|
98
99
|
|
99
|
-
def read_line?
|
100
|
-
@stream.gets(CRLF, chomp: true)
|
101
|
-
end
|
102
|
-
|
103
|
-
def read_line
|
104
|
-
read_line? or raise EOFError
|
105
|
-
end
|
106
|
-
|
107
100
|
def read_trailer
|
108
|
-
while line = read_line?
|
101
|
+
while line = @connection.read_line?
|
109
102
|
# Empty line indicates end of trailer:
|
110
103
|
break if line.empty?
|
111
104
|
|
@@ -9,8 +9,8 @@ module Protocol
|
|
9
9
|
module HTTP1
|
10
10
|
module Body
|
11
11
|
class Fixed < HTTP::Body::Readable
|
12
|
-
def initialize(
|
13
|
-
@
|
12
|
+
def initialize(connection, length)
|
13
|
+
@connection = connection
|
14
14
|
|
15
15
|
@length = length
|
16
16
|
@remaining = length
|
@@ -20,15 +20,15 @@ module Protocol
|
|
20
20
|
attr :remaining
|
21
21
|
|
22
22
|
def empty?
|
23
|
-
@
|
23
|
+
@connection.nil? or @remaining == 0
|
24
24
|
end
|
25
25
|
|
26
26
|
def discard
|
27
|
-
if
|
28
|
-
@
|
27
|
+
if connection = @connection
|
28
|
+
@connection = nil
|
29
29
|
|
30
30
|
if @remaining != 0
|
31
|
-
|
31
|
+
connection.close_read
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -39,25 +39,30 @@ module Protocol
|
|
39
39
|
super
|
40
40
|
end
|
41
41
|
|
42
|
-
# @raises EOFError if the
|
42
|
+
# @raises EOFError if the connection is closed before the expected length is read.
|
43
43
|
def read
|
44
44
|
if @remaining > 0
|
45
|
-
if @
|
46
|
-
# `readpartial` will raise `EOFError` if the
|
47
|
-
chunk = @
|
45
|
+
if @connection
|
46
|
+
# `readpartial` will raise `EOFError` if the connection is finished, or `IOError` if the connection is closed.
|
47
|
+
chunk = @connection.readpartial(@remaining)
|
48
48
|
|
49
49
|
@remaining -= chunk.bytesize
|
50
50
|
|
51
|
+
if @remaining == 0
|
52
|
+
@connection.receive_end_stream!
|
53
|
+
@connection = nil
|
54
|
+
end
|
55
|
+
|
51
56
|
return chunk
|
52
57
|
end
|
53
58
|
|
54
|
-
# If the
|
55
|
-
raise EOFError, "
|
59
|
+
# If the connection has been closed before we have read the expected length, raise an error:
|
60
|
+
raise EOFError, "connection closed before expected length was read!"
|
56
61
|
end
|
57
62
|
end
|
58
63
|
|
59
64
|
def inspect
|
60
|
-
"\#<#{self.class} length=#{@length} remaining=#{@remaining} state=#{@
|
65
|
+
"\#<#{self.class} length=#{@length} remaining=#{@remaining} state=#{@connection ? 'open' : 'closed'}>"
|
61
66
|
end
|
62
67
|
end
|
63
68
|
end
|
@@ -8,23 +8,25 @@ require "protocol/http/body/readable"
|
|
8
8
|
module Protocol
|
9
9
|
module HTTP1
|
10
10
|
module Body
|
11
|
-
# A body that reads all remaining data from the
|
11
|
+
# A body that reads all remaining data from the connection.
|
12
12
|
class Remainder < HTTP::Body::Readable
|
13
13
|
BLOCK_SIZE = 1024 * 64
|
14
14
|
|
15
|
-
# block_size may be removed in the future. It is better managed by
|
16
|
-
def initialize(
|
17
|
-
@
|
15
|
+
# block_size may be removed in the future. It is better managed by connection.
|
16
|
+
def initialize(connection)
|
17
|
+
@connection = connection
|
18
18
|
end
|
19
19
|
|
20
20
|
def empty?
|
21
|
-
@
|
21
|
+
@connection.nil?
|
22
22
|
end
|
23
23
|
|
24
24
|
def discard
|
25
|
-
if
|
26
|
-
@
|
27
|
-
|
25
|
+
if connection = @connection
|
26
|
+
@connection = nil
|
27
|
+
|
28
|
+
# Ensure no further requests can be read from the connection, as we are discarding the body which may not be fully read:
|
29
|
+
connection.close_read
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
@@ -35,15 +37,14 @@ module Protocol
|
|
35
37
|
end
|
36
38
|
|
37
39
|
def read
|
38
|
-
@
|
39
|
-
rescue EOFError
|
40
|
-
@
|
41
|
-
|
42
|
-
return nil
|
40
|
+
@connection&.readpartial(BLOCK_SIZE)
|
41
|
+
rescue EOFError
|
42
|
+
@connection.receive_end_stream!
|
43
|
+
@connection = nil
|
43
44
|
end
|
44
45
|
|
45
46
|
def inspect
|
46
|
-
"\#<#{self.class} state=#{@
|
47
|
+
"\#<#{self.class} state=#{@connection ? 'open' : 'closed'}>"
|
47
48
|
end
|
48
49
|
end
|
49
50
|
end
|
@@ -50,10 +50,11 @@ module Protocol
|
|
50
50
|
HTTP10 = "HTTP/1.0"
|
51
51
|
HTTP11 = "HTTP/1.1"
|
52
52
|
|
53
|
-
def initialize(stream, persistent
|
53
|
+
def initialize(stream, persistent: true, state: :idle)
|
54
54
|
@stream = stream
|
55
55
|
|
56
56
|
@persistent = persistent
|
57
|
+
@state = state
|
57
58
|
|
58
59
|
@count = 0
|
59
60
|
end
|
@@ -70,6 +71,64 @@ module Protocol
|
|
70
71
|
# depending on the response semantics, may be reset to false anyway.
|
71
72
|
attr_accessor :persistent
|
72
73
|
|
74
|
+
# The current state of the connection.
|
75
|
+
#
|
76
|
+
# ```
|
77
|
+
# ┌────────┐
|
78
|
+
# │ │
|
79
|
+
# ┌───────────────────────►│ idle │
|
80
|
+
# │ │ │
|
81
|
+
# │ └───┬────┘
|
82
|
+
# │ │
|
83
|
+
# │ │ send request /
|
84
|
+
# │ │ receive request
|
85
|
+
# │ │
|
86
|
+
# │ ▼
|
87
|
+
# │ ┌────────┐
|
88
|
+
# │ recv ES │ │ send ES
|
89
|
+
# │ ┌────────────┤ open ├────────────┐
|
90
|
+
# │ │ │ │ │
|
91
|
+
# │ ▼ └───┬────┘ ▼
|
92
|
+
# │ ┌──────────┐ │ ┌──────────┐
|
93
|
+
# │ │ half │ │ │ half │
|
94
|
+
# │ │ closed │ │ │ closed │
|
95
|
+
# │ │ (remote) │ │ │ (local) │
|
96
|
+
# │ └────┬─────┘ │ └─────┬────┘
|
97
|
+
# │ │ │ │
|
98
|
+
# │ │ send ES / │ recv ES / │
|
99
|
+
# │ │ close ▼ close │
|
100
|
+
# │ │ ┌────────┐ │
|
101
|
+
# │ └───────────►│ │◄───────────┘
|
102
|
+
# │ │ closed │
|
103
|
+
# └────────────────────────┤ │
|
104
|
+
# persistent └────────┘
|
105
|
+
# ```
|
106
|
+
#
|
107
|
+
# - `ES`: the body was fully received or sent (end of stream)
|
108
|
+
#
|
109
|
+
# State transition methods use a trailing "!".
|
110
|
+
attr_accessor :state
|
111
|
+
|
112
|
+
def idle?
|
113
|
+
@state == :idle
|
114
|
+
end
|
115
|
+
|
116
|
+
def open?
|
117
|
+
@state == :open
|
118
|
+
end
|
119
|
+
|
120
|
+
def half_closed_local?
|
121
|
+
@state == :half_closed_local
|
122
|
+
end
|
123
|
+
|
124
|
+
def half_closed_remote?
|
125
|
+
@state == :half_closed_remote
|
126
|
+
end
|
127
|
+
|
128
|
+
def closed?
|
129
|
+
@state == :closed
|
130
|
+
end
|
131
|
+
|
73
132
|
# The number of requests processed.
|
74
133
|
attr :count
|
75
134
|
|
@@ -130,7 +189,15 @@ module Protocol
|
|
130
189
|
@stream&.close
|
131
190
|
end
|
132
191
|
|
192
|
+
def open!
|
193
|
+
raise ProtocolError, "Cannot write request in #{@state}!" unless @state == :idle
|
194
|
+
|
195
|
+
@state = :open
|
196
|
+
end
|
197
|
+
|
133
198
|
def write_request(authority, method, path, version, headers)
|
199
|
+
open!
|
200
|
+
|
134
201
|
@stream.write("#{method} #{path} #{version}\r\n")
|
135
202
|
@stream.write("host: #{authority}\r\n")
|
136
203
|
|
@@ -138,6 +205,10 @@ module Protocol
|
|
138
205
|
end
|
139
206
|
|
140
207
|
def write_response(version, status, headers, reason = Reason::DESCRIPTIONS[status])
|
208
|
+
unless @state == :open or @state == :half_closed_remote
|
209
|
+
raise ProtocolError, "Cannot write response in #{@state}!"
|
210
|
+
end
|
211
|
+
|
141
212
|
# Safari WebSockets break if no reason is given:
|
142
213
|
@stream.write("#{version} #{status} #{reason}\r\n")
|
143
214
|
|
@@ -145,6 +216,10 @@ module Protocol
|
|
145
216
|
end
|
146
217
|
|
147
218
|
def write_interim_response(version, status, headers, reason = Reason::DESCRIPTIONS[status])
|
219
|
+
unless @state == :open or @state == :half_closed_remote
|
220
|
+
raise ProtocolError, "Cannot write interim response in #{@state}!"
|
221
|
+
end
|
222
|
+
|
148
223
|
@stream.write("#{version} #{status} #{reason}\r\n")
|
149
224
|
|
150
225
|
write_headers(headers)
|
@@ -173,6 +248,14 @@ module Protocol
|
|
173
248
|
end
|
174
249
|
end
|
175
250
|
|
251
|
+
def readpartial(length)
|
252
|
+
@stream.readpartial(length)
|
253
|
+
end
|
254
|
+
|
255
|
+
def read(length)
|
256
|
+
@stream.read(length)
|
257
|
+
end
|
258
|
+
|
176
259
|
def read_line?
|
177
260
|
@stream.gets(CRLF, chomp: true)
|
178
261
|
end
|
@@ -181,6 +264,10 @@ module Protocol
|
|
181
264
|
read_line? or raise EOFError
|
182
265
|
end
|
183
266
|
|
267
|
+
def close_read
|
268
|
+
@stream.close_read
|
269
|
+
end
|
270
|
+
|
184
271
|
def read_request_line
|
185
272
|
return unless line = read_line?
|
186
273
|
|
@@ -194,6 +281,8 @@ module Protocol
|
|
194
281
|
end
|
195
282
|
|
196
283
|
def read_request
|
284
|
+
open!
|
285
|
+
|
197
286
|
method, path, version = read_request_line
|
198
287
|
return unless method
|
199
288
|
|
@@ -203,6 +292,10 @@ module Protocol
|
|
203
292
|
|
204
293
|
body = read_request_body(method, headers)
|
205
294
|
|
295
|
+
unless body
|
296
|
+
self.receive_end_stream!
|
297
|
+
end
|
298
|
+
|
206
299
|
@count += 1
|
207
300
|
|
208
301
|
return headers.delete(HOST), method, path, version, headers, body
|
@@ -216,16 +309,30 @@ module Protocol
|
|
216
309
|
return version, status, reason
|
217
310
|
end
|
218
311
|
|
312
|
+
private def interim_status?(status)
|
313
|
+
status != 101 and status >= 100 and status < 200
|
314
|
+
end
|
315
|
+
|
219
316
|
def read_response(method)
|
317
|
+
unless @state == :open or @state == :half_closed_local
|
318
|
+
raise ProtocolError, "Cannot read response in #{@state}!"
|
319
|
+
end
|
320
|
+
|
220
321
|
version, status, reason = read_response_line
|
221
322
|
|
222
323
|
headers = read_headers
|
223
324
|
|
224
325
|
@persistent = persistent?(version, method, headers)
|
225
326
|
|
226
|
-
|
227
|
-
|
228
|
-
|
327
|
+
unless interim_status?(status)
|
328
|
+
body = read_response_body(method, status, headers)
|
329
|
+
|
330
|
+
unless body
|
331
|
+
self.receive_end_stream!
|
332
|
+
end
|
333
|
+
|
334
|
+
@count += 1
|
335
|
+
end
|
229
336
|
|
230
337
|
return version, status, reason, headers, body
|
231
338
|
end
|
@@ -383,6 +490,32 @@ module Protocol
|
|
383
490
|
@stream.close_write
|
384
491
|
end
|
385
492
|
|
493
|
+
def idle!
|
494
|
+
@state = :idle
|
495
|
+
end
|
496
|
+
|
497
|
+
def closed!
|
498
|
+
unless @state == :half_closed_local or @state == :half_closed_remote
|
499
|
+
raise ProtocolError, "Cannot close in #{@state}!"
|
500
|
+
end
|
501
|
+
|
502
|
+
if @persistent
|
503
|
+
self.idle!
|
504
|
+
else
|
505
|
+
@state = :closed
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
def send_end_stream!
|
510
|
+
if @state == :open
|
511
|
+
@state = :half_closed_local
|
512
|
+
elsif @state == :half_closed_remote
|
513
|
+
self.closed!
|
514
|
+
else
|
515
|
+
raise ProtocolError, "Cannot send end stream in #{@state}!"
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
386
519
|
def write_body(version, body, head = false, trailer = nil)
|
387
520
|
# HTTP/1.0 cannot in any case handle trailers.
|
388
521
|
if version == HTTP10 # or te: trailers was not present (strictly speaking not required.)
|
@@ -412,22 +545,37 @@ module Protocol
|
|
412
545
|
write_connection_header(version)
|
413
546
|
write_body_and_close(body, head)
|
414
547
|
end
|
548
|
+
ensure
|
549
|
+
send_end_stream!
|
550
|
+
end
|
551
|
+
|
552
|
+
def receive_end_stream!
|
553
|
+
if @state == :open
|
554
|
+
@state = :half_closed_remote
|
555
|
+
elsif @state == :half_closed_local
|
556
|
+
self.closed!
|
557
|
+
else
|
558
|
+
raise ProtocolError, "Cannot receive end stream in #{@state}!"
|
559
|
+
end
|
415
560
|
end
|
416
561
|
|
417
562
|
def read_chunked_body(headers)
|
418
|
-
Body::Chunked.new(
|
563
|
+
Body::Chunked.new(self, headers)
|
419
564
|
end
|
420
565
|
|
421
566
|
def read_fixed_body(length)
|
422
|
-
Body::Fixed.new(
|
567
|
+
Body::Fixed.new(self, length)
|
423
568
|
end
|
424
569
|
|
425
570
|
def read_remainder_body
|
426
571
|
@persistent = false
|
427
|
-
Body::Remainder.new(
|
572
|
+
Body::Remainder.new(self)
|
428
573
|
end
|
429
574
|
|
430
575
|
def read_head_body(length)
|
576
|
+
# We are not receiving any body:
|
577
|
+
self.receive_end_stream!
|
578
|
+
|
431
579
|
Protocol::HTTP::Body::Head.new(length)
|
432
580
|
end
|
433
581
|
|
data/lib/protocol/http1/error.rb
CHANGED
@@ -10,6 +10,10 @@ module Protocol
|
|
10
10
|
class Error < HTTP::Error
|
11
11
|
end
|
12
12
|
|
13
|
+
# The protocol was violated in some way, e.g. trying to write a request while reading a response.
|
14
|
+
class ProtocolError < Error
|
15
|
+
end
|
16
|
+
|
13
17
|
# The request was not able to be parsed correctly, or failed some kind of validation.
|
14
18
|
class BadRequest < Error
|
15
19
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: protocol-http1
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.24.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -42,7 +42,7 @@ cert_chain:
|
|
42
42
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
43
43
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
44
44
|
-----END CERTIFICATE-----
|
45
|
-
date: 2024-09-
|
45
|
+
date: 2024-09-18 00:00:00.000000000 Z
|
46
46
|
dependencies:
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: protocol-http
|
metadata.gz.sig
CHANGED
Binary file
|