protocol-http 0.45.0 → 0.46.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 +65 -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,65 @@
|
|
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 can include directives like `urgency` to specify the importance of a request, and `progressive` to indicate whether a response can be delivered incrementally.
|
14
|
+
class Priority < Split
|
15
|
+
# Urgency levels as defined in RFC 9218:
|
16
|
+
#
|
17
|
+
# These levels indicate the relative importance of a request, helping servers and intermediaries allocate resources efficiently. Properly setting urgency can significantly improve user-perceived performance by prioritizing critical content and deferring less important tasks.
|
18
|
+
module Urgency
|
19
|
+
# `background` priority indicates a request that is not time-sensitive and can be processed with minimal impact to other tasks. It is ideal for requests like analytics or logging, which do not directly impact the user's current experience.
|
20
|
+
BACKGROUND = "background"
|
21
|
+
|
22
|
+
# `low` priority indicates a request that is important but not critical. It is suitable for content like non-blocking images, videos, or scripts that enhance the experience but do not affect core functionality.
|
23
|
+
LOW = "low"
|
24
|
+
|
25
|
+
# `normal` priority (default) indicates the standard priority for most requests. It is appropriate for content like text, CSS, or essential images that are necessary for the primary user experience but do not require urgent delivery.
|
26
|
+
NORMAL = "normal"
|
27
|
+
|
28
|
+
# `high` priority indicates the highest priority, used for requests that are essential and time-critical to the user experience. Examples include content above-the-fold on a webpage, critical API calls, or resources required for rendering.
|
29
|
+
HIGH = "high"
|
30
|
+
end
|
31
|
+
|
32
|
+
# The `progressive` flag indicates that the response can be delivered incrementally (progressively) as data becomes available. This is particularly useful for large resources like images or video streams, where partial delivery improves the user experience by allowing content to render or play before the full response is received.
|
33
|
+
PROGRESSIVE = "progressive"
|
34
|
+
|
35
|
+
# Initialize the priority header with the given value.
|
36
|
+
#
|
37
|
+
# @parameter value [String | Nil] the value of the priority header, if any.
|
38
|
+
def initialize(value = nil)
|
39
|
+
super(value&.downcase)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add a value to the priority header.
|
43
|
+
def << value
|
44
|
+
super(value.downcase)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the urgency level if specified.
|
48
|
+
#
|
49
|
+
# @returns [String | Nil] the urgency level if specified, or `nil`.
|
50
|
+
def urgency
|
51
|
+
if value = self.find{|value| value.start_with?("urgency=")}
|
52
|
+
_, level = value.split("=", 2)
|
53
|
+
|
54
|
+
return level
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# @returns [Boolean] whether the request should be delivered progressively.
|
59
|
+
def progressive?
|
60
|
+
self.include?(PROGRESSIVE)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
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
|