Skip to content

Commit

Permalink
Improved documentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix committed Jun 19, 2024
1 parent 5e9b58e commit e64a6cb
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 15 deletions.
5 changes: 5 additions & 0 deletions lib/protocol/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@
# Copyright, 2018-2023, by Samuel Williams.

require_relative "http/version"

require_relative 'http/headers'
require_relative 'http/request'
require_relative 'http/response'
require_relative 'http/middleware'
6 changes: 5 additions & 1 deletion lib/protocol/http/body/buffered.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ module Body
# A body which buffers all it's contents.
class Buffered < Readable
# Wraps an array into a buffered body.
# @return [Readable, nil] the wrapped body or nil if nil was given.
#
# For compatibility, also accepts anything that behaves like an `Array(String)`.
#
# @parameter body [String | Array(String) | Readable | nil] the body to wrap.
# @returns [Readable | nil] the wrapped body or nil if nil was given.
def self.wrap(body)
if body.is_a?(Readable)
return body
Expand Down
48 changes: 34 additions & 14 deletions lib/protocol/http/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@

module Protocol
module HTTP
# Represents an HTTP request which can be used both server and client-side.
#
# ~~~ ruby
# 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"}]
# ~~~
class Request
prepend Body::Reader

Expand All @@ -25,50 +36,59 @@ def initialize(scheme = nil, authority = nil, method = nil, path = nil, version
@protocol = protocol
end

# The request scheme, usually one of "http" or "https".
# @attribute [String] the request scheme, usually `"http"` or `"https"`.
attr_accessor :scheme

# The request authority, usually a hostname and port number.
# @attribute [String] the request authority, usually a hostname and port number, e.g. `"example.com:80"`.
attr_accessor :authority

# The request method, usually one of "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT" or "OPTIONS".
# @attribute [String] the request method, usually one of `"GET"`, `"HEAD"`, `"POST"`, `"PUT"`, `"DELETE"`, `"CONNECT"` or `"OPTIONS"`, etc.
attr_accessor :method

# The request path, usually a path and query string.
# @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](https://www.rfc-editor.org/rfc/rfc9110#target.resource).
attr_accessor :path

# The request version, usually "http/1.0", "http/1.1", "h2", or "h3".
# @attribute [String] the request version, usually `"http/1.0"`, `"http/1.1"`, `"h2"`, or `"h3"`.
attr_accessor :version

# The request headers, contains metadata associated with the request such as the user agent, accept (content type), accept-language, etc.
# @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 :headers

# The request body, an instance of Protocol::HTTP::Body::Readable or similar.
# @attribute [Body::Readable] the request body. It should only be read once (it may not be idempotent).
attr_accessor :body

# The request protocol, usually empty, but occasionally "websocket" or "webtransport", can be either single value `String` or multi-value `Array` of `String` instances. 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.
# @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 :protocol

# Send the request to the given connection.
def call(connection)
connection.call(self)
end

# Whether this is a HEAD request: no body is expected in the response.
def head?
@method == Methods::HEAD
end

# Whether this is a CONNECT request: typically used to establish a tunnel.
def connect?
@method == Methods::CONNECT
end

# A short-cut method which exposes the main request variables that you'd typically care about.
#
# @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 {Body::Buffered.wrap} for more information about .
def self.[](method, path, headers = nil, body = nil)
body = Body::Buffered.wrap(body)
headers = ::Protocol::HTTP::Headers[headers]

self.new(nil, nil, method, path, nil, headers, body)
end

# Whether the request can be replayed without side-effects.
def idempotent?
@method != Methods::POST && (@body.nil? || @body.empty?)
end
Expand Down
40 changes: 40 additions & 0 deletions lib/protocol/http/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,27 @@

module Protocol
module HTTP
# Represents an HTTP response which can be used both server and client-side.
#
# ~~~ ruby
# require 'protocol/http'
#
# # Long form:
# Protocol::HTTP::Response.new("http/1.1", 200, Protocol::HTTP::Headers[["content-type", "text/html"]], Protocol::HTTP::Body::Buffered.wrap("Hello, World!"))
#
# # Short form:
# Protocol::HTTP::Response[200, {"content-type" => "text/html"}, ["Hello, World!"]]
# ~~~
class Response
prepend Body::Reader

# Create a new response.
#
# @parameter version [String | Nil] The HTTP version, e.g. `"HTTP/1.1"`. If `nil`, the version may be provided by the server sending the response.
# @parameter status [Integer] The HTTP status code, e.g. `200`, `404`, etc.
# @parameter headers [Hash] The headers, e.g. `{"content-type" => "text/html"}`, etc.
# @parameter body [Body::Readable] The body, e.g. `"Hello, World!"`, etc.
# @parameter protocol [String | Array(String)] The protocol, e.g. `"websocket"`, etc.
def initialize(version = nil, status = 200, headers = Headers.new, body = nil, protocol = nil)
@version = version
@status = status
Expand All @@ -19,12 +37,22 @@ def initialize(version = nil, status = 200, headers = Headers.new, body = nil, p
@protocol = protocol
end

# @attribute [String | Nil] The HTTP version, usually one of `"HTTP/1.1"`, `"HTTP/2"`, etc.
attr_accessor :version

# @attribute [Integer] The HTTP status code, e.g. `200`, `404`, etc.
attr_accessor :status

# @attribute [Hash] The headers, e.g. `{"content-type" => "text/html"}`, etc.
attr_accessor :headers

# @attribute [Body::Readable] The body, e.g. `"Hello, World!"`, etc.
attr_accessor :body

# @attribute [String | Array(String) | Nil] The protocol, e.g. `"websocket"`, etc.
attr_accessor :protocol

# Whether the response is considered a hijack: the connection has been taken over by the application and the server should not send any more data.
def hijack?
false
end
Expand Down Expand Up @@ -93,13 +121,25 @@ def internal_server_error?
# @deprecated Use {#internal_server_error?} instead.
alias server_failure? internal_server_error?

# A short-cut method which exposes the main response variables that you'd typically care about. It follows the same order as the `Rack` response tuple, but also includes the protocol.
#
# ~~~ ruby
# Response[200, {"content-type" => "text/html"}, ["Hello, World!"]]
# ~~~
#
# @parameter status [Integer] The HTTP status code, e.g. `200`, `404`, etc.
# @parameter headers [Hash] The headers, e.g. `{"content-type" => "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.[](status, headers = nil, body = nil, protocol = nil)
body = Body::Buffered.wrap(body)
headers = ::Protocol::HTTP::Headers[headers]

self.new(nil, status, headers, body, protocol)
end

# Create a response for the given exception.
#
# @parameter exception [Exception] The exception to generate the response for.
def self.for_exception(exception)
Response[500, Headers['content-type' => 'text/plain'], ["#{exception.class}: #{exception.message}"]]
end
Expand Down

0 comments on commit e64a6cb

Please sign in to comment.