Documentation
¶
Overview ¶
Package chirp implements the Chirp v0 protocol.
Chirp is a lightweight remote procedure call protocol. Peers exchange binary packets over a shared reliable channel. The protocol is intended to be easy to implement in a variety of different languages and to perform efficiently over stream-oriented local transport mechanisms such as sockets and pipes. It uses a byte-oriented packet format with fixed headers to minimize the amount of bit-level manipulation necessary to encode and decode messages.
Peers ¶
The core type defined by this package is the Peer. Peers concurrently initiate and service calls with another peer over a Channel.
To create a new, unstarted peer:
p := chirp.NewPeer()
To start the service routine, call the Start method with a channel connected to another peer:
p.Start(ch)
The peer runs until Peer.Stop is called, the channel is closed by the remote peer, or a protocol fatal error occurs. Call Peer.Wait to wait for the peer to exit an return its status:
if err := p.Wait(); err != nil {
log.Fatalf("Peer failed: %v", err)
}
Channels ¶
The Channel interface defines the ability to send and receive packets as defined by the Chirp specification. A Channel implementation must allow concurrent use by one sender and one receiver.
The github.com/creachadair/chirp/channel package provides some basic implementations of this interface.
Calls ¶
A call is an exchange between two peers, consisting of a request and a corresponding response. This is the primary communication between peers. The peer that initiates the call is the caller, the peer that responds is the callee. Calls may propagate in either direction.
To define method handlers for inbound calls on the peer, use the Peer.Handle method to register a handler for a method name:
func echo(ctx context.Context, req *chirp.Request) ([]byte, error) {
return req.Data, nil
}
p.Handle("do-a-thing", echo)
To issue a call to the remote peer, use the Peer.Call method:
rsp, err := p.Call(ctx, "do-a-thing", []byte("some data"))
if err != nil {
log.Fatalf("Call failed: %v", err)
}
Errors returned by p.Call have concrete type *chirp.CallError.
Callbacks ¶
A method handler may "call back" to methods of the remote peer. To do so, the handler uses ContextPeer to obtain the local peer, and executes its Peer.Call method. This behaves as any other call made by the local peer:
func handle(ctx context.Context, req *chirp.Request) ([]byte, error) {
rsp, err := chirp.ContextPeer(ctx).Call(ctx, "hello", []byte("world"))
if err != nil {
return nil, err
}
return doStuffWith(rsp, req.Data)
}
Local Calls ¶
To invoke a handler directly on the local peer, use Peer.Exec:
rsp, err := p.Exec(ctx, "do-a-thing", []byte("some data"))
if err != nil {
log.Fatalf("Exec failed: %v", err)
}
Exec does not send any packets to the remote peer. If the method handler invokes Peer.Call, that call also invokes its target locally. Errors reported by p.Exec have concrete type *chirp.CallError.
Custom Packet Handlers ¶
To handle packet types other than Request, Response, and Cancel, use the Peer.SendPacket and Peer.HandlePacket methods. SendPacket allows a peer to send an arbitrary packet to its connected peer. Peers that do not understand a packet type will silently discard it (per the spec).
HandlePacket registers a callback that will be invoked when a packet is received matching the specified type. If the callback reports an error or panics, it is treated as protocol fatal.
Metrics ¶
Peers maintain a collection of metrics while running. Use the Peer.Metrics method to obtain an expvar.Map containing the metrics exported by the peer. By default, metrics are shared globally among all peers.
The metrics currently exported by peers include:
- packets_received: counter of packets received
- packets_sent: counter of packets sent
- packets_dropped: counter of packets received and discarded
- calls_in: counter of inbound call requests received
- calls_in_failed: counter of inbound call requests resulting in errors
- calls_active: gauge of inbound calls currently active
- calls_out: counter of outbound call requests sent
- calls_out_failed: counter of outbound call requests resulting in errors
- cancels_in: counter of cancellation requests received
- calls_pending: gauge of outbound calls currently pending
Additional metrics may be added in the future. It is safe for the caller to modify the metrics map to add, update, and remove entries.
Using Peer.Detach "detaches" the metrics for a peer, and thereafter the metrics for that peer and its (recursive) clones will not affect the global metrics, nor the metrics of the peer from which they were detached.
p := chirp.NewPeer() // p updates global metrics p.Detach() // now p has its own metrics cp := p.Clone() // cp updates the same metrics as p ccp := cp.Clone() // ccp updates the same metrics as cp and p
Example ¶
package main
import (
"context"
"fmt"
"log"
"net"
"os"
"path/filepath"
"strconv"
"github.com/creachadair/chirp"
"github.com/creachadair/chirp/channel"
)
func main() {
socketPath := filepath.Join(os.TempDir(), "example.sock")
// Server: This peer exports a "strlen" method that returns the length of
// its argument as a decimal-coded integer.
//
// Method handlers can be added to or removed from a peer at any time,
// including while it is running.
p := chirp.NewPeer().
Handle("strlen", func(ctx context.Context, req *chirp.Request) ([]byte, error) {
return []byte(strconv.Itoa(len(req.Data))), nil
})
// Set up a Unix-domain socket to receive peer connections.
lst, err := net.Listen("unix", socketPath)
if err != nil {
log.Fatalf("Listen: %v", err)
}
go func() {
conn, err := lst.Accept()
if err != nil {
log.Fatalf("Accept: %v", err)
}
lst.Close()
p.Start(channel.IO(conn, conn))
}()
// Connect to the server socket and set up a peer.
conn, err := net.Dial("unix", socketPath)
if err != nil {
log.Fatal("Dial", err)
}
// Client: This peer does not export any methods of its own, it is used to
// send a request to the "server" peer established above.
q := chirp.NewPeer().Start(channel.IO(conn, conn))
rsp, err := q.Call(context.Background(), "strlen", []byte("hello, world"))
if err != nil {
log.Fatal("Call", err)
}
// Cleanup: Stop the client and wait for the peers to exit.
q.Stop()
p.Wait()
fmt.Println(string(rsp.Data))
}
Output: 12
Index ¶
- Constants
- Variables
- func SplitAddress(s string) (network, address string)
- type CallError
- type Cancel
- type Channel
- type ErrorData
- type Handler
- type Packet
- type PacketDir
- type PacketHandler
- type PacketLogger
- type PacketType
- type Peer
- func (p *Peer) Call(ctx context.Context, method string, data []byte) (_ *Response, err error)
- func (p *Peer) Clone() *Peer
- func (p *Peer) Detach() *Peer
- func (p *Peer) Exec(ctx context.Context, method string, data []byte) ([]byte, error)
- func (p *Peer) Handle(method string, handler Handler) *Peer
- func (p *Peer) HandlePacket(ptype PacketType, handler PacketHandler) *Peer
- func (p *Peer) LogPackets(log PacketLogger) *Peer
- func (p *Peer) Metrics() *expvar.Map
- func (p *Peer) NewContext(base func() context.Context) *Peer
- func (p *Peer) RemoteAddr() net.Addr
- func (p *Peer) SendPacket(ptype PacketType, payload []byte) error
- func (p *Peer) Start(ch Channel) *Peer
- func (p *Peer) Stop() error
- func (p *Peer) Wait() error
- type Request
- type Response
- type ResultCode
Examples ¶
Constants ¶
const MaxMethodLen = 255
MaxMethodLen is the maximum permitted length for a method name.
Variables ¶
var ErrUnknownMethod errUnknownMethod
ErrUnknownMethod is a sentinel error that a Handler may return to cause the call to report an unknown method error. This is intended for use in wildcard handlers (see Peer.Handle), although any handler may return it.
Functions ¶
func SplitAddress ¶
SplitAddress parses an address string to guess a network type and target.
The assignment of a network type uses the following heuristics:
If s has the form [host]:port, the network is assigned as "tcp" unless host contains "/", port == "", or port contains characters besides ASCII letters, digits, and "-". In those cases it is assigned as "unix".
If s does not have that form, it is assigned as "tcp" if it is entirely numeric, otherwise it is assigned as "unix".
Types ¶
type CallError ¶
type CallError struct {
ErrorData
Err error // nil for service errors
Response *Response // set if a the error came from a call response
}
CallError is the concrete type of errors reported by the Peer.Call method.
For errors originating locally, including channel failures and context deadline or cancellation errors, the Err field contains the error value.
For errors reported by the remote peer ("service" errors) the ErrorData contains the error details. For errors arising from a response, the Response field contains the complete response message.
type Cancel ¶
type Cancel struct {
RequestID uint32
}
Cancel is the payload format for a Chirp v0 cancel request packet.
type Channel ¶
type Channel interface {
// Send the packet in binary format to the receiver.
Send(Packet) error
// Receive the next available packet from the channel.
Recv() (Packet, error)
// Close the channel, causing any pending send or receive operations to
// terminate and report an error. After a channel is closed, all further
// operations on it must report an error.
Close() error
}
A Channel is a reliable stream of packets shared by two peers.
The methods of an implementation must be safe for concurrent use by one sender and one receiver.
type ErrorData ¶
ErrorData is the response data format for a service error response.
An ErrorData value satisfies the error interface, allowing a handler to return one to control the error code and ancillary data reported.
type Handler ¶
A Handler processes a request from the remote peer. A handler can obtain the Peer it is attached to from its context argument using ContextPeer.
By default, the error reported by a handler is returned to the caller with error code 0 and the text of the error as its message. A handler may return a value of concrete type ErrorData or *ErrorData to control the error code, message, and auxiliary error data.
type Packet ¶
type Packet struct {
Protocol byte
Type PacketType
Payload []byte
}
Packet is the parsed format of a Chirp v0 packet.
func (*Packet) ReadFrom ¶
ReadFrom reads a packet from r in binary format. It satisfies io.ReaderFrom.
This method does not consume any data from r beyond the packet decoded. On success the reported length is exactly the size of the packet. On error, the reported length may be less than the full size.
type PacketDir ¶
type PacketDir string
PacketDir indicates the "direction" of a packet, either Send or Recv.
type PacketHandler ¶
A PacketHandler processes a packet from the remote peer. A packet handler can obtain the Peer it is attached to from its context argument using the ContextPeer helper. Any error reported by a packet handler is protocol fatal.
type PacketLogger ¶
A PacketLogger logs a packet exchanged with the remote peer. The value of dir is either Send for a packet sent by the local peer, or Recv for a packet received from the remote peer.
type PacketType ¶
type PacketType uint16
PacketType describes the structure type of a Chirp v0 packet.
All packet type values from 0 to 127 inclusive are reserved by the protocol and MUST NOT be used for any other purpose. Packet type values from 128 to 65535 are reserved for use by the implementation.
const ( PacketRequest PacketType = 2 // The initial request for a call PacketCancel PacketType = 3 // A cancellation signal for a pending call PacketResponse PacketType = 4 // The final response from a call )
func (PacketType) String ¶
func (p PacketType) String() string
type Peer ¶
type Peer struct {
// contains filtered or unexported fields
}
A Peer implements a Chirp v0 peer. A zero-valued Peer is ready for use, but must not be copied after any method has been called.
Call Peer.Start with a channel to start the service routine for the peer. Once started, a peer runs until Peer.Stop is called, the channel closes, or a protocol fatal error occurs. Use Peer.Wait to wait for the peer to exit and report its status.
Calling Peer.Stop terminates all method handlers and outbound calls currently executing.
Call Peer.Handle to add handlers to the local peer. Use Peer.Call to invoke a call on the remote peer. Both of these methods are safe for concurrent use by multiple goroutines.
func ContextPeer ¶
ContextPeer returns the Peer associated with the given context, or nil if none is defined. The context passed to a method Handler has this value.
func (*Peer) Call ¶
Call sends a call to the remote peer for the specified method and data, and blocks until ctx ends or until the response is received. If ctx ends before the peer replies, the call will be automatically cancelled.
In case of any error, Call returns a nil *Response. The concrete type of the error value is *CallError, and if the error came from the remote peer the corresponding response can be recovered from its Response field.
func (*Peer) Clone ¶
Clone returns a new unstarted peer that has the same method handlers, packet handlers, logger, metrics, and base context function as p. After cloning, further changes to either peer do not affect the other.
func (*Peer) Detach ¶
Detach detaches the peer metrics for p, and returns p. Calling Detach resets all the metrics for p. Any peers (recursively) cloned from p after detaching will share this new metrics pool.
func (*Peer) Exec ¶
Exec executes the (local) handler on p for the method, if one exists. If no handler is defined for method, Exec reports an internal error with an empty result; otherwise it returns the result of calling the handler with the given data.
If the handler for the specified method itself executes Peer.Call, the call will dispatch to p itself, as if calling Peer.Exec.
func (*Peer) Handle ¶
Handle registers a handler for the specified method name. It is safe to call this while the peer is running. Passing a nil Handler removes any handler for the specified method. Handle returns p to permit chaining.
As a special case, if method == "" the handler is called for any request with a method name that does not have a more specific handler registered. If len(method) > MaxMethodLen, Handle will panic.
func (*Peer) HandlePacket ¶
func (p *Peer) HandlePacket(ptype PacketType, handler PacketHandler) *Peer
HandlePacket registers a callback that will be invoked whenever the remote peer sends a packet with the specified type. This method will panic if a reserved packet type is specified. Passing a nil callback removes any handler for the specified packet type. HandlePacket returns p to permit chaining.
Packet handlers are invoked synchronously with the processing of packets sent by the remote peer, and there will be at most one packet handler active at a time. If a packet handler panics or reports an error, it is protocol fatal and will terminate the peer.
func (*Peer) LogPackets ¶
func (p *Peer) LogPackets(log PacketLogger) *Peer
LogPackets registers a callback that will be invoked for each packet exchanged with the remote peer, regardless of type, including packets to be discarded.
Passing a nil callback disables packet logging. The packet logger is invoked synchronously with dispatch, prior to sending or calling a packet handler.
func (*Peer) Metrics ¶
Metrics returns a metrics map for the peer. It is safe for the caller to add additional metrics to the map while the peer is active.
func (*Peer) NewContext ¶
NewContext registers a function that will be called to create a new base context for method and packet handlers. This allows request-specific host resources to be plumbed into a handler. If it is not set a background context is used.
func (*Peer) RemoteAddr ¶ added in v0.2.3
RemoteAddr reports the net.Addr of the remote peer. It returns nil if p is not running, or if the Channel connecting p to the peer does not have a recoverable address.
func (*Peer) SendPacket ¶
func (p *Peer) SendPacket(ptype PacketType, payload []byte) error
SendPacket sends a packet to the remote peer. Any error is protocol fatal. Any packet type can be sent, including reserved types. The caller is responsible for ensuring such packets have a valid payload.
func (*Peer) Start ¶
Start starts the peer running on the given channel. The peer runs until the channel closes or a protocol fatal error occurs. Start does not block; call Peer.Wait to wait for the peer to exit and report its status.
func (*Peer) Stop ¶
Stop closes the channel and terminates the peer. It blocks until the peer has exited and returns its status. After Stop completes it is safe to restart the peer with a new channel.
func (*Peer) Wait ¶
Wait blocks until p terminates and reports the error that cause it to stop. After Wait completes it is safe to restart the peer with a new channel.
If p is not running, or has stopped because of a closed channel, Wait returns nil; otherwise it returns the error that triggered protocol failure.
type Request ¶
Request is the payload format for a Chirp v0 request packet.
func (Request) Encode ¶
Encode encodes the request data in binary format. This method will panic if r.Method is longer than MaxMethodLen.
type Response ¶
type Response struct {
RequestID uint32
Code ResultCode
Data []byte
}
Response is the payload format for a Chirp v0 response packet.
type ResultCode ¶
type ResultCode byte
ResultCode describes the result status of a completed call. All result codes not defined here are reserved for future use by the protocol.
const ( CodeSuccess ResultCode = 0 // Call completed succesfully CodeUnknownMethod ResultCode = 1 // Requested an unknown method CodeDuplicateID ResultCode = 2 // Duplicate request ID CodeCanceled ResultCode = 3 // Call was canceled CodeServiceError ResultCode = 4 // Call failed due to a service error )
func (ResultCode) String ¶
func (c ResultCode) String() string
Directories
¶
| Path | Synopsis |
|---|---|
|
Package channel provides implementations of the chirp.Channel interface.
|
Package channel provides implementations of the chirp.Channel interface. |
|
cmd
|
|
|
chirp
command
Program chirp is a command-line utility for interacting with Chirp v0 peers.
|
Program chirp is a command-line utility for interacting with Chirp v0 peers. |
|
Package packet provides support for encoding and decoding binary packet data.
|
Package packet provides support for encoding and decoding binary packet data. |
|
Package peers provides support code for managing and testing peers.
|
Package peers provides support code for managing and testing peers. |