protocol-http 0.66.0 → 0.68.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/http/header/range.rb +165 -0
- data/lib/protocol/http/headers.rb +2 -1
- data/lib/protocol/http/status.rb +89 -0
- data/lib/protocol/http/version.rb +1 -1
- data/lib/protocol/http.rb +1 -0
- data/readme.md +8 -10
- data/releases.md +8 -0
- data.tar.gz.sig +0 -0
- metadata +3 -1
- 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: a3428056fc04e13182008e3f50f457e9b63b688c887c528e4afc8606e447c620
|
|
4
|
+
data.tar.gz: e1c5a2b380ff917013e2cdf1e8c6bda8a4995d3850fa960bf933177ebc518295
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6a0554e8f574add7c898db51ecccce34def388ba266c648487ef6f538c0db0d99966f2ee0c15d1462088587e29676ddf94f07531c257326442320cd2d30ab157
|
|
7
|
+
data.tar.gz: 4acaacb6205bc8dcdd5be3ed7a5993dff2012b7ac61ad3a8a1e6ea607c1f01b481218ef3b60efa6bc34a9a86f982077aabe9ec7bafe43b8659b9272cb1e11d6a
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "../error"
|
|
7
|
+
|
|
8
|
+
module Protocol
|
|
9
|
+
module HTTP
|
|
10
|
+
module Header
|
|
11
|
+
# Represents a `range` request header.
|
|
12
|
+
class Range
|
|
13
|
+
ParseError = Class.new(Error)
|
|
14
|
+
|
|
15
|
+
TOKEN = /[!#$%&'*+\-.0-9A-Z^_`a-z|~]+/
|
|
16
|
+
HEADER = /\A(?<unit>#{TOKEN})=(?<ranges>.*)\z/
|
|
17
|
+
BYTE_RANGE = /\A(?:(?<first>\d+)-(?<last>\d*)|-(?<suffix>\d+))\z/
|
|
18
|
+
OTHER_RANGE = /\A[\x21-\x2B\x2D-\x7E]+\z/
|
|
19
|
+
SEPARATOR = /\s*,\s*/
|
|
20
|
+
|
|
21
|
+
# Represents one byte-range-spec or suffix-byte-range-spec.
|
|
22
|
+
ByteRange = Struct.new(:first, :last) do
|
|
23
|
+
# Parse one byte range.
|
|
24
|
+
# @parameter value [String] The byte range to parse.
|
|
25
|
+
# @returns [ByteRange] The parsed byte range.
|
|
26
|
+
def self.parse(value)
|
|
27
|
+
unless match = BYTE_RANGE.match(value)
|
|
28
|
+
raise ParseError, "Invalid byte range: #{value.inspect}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if suffix = match[:suffix]
|
|
32
|
+
return self.new(nil, Integer(suffix))
|
|
33
|
+
else
|
|
34
|
+
first = Integer(match[:first])
|
|
35
|
+
last = match[:last]
|
|
36
|
+
last = last.empty? ? nil : Integer(last)
|
|
37
|
+
|
|
38
|
+
if last && last < first
|
|
39
|
+
raise ParseError, "Invalid byte range: #{value.inspect}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
return self.new(first, last)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Resolve this byte range against the selected representation size.
|
|
47
|
+
# @parameter size [Integer] The size of the selected representation.
|
|
48
|
+
# @returns [::Range | Nil] The resolved range, or `nil` when it is unsatisfiable.
|
|
49
|
+
def resolve(size)
|
|
50
|
+
if first
|
|
51
|
+
if first < size
|
|
52
|
+
return first..[last || size - 1, size - 1].min
|
|
53
|
+
end
|
|
54
|
+
elsif last > 0 && size > 0
|
|
55
|
+
return [0, size - last].max..size - 1
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
return nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Convert this byte range to its wire representation.
|
|
62
|
+
# @returns [String] The serialized byte range.
|
|
63
|
+
def to_s
|
|
64
|
+
if first
|
|
65
|
+
"#{first}-#{last}"
|
|
66
|
+
else
|
|
67
|
+
"-#{last}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Parse a raw range header value.
|
|
73
|
+
# @parameter value [String] The raw header value.
|
|
74
|
+
# @returns [Range] The parsed range header.
|
|
75
|
+
def self.parse(value)
|
|
76
|
+
unless match = HEADER.match(value)
|
|
77
|
+
raise ParseError, "Invalid range header: #{value.inspect}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
unit = match[:unit].downcase
|
|
81
|
+
ranges = match[:ranges].split(SEPARATOR, -1)
|
|
82
|
+
|
|
83
|
+
if ranges.empty? || ranges.any?(&:empty?)
|
|
84
|
+
raise ParseError, "Invalid range set: #{match[:ranges].inspect}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if unit == "bytes"
|
|
88
|
+
ranges.map!{|range| ByteRange.parse(range)}
|
|
89
|
+
elsif ranges.any?{|range| !OTHER_RANGE.match?(range)}
|
|
90
|
+
raise ParseError, "Invalid range set: #{match[:ranges].inspect}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
return self.new(unit, ranges)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Coerce a value into a range header.
|
|
97
|
+
# @parameter value [Object] The value to coerce.
|
|
98
|
+
# @returns [Range] The parsed range header.
|
|
99
|
+
def self.coerce(value)
|
|
100
|
+
self.parse(value.to_s)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Initialize a range header.
|
|
104
|
+
# @parameter unit [String] The range unit.
|
|
105
|
+
# @parameter ranges [Array] The range specifiers.
|
|
106
|
+
def initialize(unit, ranges)
|
|
107
|
+
@unit = unit
|
|
108
|
+
@ranges = ranges
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @attribute [String] The range unit.
|
|
112
|
+
attr :unit
|
|
113
|
+
|
|
114
|
+
# @attribute [Array] The range specifiers.
|
|
115
|
+
attr :ranges
|
|
116
|
+
|
|
117
|
+
# Whether this header contains byte ranges.
|
|
118
|
+
# @returns [Boolean] Whether the range unit is `bytes`.
|
|
119
|
+
def bytes?
|
|
120
|
+
@unit == "bytes"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Resolve all byte ranges against the selected representation size.
|
|
124
|
+
# @parameter size [Integer] The size of the selected representation.
|
|
125
|
+
# @returns [Array(::Range)] The satisfiable byte ranges.
|
|
126
|
+
def resolve(size)
|
|
127
|
+
unless bytes?
|
|
128
|
+
raise ArgumentError, "Cannot resolve #{@unit.inspect} ranges as byte ranges!"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
size = Integer(size)
|
|
132
|
+
raise ArgumentError, "Size must not be negative!" if size < 0
|
|
133
|
+
|
|
134
|
+
@ranges.filter_map{|range| range.resolve(size)}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Combine another raw range header value with this one.
|
|
138
|
+
# @parameter value [String] The raw range header value.
|
|
139
|
+
def <<(value)
|
|
140
|
+
other = self.class.parse(value)
|
|
141
|
+
|
|
142
|
+
unless other.unit == @unit
|
|
143
|
+
raise ParseError, "Cannot combine range units: #{@unit.inspect} and #{other.unit.inspect}"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
@ranges.concat(other.ranges)
|
|
147
|
+
|
|
148
|
+
return self
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Convert this header to its wire representation.
|
|
152
|
+
# @returns [String] The serialized range header.
|
|
153
|
+
def to_s
|
|
154
|
+
"#{@unit}=#{@ranges.join(",")}"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Whether this header is acceptable in HTTP trailers.
|
|
158
|
+
# @returns [Boolean] `false`, as range headers apply to a selected representation.
|
|
159
|
+
def self.trailer?
|
|
160
|
+
false
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -18,6 +18,7 @@ require_relative "header/vary"
|
|
|
18
18
|
require_relative "header/authorization"
|
|
19
19
|
require_relative "header/date"
|
|
20
20
|
require_relative "header/priority"
|
|
21
|
+
require_relative "header/range"
|
|
21
22
|
require_relative "header/trailer"
|
|
22
23
|
require_relative "header/server_timing"
|
|
23
24
|
require_relative "header/digest"
|
|
@@ -347,7 +348,7 @@ module Protocol
|
|
|
347
348
|
"host" => false,
|
|
348
349
|
"location" => false,
|
|
349
350
|
"max-forwards" => false,
|
|
350
|
-
"range" =>
|
|
351
|
+
"range" => Header::Range,
|
|
351
352
|
"referer" => false,
|
|
352
353
|
"retry-after" => false,
|
|
353
354
|
"server" => false,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
module Protocol
|
|
7
|
+
module HTTP
|
|
8
|
+
# HTTP status codes and their human-readable descriptions.
|
|
9
|
+
module Status
|
|
10
|
+
# Human-readable descriptions for registered and conventional HTTP status codes.
|
|
11
|
+
DESCRIPTIONS = {
|
|
12
|
+
100 => "Continue",
|
|
13
|
+
101 => "Switching Protocols",
|
|
14
|
+
102 => "Processing",
|
|
15
|
+
103 => "Early Hints",
|
|
16
|
+
|
|
17
|
+
200 => "OK",
|
|
18
|
+
201 => "Created",
|
|
19
|
+
202 => "Accepted",
|
|
20
|
+
203 => "Non-Authoritative Information",
|
|
21
|
+
204 => "No Content",
|
|
22
|
+
205 => "Reset Content",
|
|
23
|
+
206 => "Partial Content",
|
|
24
|
+
207 => "Multi-Status",
|
|
25
|
+
208 => "Already Reported",
|
|
26
|
+
226 => "IM Used",
|
|
27
|
+
|
|
28
|
+
300 => "Multiple Choices",
|
|
29
|
+
301 => "Moved Permanently",
|
|
30
|
+
302 => "Found",
|
|
31
|
+
303 => "See Other",
|
|
32
|
+
304 => "Not Modified",
|
|
33
|
+
305 => "Use Proxy",
|
|
34
|
+
306 => "Switch Proxy",
|
|
35
|
+
307 => "Temporary Redirect",
|
|
36
|
+
308 => "Permanent Redirect",
|
|
37
|
+
|
|
38
|
+
400 => "Bad Request",
|
|
39
|
+
401 => "Unauthorized",
|
|
40
|
+
402 => "Payment Required",
|
|
41
|
+
403 => "Forbidden",
|
|
42
|
+
404 => "Not Found",
|
|
43
|
+
405 => "Method Not Allowed",
|
|
44
|
+
406 => "Not Acceptable",
|
|
45
|
+
407 => "Proxy Authentication Required",
|
|
46
|
+
408 => "Request Timeout",
|
|
47
|
+
409 => "Conflict",
|
|
48
|
+
410 => "Gone",
|
|
49
|
+
411 => "Length Required",
|
|
50
|
+
412 => "Precondition Failed",
|
|
51
|
+
413 => "Content Too Large",
|
|
52
|
+
414 => "URI Too Long",
|
|
53
|
+
415 => "Unsupported Media Type",
|
|
54
|
+
416 => "Range Not Satisfiable",
|
|
55
|
+
417 => "Expectation Failed",
|
|
56
|
+
418 => "I'm a Teapot",
|
|
57
|
+
421 => "Misdirected Request",
|
|
58
|
+
422 => "Unprocessable Content",
|
|
59
|
+
423 => "Locked",
|
|
60
|
+
424 => "Failed Dependency",
|
|
61
|
+
425 => "Too Early",
|
|
62
|
+
426 => "Upgrade Required",
|
|
63
|
+
428 => "Precondition Required",
|
|
64
|
+
429 => "Too Many Requests",
|
|
65
|
+
431 => "Request Header Fields Too Large",
|
|
66
|
+
451 => "Unavailable For Legal Reasons",
|
|
67
|
+
|
|
68
|
+
500 => "Internal Server Error",
|
|
69
|
+
501 => "Not Implemented",
|
|
70
|
+
502 => "Bad Gateway",
|
|
71
|
+
503 => "Service Unavailable",
|
|
72
|
+
504 => "Gateway Timeout",
|
|
73
|
+
505 => "HTTP Version Not Supported",
|
|
74
|
+
506 => "Variant Also Negotiates",
|
|
75
|
+
507 => "Insufficient Storage",
|
|
76
|
+
508 => "Loop Detected",
|
|
77
|
+
510 => "Not Extended",
|
|
78
|
+
511 => "Network Authentication Required",
|
|
79
|
+
}.freeze
|
|
80
|
+
|
|
81
|
+
# Look up the human-readable description for a status code.
|
|
82
|
+
# @parameter code [Integer] The HTTP status code.
|
|
83
|
+
# @returns [String | Nil] The standard description, if known.
|
|
84
|
+
def self.description(code)
|
|
85
|
+
return DESCRIPTIONS[code]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
data/lib/protocol/http.rb
CHANGED
data/readme.md
CHANGED
|
@@ -30,6 +30,14 @@ Please see the [project documentation](https://socketry.github.io/protocol-http/
|
|
|
30
30
|
|
|
31
31
|
Please see the [project releases](https://socketry.github.io/protocol-http/releases/index) for all releases.
|
|
32
32
|
|
|
33
|
+
### v0.68.0
|
|
34
|
+
|
|
35
|
+
- Add HTTP status descriptions.
|
|
36
|
+
|
|
37
|
+
### v0.67.0
|
|
38
|
+
|
|
39
|
+
- Parse and resolve HTTP `Range` header values according to the default headers policy.
|
|
40
|
+
|
|
33
41
|
### v0.66.0
|
|
34
42
|
|
|
35
43
|
- Introduce `Protocol::HTTP::RemoteError` for remote endpoint failures where application processing may have occurred.
|
|
@@ -64,16 +72,6 @@ Please see the [project releases](https://socketry.github.io/protocol-http/relea
|
|
|
64
72
|
- Introduce `Protocol::HTTP::Middleware.load` method for loading middleware applications from files.
|
|
65
73
|
- Prevent `ZLib::BufError` when deflating empty chunks by skipping deflation for empty chunks.
|
|
66
74
|
|
|
67
|
-
### v0.58.1
|
|
68
|
-
|
|
69
|
-
- `Protocol::HTTP::DuplicateHeaderError` now includes the existing and new values for better debugging.
|
|
70
|
-
|
|
71
|
-
### v0.58.0
|
|
72
|
-
|
|
73
|
-
- Move trailer validation to `Headers#add` method to ensure all additions are checked at the time of addition as this is a hard requirement.
|
|
74
|
-
- Introduce `Headers#header` method to enumerate only the main headers, excluding trailers. This can be used after invoking `Headers#trailer!` to avoid race conditions.
|
|
75
|
-
- Fix `Headers#to_h` so that indexed headers are not left in an inconsistent state if errors occur during processing.
|
|
76
|
-
|
|
77
75
|
## See Also
|
|
78
76
|
|
|
79
77
|
- [protocol-http1](https://github.com/socketry/protocol-http1) — HTTP/1 client/server implementation using this
|
data/releases.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.68.0
|
|
4
|
+
|
|
5
|
+
- Add HTTP status descriptions.
|
|
6
|
+
|
|
7
|
+
## v0.67.0
|
|
8
|
+
|
|
9
|
+
- Parse and resolve HTTP `Range` header values according to the default headers policy.
|
|
10
|
+
|
|
3
11
|
## v0.66.0
|
|
4
12
|
|
|
5
13
|
- Introduce `Protocol::HTTP::RemoteError` for remote endpoint failures where application processing may have occurred.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: protocol-http
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.68.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -97,6 +97,7 @@ files:
|
|
|
97
97
|
- lib/protocol/http/header/generic.rb
|
|
98
98
|
- lib/protocol/http/header/multiple.rb
|
|
99
99
|
- lib/protocol/http/header/priority.rb
|
|
100
|
+
- lib/protocol/http/header/range.rb
|
|
100
101
|
- lib/protocol/http/header/server_timing.rb
|
|
101
102
|
- lib/protocol/http/header/set_cookie.rb
|
|
102
103
|
- lib/protocol/http/header/split.rb
|
|
@@ -112,6 +113,7 @@ files:
|
|
|
112
113
|
- lib/protocol/http/quoted_string.rb
|
|
113
114
|
- lib/protocol/http/request.rb
|
|
114
115
|
- lib/protocol/http/response.rb
|
|
116
|
+
- lib/protocol/http/status.rb
|
|
115
117
|
- lib/protocol/http/version.rb
|
|
116
118
|
- license.md
|
|
117
119
|
- readme.md
|
metadata.gz.sig
CHANGED
|
Binary file
|