protocol-http 0.44.0 → 0.46.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/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 +83 -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
@@ -13,9 +13,14 @@ require_relative "header/etags"
|
|
13
13
|
require_relative "header/vary"
|
14
14
|
require_relative "header/authorization"
|
15
15
|
require_relative "header/date"
|
16
|
+
require_relative "header/priority"
|
16
17
|
|
17
18
|
module Protocol
|
18
19
|
module HTTP
|
20
|
+
# @namespace
|
21
|
+
module Header
|
22
|
+
end
|
23
|
+
|
19
24
|
# Headers are an array of key-value pairs. Some header keys represent multiple values.
|
20
25
|
class Headers
|
21
26
|
Split = Header::Split
|
@@ -24,6 +29,7 @@ module Protocol
|
|
24
29
|
TRAILER = "trailer"
|
25
30
|
|
26
31
|
# Construct an instance from a headers Array or Hash. No-op if already an instance of `Headers`. If the underlying array is frozen, it will be duped.
|
32
|
+
#
|
27
33
|
# @return [Headers] an instance of headers.
|
28
34
|
def self.[] headers
|
29
35
|
if headers.nil?
|
@@ -47,6 +53,10 @@ module Protocol
|
|
47
53
|
return self.new(fields)
|
48
54
|
end
|
49
55
|
|
56
|
+
# Initialize the headers with the specified fields.
|
57
|
+
#
|
58
|
+
# @parameter fields [Array] An array of `[key, value]` pairs.
|
59
|
+
# @parameter indexed [Hash] A hash table of normalized headers, if available.
|
50
60
|
def initialize(fields = [], indexed = nil)
|
51
61
|
@fields = fields
|
52
62
|
@indexed = indexed
|
@@ -55,6 +65,9 @@ module Protocol
|
|
55
65
|
@tail = nil
|
56
66
|
end
|
57
67
|
|
68
|
+
# Initialize a copy of the headers.
|
69
|
+
#
|
70
|
+
# @parameter other [Headers] The headers to copy.
|
58
71
|
def initialize_dup(other)
|
59
72
|
super
|
60
73
|
|
@@ -62,13 +75,14 @@ module Protocol
|
|
62
75
|
@indexed = @indexed.dup
|
63
76
|
end
|
64
77
|
|
78
|
+
# Clear all headers.
|
65
79
|
def clear
|
66
80
|
@fields.clear
|
67
81
|
@indexed = nil
|
68
82
|
@tail = nil
|
69
83
|
end
|
70
84
|
|
71
|
-
# Flatten trailer into the headers.
|
85
|
+
# Flatten trailer into the headers, in-place.
|
72
86
|
def flatten!
|
73
87
|
if @tail
|
74
88
|
self.delete(TRAILER)
|
@@ -78,29 +92,27 @@ module Protocol
|
|
78
92
|
return self
|
79
93
|
end
|
80
94
|
|
95
|
+
# Flatten trailer into the headers, returning a new instance of {Headers}.
|
81
96
|
def flatten
|
82
97
|
self.dup.flatten!
|
83
98
|
end
|
84
99
|
|
85
|
-
# An array of `[key, value]` pairs.
|
100
|
+
# @attribute [Array] An array of `[key, value]` pairs.
|
86
101
|
attr :fields
|
87
102
|
|
88
|
-
# @returns Whether there are any trailers.
|
103
|
+
# @returns [Boolean] Whether there are any trailers.
|
89
104
|
def trailer?
|
90
105
|
@tail != nil
|
91
106
|
end
|
92
107
|
|
93
108
|
# Record the current headers, and prepare to add trailers.
|
94
109
|
#
|
95
|
-
# This method is typically used after headers are sent to capture any
|
96
|
-
# additional headers which should then be sent as trailers.
|
110
|
+
# This method is typically used after headers are sent to capture any additional headers which should then be sent as trailers.
|
97
111
|
#
|
98
|
-
# A sender that intends to generate one or more trailer fields in a
|
99
|
-
# message should generate a trailer header field in the header section of
|
100
|
-
# that message to indicate which fields might be present in the trailers.
|
112
|
+
# A sender that intends to generate one or more trailer fields in a message should generate a trailer header field in the header section of that message to indicate which fields might be present in the trailers.
|
101
113
|
#
|
102
114
|
# @parameter names [Array] The trailer header names which will be added later.
|
103
|
-
# @yields
|
115
|
+
# @yields {|name, value| ...} the trailing headers if a block is given.
|
104
116
|
# @returns An enumerator which is suitable for iterating over trailers.
|
105
117
|
def trailer!(&block)
|
106
118
|
@tail ||= @fields.size
|
@@ -117,6 +129,7 @@ module Protocol
|
|
117
129
|
end
|
118
130
|
end
|
119
131
|
|
132
|
+
# Freeze the headers, and ensure the indexed hash is generated.
|
120
133
|
def freeze
|
121
134
|
return if frozen?
|
122
135
|
|
@@ -129,24 +142,35 @@ module Protocol
|
|
129
142
|
super
|
130
143
|
end
|
131
144
|
|
145
|
+
# @returns [Boolean] Whether the headers are empty.
|
132
146
|
def empty?
|
133
147
|
@fields.empty?
|
134
148
|
end
|
135
149
|
|
150
|
+
# Enumerate all header keys and values.
|
151
|
+
#
|
152
|
+
# @yields {|key, value| ...}
|
153
|
+
# @parameter key [String] The header key.
|
154
|
+
# @parameter value [String] The header value.
|
136
155
|
def each(&block)
|
137
156
|
@fields.each(&block)
|
138
157
|
end
|
139
158
|
|
159
|
+
# @returns [Boolean] Whether the headers include the specified key.
|
140
160
|
def include? key
|
141
161
|
self[key] != nil
|
142
162
|
end
|
143
163
|
|
144
164
|
alias key? include?
|
145
165
|
|
166
|
+
# @returns [Array] All the keys of the headers.
|
146
167
|
def keys
|
147
168
|
self.to_h.keys
|
148
169
|
end
|
149
170
|
|
171
|
+
# Extract the specified keys from the headers.
|
172
|
+
#
|
173
|
+
# @parameter keys [Array] The keys to extract.
|
150
174
|
def extract(keys)
|
151
175
|
deleted, @fields = @fields.partition do |field|
|
152
176
|
keys.include?(field.first.downcase)
|
@@ -163,21 +187,23 @@ module Protocol
|
|
163
187
|
|
164
188
|
# Add the specified header key value pair.
|
165
189
|
#
|
166
|
-
# @
|
167
|
-
# @
|
190
|
+
# @parameter key [String] the header key.
|
191
|
+
# @parameter value [String] the header value to assign.
|
168
192
|
def add(key, value)
|
169
193
|
self[key] = value
|
170
194
|
end
|
171
195
|
|
172
196
|
# Set the specified header key to the specified value, replacing any existing header keys with the same name.
|
173
|
-
#
|
174
|
-
# @
|
197
|
+
#
|
198
|
+
# @parameter key [String] the header key to replace.
|
199
|
+
# @parameter value [String] the header value to assign.
|
175
200
|
def set(key, value)
|
176
201
|
# TODO This could be a bit more efficient:
|
177
202
|
self.delete(key)
|
178
203
|
self.add(key, value)
|
179
204
|
end
|
180
205
|
|
206
|
+
# Merge the headers into this instance.
|
181
207
|
def merge!(headers)
|
182
208
|
headers.each do |key, value|
|
183
209
|
self[key] = value
|
@@ -186,13 +212,15 @@ module Protocol
|
|
186
212
|
return self
|
187
213
|
end
|
188
214
|
|
215
|
+
# Merge the headers into a new instance of {Headers}.
|
189
216
|
def merge(headers)
|
190
217
|
self.dup.merge!(headers)
|
191
218
|
end
|
192
219
|
|
193
220
|
# Append the value to the given key. Some values can be appended multiple times, others can only be set once.
|
194
|
-
#
|
195
|
-
# @
|
221
|
+
#
|
222
|
+
# @parameter key [String] The header key.
|
223
|
+
# @parameter value [String] The header value.
|
196
224
|
def []= key, value
|
197
225
|
if @indexed
|
198
226
|
merge_into(@indexed, key.downcase, value)
|
@@ -201,8 +229,9 @@ module Protocol
|
|
201
229
|
@fields << [key, value]
|
202
230
|
end
|
203
231
|
|
232
|
+
# The policy for various headers, including how they are merged and normalized.
|
204
233
|
POLICY = {
|
205
|
-
# Headers which may only be specified once
|
234
|
+
# Headers which may only be specified once:
|
206
235
|
"content-type" => false,
|
207
236
|
"content-disposition" => false,
|
208
237
|
"content-length" => false,
|
@@ -212,11 +241,13 @@ module Protocol
|
|
212
241
|
"from" => false,
|
213
242
|
"location" => false,
|
214
243
|
"max-forwards" => false,
|
244
|
+
"retry-after" => false,
|
215
245
|
|
216
246
|
# Custom headers:
|
217
247
|
"connection" => Header::Connection,
|
218
248
|
"cache-control" => Header::CacheControl,
|
219
249
|
"vary" => Header::Vary,
|
250
|
+
"priority" => Header::Priority,
|
220
251
|
|
221
252
|
# Headers specifically for proxies:
|
222
253
|
"via" => Split,
|
@@ -248,7 +279,10 @@ module Protocol
|
|
248
279
|
"if-unmodified-since" => Header::Date,
|
249
280
|
}.tap{|hash| hash.default = Split}
|
250
281
|
|
251
|
-
# Delete all
|
282
|
+
# Delete all header values for the given key, and return the merged value.
|
283
|
+
#
|
284
|
+
# @parameter key [String] The header key.
|
285
|
+
# @returns [String | Array | Object] The merged header value.
|
252
286
|
def delete(key)
|
253
287
|
deleted, @fields = @fields.partition do |field|
|
254
288
|
field.first.downcase == key
|
@@ -273,6 +307,11 @@ module Protocol
|
|
273
307
|
end
|
274
308
|
end
|
275
309
|
|
310
|
+
# Merge the value into the hash according to the policy for the given key.
|
311
|
+
#
|
312
|
+
# @parameter hash [Hash] The hash to merge into.
|
313
|
+
# @parameter key [String] The header key.
|
314
|
+
# @parameter value [String] The raw header value.
|
276
315
|
protected def merge_into(hash, key, value)
|
277
316
|
if policy = POLICY[key]
|
278
317
|
if current_value = hash[key]
|
@@ -286,11 +325,17 @@ module Protocol
|
|
286
325
|
end
|
287
326
|
end
|
288
327
|
|
328
|
+
# Get the value of the specified header key.
|
329
|
+
#
|
330
|
+
# @parameter key [String] The header key.
|
331
|
+
# @returns [String | Array | Object] The header value.
|
289
332
|
def [] key
|
290
333
|
to_h[key]
|
291
334
|
end
|
292
335
|
|
293
|
-
#
|
336
|
+
# Compute a hash table of headers, where the keys are normalized to lower case and the values are normalized according to the policy for that header.
|
337
|
+
#
|
338
|
+
# @returns [Hash] A hash table of `{key, value}` pairs.
|
294
339
|
def to_h
|
295
340
|
@indexed ||= @fields.inject({}) do |hash, (key, value)|
|
296
341
|
merge_into(hash, key.downcase, value)
|
@@ -301,10 +346,16 @@ module Protocol
|
|
301
346
|
|
302
347
|
alias as_json to_h
|
303
348
|
|
349
|
+
# Inspect the headers.
|
350
|
+
#
|
351
|
+
# @returns [String] A string representation of the headers.
|
304
352
|
def inspect
|
305
353
|
"#<#{self.class} #{@fields.inspect}>"
|
306
354
|
end
|
307
355
|
|
356
|
+
# Compare this object to another object. May depend on the order of the fields.
|
357
|
+
#
|
358
|
+
# @returns [Boolean] Whether the other object is equal to this one.
|
308
359
|
def == other
|
309
360
|
case other
|
310
361
|
when Hash
|
@@ -320,29 +371,42 @@ module Protocol
|
|
320
371
|
class Merged
|
321
372
|
include Enumerable
|
322
373
|
|
374
|
+
# Construct a merged list of headers.
|
375
|
+
#
|
376
|
+
# @parameter *all [Array] An array of all headers to merge.
|
323
377
|
def initialize(*all)
|
324
378
|
@all = all
|
325
379
|
end
|
326
380
|
|
381
|
+
# @returns [Array] A list of all headers, in the order they were added, as `[key, value]` pairs.
|
327
382
|
def fields
|
328
383
|
each.to_a
|
329
384
|
end
|
330
385
|
|
386
|
+
# @returns [Headers] A new instance of {Headers} containing all the merged headers.
|
331
387
|
def flatten
|
332
388
|
Headers.new(fields)
|
333
389
|
end
|
334
390
|
|
391
|
+
# Clear the references to all headers.
|
335
392
|
def clear
|
336
393
|
@all.clear
|
337
394
|
end
|
338
395
|
|
396
|
+
# Add a new set of headers to the merged list.
|
397
|
+
#
|
398
|
+
# @parameter headers [Headers | Array | Hash] A list of headers to add.
|
339
399
|
def << headers
|
340
400
|
@all << headers
|
341
401
|
|
342
402
|
return self
|
343
403
|
end
|
344
404
|
|
345
|
-
#
|
405
|
+
# Enumerate all headers in the merged list.
|
406
|
+
#
|
407
|
+
# @yields {|key, value| ...} The header key and value.
|
408
|
+
# @parameter key [String] The header key (lower case).
|
409
|
+
# @parameter value [String] The header value.
|
346
410
|
def each(&block)
|
347
411
|
return to_enum unless block_given?
|
348
412
|
|
@@ -50,6 +50,11 @@ module Protocol
|
|
50
50
|
# The PATCH method applies partial modifications to a resource.
|
51
51
|
PATCH = "PATCH"
|
52
52
|
|
53
|
+
# Check if the given name is a valid HTTP method, according to this module.
|
54
|
+
#
|
55
|
+
# Note that this method only knows about the methods defined in this module, however there are many other methods defined in different specifications.
|
56
|
+
#
|
57
|
+
# @returns [Boolean] True if the name is a valid HTTP method.
|
53
58
|
def self.valid?(name)
|
54
59
|
const_defined?(name)
|
55
60
|
rescue NameError
|
@@ -8,25 +8,42 @@ require_relative "../middleware"
|
|
8
8
|
module Protocol
|
9
9
|
module HTTP
|
10
10
|
class Middleware
|
11
|
+
# A convenient interface for constructing middleware stacks.
|
11
12
|
class Builder
|
13
|
+
# Initialize the builder with the given default application.
|
14
|
+
#
|
15
|
+
# @parameter default_app [Object] The default application to use if no middleware is specified.
|
12
16
|
def initialize(default_app = NotFound)
|
13
17
|
@use = []
|
14
18
|
@app = default_app
|
15
19
|
end
|
16
20
|
|
21
|
+
# Use the given middleware with the given arguments and options.
|
22
|
+
#
|
23
|
+
# @parameter middleware [Class | Object] The middleware class to use.
|
24
|
+
# @parameter arguments [Array] The arguments to pass to the middleware constructor.
|
25
|
+
# @parameter options [Hash] The options to pass to the middleware constructor.
|
26
|
+
# @parameter block [Proc] The block to pass to the middleware constructor.
|
17
27
|
def use(middleware, *arguments, **options, &block)
|
18
28
|
@use << proc {|app| middleware.new(app, *arguments, **options, &block)}
|
19
29
|
end
|
20
30
|
|
31
|
+
# Specify the (default) middleware application to use.
|
32
|
+
#
|
33
|
+
# @parameter app [Middleware] The application to use if no middleware is able to handle the request.
|
21
34
|
def run(app)
|
22
35
|
@app = app
|
23
36
|
end
|
24
37
|
|
38
|
+
# Convert the builder to an application by chaining the middleware together.
|
39
|
+
#
|
40
|
+
# @returns [Middleware] The application.
|
25
41
|
def to_app
|
26
42
|
@use.reverse.inject(@app) {|app, use| use.call(app)}
|
27
43
|
end
|
28
44
|
end
|
29
45
|
|
46
|
+
# Build a middleware application using the given block.
|
30
47
|
def self.build(&block)
|
31
48
|
builder = Builder.new
|
32
49
|
|
@@ -22,49 +22,77 @@ module Protocol
|
|
22
22
|
# You do not need to use the Middleware class to implement middleware. You can implement the interface directly.
|
23
23
|
class Middleware < Methods
|
24
24
|
# Convert a block to a middleware delegate.
|
25
|
+
#
|
26
|
+
# @parameter block [Proc] The block to convert to a middleware delegate.
|
27
|
+
# @returns [Middleware] The middleware delegate.
|
25
28
|
def self.for(&block)
|
29
|
+
# Add a close method to the block.
|
26
30
|
def block.close
|
27
31
|
end
|
28
32
|
|
29
33
|
return self.new(block)
|
30
34
|
end
|
31
35
|
|
36
|
+
# Initialize the middleware with the given delegate.
|
37
|
+
#
|
38
|
+
# @parameter delegate [Object] The delegate object. A delegate is used for passing along requests that are not handled by *this* middleware.
|
32
39
|
def initialize(delegate)
|
33
40
|
@delegate = delegate
|
34
41
|
end
|
35
42
|
|
43
|
+
# @attribute [Object] The delegate object that is used for passing along requests that are not handled by *this* middleware.
|
36
44
|
attr :delegate
|
37
45
|
|
46
|
+
# Close the middleware. Invokes the close method on the delegate.
|
38
47
|
def close
|
39
48
|
@delegate.close
|
40
49
|
end
|
41
50
|
|
51
|
+
# Call the middleware with the given request. Invokes the call method on the delegate.
|
42
52
|
def call(request)
|
43
53
|
@delegate.call(request)
|
44
54
|
end
|
45
55
|
|
56
|
+
# A simple middleware that always returns a 200 response.
|
46
57
|
module Okay
|
58
|
+
# Close the middleware - idempotent no-op.
|
47
59
|
def self.close
|
48
60
|
end
|
49
61
|
|
62
|
+
# Call the middleware with the given request, always returning a 200 response.
|
63
|
+
#
|
64
|
+
# @parameter request [Request] The request object.
|
65
|
+
# @returns [Response] The response object, which always contains a 200 status code.
|
50
66
|
def self.call(request)
|
51
67
|
Response[200]
|
52
68
|
end
|
53
69
|
end
|
54
70
|
|
71
|
+
# A simple middleware that always returns a 404 response.
|
55
72
|
module NotFound
|
73
|
+
# Close the middleware - idempotent no-op.
|
56
74
|
def self.close
|
57
75
|
end
|
58
76
|
|
77
|
+
# Call the middleware with the given request, always returning a 404 response. This middleware is useful as a default.
|
78
|
+
#
|
79
|
+
# @parameter request [Request] The request object.
|
80
|
+
# @returns [Response] The response object, which always contains a 404 status code.
|
59
81
|
def self.call(request)
|
60
82
|
Response[404]
|
61
83
|
end
|
62
84
|
end
|
63
85
|
|
86
|
+
# A simple middleware that always returns "Hello World!".
|
64
87
|
module HelloWorld
|
88
|
+
# Close the middleware - idempotent no-op.
|
65
89
|
def self.close
|
66
90
|
end
|
67
91
|
|
92
|
+
# Call the middleware with the given request.
|
93
|
+
#
|
94
|
+
# @parameter request [Request] The request object.
|
95
|
+
# @returns [Response] The response object, whihc always contains "Hello World!".
|
68
96
|
def self.call(request)
|
69
97
|
Response[200, Headers["content-type" => "text/plain"], ["Hello World!"]]
|
70
98
|
end
|
data/lib/protocol/http/peer.rb
CHANGED
@@ -7,12 +7,18 @@ module Protocol
|
|
7
7
|
module HTTP
|
8
8
|
# Provide a well defined, cached representation of a peer (address).
|
9
9
|
class Peer
|
10
|
+
# Create a new peer object for the given IO object, using the remote address if available.
|
11
|
+
#
|
12
|
+
# @returns [Peer | Nil] The peer object, or nil if the remote address is not available.
|
10
13
|
def self.for(io)
|
11
14
|
if address = io.remote_address
|
12
15
|
return new(address)
|
13
16
|
end
|
14
17
|
end
|
15
18
|
|
19
|
+
# Initialize the peer with the given address.
|
20
|
+
#
|
21
|
+
# @parameter address [Addrinfo] The remote address of the peer.
|
16
22
|
def initialize(address)
|
17
23
|
@address = address
|
18
24
|
|
@@ -21,7 +27,10 @@ module Protocol
|
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
30
|
+
# @attribute [Addrinfo] The remote address of the peer.
|
24
31
|
attr :address
|
32
|
+
|
33
|
+
# @attribute [String] The IP address of the peer, if available.
|
25
34
|
attr :ip_address
|
26
35
|
|
27
36
|
alias remote_address address
|
@@ -19,6 +19,12 @@ module Protocol
|
|
19
19
|
self.new(path, query, fragment, parameters)
|
20
20
|
end
|
21
21
|
|
22
|
+
# Initialize the reference.
|
23
|
+
#
|
24
|
+
# @parameter path [String] The path component, e.g. `/foo/bar/index.html`.
|
25
|
+
# @parameter query [String | Nil] The un-parsed query string, e.g. 'x=10&y=20'.
|
26
|
+
# @parameter fragment [String | Nil] The fragment, the part after the '#'.
|
27
|
+
# @parameter parameters [Hash | Nil] User supplied parameters that will be appended to the query part.
|
22
28
|
def initialize(path = "/", query = nil, fragment = nil, parameters = nil)
|
23
29
|
@path = path
|
24
30
|
@query = query
|
@@ -26,18 +32,21 @@ module Protocol
|
|
26
32
|
@parameters = parameters
|
27
33
|
end
|
28
34
|
|
29
|
-
# The path component, e.g.
|
35
|
+
# @attribute [String] The path component, e.g. `/foo/bar/index.html`.
|
30
36
|
attr_accessor :path
|
31
37
|
|
32
|
-
# The un-parsed query string, e.g. 'x=10&y=20'
|
38
|
+
# @attribute [String] The un-parsed query string, e.g. 'x=10&y=20'.
|
33
39
|
attr_accessor :query
|
34
40
|
|
35
|
-
#
|
41
|
+
# @attribute [String] The fragment, the part after the '#'.
|
36
42
|
attr_accessor :fragment
|
37
43
|
|
38
|
-
# User supplied parameters that will be appended to the query part.
|
44
|
+
# @attribute [Hash] User supplied parameters that will be appended to the query part.
|
39
45
|
attr_accessor :parameters
|
40
46
|
|
47
|
+
# Freeze the reference.
|
48
|
+
#
|
49
|
+
# @returns [Reference] The frozen reference.
|
41
50
|
def freeze
|
42
51
|
return self if frozen?
|
43
52
|
|
@@ -49,14 +58,25 @@ module Protocol
|
|
49
58
|
super
|
50
59
|
end
|
51
60
|
|
61
|
+
# Implicit conversion to an array.
|
62
|
+
#
|
63
|
+
# @returns [Array] The reference as an array, `[path, query, fragment, parameters]`.
|
52
64
|
def to_ary
|
53
65
|
[@path, @query, @fragment, @parameters]
|
54
66
|
end
|
55
67
|
|
68
|
+
# Compare two references.
|
69
|
+
#
|
70
|
+
# @parameter other [Reference] The other reference to compare.
|
71
|
+
# @returns [Integer] -1, 0, 1 if the reference is less than, equal to, or greater than the other reference.
|
56
72
|
def <=> other
|
57
73
|
to_ary <=> other.to_ary
|
58
74
|
end
|
59
75
|
|
76
|
+
# Type-cast a reference.
|
77
|
+
#
|
78
|
+
# @parameter reference [Reference | String] The reference to type-cast.
|
79
|
+
# @returns [Reference] The type-casted reference.
|
60
80
|
def self.[] reference
|
61
81
|
if reference.is_a? self
|
62
82
|
return reference
|
@@ -65,19 +85,23 @@ module Protocol
|
|
65
85
|
end
|
66
86
|
end
|
67
87
|
|
88
|
+
# @returns [Boolean] Whether the reference has parameters.
|
68
89
|
def parameters?
|
69
90
|
@parameters and !@parameters.empty?
|
70
91
|
end
|
71
92
|
|
93
|
+
# @returns [Boolean] Whether the reference has a query string.
|
72
94
|
def query?
|
73
95
|
@query and !@query.empty?
|
74
96
|
end
|
75
97
|
|
98
|
+
# @returns [Boolean] Whether the reference has a fragment.
|
76
99
|
def fragment?
|
77
100
|
@fragment and !@fragment.empty?
|
78
101
|
end
|
79
102
|
|
80
|
-
|
103
|
+
# Append the reference to the given buffer.
|
104
|
+
def append(buffer = String.new)
|
81
105
|
if query?
|
82
106
|
buffer << URL.escape_path(@path) << "?" << @query
|
83
107
|
buffer << "&" << URL.encode(@parameters) if parameters?
|
@@ -93,8 +117,11 @@ module Protocol
|
|
93
117
|
return buffer
|
94
118
|
end
|
95
119
|
|
120
|
+
# Convert the reference to a string, e.g. `/foo/bar/index.html?x=10&y=20#section`
|
121
|
+
#
|
122
|
+
# @returns [String] The reference as a string.
|
96
123
|
def to_s
|
97
|
-
append
|
124
|
+
append
|
98
125
|
end
|
99
126
|
|
100
127
|
# Merges two references as specified by RFC2396, similar to `URI.join`.
|
@@ -25,6 +25,17 @@ module Protocol
|
|
25
25
|
class Request
|
26
26
|
prepend Body::Reader
|
27
27
|
|
28
|
+
# Initialize the request.
|
29
|
+
#
|
30
|
+
# @parameter scheme [String | Nil] The request scheme, usually `"http"` or `"https"`.
|
31
|
+
# @parameter authority [String | Nil] The request authority, usually a hostname and port number, e.g. `"example.com:80"`.
|
32
|
+
# @parameter method [String | Nil] The request method, usually one of `"GET"`, `"HEAD"`, `"POST"`, `"PUT"`, `"DELETE"`, `"CONNECT"` or `"OPTIONS"`, etc.
|
33
|
+
# @parameter path [String | Nil] The request path, usually a path and query string, e.g. `"/index.html"`, `"/search?q=hello"`, etc.
|
34
|
+
# @parameter version [String | Nil] The request version, usually `"http/1.0"`, `"http/1.1"`, `"h2"`, or `"h3"`.
|
35
|
+
# @parameter headers [Headers] The request headers, usually containing metadata associated with the request such as the `"user-agent"`, `"accept"` (content type), `"accept-language"`, etc.
|
36
|
+
# @parameter body [Body::Readable] The request body.
|
37
|
+
# @parameter protocol [String | Array(String) | Nil] The request protocol, usually empty, but occasionally `"websocket"` or `"webtransport"`.
|
38
|
+
# @parameter interim_response [Proc] A callback which is called when an interim response is received.
|
28
39
|
def initialize(scheme = nil, authority = nil, method = nil, path = nil, version = nil, headers = Headers.new, body = nil, protocol = nil, interim_response = nil)
|
29
40
|
@scheme = scheme
|
30
41
|
@authority = authority
|
@@ -81,6 +92,11 @@ module Protocol
|
|
81
92
|
@interim_response&.call(status, headers)
|
82
93
|
end
|
83
94
|
|
95
|
+
# Register a callback to be called when an interim response is received.
|
96
|
+
#
|
97
|
+
# @yields {|status, headers| ...} The callback to be called when an interim response is received.
|
98
|
+
# @parameter status [Integer] The HTTP status code, e.g. `100`, `101`, etc.
|
99
|
+
# @parameter headers [Hash] The headers, e.g. `{"link" => "</style.css>; rel=stylesheet"}`, etc.
|
84
100
|
def on_interim_response(&block)
|
85
101
|
if interim_response = @interim_response
|
86
102
|
@interim_response = ->(status, headers) do
|
@@ -120,6 +136,9 @@ module Protocol
|
|
120
136
|
@method != Methods::POST && (@body.nil? || @body.empty?)
|
121
137
|
end
|
122
138
|
|
139
|
+
# Convert the request to a hash, suitable for serialization.
|
140
|
+
#
|
141
|
+
# @returns [Hash] The request as a hash.
|
123
142
|
def as_json(...)
|
124
143
|
{
|
125
144
|
scheme: @scheme,
|
@@ -133,10 +152,16 @@ module Protocol
|
|
133
152
|
}
|
134
153
|
end
|
135
154
|
|
155
|
+
# Convert the request to JSON.
|
156
|
+
#
|
157
|
+
# @returns [String] The request as JSON.
|
136
158
|
def to_json(...)
|
137
159
|
as_json.to_json(...)
|
138
160
|
end
|
139
161
|
|
162
|
+
# Summarize the request as a string.
|
163
|
+
#
|
164
|
+
# @returns [String] The request as a string.
|
140
165
|
def to_s
|
141
166
|
"#{@scheme}://#{@authority}: #{@method} #{@path} #{@version}"
|
142
167
|
end
|
@@ -151,6 +151,9 @@ module Protocol
|
|
151
151
|
Response[500, Headers["content-type" => "text/plain"], ["#{exception.class}: #{exception.message}"]]
|
152
152
|
end
|
153
153
|
|
154
|
+
# Convert the response to a hash suitable for serialization.
|
155
|
+
#
|
156
|
+
# @returns [Hash] The response as a hash.
|
154
157
|
def as_json(...)
|
155
158
|
{
|
156
159
|
version: @version,
|
@@ -161,14 +164,23 @@ module Protocol
|
|
161
164
|
}
|
162
165
|
end
|
163
166
|
|
167
|
+
# Convert the response to JSON.
|
168
|
+
#
|
169
|
+
# @returns [String] The response as JSON.
|
164
170
|
def to_json(...)
|
165
171
|
as_json.to_json(...)
|
166
172
|
end
|
167
173
|
|
174
|
+
# Summarise the response as a string.
|
175
|
+
#
|
176
|
+
# @returns [String] The response as a string.
|
168
177
|
def to_s
|
169
178
|
"#{@status} #{@version}"
|
170
179
|
end
|
171
180
|
|
181
|
+
# Implicit conversion to an array.
|
182
|
+
#
|
183
|
+
# @returns [Array] The response as an array, e.g. `[status, headers, body]`.
|
172
184
|
def to_ary
|
173
185
|
return @status, @headers, @body
|
174
186
|
end
|