Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions lib/protocol/http/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ class Request
# @parameter body [Body::Readable] The request body.
# @parameter protocol [String | Array(String) | Nil] The request protocol, usually empty, but occasionally `"websocket"` or `"webtransport"`.
# @parameter interim_response [Proc] A callback which is called when an interim response is received.
def initialize(scheme = nil, authority = nil, method = nil, path = nil, version = nil, headers = Headers.new, body = nil, protocol = nil, interim_response = nil)
# @parameter idempotent [Boolean | Nil] An override for {#idempotent?}, or `nil` to use the default method-based heuristic.
def initialize(scheme = nil, authority = nil, method = nil, path = nil, version = nil, headers = Headers.new, body = nil, protocol = nil, interim_response = nil, idempotent: nil)
@scheme = scheme
@authority = authority
@method = method
Expand All @@ -46,6 +47,7 @@ def initialize(scheme = nil, authority = nil, method = nil, path = nil, version
@body = body
@protocol = protocol
@interim_response = interim_response
@idempotent = idempotent
end

# @attribute [String] the request scheme, usually `"http"` or `"https"`.
Expand Down Expand Up @@ -75,6 +77,9 @@ def initialize(scheme = nil, authority = nil, method = nil, path = nil, version
# @attribute [Proc] a callback which is called when an interim response is received.
attr_accessor :interim_response

# @attribute [Boolean | Nil] an override for {#idempotent?}, or `nil` to use the default method-based heuristic.
attr_accessor :idempotent

# A request that is generated by a server, may choose to include the peer (address) associated with the request. It should be implemented by a sub-class.
#
# @returns [Peer | Nil] The peer (address) associated with the request.
Expand Down Expand Up @@ -124,16 +129,21 @@ def connect?
# @parameter path [String] The path, e.g. `"/index.html"`, `"/search?q=hello"`, etc.
# @parameter headers [Hash] The headers, e.g. `{"accept" => "text/html"}`, etc.
# @parameter body [String | Array(String) | Body::Readable] The body, e.g. `"Hello, World!"`, etc. See {Body::Buffered.wrap} for more information about .
def self.[](method, path = nil, _headers = nil, _body = nil, scheme: nil, authority: nil, headers: _headers, body: _body, protocol: nil, interim_response: nil)
# @parameter idempotent [Boolean | Nil] Override the default idempotency heuristic.
def self.[](method, path = nil, _headers = nil, _body = nil, scheme: nil, authority: nil, headers: _headers, body: _body, protocol: nil, interim_response: nil, idempotent: nil)
path = path&.to_s
body = Body::Buffered.wrap(body)
headers = Headers[headers]

self.new(scheme, authority, method, path, nil, headers, body, protocol, interim_response)
self.new(scheme, authority, method, path, nil, headers, body, protocol, interim_response, idempotent: idempotent)
end

# Whether the request can be replayed without side-effects.
def idempotent?
unless @idempotent.nil?
return @idempotent
end

@method != Methods::POST && (@body.nil? || @body.empty?)
end

Expand Down
40 changes: 40 additions & 0 deletions test/protocol/http/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@
expect(request).to be(:idempotent?)
end

it "defaults idempotent to nil" do
expect(request.idempotent).to be_nil
end

it "should have a string representation" do
expect(request.to_s).to be == "http://localhost: GET /index.html HTTP/1.0"
end
Expand All @@ -141,6 +145,42 @@
end
end

with "simple POST request" do
let(:request) {subject.new("http", "localhost", "POST", "/submit", "HTTP/1.1", headers, nil)}

it "should not be idempotent" do
expect(request).not.to be(:idempotent?)
end

with "idempotent: true" do
let(:request) {subject.new("http", "localhost", "POST", "/submit", "HTTP/1.1", headers, nil, nil, nil, idempotent: true)}

it "should be idempotent" do
expect(request).to be(:idempotent?)
end
end
end

with "idempotent: false" do
let(:request) {subject.new("http", "localhost", "GET", "/index.html", "HTTP/1.0", headers, body, nil, nil, idempotent: false)}

it "should not be idempotent" do
expect(request).not.to be(:idempotent?)
end
end

with ".[] with idempotent: true" do
let(:request) {subject["POST", "/submit", idempotent: true]}

it "should be idempotent" do
expect(request).to be(:idempotent?)
end

it "should expose the idempotent attribute" do
expect(request.idempotent).to be == true
end
end

with "interim response" do
let(:request) {subject.new("http", "localhost", "GET", "/index.html", "HTTP/1.0", headers, body)}

Expand Down
Loading