Documentation
¶
Overview ¶
Package hostset chooses which address answers for an overlay host.
A BRC-180 manifest names ONE base URL per service, secured by control of the domain. The name behind that URL may resolve to many addresses, and every one of them is a replica: every host behind the name is sent the same objects, no host learns anything from another, and the reader verifies each answer itself. So any address's answer is as good as any other's, a wrong one is caught rather than trusted, and the only questions left are availability (which address is up) and agreement (do they say the same thing). This package answers the first with a policy and hands the caller the material for the second.
The fan-out therefore lives at the name, in DNS, where the operator already controls it, and not in a client-side host list or a manifest extension. A manifest that listed several base URLs would respecify what BRC-180 rule 2 says the base URL already means, and a client list would be a second source of truth for what the domain declares.
Index ¶
Examples ¶
Constants ¶
const DefaultMaxBody int64 = 64 << 20
DefaultMaxBody bounds a response body when Client.MaxBody is zero. Lookup answers carry BEEF, and a BEEF with its ancestry can run to megabytes, so the bound is large. It is still a bound.
Variables ¶
var ErrBodyTooLarge = errors.New("hostset: response body exceeds the bound")
ErrBodyTooLarge refuses a response above the bound.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// Source lists the hosts; nil means DNS{}.
Source Source
// Policy defaults to PolicyFirst.
Policy Policy
// Quorum is how many hosts must answer. Zero and one mean one. A larger
// value collects that many successful answers from distinct hosts, in
// policy order, and fails naming the shortfall if fewer answer. The
// caller compares the bodies; whether they must agree is its rule, not
// this package's.
Quorum int
// HTTP supplies the TLS configuration, the overall timeout and the
// redirect policy. Its Transport is cloned, never used directly; see
// transport.
HTTP *http.Client
// Timeout bounds each attempt. Defaults to 15s.
Timeout time.Duration
// MaxBody bounds each response body. Defaults to DefaultMaxBody.
MaxBody int64
// contains filtered or unexported fields
}
Client runs one request against the hosts behind a base URL.
func (*Client) Do ¶
func (c *Client) Do(ctx context.Context, base string, build func(h Host) (*http.Request, error)) ([]Result, error)
Do resolves base to hosts, runs build against each in policy order and returns every attempt made, in that order. The error, when there is one, names each host that failed and why.
build is called once per attempt so that each request has a fresh body; a request built once and reused would arrive at the second host with its body already consumed by the first. Build the request against h.Base.
type DNS ¶
DNS resolves the base URL's hostname to every A and AAAA record. This is where a BRC-180 base URL fans out: the operator publishes several records, or a routing policy that answers with several, and this source sees them all. An IP literal yields itself.
type Host ¶
type Host struct {
// Base is the base URL exactly as configured. Requests are built against
// it, so the scheme, port and path the operator gave are preserved.
Base string
// Name is Base's hostname. It stays in the URL, the Host header and the
// TLS handshake whatever address is dialed.
Name string
// Addr is the ip:port to dial (name:port for a Static host, which is
// dialed by name).
Addr string
}
Host is one address that answers for a base URL.
type Policy ¶
type Policy string
Policy says how the hosts a Source returns are tried.
const ( // PolicyFirst tries hosts in the order given and stops at the first // success. A failure is a connect error, a timeout, a body over the // bound or a 5xx. A 4xx is an answer: the host was up and said no, and // asking the next replica for a second opinion would only hide that. PolicyFirst Policy = "first" // PolicyRandom shuffles, then behaves as PolicyFirst. It spreads a // fleet of readers over a fleet of replicas without any coordination. PolicyRandom Policy = "random" // PolicyAll queries every host. It is what a fork check needs: a reader // that refuses a name whose replicas disagree can only see disagreement // if it asks everyone. PolicyAll Policy = "all" )
type Result ¶
Result is one attempt against one host. Err is non-nil for every failure as PolicyFirst defines it, including a 5xx, so Err == nil is the single test for "this host answered"; Status and Body are kept alongside so the caller can see what a failing host said.
type Static ¶
type Static struct {
Bases []string
}
Static is an explicit list of base URLs, for a caller given several, on its command line or in its configuration. Each entry is dialed by its own name. The base argument to Hosts is ignored, because the list IS the answer.
func (Static) Hosts ¶
Hosts implements Source.
Example ¶
A Static source is the list a caller was given; each entry is dialed by its own name. The DNS source, the default, instead resolves the one base URL a manifest names to every address behind it.
package main
import (
"context"
"fmt"
"github.com/lightwebinc/bcommon/hostset"
)
func main() {
src := hostset.Static{Bases: []string{"https://a.overlay.example.com", "https://b.overlay.example.com:8443/api"}}
hosts, err := src.Hosts(context.Background(), "ignored")
if err != nil {
fmt.Println(err)
return
}
for _, h := range hosts {
fmt.Println(h.Base, h.Name, h.Addr)
}
ips, err := hostset.DNS{}.Hosts(context.Background(), "https://[2001:db8::10]:8443")
fmt.Println(ips, err)
}
Output: https://a.overlay.example.com a.overlay.example.com a.overlay.example.com:443 https://b.overlay.example.com:8443/api b.overlay.example.com b.overlay.example.com:8443 [{https://[2001:db8::10]:8443 2001:db8::10 [2001:db8::10]:8443}] <nil>