protocol-http 0.45.0 → 0.47.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/http/accept_encoding.rb +15 -3
- data/lib/protocol/http/body/buffered.rb +29 -2
- data/lib/protocol/http/body/completable.rb +13 -0
- data/lib/protocol/http/body/deflate.rb +33 -0
- data/lib/protocol/http/body/digestable.rb +19 -4
- data/lib/protocol/http/body/file.rb +37 -1
- data/lib/protocol/http/body/head.rb +8 -0
- data/lib/protocol/http/body/inflate.rb +10 -2
- data/lib/protocol/http/body/readable.rb +32 -11
- data/lib/protocol/http/body/reader.rb +17 -0
- data/lib/protocol/http/body/rewindable.rb +19 -1
- data/lib/protocol/http/body/stream.rb +34 -6
- data/lib/protocol/http/body/streamable.rb +46 -5
- data/lib/protocol/http/body/wrapper.rb +25 -3
- data/lib/protocol/http/body/writable.rb +48 -7
- data/lib/protocol/http/body.rb +16 -0
- data/lib/protocol/http/content_encoding.rb +13 -3
- data/lib/protocol/http/cookie.rb +23 -0
- data/lib/protocol/http/header/authorization.rb +10 -2
- data/lib/protocol/http/header/cache_control.rb +42 -10
- data/lib/protocol/http/header/connection.rb +18 -1
- data/lib/protocol/http/header/cookie.rb +10 -3
- data/lib/protocol/http/header/date.rb +9 -0
- data/lib/protocol/http/header/etag.rb +11 -0
- data/lib/protocol/http/header/etags.rb +35 -4
- data/lib/protocol/http/header/multiple.rb +9 -1
- data/lib/protocol/http/header/priority.rb +55 -0
- data/lib/protocol/http/header/split.rb +17 -3
- data/lib/protocol/http/header/vary.rb +10 -1
- data/lib/protocol/http/headers.rb +82 -19
- data/lib/protocol/http/methods.rb +5 -0
- data/lib/protocol/http/middleware/builder.rb +17 -0
- data/lib/protocol/http/middleware.rb +28 -0
- data/lib/protocol/http/peer.rb +9 -0
- data/lib/protocol/http/reference.rb +33 -6
- data/lib/protocol/http/request.rb +25 -0
- data/lib/protocol/http/response.rb +12 -0
- data/lib/protocol/http/url.rb +34 -8
- data/lib/protocol/http/version.rb +1 -1
- data/readme.md +4 -0
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +4 -2
- metadata.gz.sig +0 -0
@@ -12,19 +12,30 @@ module Protocol
|
|
12
12
|
module HTTP
|
13
13
|
# Encode a response according the the request's acceptable encodings.
|
14
14
|
class ContentEncoding < Middleware
|
15
|
+
# The default wrappers to use for encoding content.
|
15
16
|
DEFAULT_WRAPPERS = {
|
16
17
|
"gzip" => Body::Deflate.method(:for)
|
17
18
|
}
|
18
19
|
|
20
|
+
# The default content types to apply encoding to.
|
19
21
|
DEFAULT_CONTENT_TYPES = %r{^(text/.*?)|(.*?/json)|(.*?/javascript)$}
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
+
# Initialize the content encoding middleware.
|
24
|
+
#
|
25
|
+
# @parameter delegate [Middleware] The next middleware in the chain.
|
26
|
+
# @parameter content_types [Regexp] The content types to apply encoding to.
|
27
|
+
# @parameter wrappers [Hash] The encoding wrappers to use.
|
28
|
+
def initialize(delegate, content_types = DEFAULT_CONTENT_TYPES, wrappers = DEFAULT_WRAPPERS)
|
29
|
+
super(delegate)
|
23
30
|
|
24
31
|
@content_types = content_types
|
25
32
|
@wrappers = wrappers
|
26
33
|
end
|
27
34
|
|
35
|
+
# Encode the response body according to the request's acceptable encodings.
|
36
|
+
#
|
37
|
+
# @parameter request [Request] The request.
|
38
|
+
# @returns [Response] The response.
|
28
39
|
def call(request)
|
29
40
|
response = super
|
30
41
|
|
@@ -40,7 +51,6 @@ module Protocol
|
|
40
51
|
|
41
52
|
# TODO use http-accept and sort by priority
|
42
53
|
if !response.body.empty? and accept_encoding = request.headers["accept-encoding"]
|
43
|
-
|
44
54
|
if content_type = response.headers["content-type"] and @content_types =~ content_type
|
45
55
|
body = response.body
|
46
56
|
|
data/lib/protocol/http/cookie.rb
CHANGED
@@ -10,24 +10,39 @@ module Protocol
|
|
10
10
|
module HTTP
|
11
11
|
# Represents an individual cookie key-value pair.
|
12
12
|
class Cookie
|
13
|
+
# Initialize the cookie with the given name, value, and directives.
|
14
|
+
#
|
15
|
+
# @parameter name [String] The name of the cookiel, e.g. "session_id".
|
16
|
+
# @parameter value [String] The value of the cookie, e.g. "1234".
|
17
|
+
# @parameter directives [Hash] The directives of the cookie, e.g. `{"path" => "/"}`.
|
13
18
|
def initialize(name, value, directives)
|
14
19
|
@name = name
|
15
20
|
@value = value
|
16
21
|
@directives = directives
|
17
22
|
end
|
18
23
|
|
24
|
+
# @attribute [String] The name of the cookie.
|
19
25
|
attr :name
|
26
|
+
|
27
|
+
# @attribute [String] The value of the cookie.
|
20
28
|
attr :value
|
29
|
+
|
30
|
+
# @attribute [Hash] The directives of the cookie.
|
21
31
|
attr :directives
|
22
32
|
|
33
|
+
# Encode the name of the cookie.
|
23
34
|
def encoded_name
|
24
35
|
URL.escape(@name)
|
25
36
|
end
|
26
37
|
|
38
|
+
# Encode the value of the cookie.
|
27
39
|
def encoded_value
|
28
40
|
URL.escape(@value)
|
29
41
|
end
|
30
42
|
|
43
|
+
# Convert the cookie to a string.
|
44
|
+
#
|
45
|
+
# @returns [String] The string representation of the cookie.
|
31
46
|
def to_s
|
32
47
|
buffer = String.new.b
|
33
48
|
|
@@ -49,6 +64,10 @@ module Protocol
|
|
49
64
|
return buffer
|
50
65
|
end
|
51
66
|
|
67
|
+
# Parse a string into a cookie.
|
68
|
+
#
|
69
|
+
# @parameter string [String] The string to parse.
|
70
|
+
# @returns [Cookie] The parsed cookie.
|
52
71
|
def self.parse(string)
|
53
72
|
head, *directives = string.split(/\s*;\s*/)
|
54
73
|
|
@@ -62,6 +81,10 @@ module Protocol
|
|
62
81
|
)
|
63
82
|
end
|
64
83
|
|
84
|
+
# Parse a list of strings into a hash of directives.
|
85
|
+
#
|
86
|
+
# @parameter strings [Array(String)] The list of strings to parse.
|
87
|
+
# @returns [Hash] The hash of directives.
|
65
88
|
def self.parse_directives(strings)
|
66
89
|
strings.collect do |string|
|
67
90
|
key, value = string.split("=", 2)
|
@@ -12,13 +12,21 @@ module Protocol
|
|
12
12
|
# ~~~ ruby
|
13
13
|
# headers.add('authorization', Authorization.basic("my_username", "my_password"))
|
14
14
|
# ~~~
|
15
|
+
#
|
16
|
+
# TODO Support other authorization mechanisms, e.g. bearer token.
|
15
17
|
class Authorization < String
|
16
|
-
# Splits the header
|
17
|
-
#
|
18
|
+
# Splits the header into the credentials.
|
19
|
+
#
|
20
|
+
# @returns [Tuple(String, String)] The username and password.
|
18
21
|
def credentials
|
19
22
|
self.split(/\s+/, 2)
|
20
23
|
end
|
21
24
|
|
25
|
+
# Generate a new basic authorization header, encoding the given username and password.
|
26
|
+
#
|
27
|
+
# @parameter username [String] The username.
|
28
|
+
# @parameter password [String] The password.
|
29
|
+
# @returns [Authorization] The basic authorization header.
|
22
30
|
def self.basic(username, password)
|
23
31
|
strict_base64_encoded = ["#{username}:#{password}"].pack("m0")
|
24
32
|
|
@@ -9,86 +9,118 @@ require_relative "split"
|
|
9
9
|
module Protocol
|
10
10
|
module HTTP
|
11
11
|
module Header
|
12
|
+
# Represents the `cache-control` header, which is a list of cache directives.
|
12
13
|
class CacheControl < Split
|
14
|
+
# The `private` directive indicates that the response is intended for a single user and must not be stored by shared caches.
|
13
15
|
PRIVATE = "private"
|
16
|
+
|
17
|
+
# The `public` directive indicates that the response may be stored by any cache, even if it would normally be considered non-cacheable.
|
14
18
|
PUBLIC = "public"
|
19
|
+
|
20
|
+
# The `no-cache` directive indicates that caches must revalidate the response with the origin server before serving it to clients.
|
15
21
|
NO_CACHE = "no-cache"
|
22
|
+
|
23
|
+
# The `no-store` directive indicates that caches must not store the response under any circumstances.
|
16
24
|
NO_STORE = "no-store"
|
25
|
+
|
26
|
+
# The `max-age` directive indicates the maximum amount of time, in seconds, that a response is considered fresh.
|
17
27
|
MAX_AGE = "max-age"
|
28
|
+
|
29
|
+
# The `s-maxage` directive is similar to `max-age` but applies only to shared caches. If both `s-maxage` and `max-age` are present, `s-maxage` takes precedence in shared caches.
|
18
30
|
S_MAXAGE = "s-maxage"
|
19
31
|
|
32
|
+
# The `static` directive is a custom directive often used to indicate that the resource is immutable or rarely changes, allowing longer caching periods.
|
20
33
|
STATIC = "static"
|
34
|
+
|
35
|
+
# The `dynamic` directive is a custom directive used to indicate that the resource is generated dynamically and may change frequently, requiring shorter caching periods.
|
21
36
|
DYNAMIC = "dynamic"
|
37
|
+
|
38
|
+
# The `streaming` directive is a custom directive used to indicate that the resource is intended for progressive or chunked delivery, such as live video streams.
|
22
39
|
STREAMING = "streaming"
|
23
40
|
|
41
|
+
# The `must-revalidate` directive indicates that once a response becomes stale, caches must not use it to satisfy subsequent requests without revalidating it with the origin server.
|
24
42
|
MUST_REVALIDATE = "must-revalidate"
|
43
|
+
|
44
|
+
# The `proxy-revalidate` directive is similar to `must-revalidate` but applies only to shared caches.
|
25
45
|
PROXY_REVALIDATE = "proxy-revalidate"
|
26
46
|
|
47
|
+
# Initializes the cache control header with the given value. The value is expected to be a comma-separated string of cache directives.
|
48
|
+
#
|
49
|
+
# @parameter value [String | Nil] the raw Cache-Control header value.
|
27
50
|
def initialize(value = nil)
|
28
51
|
super(value&.downcase)
|
29
52
|
end
|
30
53
|
|
54
|
+
# Adds a directive to the Cache-Control header. The value will be normalized to lowercase before being added.
|
55
|
+
#
|
56
|
+
# @parameter value [String] the directive to add.
|
31
57
|
def << value
|
32
58
|
super(value.downcase)
|
33
59
|
end
|
34
60
|
|
61
|
+
# @returns [Boolean] whether the `static` directive is present.
|
35
62
|
def static?
|
36
63
|
self.include?(STATIC)
|
37
64
|
end
|
38
65
|
|
66
|
+
# @returns [Boolean] whether the `dynamic` directive is present.
|
39
67
|
def dynamic?
|
40
68
|
self.include?(DYNAMIC)
|
41
69
|
end
|
42
70
|
|
71
|
+
# @returns [Boolean] whether the `streaming` directive is present.
|
43
72
|
def streaming?
|
44
73
|
self.include?(STREAMING)
|
45
74
|
end
|
46
75
|
|
76
|
+
# @returns [Boolean] whether the `private` directive is present.
|
47
77
|
def private?
|
48
78
|
self.include?(PRIVATE)
|
49
79
|
end
|
50
80
|
|
81
|
+
# @returns [Boolean] whether the `public` directive is present.
|
51
82
|
def public?
|
52
83
|
self.include?(PUBLIC)
|
53
84
|
end
|
54
85
|
|
86
|
+
# @returns [Boolean] whether the `no-cache` directive is present.
|
55
87
|
def no_cache?
|
56
88
|
self.include?(NO_CACHE)
|
57
89
|
end
|
58
90
|
|
91
|
+
# @returns [Boolean] whether the `no-store` directive is present.
|
59
92
|
def no_store?
|
60
93
|
self.include?(NO_STORE)
|
61
94
|
end
|
62
95
|
|
63
|
-
#
|
64
|
-
# See https://www.rfc-editor.org/rfc/rfc9111.html#name-must-revalidate
|
96
|
+
# @returns [Boolean] whether the `must-revalidate` directive is present.
|
65
97
|
def must_revalidate?
|
66
98
|
self.include?(MUST_REVALIDATE)
|
67
99
|
end
|
68
100
|
|
69
|
-
#
|
70
|
-
# See https://www.rfc-editor.org/rfc/rfc9111.html#name-proxy-revalidate
|
101
|
+
# @returns [Boolean] whether the `proxy-revalidate` directive is present.
|
71
102
|
def proxy_revalidate?
|
72
103
|
self.include?(PROXY_REVALIDATE)
|
73
104
|
end
|
74
105
|
|
75
|
-
#
|
76
|
-
# See https://www.rfc-editor.org/rfc/rfc9111.html#name-max-age-2
|
106
|
+
# @returns [Integer | Nil] the value of the `max-age` directive in seconds, or `nil` if the directive is not present or invalid.
|
77
107
|
def max_age
|
78
108
|
find_integer_value(MAX_AGE)
|
79
109
|
end
|
80
110
|
|
81
|
-
#
|
82
|
-
# max-age when present.
|
83
|
-
# See https://www.rfc-editor.org/rfc/rfc9111.html#name-s-maxage
|
111
|
+
# @returns [Integer | Nil] the value of the `s-maxage` directive in seconds, or `nil` if the directive is not present or invalid.
|
84
112
|
def s_maxage
|
85
113
|
find_integer_value(S_MAXAGE)
|
86
114
|
end
|
87
115
|
|
88
116
|
private
|
89
117
|
|
118
|
+
# Finds and parses an integer value from a directive.
|
119
|
+
#
|
120
|
+
# @parameter value_name [String] the directive name to search for (e.g., "max-age").
|
121
|
+
# @returns [Integer | Nil] the parsed integer value, or `nil` if not found or invalid.
|
90
122
|
def find_integer_value(value_name)
|
91
|
-
if value = self.find{|value| value.start_with?(value_name)}
|
123
|
+
if value = self.find { |value| value.start_with?(value_name) }
|
92
124
|
_, age = value.split("=", 2)
|
93
125
|
|
94
126
|
if age =~ /\A[0-9]+\z/
|
@@ -9,31 +9,48 @@ require_relative "split"
|
|
9
9
|
module Protocol
|
10
10
|
module HTTP
|
11
11
|
module Header
|
12
|
+
# Represents the `connection` HTTP header, which controls options for the current connection.
|
13
|
+
#
|
14
|
+
# The `connection` header is used to specify control options such as whether the connection should be kept alive, closed, or upgraded to a different protocol.
|
12
15
|
class Connection < Split
|
16
|
+
# The `keep-alive` directive indicates that the connection should remain open for future requests or responses, avoiding the overhead of opening a new connection.
|
13
17
|
KEEP_ALIVE = "keep-alive"
|
18
|
+
|
19
|
+
# The `close` directive indicates that the connection should be closed after the current request and response are complete.
|
14
20
|
CLOSE = "close"
|
21
|
+
|
22
|
+
# The `upgrade` directive indicates that the connection should be upgraded to a different protocol, as specified in the `Upgrade` header.
|
15
23
|
UPGRADE = "upgrade"
|
16
24
|
|
25
|
+
# Initializes the connection header with the given value. The value is expected to be a comma-separated string of directives.
|
26
|
+
#
|
27
|
+
# @parameter value [String | Nil] the raw `connection` header value.
|
17
28
|
def initialize(value = nil)
|
18
29
|
super(value&.downcase)
|
19
30
|
end
|
20
31
|
|
32
|
+
# Adds a directive to the `connection` header. The value will be normalized to lowercase before being added.
|
33
|
+
#
|
34
|
+
# @parameter value [String] the directive to add.
|
21
35
|
def << value
|
22
36
|
super(value.downcase)
|
23
37
|
end
|
24
38
|
|
39
|
+
# @returns [Boolean] whether the `keep-alive` directive is present and the connection is not marked for closure with the `close` directive.
|
25
40
|
def keep_alive?
|
26
41
|
self.include?(KEEP_ALIVE) && !close?
|
27
42
|
end
|
28
43
|
|
44
|
+
# @returns [Boolean] whether the `close` directive is present, indicating that the connection should be closed after the current request and response.
|
29
45
|
def close?
|
30
46
|
self.include?(CLOSE)
|
31
47
|
end
|
32
48
|
|
49
|
+
# @returns [Boolean] whether the `upgrade` directive is present, indicating that the connection should be upgraded to a different protocol.
|
33
50
|
def upgrade?
|
34
51
|
self.include?(UPGRADE)
|
35
52
|
end
|
36
53
|
end
|
37
54
|
end
|
38
55
|
end
|
39
|
-
end
|
56
|
+
end
|
@@ -9,8 +9,13 @@ require_relative "../cookie"
|
|
9
9
|
module Protocol
|
10
10
|
module HTTP
|
11
11
|
module Header
|
12
|
-
# The
|
12
|
+
# The `cookie` header contains stored HTTP cookies previously sent by the server with the `set-cookie` header.
|
13
|
+
#
|
14
|
+
# It is used by clients to send key-value pairs representing stored cookies back to the server.
|
13
15
|
class Cookie < Multiple
|
16
|
+
# Parses the `cookie` header into a hash of cookie names and their corresponding cookie objects.
|
17
|
+
#
|
18
|
+
# @returns [Hash(String, HTTP::Cookie)] a hash where keys are cookie names and values are {HTTP::Cookie} objects.
|
14
19
|
def to_h
|
15
20
|
cookies = self.collect do |string|
|
16
21
|
HTTP::Cookie.parse(string)
|
@@ -20,9 +25,11 @@ module Protocol
|
|
20
25
|
end
|
21
26
|
end
|
22
27
|
|
23
|
-
# The
|
28
|
+
# The `set-cookie` header sends cookies from the server to the user agent.
|
29
|
+
#
|
30
|
+
# It is used to store cookies on the client side, which are then sent back to the server in subsequent requests using the `cookie` header.
|
24
31
|
class SetCookie < Cookie
|
25
32
|
end
|
26
33
|
end
|
27
34
|
end
|
28
|
-
end
|
35
|
+
end
|
@@ -8,11 +8,20 @@ require "time"
|
|
8
8
|
module Protocol
|
9
9
|
module HTTP
|
10
10
|
module Header
|
11
|
+
# The `date` header represents the date and time at which the message was originated.
|
12
|
+
#
|
13
|
+
# This header is typically included in HTTP responses and follows the format defined in RFC 9110.
|
11
14
|
class Date < String
|
15
|
+
# Replaces the current value of the `date` header with the specified value.
|
16
|
+
#
|
17
|
+
# @parameter value [String] the new value for the `date` header.
|
12
18
|
def << value
|
13
19
|
replace(value)
|
14
20
|
end
|
15
21
|
|
22
|
+
# Converts the `date` header value to a `Time` object.
|
23
|
+
#
|
24
|
+
# @returns [Time] the parsed time object corresponding to the `date` header value.
|
16
25
|
def to_time
|
17
26
|
::Time.parse(self)
|
18
27
|
end
|
@@ -6,11 +6,22 @@
|
|
6
6
|
module Protocol
|
7
7
|
module HTTP
|
8
8
|
module Header
|
9
|
+
# The `etag` header represents the entity tag for a resource.
|
10
|
+
#
|
11
|
+
# The `etag` header provides a unique identifier for a specific version of a resource, typically used for cache validation or conditional requests. It can be either a strong or weak validator as defined in RFC 9110.
|
9
12
|
class ETag < String
|
13
|
+
# Replaces the current value of the `etag` header with the specified value.
|
14
|
+
#
|
15
|
+
# @parameter value [String] the new value for the `etag` header.
|
10
16
|
def << value
|
11
17
|
replace(value)
|
12
18
|
end
|
13
19
|
|
20
|
+
# Checks whether the `etag` is a weak validator.
|
21
|
+
#
|
22
|
+
# Weak validators indicate semantically equivalent content but may not be byte-for-byte identical.
|
23
|
+
#
|
24
|
+
# @returns [Boolean] whether the `etag` is weak.
|
14
25
|
def weak?
|
15
26
|
self.start_with?("W/")
|
16
27
|
end
|
@@ -9,32 +9,63 @@ require_relative "split"
|
|
9
9
|
module Protocol
|
10
10
|
module HTTP
|
11
11
|
module Header
|
12
|
+
# The `etags` header represents a list of entity tags (ETags) for resources.
|
13
|
+
#
|
14
|
+
# The `etags` header is used for conditional requests to compare the current version of a resource with previously stored versions. It supports both strong and weak validators, as well as the wildcard character (`*`) to indicate a match for any resource.
|
12
15
|
class ETags < Split
|
16
|
+
# Checks if the `etags` header contains the wildcard (`*`) character.
|
17
|
+
#
|
18
|
+
# The wildcard character matches any resource version, regardless of its actual value.
|
19
|
+
#
|
20
|
+
# @returns [Boolean] whether the wildcard is present.
|
13
21
|
def wildcard?
|
14
22
|
self.include?("*")
|
15
23
|
end
|
16
24
|
|
17
|
-
#
|
25
|
+
# Checks if the specified ETag matches the `etags` header.
|
26
|
+
#
|
27
|
+
# This method returns `true` if the wildcard is present or if the exact ETag is found in the list. Note that this implementation is not strictly compliant with the RFC-specified format.
|
28
|
+
#
|
29
|
+
# @parameter etag [String] the ETag to compare against the `etags` header.
|
30
|
+
# @returns [Boolean] whether the specified ETag matches.
|
18
31
|
def match?(etag)
|
19
32
|
wildcard? || self.include?(etag)
|
20
33
|
end
|
21
34
|
|
22
|
-
#
|
35
|
+
# Checks for a strong match with the specified ETag, useful with the `if-match` header.
|
36
|
+
#
|
37
|
+
# A strong match requires that the ETag in the header list matches the specified ETag and that neither is a weak validator.
|
38
|
+
#
|
39
|
+
# @parameter etag [String] the ETag to compare against the `etags` header.
|
40
|
+
# @returns [Boolean] whether a strong match is found.
|
23
41
|
def strong_match?(etag)
|
24
42
|
wildcard? || (!weak_tag?(etag) && self.include?(etag))
|
25
43
|
end
|
26
44
|
|
27
|
-
#
|
45
|
+
# Checks for a weak match with the specified ETag, useful with the `if-none-match` header.
|
46
|
+
#
|
47
|
+
# A weak match allows for semantically equivalent content, including weak validators and their strong counterparts.
|
48
|
+
#
|
49
|
+
# @parameter etag [String] the ETag to compare against the `etags` header.
|
50
|
+
# @returns [Boolean] whether a weak match is found.
|
28
51
|
def weak_match?(etag)
|
29
52
|
wildcard? || self.include?(etag) || self.include?(opposite_tag(etag))
|
30
53
|
end
|
31
54
|
|
32
55
|
private
|
33
|
-
|
56
|
+
|
57
|
+
# Converts a weak tag to its strong counterpart or vice versa.
|
58
|
+
#
|
59
|
+
# @parameter etag [String] the ETag to convert.
|
60
|
+
# @returns [String] the opposite form of the provided ETag.
|
34
61
|
def opposite_tag(etag)
|
35
62
|
weak_tag?(etag) ? etag[2..-1] : "W/#{etag}"
|
36
63
|
end
|
37
64
|
|
65
|
+
# Checks if the given ETag is a weak validator.
|
66
|
+
#
|
67
|
+
# @parameter tag [String] the ETag to check.
|
68
|
+
# @returns [Boolean] whether the tag is weak.
|
38
69
|
def weak_tag?(tag)
|
39
70
|
tag&.start_with? "W/"
|
40
71
|
end
|
@@ -6,14 +6,22 @@
|
|
6
6
|
module Protocol
|
7
7
|
module HTTP
|
8
8
|
module Header
|
9
|
-
#
|
9
|
+
# Represents headers that can contain multiple distinct values separated by newline characters.
|
10
|
+
#
|
11
|
+
# This isn't a specific header but is used as a base for headers that store multiple values, such as cookies. The values are split and stored as an array internally, and serialized back to a newline-separated string when needed.
|
10
12
|
class Multiple < Array
|
13
|
+
# Initializes the multiple header with the given value. As the header key-value pair can only contain one value, the value given here is added to the internal array, and subsequent values can be added using the `<<` operator.
|
14
|
+
#
|
15
|
+
# @parameter value [String] the raw header value.
|
11
16
|
def initialize(value)
|
12
17
|
super()
|
13
18
|
|
14
19
|
self << value
|
15
20
|
end
|
16
21
|
|
22
|
+
# Serializes the stored values into a newline-separated string.
|
23
|
+
#
|
24
|
+
# @returns [String] the serialized representation of the header values.
|
17
25
|
def to_s
|
18
26
|
join("\n")
|
19
27
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2024, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative "split"
|
7
|
+
|
8
|
+
module Protocol
|
9
|
+
module HTTP
|
10
|
+
module Header
|
11
|
+
# Represents the `priority` header, used to indicate the relative importance of an HTTP request.
|
12
|
+
#
|
13
|
+
# The `priority` header allows clients to express their preference for how resources should be prioritized by the server. It supports directives like `u=` to specify the urgency level of a request, and `i` to indicate whether a response can be delivered incrementally. The urgency levels range from 0 (highest priority) to 7 (lowest priority), while the `i` directive is a boolean flag.
|
14
|
+
class Priority < Split
|
15
|
+
# Initialize the priority header with the given value.
|
16
|
+
#
|
17
|
+
# @parameter value [String | Nil] the value of the priority header, if any. The value should be a comma-separated string of directives.
|
18
|
+
def initialize(value = nil)
|
19
|
+
super(value&.downcase)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Add a value to the priority header.
|
23
|
+
#
|
24
|
+
# @parameter value [String] the directive to add to the header.
|
25
|
+
def << value
|
26
|
+
super(value.downcase)
|
27
|
+
end
|
28
|
+
|
29
|
+
# The default urgency level if not specified.
|
30
|
+
DEFAULT_URGENCY = 3
|
31
|
+
|
32
|
+
# Returns the urgency level if specified. 0 is the highest priority, and 7 is the lowest.
|
33
|
+
#
|
34
|
+
# @returns [Integer | Nil] the urgency level if specified, or `nil` if not present.
|
35
|
+
def urgency(default = DEFAULT_URGENCY)
|
36
|
+
if value = self.find { |value| value.start_with?("u=") }
|
37
|
+
_, level = value.split("=", 2)
|
38
|
+
return level.to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
return default
|
42
|
+
end
|
43
|
+
|
44
|
+
# Checks if the response should be delivered incrementally.
|
45
|
+
#
|
46
|
+
# The `i` directive, when present, indicates that the response can be delivered incrementally as data becomes available.
|
47
|
+
#
|
48
|
+
# @returns [Boolean] whether the request should be delivered incrementally.
|
49
|
+
def incremental?
|
50
|
+
self.include?("i")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -6,22 +6,36 @@
|
|
6
6
|
module Protocol
|
7
7
|
module HTTP
|
8
8
|
module Header
|
9
|
-
#
|
9
|
+
# Represents headers that can contain multiple distinct values separated by commas.
|
10
|
+
#
|
11
|
+
# This isn't a specific header class is a utility for handling headers with comma-separated values, such as `accept`, `cache-control`, and other similar headers. The values are split and stored as an array internally, and serialized back to a comma-separated string when needed.
|
10
12
|
class Split < Array
|
13
|
+
# Regular expression used to split values on commas, with optional surrounding whitespace.
|
11
14
|
COMMA = /\s*,\s*/
|
12
15
|
|
13
|
-
|
16
|
+
# Initializes a `Split` header with the given value. If the value is provided, it is split into distinct entries and stored as an array.
|
17
|
+
#
|
18
|
+
# @parameter value [String | Nil] the raw header value containing multiple entries separated by commas, or `nil` for an empty header.
|
19
|
+
def initialize(value = nil)
|
14
20
|
if value
|
15
21
|
super(value.split(COMMA))
|
16
22
|
else
|
17
|
-
super(
|
23
|
+
super()
|
18
24
|
end
|
19
25
|
end
|
20
26
|
|
27
|
+
# Adds one or more comma-separated values to the header.
|
28
|
+
#
|
29
|
+
# The input string is split into distinct entries and appended to the array.
|
30
|
+
#
|
31
|
+
# @parameter value [String] the value or values to add, separated by commas.
|
21
32
|
def << value
|
22
33
|
self.push(*value.split(COMMA))
|
23
34
|
end
|
24
35
|
|
36
|
+
# Serializes the stored values into a comma-separated string.
|
37
|
+
#
|
38
|
+
# @returns [String] the serialized representation of the header values.
|
25
39
|
def to_s
|
26
40
|
join(",")
|
27
41
|
end
|
@@ -8,15 +8,24 @@ require_relative "split"
|
|
8
8
|
module Protocol
|
9
9
|
module HTTP
|
10
10
|
module Header
|
11
|
+
# Represents the `vary` header, which specifies the request headers a server considers when determining the response.
|
12
|
+
#
|
13
|
+
# The `vary` header is used in HTTP responses to indicate which request headers affect the selected response. It allows caches to differentiate stored responses based on specific request headers.
|
11
14
|
class Vary < Split
|
15
|
+
# Initializes a `Vary` header with the given value. The value is split into distinct entries and converted to lowercase for normalization.
|
16
|
+
#
|
17
|
+
# @parameter value [String] the raw header value containing request header names separated by commas.
|
12
18
|
def initialize(value)
|
13
19
|
super(value.downcase)
|
14
20
|
end
|
15
21
|
|
22
|
+
# Adds one or more comma-separated values to the `vary` header. The values are converted to lowercase for normalization.
|
23
|
+
#
|
24
|
+
# @parameter value [String] the value or values to add, separated by commas.
|
16
25
|
def << value
|
17
26
|
super(value.downcase)
|
18
27
|
end
|
19
28
|
end
|
20
29
|
end
|
21
30
|
end
|
22
|
-
end
|
31
|
+
end
|