protocol-http 0.51.1 → 0.53.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/context/design-overview.md +209 -0
- data/context/getting-started.md +130 -0
- data/context/hypertext-references.md +140 -0
- data/context/index.yaml +36 -0
- data/context/message-body.md +330 -0
- data/context/middleware.md +195 -0
- data/context/streaming.md +132 -0
- data/context/url-parsing.md +130 -0
- data/lib/protocol/http/accept_encoding.rb +1 -1
- data/lib/protocol/http/body/buffered.rb +4 -2
- data/lib/protocol/http/body/completable.rb +18 -1
- data/lib/protocol/http/body/deflate.rb +13 -2
- data/lib/protocol/http/body/digestable.rb +12 -2
- data/lib/protocol/http/body/file.rb +6 -2
- data/lib/protocol/http/body/head.rb +7 -0
- data/lib/protocol/http/body/inflate.rb +2 -2
- data/lib/protocol/http/body/rewindable.rb +11 -1
- data/lib/protocol/http/body/stream.rb +17 -2
- data/lib/protocol/http/body/streamable.rb +18 -2
- data/lib/protocol/http/body/writable.rb +3 -3
- data/lib/protocol/http/error.rb +1 -1
- data/lib/protocol/http/header/cache_control.rb +1 -1
- data/lib/protocol/http/headers.rb +16 -6
- data/lib/protocol/http/reference.rb +29 -15
- data/lib/protocol/http/request.rb +1 -1
- data/lib/protocol/http/response.rb +2 -1
- data/lib/protocol/http/version.rb +1 -1
- data/readme.md +21 -2
- data/releases.md +11 -0
- data.tar.gz.sig +0 -0
- metadata +9 -1
- metadata.gz.sig +3 -2
@@ -0,0 +1,130 @@
|
|
1
|
+
# URL Parsing
|
2
|
+
|
3
|
+
This guide explains how to use `Protocol::HTTP::URL` for parsing and manipulating URL components, particularly query strings and parameters.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
{ruby Protocol::HTTP::URL} provides utilities for parsing and manipulating URL components, particularly query strings and parameters. It offers robust encoding/decoding capabilities for complex parameter structures.
|
8
|
+
|
9
|
+
While basic query parameter encoding follows the `application/x-www-form-urlencoded` standard, there is no universal standard for serializing complex nested structures (arrays, nested objects) in URLs. Different frameworks use varying conventions for these cases, and this implementation follows common patterns where possible.
|
10
|
+
|
11
|
+
## Basic Query Parameter Parsing
|
12
|
+
|
13
|
+
``` ruby
|
14
|
+
require 'protocol/http/url'
|
15
|
+
|
16
|
+
# Parse query parameters from a URL:
|
17
|
+
reference = Protocol::HTTP::Reference.parse("/search?q=ruby&category=programming&page=2")
|
18
|
+
parameters = Protocol::HTTP::URL.decode(reference.query)
|
19
|
+
# => {"q" => "ruby", "category" => "programming", "page" => "2"}
|
20
|
+
|
21
|
+
# Symbolize keys for easier access:
|
22
|
+
parameters = Protocol::HTTP::URL.decode(reference.query, symbolize_keys: true)
|
23
|
+
# => {:q => "ruby", :category => "programming", :page => "2"}
|
24
|
+
```
|
25
|
+
|
26
|
+
## Complex Parameter Structures
|
27
|
+
|
28
|
+
The URL module handles nested parameters, arrays, and complex data structures:
|
29
|
+
|
30
|
+
``` ruby
|
31
|
+
# Array parameters:
|
32
|
+
query = "tags[]=ruby&tags[]=programming&tags[]=web"
|
33
|
+
parameters = Protocol::HTTP::URL.decode(query)
|
34
|
+
# => {"tags" => ["ruby", "programming", "web"]}
|
35
|
+
|
36
|
+
# Nested hash parameters:
|
37
|
+
query = "user[name]=John&user[email]=john@example.com&user[preferences][theme]=dark"
|
38
|
+
parameters = Protocol::HTTP::URL.decode(query)
|
39
|
+
# => {"user" => {"name" => "John", "email" => "john@example.com", "preferences" => {"theme" => "dark"}}}
|
40
|
+
|
41
|
+
# Mixed structures:
|
42
|
+
query = "filters[categories][]=books&filters[categories][]=movies&filters[price][min]=10&filters[price][max]=100"
|
43
|
+
parameters = Protocol::HTTP::URL.decode(query)
|
44
|
+
# => {"filters" => {"categories" => ["books", "movies"], "price" => {"min" => "10", "max" => "100"}}}
|
45
|
+
```
|
46
|
+
|
47
|
+
## Encoding Parameters to Query Strings
|
48
|
+
|
49
|
+
``` ruby
|
50
|
+
# Simple parameters:
|
51
|
+
parameters = {"search" => "protocol-http", "limit" => "20"}
|
52
|
+
query = Protocol::HTTP::URL.encode(parameters)
|
53
|
+
# => "search=protocol-http&limit=20"
|
54
|
+
|
55
|
+
# Array parameters:
|
56
|
+
parameters = {"tags" => ["ruby", "http", "protocol"]}
|
57
|
+
query = Protocol::HTTP::URL.encode(parameters)
|
58
|
+
# => "tags[]=ruby&tags[]=http&tags[]=protocol"
|
59
|
+
|
60
|
+
# Nested parameters:
|
61
|
+
parameters = {
|
62
|
+
user: {
|
63
|
+
profile: {
|
64
|
+
name: "Alice",
|
65
|
+
settings: {
|
66
|
+
notifications: true,
|
67
|
+
theme: "light"
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
query = Protocol::HTTP::URL.encode(parameters)
|
73
|
+
# => "user[profile][name]=Alice&user[profile][settings][notifications]=true&user[profile][settings][theme]=light"
|
74
|
+
```
|
75
|
+
|
76
|
+
## URL Escaping and Unescaping
|
77
|
+
|
78
|
+
``` ruby
|
79
|
+
# Escape special characters:
|
80
|
+
Protocol::HTTP::URL.escape("hello world!")
|
81
|
+
# => "hello%20world%21"
|
82
|
+
|
83
|
+
# Escape path components (preserves path separators):
|
84
|
+
Protocol::HTTP::URL.escape_path("/path/with spaces/file.html")
|
85
|
+
# => "/path/with%20spaces/file.html"
|
86
|
+
|
87
|
+
# Unescape percent-encoded strings:
|
88
|
+
Protocol::HTTP::URL.unescape("hello%20world%21")
|
89
|
+
# => "hello world!"
|
90
|
+
|
91
|
+
# Handle Unicode characters:
|
92
|
+
Protocol::HTTP::URL.escape("café")
|
93
|
+
# => "caf%C3%A9"
|
94
|
+
|
95
|
+
Protocol::HTTP::URL.unescape("caf%C3%A9")
|
96
|
+
# => "café"
|
97
|
+
```
|
98
|
+
|
99
|
+
## Scanning and Processing Query Strings
|
100
|
+
|
101
|
+
For custom processing, you can scan query strings directly:
|
102
|
+
|
103
|
+
``` ruby
|
104
|
+
query = "name=John&age=30&active=true"
|
105
|
+
|
106
|
+
Protocol::HTTP::URL.scan(query) do |key, value|
|
107
|
+
puts "#{key}: #{value}"
|
108
|
+
end
|
109
|
+
# Output:
|
110
|
+
# name: John
|
111
|
+
# age: 30
|
112
|
+
# active: true
|
113
|
+
```
|
114
|
+
|
115
|
+
## Security and Limits
|
116
|
+
|
117
|
+
The URL module includes built-in protection against deeply nested parameter attacks:
|
118
|
+
|
119
|
+
``` ruby
|
120
|
+
# This will raise an error to prevent excessive nesting:
|
121
|
+
begin
|
122
|
+
Protocol::HTTP::URL.decode("a[b][c][d][e][f][g][h][i]=value")
|
123
|
+
rescue ArgumentError => error
|
124
|
+
puts error.message
|
125
|
+
# => "Key length exceeded limit!"
|
126
|
+
end
|
127
|
+
|
128
|
+
# You can adjust the maximum nesting level:
|
129
|
+
Protocol::HTTP::URL.decode("a[b][c]=value", 5) # Allow up to 5 levels of nesting
|
130
|
+
```
|
@@ -21,7 +21,7 @@ module Protocol
|
|
21
21
|
# The default wrappers to use for decoding content.
|
22
22
|
DEFAULT_WRAPPERS = {
|
23
23
|
"gzip" => Body::Inflate.method(:for),
|
24
|
-
"identity" => ->(body) {
|
24
|
+
"identity" => ->(body) {body}, # Identity means no encoding
|
25
25
|
|
26
26
|
# There is no point including this:
|
27
27
|
# 'identity' => ->(body){body},
|
@@ -153,8 +153,10 @@ module Protocol
|
|
153
153
|
#
|
154
154
|
# @returns [String] a string representation of the buffered body.
|
155
155
|
def inspect
|
156
|
-
if @chunks
|
157
|
-
"
|
156
|
+
if @chunks and @chunks.size > 0
|
157
|
+
"#<#{self.class} #{@index}/#{@chunks.size} chunks, #{self.length} bytes>"
|
158
|
+
else
|
159
|
+
"#<#{self.class} empty>"
|
158
160
|
end
|
159
161
|
end
|
160
162
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "wrapper"
|
7
7
|
|
@@ -53,6 +53,23 @@ module Protocol
|
|
53
53
|
|
54
54
|
super
|
55
55
|
end
|
56
|
+
|
57
|
+
# Convert the body to a hash suitable for serialization.
|
58
|
+
#
|
59
|
+
# @returns [Hash] The body as a hash.
|
60
|
+
def as_json(...)
|
61
|
+
super.merge(
|
62
|
+
callback: @callback&.to_s
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Inspect the completable body.
|
67
|
+
#
|
68
|
+
# @returns [String] a string representation of the completable body.
|
69
|
+
def inspect
|
70
|
+
callback_status = @callback ? "callback pending" : "callback completed"
|
71
|
+
return "#{super} | #<#{self.class} #{callback_status}>"
|
72
|
+
end
|
56
73
|
end
|
57
74
|
end
|
58
75
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "wrapper"
|
7
7
|
|
@@ -75,11 +75,22 @@ module Protocol
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
+
# Convert the body to a hash suitable for serialization.
|
79
|
+
#
|
80
|
+
# @returns [Hash] The body as a hash.
|
81
|
+
def as_json(...)
|
82
|
+
super.merge(
|
83
|
+
input_length: @input_length,
|
84
|
+
output_length: @output_length,
|
85
|
+
compression_ratio: (ratio * 100).round(2)
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
78
89
|
# Inspect the body, including the compression ratio.
|
79
90
|
#
|
80
91
|
# @returns [String] a string representation of the body.
|
81
92
|
def inspect
|
82
|
-
"#{super} |
|
93
|
+
"#{super} | #<#{self.class} #{(ratio*100).round(2)}%>"
|
83
94
|
end
|
84
95
|
end
|
85
96
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2020-
|
4
|
+
# Copyright, 2020-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "wrapper"
|
7
7
|
|
@@ -34,7 +34,7 @@ module Protocol
|
|
34
34
|
@digest = digest
|
35
35
|
@callback = callback
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
# @attribute [Digest] digest the digest object.
|
39
39
|
attr :digest
|
40
40
|
|
@@ -64,6 +64,16 @@ module Protocol
|
|
64
64
|
return nil
|
65
65
|
end
|
66
66
|
end
|
67
|
+
|
68
|
+
# Convert the body to a hash suitable for serialization.
|
69
|
+
#
|
70
|
+
# @returns [Hash] The body as a hash.
|
71
|
+
def as_json(...)
|
72
|
+
super.merge(
|
73
|
+
digest_class: @digest.class.name,
|
74
|
+
callback: @callback&.to_s
|
75
|
+
)
|
76
|
+
end
|
67
77
|
end
|
68
78
|
end
|
69
79
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "readable"
|
7
7
|
|
@@ -135,7 +135,11 @@ module Protocol
|
|
135
135
|
#
|
136
136
|
# @returns [String] a string representation of the file body.
|
137
137
|
def inspect
|
138
|
-
|
138
|
+
if @offset > 0
|
139
|
+
"#<#{self.class} #{@file.inspect} +#{@offset}, #{@remaining} bytes remaining>"
|
140
|
+
else
|
141
|
+
"#<#{self.class} #{@file.inspect}, #{@remaining} bytes remaining>"
|
142
|
+
end
|
139
143
|
end
|
140
144
|
end
|
141
145
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require "zlib"
|
7
7
|
|
@@ -34,7 +34,7 @@ module Protocol
|
|
34
34
|
|
35
35
|
break unless chunk&.empty?
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
if chunk
|
39
39
|
@output_length += chunk.bytesize
|
40
40
|
elsif !stream.closed?
|
@@ -82,11 +82,21 @@ module Protocol
|
|
82
82
|
true
|
83
83
|
end
|
84
84
|
|
85
|
+
# Convert the body to a hash suitable for serialization.
|
86
|
+
#
|
87
|
+
# @returns [Hash] The body as a hash.
|
88
|
+
def as_json(...)
|
89
|
+
super.merge(
|
90
|
+
index: @index,
|
91
|
+
chunks: @chunks.size
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
85
95
|
# Inspect the rewindable body.
|
86
96
|
#
|
87
97
|
# @returns [String] a string representation of the body.
|
88
98
|
def inspect
|
89
|
-
"
|
99
|
+
"#{super} | #<#{self.class} #{@index}/#{@chunks.size} chunks read>"
|
90
100
|
end
|
91
101
|
end
|
92
102
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
# Copyright, 2023, by Genki Takiuchi.
|
6
6
|
|
7
7
|
require_relative "buffered"
|
@@ -123,7 +123,7 @@ module Protocol
|
|
123
123
|
if buffer.bytesize > length
|
124
124
|
# This ensures the subsequent `slice!` works correctly.
|
125
125
|
buffer.force_encoding(Encoding::BINARY)
|
126
|
-
|
126
|
+
|
127
127
|
@buffer = buffer.byteslice(length, buffer.bytesize)
|
128
128
|
buffer.slice!(length, buffer.bytesize)
|
129
129
|
end
|
@@ -386,6 +386,21 @@ module Protocol
|
|
386
386
|
@closed
|
387
387
|
end
|
388
388
|
|
389
|
+
# Inspect the stream.
|
390
|
+
#
|
391
|
+
# @returns [String] a string representation of the stream.
|
392
|
+
def inspect
|
393
|
+
buffer_info = @buffer ? "#{@buffer.bytesize} bytes buffered" : "no buffer"
|
394
|
+
|
395
|
+
status = []
|
396
|
+
status << "closed" if @closed
|
397
|
+
status << "read-closed" if @closed_read
|
398
|
+
|
399
|
+
status_info = status.empty? ? "open" : status.join(", ")
|
400
|
+
|
401
|
+
return "#<#{self.class} #{buffer_info}, #{status_info}>"
|
402
|
+
end
|
403
|
+
|
389
404
|
# @returns [Boolean] Whether there are any output chunks remaining.
|
390
405
|
def empty?
|
391
406
|
@output.empty?
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "readable"
|
7
7
|
require_relative "writable"
|
@@ -147,7 +147,23 @@ module Protocol
|
|
147
147
|
#
|
148
148
|
# @parameter error [Exception | Nil] The error that caused this stream to be closed, if any.
|
149
149
|
def close_output(error = nil)
|
150
|
-
@output
|
150
|
+
if output = @output
|
151
|
+
@output = nil
|
152
|
+
output.close(error)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Inspect the streaming body.
|
157
|
+
#
|
158
|
+
# @returns [String] a string representation of the streaming body.
|
159
|
+
def inspect
|
160
|
+
if @block
|
161
|
+
"#<#{self.class} block available, not consumed>"
|
162
|
+
elsif @output
|
163
|
+
"#<#{self.class} block consumed, output active>"
|
164
|
+
else
|
165
|
+
"#<#{self.class} block consumed, output closed>"
|
166
|
+
end
|
151
167
|
end
|
152
168
|
end
|
153
169
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2024, by Samuel Williams.
|
4
|
+
# Copyright, 2024-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "readable"
|
7
7
|
|
@@ -166,9 +166,9 @@ module Protocol
|
|
166
166
|
# @returns [String] A string representation of the body.
|
167
167
|
def inspect
|
168
168
|
if @error
|
169
|
-
"
|
169
|
+
"#<#{self.class} #{@count} chunks written, #{status}, error=#{@error}>"
|
170
170
|
else
|
171
|
-
"
|
171
|
+
"#<#{self.class} #{@count} chunks written, #{status}>"
|
172
172
|
end
|
173
173
|
end
|
174
174
|
|
data/lib/protocol/http/error.rb
CHANGED
@@ -120,7 +120,7 @@ module Protocol
|
|
120
120
|
# @parameter value_name [String] the directive name to search for (e.g., "max-age").
|
121
121
|
# @returns [Integer | Nil] the parsed integer value, or `nil` if not found or invalid.
|
122
122
|
def find_integer_value(value_name)
|
123
|
-
if value = self.find
|
123
|
+
if value = self.find{|value| value.start_with?(value_name)}
|
124
124
|
_, age = value.split("=", 2)
|
125
125
|
|
126
126
|
if age =~ /\A[0-9]+\z/
|
@@ -64,13 +64,15 @@ module Protocol
|
|
64
64
|
# Initialize the headers with the specified fields.
|
65
65
|
#
|
66
66
|
# @parameter fields [Array] An array of `[key, value]` pairs.
|
67
|
-
# @parameter
|
68
|
-
def initialize(fields = [],
|
67
|
+
# @parameter tail [Integer | Nil] The index of the trailer start in the @fields array.
|
68
|
+
def initialize(fields = [], tail = nil, indexed: nil)
|
69
69
|
@fields = fields
|
70
|
-
@indexed = indexed
|
71
70
|
|
72
|
-
# Marks where trailer start in the @fields array
|
73
|
-
@tail =
|
71
|
+
# Marks where trailer start in the @fields array:
|
72
|
+
@tail = tail
|
73
|
+
|
74
|
+
# The cached index of headers:
|
75
|
+
@indexed = nil
|
74
76
|
end
|
75
77
|
|
76
78
|
# Initialize a copy of the headers.
|
@@ -86,8 +88,8 @@ module Protocol
|
|
86
88
|
# Clear all headers.
|
87
89
|
def clear
|
88
90
|
@fields.clear
|
89
|
-
@indexed = nil
|
90
91
|
@tail = nil
|
92
|
+
@indexed = nil
|
91
93
|
end
|
92
94
|
|
93
95
|
# Flatten trailer into the headers, in-place.
|
@@ -108,6 +110,14 @@ module Protocol
|
|
108
110
|
# @attribute [Array] An array of `[key, value]` pairs.
|
109
111
|
attr :fields
|
110
112
|
|
113
|
+
# @attribute [Integer | Nil] The index where trailers begin.
|
114
|
+
attr :tail
|
115
|
+
|
116
|
+
# @returns [Array] The fields of the headers.
|
117
|
+
def to_a
|
118
|
+
@fields
|
119
|
+
end
|
120
|
+
|
111
121
|
# @returns [Boolean] Whether there are any trailers.
|
112
122
|
def trailer?
|
113
123
|
@tail != nil
|
@@ -142,24 +142,38 @@ module Protocol
|
|
142
142
|
end
|
143
143
|
|
144
144
|
# Update the reference with the given path, parameters and fragment.
|
145
|
-
#
|
146
|
-
# @
|
147
|
-
# @
|
148
|
-
# @
|
149
|
-
# @
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
145
|
+
#
|
146
|
+
# @parameter path [String] Append the string to this reference similar to `File.join`.
|
147
|
+
# @parameter parameters [Hash] Append the parameters to this reference.
|
148
|
+
# @parameter fragment [String] Set the fragment to this value.
|
149
|
+
# @parameter pop [Boolean] If the path contains a trailing filename, pop the last component of the path before appending the new path.
|
150
|
+
# @parameter merge [Boolean] If the parameters are specified, merge them with the existing parameters, otherwise replace them (including query string).
|
151
|
+
def with(path: nil, parameters: false, fragment: @fragment, pop: false, merge: true)
|
152
|
+
if merge
|
153
|
+
# Merge mode: combine new parameters with existing, keep query:
|
154
|
+
# parameters = (@parameters || {}).merge(parameters || {})
|
155
|
+
if @parameters
|
156
|
+
if parameters
|
157
|
+
parameters = @parameters.merge(parameters)
|
158
|
+
else
|
159
|
+
parameters = @parameters
|
160
|
+
end
|
161
|
+
elsif !parameters
|
155
162
|
parameters = @parameters
|
156
163
|
end
|
157
|
-
|
158
|
-
|
159
|
-
if @query and !merge
|
160
|
-
query = nil
|
161
|
-
else
|
164
|
+
|
162
165
|
query = @query
|
166
|
+
else
|
167
|
+
# Replace mode: use new parameters if provided, clear query when replacing:
|
168
|
+
if parameters == false
|
169
|
+
# No new parameters provided, keep existing:
|
170
|
+
parameters = @parameters
|
171
|
+
query = @query
|
172
|
+
else
|
173
|
+
# New parameters provided, replace and clear query:
|
174
|
+
# parameters = parameters
|
175
|
+
query = nil
|
176
|
+
end
|
163
177
|
end
|
164
178
|
|
165
179
|
if path
|
@@ -68,7 +68,7 @@ module Protocol
|
|
68
68
|
|
69
69
|
# @attribute [Body::Readable] the request body. It should only be read once (it may not be idempotent).
|
70
70
|
attr_accessor :body
|
71
|
-
|
71
|
+
|
72
72
|
# @attribute [String | Array(String) | Nil] the request protocol, usually empty, but occasionally `"websocket"` or `"webtransport"`. In HTTP/1, it is used to request a connection upgrade, and in HTTP/2 it is used to indicate a specfic protocol for the stream.
|
73
73
|
attr_accessor :protocol
|
74
74
|
|
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "body/buffered"
|
7
7
|
require_relative "body/reader"
|
8
|
+
require_relative "headers"
|
8
9
|
|
9
10
|
module Protocol
|
10
11
|
module HTTP
|
data/readme.md
CHANGED
@@ -14,16 +14,35 @@ Provides abstractions for working with the HTTP protocol.
|
|
14
14
|
|
15
15
|
Please see the [project documentation](https://socketry.github.io/protocol-http/) for more details.
|
16
16
|
|
17
|
-
- [Streaming](https://socketry.github.io/protocol-http/guides/streaming/index) - This guide gives an overview of how to implement streaming requests and responses.
|
18
|
-
|
19
17
|
- [Getting Started](https://socketry.github.io/protocol-http/guides/getting-started/index) - This guide explains how to use `protocol-http` for building abstract HTTP interfaces.
|
20
18
|
|
19
|
+
- [Message Body](https://socketry.github.io/protocol-http/guides/message-body/index) - This guide explains how to work with HTTP request and response message bodies using `Protocol::HTTP::Body` classes.
|
20
|
+
|
21
|
+
- [Middleware](https://socketry.github.io/protocol-http/guides/middleware/index) - This guide explains how to build and use HTTP middleware with `Protocol::HTTP::Middleware`.
|
22
|
+
|
23
|
+
- [Hypertext References](https://socketry.github.io/protocol-http/guides/hypertext-references/index) - This guide explains how to use `Protocol::HTTP::Reference` for constructing and manipulating hypertext references (URLs with parameters).
|
24
|
+
|
25
|
+
- [URL Parsing](https://socketry.github.io/protocol-http/guides/url-parsing/index) - This guide explains how to use `Protocol::HTTP::URL` for parsing and manipulating URL components, particularly query strings and parameters.
|
26
|
+
|
27
|
+
- [Streaming](https://socketry.github.io/protocol-http/guides/streaming/index) - This guide gives an overview of how to implement streaming requests and responses.
|
28
|
+
|
21
29
|
- [Design Overview](https://socketry.github.io/protocol-http/guides/design-overview/index) - This guide explains the high level design of `protocol-http` in the context of wider design patterns that can be used to implement HTTP clients and servers.
|
22
30
|
|
23
31
|
## Releases
|
24
32
|
|
25
33
|
Please see the [project releases](https://socketry.github.io/protocol-http/releases/index) for all releases.
|
26
34
|
|
35
|
+
### v0.53.0
|
36
|
+
|
37
|
+
- Improve consistency of Body `#inspect`.
|
38
|
+
- Improve `as_json` support for Body wrappers.
|
39
|
+
|
40
|
+
### v0.52.0
|
41
|
+
|
42
|
+
- Add `Protocol::HTTP::Headers#to_a` method that returns the fields array, providing compatibility with standard Ruby array conversion pattern.
|
43
|
+
- Expose `tail` in `Headers.new` so that trailers can be accurately reproduced.
|
44
|
+
- Add agent context.
|
45
|
+
|
27
46
|
### v0.51.0
|
28
47
|
|
29
48
|
- `Protocol::HTTP::Headers` now raise a `DuplicateHeaderError` when a duplicate singleton header (e.g. `content-length`) is added.
|
data/releases.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# Releases
|
2
2
|
|
3
|
+
## v0.53.0
|
4
|
+
|
5
|
+
- Improve consistency of Body `#inspect`.
|
6
|
+
- Improve `as_json` support for Body wrappers.
|
7
|
+
|
8
|
+
## v0.52.0
|
9
|
+
|
10
|
+
- Add `Protocol::HTTP::Headers#to_a` method that returns the fields array, providing compatibility with standard Ruby array conversion pattern.
|
11
|
+
- Expose `tail` in `Headers.new` so that trailers can be accurately reproduced.
|
12
|
+
- Add agent context.
|
13
|
+
|
3
14
|
## v0.51.0
|
4
15
|
|
5
16
|
- `Protocol::HTTP::Headers` now raise a `DuplicateHeaderError` when a duplicate singleton header (e.g. `content-length`) is added.
|
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.53.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -53,6 +53,14 @@ executables: []
|
|
53
53
|
extensions: []
|
54
54
|
extra_rdoc_files: []
|
55
55
|
files:
|
56
|
+
- context/design-overview.md
|
57
|
+
- context/getting-started.md
|
58
|
+
- context/hypertext-references.md
|
59
|
+
- context/index.yaml
|
60
|
+
- context/message-body.md
|
61
|
+
- context/middleware.md
|
62
|
+
- context/streaming.md
|
63
|
+
- context/url-parsing.md
|
56
64
|
- lib/protocol/http.rb
|
57
65
|
- lib/protocol/http/accept_encoding.rb
|
58
66
|
- lib/protocol/http/body.rb
|