Protocol::HTTPSourceProtocolHTTPRequest

class Request

Represents an HTTP request which can be used both server and client-side.

require 'protocol/http'

# Long form:
Protocol::HTTP::Request.new("http", "example.com", "GET", "/index.html", "HTTP/1.1", Protocol::HTTP::Headers[["accept", "text/html"]])

# Short form:
Protocol::HTTP::Request["GET", "/index.html", {"accept" => "text/html"}]

Definitions

def initialize(scheme = nil, authority = nil, method = nil, path = nil, version = nil, headers = Headers.new, body = nil, protocol = nil, interim_response = nil)

Initialize the request.

Signature

parameter scheme String | Nil

The request scheme, usually "http" or "https".

parameter authority String | Nil

The request authority, usually a hostname and port number, e.g. "example.com:80".

parameter method String | Nil

The request method, usually one of "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT" or "OPTIONS", etc.

parameter path String | Nil

The request path, usually a path and query string, e.g. "/index.html", "/search?q=hello", etc.

parameter version String | Nil

The request version, usually "http/1.0", "http/1.1", "h2", or "h3".

parameter headers Headers

The request headers, usually containing metadata associated with the request such as the "user-agent", "accept" (content type), "accept-language", etc.

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.

Implementation

def initialize(scheme = nil, authority = nil, method = nil, path = nil, version = nil, headers = Headers.new, body = nil, protocol = nil, interim_response = nil)
	@scheme = scheme
	@authority = authority
	@method = method
	@path = path
	@version = version
	@headers = headers
	@body = body
	@protocol = protocol
	@interim_response = interim_response
end

attr_accessor :scheme

Signature

attribute String

the request scheme, usually "http" or "https".

attr_accessor :authority

Signature

attribute String

the request authority, usually a hostname and port number, e.g. "example.com:80".

attr_accessor :method

Signature

attribute String

the request method, usually one of "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT" or "OPTIONS", etc.

attr_accessor :path

Signature

attribute String

the request path, usually a path and query string, e.g. "/index.html", "/search?q=hello", however it can be any valid request target.

attr_accessor :version

Signature

attribute String

the request version, usually "http/1.0", "http/1.1", "h2", or "h3".

attr_accessor :headers

Signature

attribute Headers

the request headers, usually containing metadata associated with the request such as the "user-agent", "accept" (content type), "accept-language", etc.

attr_accessor :body

Signature

attribute Body::Readable

the request body. It should only be read once (it may not be idempotent).

attr_accessor :protocol

Signature

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.

attr_accessor :interim_response

Signature

attribute Proc

a callback which is called when an interim response is received.

def peer

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.

Signature

returns Peer | Nil

The peer (address) associated with the request.

Implementation

def peer
	nil
end

def call(connection)

Send the request to the given connection.

Implementation

def call(connection)
	connection.call(self)
end

def send_interim_response(status, headers)

Send an interim response back to the origin of this request, if possible.

Implementation

def send_interim_response(status, headers)
	@interim_response&.call(status, headers)
end

def on_interim_response(&block)

Register a callback to be called when an interim response is received.

Signature

yields {|status, headers| ...}

The callback to be called when an interim response is received.

parameter status Integer

The HTTP status code, e.g. 100, 101, etc.

parameter headers Hash

The headers, e.g. {"link" => "</style.css>; rel=stylesheet"}, etc.

Implementation

def on_interim_response(&block)
	if interim_response = @interim_response
		@interim_response = ->(status, headers) do
			block.call(status, headers)
			interim_response.call(status, headers)
		end
	else
		@interim_response = block
	end
end

def head?

Whether this is a HEAD request: no body is expected in the response.

Implementation

def head?
	@method == Methods::HEAD
end

def connect?

Whether this is a CONNECT request: typically used to establish a tunnel.

Implementation

def connect?
	@method == Methods::CONNECT
end

def self.[](method, path = nil, _headers = nil, _body = nil, scheme: nil, authority: nil, headers: _headers, body: _body, protocol: nil, interim_response: nil)

A short-cut method which exposes the main request variables that you'd typically care about.

Signature

parameter method String

The HTTP method, e.g. "GET", "POST", etc.

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 Protocol::HTTP::Body::Buffered.wrap for more information about .

Implementation

def self.[](method, path = nil, _headers = nil, _body = nil, scheme: nil, authority: nil, headers: _headers, body: _body, protocol: nil, interim_response: 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)
end

def idempotent?

Whether the request can be replayed without side-effects.

Implementation

def idempotent?
	# QUERY requests are idempotent, even if they have a body:
	if @method == Methods::QUERY
		return true
	end
	
	# POST requests are not idempotent, even if they have no body:
	if @method == Methods::POST
		return false
	end
	
	# All other requests are idempotent if they have no body:
	if @body.nil? || @body.empty?
		return true
	end
	
	# Otherwise, we don't know if the request is idempotent or not, so we assume it is not:
	return false
end

def rewind!

Rewind the request body so it can be sent again.

Signature

returns Boolean

Whether the request body was rewound.

Implementation

def rewind!
	if body = @body
		return body.rewind
	end
	
	# Requests without a body can be sent again immediately.
	return true
end

def retry!

Prepare the request body to be sent again, if it is safe to retry.

Signature

returns Boolean

Whether the request was prepared for retry.

Implementation

def retry!
	# Only idempotent request methods can be retried safely.
	if @method == Methods::POST || @method == Methods::PATCH || @method == Methods::CONNECT
		return false
	end
	
	return self.rewind!
end

def as_json(...)

Convert the request to a hash, suitable for serialization.

Signature

returns Hash

The request as a hash.

Implementation

def as_json(...)
	{
		scheme: @scheme,
		authority: @authority,
		method: @method,
		path: @path,
		version: @version,
		headers: @headers&.as_json,
		body: @body&.as_json,
		protocol: @protocol
	}
end

def to_json(...)

Convert the request to JSON.

Signature

returns String

The request as JSON.

Implementation

def to_json(...)
	as_json.to_json(...)
end

def to_s

Summarize the request as a string.

Signature

returns String

The request as a string.

Implementation

def to_s
	"#{@scheme}://#{@authority}: #{@method} #{@path} #{@version}"
end