Documentation
¶
Index ¶
- Constants
- type EventListener
- type HTTPOption
- func WithConcurrentEventListener(listener EventListener) HTTPOption
- func WithEventListener(listener EventListener) HTTPOption
- func WithHTTPCodec(codec transport.InboundCodec[*http.Request, *http.Response]) HTTPOption
- func WithListenerPanicLogger(logger ListenerPanicLogger) HTTPOption
- func WithMaxConcurrency(n int) HTTPOption
- func WithPanicLogger(logger dispatcher.PanicLogger) HTTPOption
- func WithReceiptTimestamps(enabled bool) HTTPOption
- func WithValidationOptions(options ...validator.Option) HTTPOption
- type HTTPServer
- func (s *HTTPServer) Execute(req execution.Request) (execution.Response, error)
- func (s *HTTPServer) ExecuteBatch(req *batch.Request) (*batch.Response, error)
- func (s *HTTPServer) Handle(command ucan.Command, fn execution.HandlerFunc)
- func (s *HTTPServer) RoundTrip(r *http.Request) (*http.Response, error)
- func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
- type ListenerPanicLogger
- type RequestDecodeListener
- type ResponseEncodeListener
- type Route
Examples ¶
Constants ¶
const DefaultMaxConcurrency = 100
DefaultMaxConcurrency is how many invocations of one request execute at the same time unless WithMaxConcurrency says otherwise. It is the value quic-go uses for MaxIncomingStreams and the floor RFC 9113 recommends for SETTINGS_MAX_CONCURRENT_STREAMS; x/net/http2 defaults to 250.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type EventListener ¶
type EventListener interface {
RequestDecodeListener
ResponseEncodeListener
}
EventListener observes both halves of the request/response round trip a server handles: a request being decoded and a response being encoded. Register one with WithEventListener.
type HTTPOption ¶
type HTTPOption func(cfg *httpServerConfig)
HTTPOption is an option configuring a UCAN HTTP server.
func WithConcurrentEventListener ¶
func WithConcurrentEventListener(listener EventListener) HTTPOption
WithConcurrentEventListener registers an EventListener whose OnRequestDecode runs on its own goroutine while the request's handlers execute, instead of before them. The server waits for it before any OnResponseEncode runs, so whatever it records is in place by the time the response leaves, and an error from it still fails the request, after the handlers have run. Its OnResponseEncode runs as it does for WithEventListener. The place for a listener that only observes the request, such as one that stores it, and whose work would otherwise delay every handler by its own duration.
func WithEventListener ¶
func WithEventListener(listener EventListener) HTTPOption
WithEventListener registers an EventListener to observe the server's requests and responses as they are decoded and encoded. Its OnRequestDecode runs before any handler does, so an error from it fails the request before anything executes: the place for a listener that gates execution.
func WithHTTPCodec ¶
func WithHTTPCodec(codec transport.InboundCodec[*http.Request, *http.Response]) HTTPOption
func WithListenerPanicLogger ¶
func WithListenerPanicLogger(logger ListenerPanicLogger) HTTPOption
WithListenerPanicLogger sets the function that records a concurrent event listener's recovered panic. The default prints the panic value and stack through the standard log package, as dispatcher.WithPanicLogger's does. Set it to route these panics wherever handler panics go. A nil logger panics.
func WithMaxConcurrency ¶
func WithMaxConcurrency(n int) HTTPOption
WithMaxConcurrency caps how many invocations of one request execute at the same time. Every invocation still gets its own goroutine; the cap is a per-request semaphore that limits how many are admitted at once, the way an HTTP/2 or QUIC server caps concurrent streams per connection. It does not limit the server as a whole: each request in flight gets its own cap.
- WithMaxConcurrency(1) runs the invocations of a request serially, one after another. - WithMaxConcurrency(0) removes the cap. - A negative value panics.
Defaults to DefaultMaxConcurrency.
func WithPanicLogger ¶
func WithPanicLogger(logger dispatcher.PanicLogger) HTTPOption
WithPanicLogger sets the function the server's dispatcher records recovered panics with, from handlers and from validation code alike. See dispatcher.WithPanicLogger for the default and the contract. A panic in a concurrent event listener is recorded by WithListenerPanicLogger instead. A nil logger panics.
func WithReceiptTimestamps ¶
func WithReceiptTimestamps(enabled bool) HTTPOption
WithReceiptTimestamps configures the server to issue receipts with issuance timestamps or not.
func WithValidationOptions ¶
func WithValidationOptions(options ...validator.Option) HTTPOption
type HTTPServer ¶
type HTTPServer struct {
// contains filtered or unexported fields
}
func NewHTTP ¶
func NewHTTP(id ucan.Issuer, options ...HTTPOption) *HTTPServer
NewHTTP creates a new server capable of handling UCAN invocations over HTTP.
func (*HTTPServer) ExecuteBatch ¶
ExecuteBatch executes every invocation in the request that is addressed to this server and returns their receipts. Invocations addressed elsewhere are skipped and get no receipt. Each handler sees every token of the request as its metadata, and the tokens handlers attach to their responses are gathered into the metadata of the returned response.
Invocations execute concurrently, each on its own goroutine, with at most DefaultMaxConcurrency of them running at once unless WithMaxConcurrency sets another cap. The cap applies to this request alone: concurrent requests each get their own, so the server as a whole runs up to the cap times the number of requests in flight. Handlers must therefore be safe to call concurrently within one request, as they already must be across requests. Receipts and metadata are gathered in request order once every invocation has finished, so the response does not depend on which handler finished first.
func (*HTTPServer) Handle ¶
func (s *HTTPServer) Handle(command ucan.Command, fn execution.HandlerFunc)
func (*HTTPServer) RoundTrip ¶
RoundTrip unpacks and executes an incoming request, returning the response.
func (*HTTPServer) ServeHTTP ¶
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
type ListenerPanicLogger ¶
ListenerPanicLogger is called with the request and the recovered value when a listener registered with WithConcurrentEventListener panics. The server has already decided the outcome by then: the request fails with an error that does not reveal the panic, since the error reaches the HTTP caller. The logger runs on the panicking goroutine inside the deferred recover, so runtime.Stack or runtime/debug.Stack called from it returns the stack of the panic.
type RequestDecodeListener ¶
type RequestDecodeListener interface {
OnRequestDecode(ctx context.Context, container ucan.Container) error
}
RequestDecodeListener is an observer with a function that is called after an execution request has been decoded by the codec. Whether it is called before the request's handlers run or alongside them depends on how the listener is registered: see WithEventListener and WithConcurrentEventListener.
type ResponseEncodeListener ¶
type ResponseEncodeListener interface {
OnResponseEncode(ctx context.Context, container ucan.Container) error
}
ResponseEncodeListener is an observer with a function that is called before an execution response is encoded by the codec.
type Route ¶
type Route struct {
Command ucan.Command
Handler execution.HandlerFunc
}
Route maps a command to the handler that executes it. A Route can be carried as a value — e.g. collected via dependency injection — and applied to a server later with HTTPServer.Handle:
for _, r := range routes {
srv.Handle(r.Command, r.Handler)
}
func NewRoute ¶
func NewRoute(cmd ucan.Command, fn execution.HandlerFunc) Route
NewRoute builds a Route from a command and a handler.
Example ¶
ExampleNewRoute bundles commands with their handlers as server.Route values and registers them on a server in one place. Because a Route's command and handler come from the same binding, their argument and result types cannot drift apart. Collecting routes as values also lets independent subsystems each contribute their own and hand them to the server for registration.
package main
import (
"context"
"fmt"
"net/http"
"net/url"
"github.com/fil-forge/ucantone/binding"
"github.com/fil-forge/ucantone/client"
"github.com/fil-forge/ucantone/execution"
"github.com/fil-forge/ucantone/multikey/ed25519"
"github.com/fil-forge/ucantone/server"
tdm "github.com/fil-forge/ucantone/testutil/datamodel"
"github.com/fil-forge/ucantone/ucan/command"
"github.com/fil-forge/ucantone/ucan/invocation"
)
// echo is the /example/echo command bound to the Go types of its arguments and
// result. Handlers and clients both derive their types from it.
var echo = binding.Bind[*tdm.TestObject, *tdm.TestObject2](command.MustParse("/example/echo"))
// ExampleNewRoute bundles commands with their handlers as [server.Route] values
// and registers them on a server in one place. Because a Route's command and
// handler come from the same binding, their argument and result types cannot
// drift apart. Collecting routes as values also lets independent subsystems
// each contribute their own and hand them to the server for registration.
func main() {
// Each subsystem contributes routes; here, the echo command and a handler
// that returns the argument bytes as a string.
routes := []server.Route{
echo.Route(func(req *binding.Request[*tdm.TestObject], res *binding.Response[*tdm.TestObject2]) error {
args := req.Task().Arguments()
return res.SetSuccess(&tdm.TestObject2{Str: string(args.Bytes)})
}),
}
service, _ := ed25519.GenerateIssuer()
srv := server.NewHTTP(service)
for _, r := range routes {
srv.Handle(r.Command, r.Handler)
}
// Drive the server in-process by using it as the client's HTTP transport.
endpoint, _ := url.Parse("http://echo.example")
c, _ := client.NewHTTP(endpoint, client.WithHTTPClient(&http.Client{Transport: srv}))
// A client invokes the command with typed arguments and unpacks the typed
// result from the receipt.
alice, _ := ed25519.GenerateIssuer()
inv, _ := echo.Invoke(alice, alice.DID(), &tdm.TestObject{Bytes: []byte("hi")}, invocation.WithAudience(service.DID()))
resp, _ := c.Execute(execution.NewRequest(context.Background(), inv))
out, _ := echo.Unpack(resp.Receipt())
fmt.Println(out.Str)
}
Output: hi
Directories
¶
| Path | Synopsis |
|---|---|
|
Package middleware holds authorization checks a server's routes can be served behind.
|
Package middleware holds authorization checks a server's routes can be served behind. |