protocol-http1 0.23.0 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|