Documentation ¶
Overview ¶
Package rerpc is a small RPC framework built on protocol buffers and net/http. It's wire-compatible with gRPC and Twirp.
This documentation is intended to explain each type and function in isolation. For walkthroughs, comparisons to grpc-go and Twirp, and other narrative docs, see https://rerpc.github.io.
Example ¶
package main import ( "context" "net/http" "time" "github.com/rerpc/rerpc" "github.com/rerpc/rerpc/health" pingpb "github.com/rerpc/rerpc/internal/ping/v1test" "github.com/rerpc/rerpc/reflection" ) // ExamplePingServer implements some trivial business logic. The protobuf // definition for this API is in internal/ping/v1test/ping.proto. type ExamplePingServer struct { pingpb.UnimplementedPingServiceReRPC } // Ping implements pingpb.PingServiceReRPC. func (*ExamplePingServer) Ping(ctx context.Context, req *pingpb.PingRequest) (*pingpb.PingResponse, error) { return &pingpb.PingResponse{Number: req.Number, Msg: req.Msg}, nil } func main() { // The business logic here is trivial, but the rest of the example is meant // to be somewhat realistic. This server has basic timeouts configured, and // it also exposes gRPC's server reflection and health check APIs. ping := &ExamplePingServer{} // our business logic reg := rerpc.NewRegistrar() // for gRPC reflection checker := health.NewChecker(reg) // basic health checks limit := rerpc.ReadMaxBytes(1024 * 1024) // limit request size // NewServeMux returns a plain net/http *ServeMux. Since a mux is an // http.Handler, reRPC works with any Go HTTP middleware (e.g., net/http's // StripPrefix). mux := rerpc.NewServeMux( pingpb.NewPingServiceHandlerReRPC(ping, reg, limit), // business logic reflection.NewHandler(reg), // server reflection health.NewHandler(checker), // health checks rerpc.NewBadRouteHandler(), // Twirp-compatible 404s ) // Timeouts, connection handling, TLS configuration, and other low-level // transport details are handled by net/http. Everything you already know (or // anything you learn) about hardening net/http Servers applies to reRPC // too. Keep in mind that any timeouts you set will also apply to streaming // RPCs! // // If you're not familiar with the many timeouts exposed by net/http, start with // https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/. srv := &http.Server{ Addr: ":http", Handler: mux, ReadTimeout: 2500 * time.Millisecond, WriteTimeout: 5 * time.Second, MaxHeaderBytes: rerpc.MaxHeaderBytes, } // You could also use golang.org/x/net/http2/h2c to serve gRPC requests // without TLS. srv.ListenAndServeTLS("testdata/server.crt", "testdata/server.key") }
Output:
Index ¶
- Constants
- func Errorf(c Code, template string, args ...interface{}) error
- func IsValidHeaderKey(key string) error
- func IsValidHeaderValue(v string) error
- func NewCallContext(ctx context.Context, spec Specification, req, res http.Header) context.Context
- func NewHandlerContext(ctx context.Context, spec Specification, req, res http.Header) context.Context
- func NewServeMux(services ...[]*Handler) *http.ServeMux
- func WithoutMetadata(ctx context.Context) context.Context
- func Wrap(c Code, err error, details ...proto.Message) error
- type CallOption
- type Chain
- type Code
- type Doer
- type Error
- type Func
- type Handler
- type HandlerOption
- type Header
- func (h Header) Add(key, value string) error
- func (h Header) Clone() http.Header
- func (h Header) Del(key string) error
- func (h Header) Get(key string) string
- func (h Header) GetBinary(key string) ([]byte, error)
- func (h Header) Set(key, value string) error
- func (h Header) SetBinary(key string, value []byte) error
- func (h Header) Values(key string) []string
- type Interceptor
- type Metadata
- type Option
- type Registrar
- type Specification
- type Stream
- type StreamFunc
- type StreamType
- type UnaryInterceptorFunc
Examples ¶
Constants ¶
const ( TypeDefaultGRPC = "application/grpc" TypeProtoGRPC = "application/grpc+proto" TypeProtoTwirp = "application/protobuf" TypeJSON = "application/json" )
ReRPC's supported HTTP Content-Types. Servers decide whether to use the gRPC or Twirp protocol based on the request's Content-Type. See the protocol documentation at https://rerpc.github.io for more information.
const ( CompressionIdentity = "identity" CompressionGzip = "gzip" )
ReRPC's supported compression methods.
const ( StreamTypeUnary StreamType = 0b00 StreamTypeClient = 0b01 StreamTypeServer = 0b10 StreamTypeBidirectional = StreamTypeClient | StreamTypeServer )
const MaxHeaderBytes = 1024 * 8
MaxHeaderBytes is 8KiB, gRPC's recommended maximum header size. To enforce this limit, set MaxHeaderBytes on your http.Server.
const (
SupportsCodeGenV0 = iota
)
These constants are used in compile-time handshakes with reRPC's generated code.
const Version = "0.0.1"
Version is the semantic version of the reRPC module.
Variables ¶
This section is empty.
Functions ¶
func Errorf ¶
Errorf calls fmt.Errorf with the supplied template and arguments, then wraps the resulting error. If the code is CodeOK, the returned error is nil. Otherwise, the returned error will be an *Error.
func IsValidHeaderKey ¶
IsValidHeaderKey checks whether the supplied key is reserved for use by reRPC, gRPC, or Twirp. Keys are canonicalized using textproto.CanonicalMIMEHeaderKey before checking. Unreserved headers are available for use by applications, but exercise caution: setting widely-used HTTP headers (e.g., Transfer-Encoding, Content-Length) may break your application in unexpected and difficult-to-debug ways.
The signature of IsValidHeaderKey obeys semantic versioning, but the list of reserved headers may expand in minor releases to keep up with the evolution of the gRPC and Twirp protocols. To minimize the chance of breakage, applications should namespace their headers with a consistent prefix (e.g., "Google-Cloud-").
Currently, the following keys are reserved: Accept, Accept-Encoding, Accept-Post, Allow, Content-Encoding, Content-Type, Te, and Trailer. Empty keys, or keys prefixed with ":", "Grpc-", "Rerpc-", and "Twirp-" are also reserved.
Unreserved keys may only contain the following ASCII characters: a-z, A-Z, 0-9, "-" (hyphen-minus), "_" (underscore), and "." (period).
func IsValidHeaderValue ¶
IsValidHeaderValue checks whether the supplied string is a valid header value. The gRPC wire protocol is more restrictive than plain HTTP, so only space and printable ASCII is allowed.
func NewCallContext ¶
NewCallContext constructs a Metadata and attaches it to the supplied context. It's useful in tests that rely on CallMetadata.
func NewHandlerContext ¶
func NewHandlerContext(ctx context.Context, spec Specification, req, res http.Header) context.Context
NewHandlerContext constructs a HandlerMetadata and attaches it to the supplied context. It's useful in tests that call HandlerMeta.
func NewServeMux ¶
NewServeMux mounts reRPC handlers on a mux. The signature is designed to work with with reRPC's generated code, which models each protobuf service as a slice of *Handlers.
func WithoutMetadata ¶
WithoutMetadata strips any Metadata from the context.
Types ¶
type CallOption ¶
type CallOption interface {
// contains filtered or unexported methods
}
A CallOption configures a reRPC client or a single call.
In addition to any options grouped in the documentation below, remember that Options are also valid CallOptions.
type Chain ¶
type Chain struct {
// contains filtered or unexported fields
}
A Chain composes multiple interceptors into one.
Example ¶
outer := rerpc.UnaryInterceptorFunc(func(next rerpc.Func) rerpc.Func { return rerpc.Func(func(ctx context.Context, req interface{}) (interface{}, error) { fmt.Println("outer interceptor: before call") res, err := next(ctx, req) fmt.Println("outer interceptor: after call") return res, err }) }) inner := rerpc.UnaryInterceptorFunc(func(next rerpc.Func) rerpc.Func { return rerpc.Func(func(ctx context.Context, req interface{}) (interface{}, error) { fmt.Println("inner interceptor: before call") res, err := next(ctx, req) fmt.Println("inner interceptor: after call") return res, err }) }) // This interceptor prevents the client from making network requests in // examples. Leave it out in real code! short := ShortCircuit(rerpc.Errorf(rerpc.CodeUnimplemented, "no networking in examples")) client := pingpb.NewPingServiceClientReRPC( "https://invalid-test-url", http.DefaultClient, rerpc.Intercept(rerpc.NewChain(outer, inner, short)), ) client.Ping(context.Background(), &pingpb.PingRequest{})
Output: outer interceptor: before call inner interceptor: before call inner interceptor: after call outer interceptor: after call
func NewChain ¶
func NewChain(interceptors ...Interceptor) *Chain
NewChain composes multiple interceptors into one. The first interceptor provided is the outermost layer of the onion: it acts first on the context and request, and last on the response and error.
func (*Chain) WrapStream ¶
func (c *Chain) WrapStream(next StreamFunc) StreamFunc
WrapStream implements Interceptor.
type Code ¶
type Code uint32
A Code is one of gRPC's canonical status codes. There are no user-defined codes, so only the codes enumerated below are valid.
See the specification at https://github.com/grpc/grpc/blob/master/doc/statuscodes.md for detailed descriptions of each code and example usage.
const ( CodeOK Code = 0 // success CodeCanceled Code = 1 // canceled, usually by the user CodeUnknown Code = 2 // unknown error CodeInvalidArgument Code = 3 // argument invalid regardless of system state CodeDeadlineExceeded Code = 4 // operation expired, may or may not have completed CodeNotFound Code = 5 // entity not found CodeAlreadyExists Code = 6 // entity already exists CodePermissionDenied Code = 7 // operation not authorized CodeResourceExhausted Code = 8 // quota exhausted CodeFailedPrecondition Code = 9 // argument invalid in current system state CodeAborted Code = 10 // operation aborted CodeOutOfRange Code = 11 // out of bounds, use instead of CodeFailedPrecondition CodeUnimplemented Code = 12 // operation not implemented or disabled CodeInternal Code = 13 // internal error, reserved for "serious errors" CodeDataLoss Code = 15 // unrecoverable data loss or corruption CodeUnauthenticated Code = 16 // request isn't authenticated )
func CodeOf ¶
CodeOf returns the error's status code if it is or wraps a *rerpc.Error, CodeOK if the error is nil, and CodeUnknown otherwise.
func (Code) MarshalText ¶
MarshalText implements encoding.TextMarshaler. Codes are marshaled in their numeric representations.
func (*Code) UnmarshalText ¶
UnmarshalText implements encoding.TextUnmarshaler. It accepts both numeric representations (as produced by MarshalText) and the all-caps strings from the gRPC specification. Note that the specification uses the British "CANCELLED" for CodeCanceled.
type Doer ¶
Doer is the transport-level interface reRPC expects HTTP clients to implement. The standard library's http.Client implements Doer.
type Error ¶
type Error struct {
// contains filtered or unexported fields
}
An Error captures three pieces of information: a Code, a human-readable message, and an optional collection of arbitrary protobuf messages called "details" (more on those below). Servers send the code, message, and details over the wire to clients. reRPC's Error wraps a standard Go error, using the underlying error's Error() string as the message. Take care not to leak sensitive information from public APIs!
Protobuf service implementations and Interceptors should return Errors (using the Wrap or Errorf functions) rather than plain Go errors. If service implementations or Interceptors instead return a plain Go error, reRPC will use AsError to find an Error to send over the wire. If no Error can be found, reRPC will use CodeUnknown and the returned error's message.
Error codes and messages are explained in the gRPC documentation linked below. Unfortunately, error details were introduced before gRPC adopted a formal proposal process, so they're not clearly documented anywhere and may differ slightly between implementations. Roughly, they're an optional mechanism for servers, middleware, and proxies to send strongly-typed errors and localized messages to clients. Error details aren't exposed over the Twirp protocol.
See https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md and https://github.com/grpc/grpc/blob/master/doc/statuscodes.md for further details.
func (*Error) SetDetails ¶
SetDetails overwrites the error's details.
type Func ¶
Func is the generic signature of a unary RPC. Interceptors wrap Funcs.
The type of the request and response struct depend on the codec being used. When using protobuf, they'll always be proto.Message implementations.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
A Handler is the server-side implementation of a single RPC defined by a protocol buffer service. It's the interface between the reRPC library and the code generated by the reRPC protoc plugin; most users won't ever need to deal with it directly.
To see an example of how Handler is used in the generated code, see the internal/ping/v1test package.
func NewBadRouteHandler ¶
func NewBadRouteHandler(opts ...HandlerOption) []*Handler
NewBadRouteHandler always returns gRPC and Twirp's equivalent of the standard library's http.StatusNotFound. To be fully compatible with the Twirp specification, include this in your call to NewServeMux (so that it handles any requests for invalid protobuf methods).
func NewHandler ¶
func NewHandler( stype StreamType, pkg, service, method string, implementation func(context.Context, StreamFunc), opts ...HandlerOption, ) *Handler
NewHandler constructs a Handler. The supplied package, service, and method names must be protobuf identifiers. For example, a handler for the URL "/acme.foo.v1.FooService/Bar" would have package "acme.foo.v1", service "FooService", and method "Bar".
Remember that NewHandler is usually called from generated code - most users won't need to deal with protobuf identifiers directly.
type HandlerOption ¶
type HandlerOption interface {
// contains filtered or unexported methods
}
A HandlerOption configures a Handler.
In addition to any options grouped in the documentation below, remember that Registrars and Options are also valid HandlerOptions.
func ServeTwirp ¶
func ServeTwirp(enable bool) HandlerOption
ServeTwirp enables or disables support for Twirp's JSON and protobuf formats. Disable Twirp if you only want your handlers to speak the gRPC protocol.
By default, handlers support Twirp.
type Header ¶
type Header struct {
// contains filtered or unexported fields
}
Header provides access to HTTP headers and trailers. It's very similar to net/http's Header, but automatically validates with IsValidHeaderKey and IsValidHeaderValue.
The zero value of Header is safe to use.
func NewHeader ¶
NewHeader wraps an http.Header in reRPC's validation logic. Keys and values added directly to the http.Header aren't validated.
func (Header) Add ¶
Add a key-value pair to the header, appending to any existing values associated with the key. Like the standard library's http.Header, keys are case-insensitive and canonicalized with textproto.CanonicalMIMEHeaderKey.
Attempting to add to an invalid key (as defined by IsValidHeaderKey) or supplying an invalid value (as defined by IsValidHeaderValue) returns an error. See IsValidHeaderKey for backward compatibility guarantees.
func (Header) Clone ¶
Clone returns a copy of the underlying HTTP headers, including all reserved keys.
func (Header) Del ¶
Del deletes all values associated with the key. Like the standard library's http.Header, keys are case-insensitive and canonicalized with textproto.CanonicalMIMEHeaderKey.
Attempting delete an invalid key (as defined by IsValidHeaderKey) returns an error. See IsValidHeaderKey for backward compatibility guarantees.
func (Header) Get ¶
Get returns the first value associated with the given key. Like the standard library's http.Header, keys are case-insensitive and canonicalized with textproto.CanonicalMIMEHeaderKey.
func (Header) GetBinary ¶
GetBinary is similar to Get, but for binary values encoded according to the gRPC specification. Briefly, binary headers have keys ending in "-Bin" and base64-encoded values. GetBinary automatically appends the "-Bin" suffix to the supplied key and base64-decodes the value.
For details on gRPC's treatment of binary headers, see https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md.
func (Header) Set ¶
Set the value associated with the given key, overwriting any existing values. Like the standard library's http.Header, keys are case-insensitive and canonicalized with textproto.CanonicalMIMEHeaderKey.
Attempting to set an invalid key (as defined by IsValidHeaderKey) or value (as defined by IsValidHeaderValue) returns an error. See IsValidHeaderKey for backward compatibility guarantees.
func (Header) SetBinary ¶
SetBinary is similar to Set, but for binary values encoded according to the gRPC specification. Briefly, binary headers have keys ending in "-Bin" and base64-encoded values. Like grpc-go, SetBinary automatically appends the "-Bin" suffix to the supplied key and base64-encodes the value.
For details on gRPC's treatment of binary headers, see https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md.
type Interceptor ¶
type Interceptor interface { Wrap(Func) Func WrapStream(StreamFunc) StreamFunc }
An Interceptor adds logic to a generated handler or client, like the decorators or middleware you may have seen in other libraries. Interceptors may replace the context, mutate the request, mutate the response, handle the returned error, retry, recover from panics, emit logs and metrics, or do nearly anything else.
The functions returned by Wrap and WrapStream must be safe to call concurrently. If chained carelessly, the interceptor's logic may run more than once - where possible, interceptors should be idempotent.
See Chain for an example of interceptor use.
func ConfiguredCallInterceptor ¶
func ConfiguredCallInterceptor(opts []CallOption) Interceptor
ConfiguredCallInterceptor returns the Interceptor configured by a collection of call options (if any). It's used in generated code.
func ConfiguredHandlerInterceptor ¶
func ConfiguredHandlerInterceptor(opts []HandlerOption) Interceptor
ConfiguredHandlerInterceptor returns the Interceptor configured by a collection of handler options (if any). It's used in generated code.
type Metadata ¶
type Metadata struct { Spec Specification // contains filtered or unexported fields }
Metadata provides a Specification and access to request and response headers for an in-progress client call or handler invocation.
func CallMetadata ¶
CallMetadata retrieves Metadata from the supplied context. It only succeeds in client calls - in other settings, the returned bool will be false. If you're writing an Interceptor that uses different logic for servers and clients, you can use CallMetadata to check which logic to apply.
To test interceptors that use CallMetadata, pass them a context constructed by NewCallContext.
Example ¶
logger := rerpc.UnaryInterceptorFunc(func(next rerpc.Func) rerpc.Func { return rerpc.Func(func(ctx context.Context, req interface{}) (interface{}, error) { if md, ok := rerpc.CallMetadata(ctx); ok { fmt.Println("calling", md.Spec.Method) } return next(ctx, req) }) }) // This interceptor prevents the client from making network requests in // examples. Leave it out in real code! short := ShortCircuit(rerpc.Errorf(rerpc.CodeUnimplemented, "no networking in examples")) client := pingpb.NewPingServiceClientReRPC( "https://invalid-test-url", http.DefaultClient, rerpc.Intercept(rerpc.NewChain(logger, short)), ) client.Ping(context.Background(), &pingpb.PingRequest{})
Output: calling Ping
func HandlerMetadata ¶
HandlerMetadata retrieves Metadata from the supplied context. It only succeeds in handler invocations (including protobuf service implementations) - in other settings, the returned bool will be false. If you're writing an Interceptor that uses different logic for servers and clients, you can use HandlerMetadata to check which logic to apply.
To test interceptors and service implementations that use HandlerMetadata, pass them a context constructed by NewHandlerContext.
type Option ¶
type Option interface { CallOption HandlerOption }
Option implements both CallOption and HandlerOption, so it can be applied both client-side and server-side.
func Gzip ¶
Gzip configures client and server compression strategies.
For handlers, enabling gzip compresses responses where it's likely to improve overall performance. By default, handlers use gzip if the client supports it, the uncompressed response message is >1 KiB, and the message is likely to compress well. Disabling gzip support instructs handlers to always send uncompressed responses.
For clients, enabling gzip compresses requests where it's likely to improve performance (using the same criteria as handlers). gRPC's compression negotiation is complex, but most first-party gRPC servers won't compress responses unless the client enables this option. Since not all servers support gzip compression, clients default to sending uncompressed requests.
func Intercept ¶
func Intercept(interceptor Interceptor) Option
Intercept configures a client or handler to use the supplied Interceptor. Note that this Option replaces any previously-configured Interceptor - to compose Interceptors, use a Chain.
func OverrideProtobufPackage ¶
OverrideProtobufPackage replaces the protobuf package name set by the generated code. This affects URLs and any Specification retrieved from a call or handler context. Using this option is usually a bad idea, but it's occasionally necessary to prevent protobuf package collisions. (For example, reRPC uses this option to serve the health and reflection APIs without generating runtime conflicts with grpc-go.)
OverrideProtobufPackage does not change the data exposed by the reflection API. To prevent inconsistencies between the reflection data and the actual service URL, using this option disables reflection for the overridden service (though other services can still be introspected).
func ReadMaxBytes ¶
ReadMaxBytes limits the performance impact of pathologically large messages sent by the other party. For handlers, ReadMaxBytes limits the size of message that the client can send. For clients, ReadMaxBytes limits the size of message that the server can respond with. Limits are applied before decompression and apply to each protobuf message, not to the stream as a whole.
Setting ReadMaxBytes to zero allows any message size. Both clients and handlers default to allowing any request size.
type Registrar ¶
type Registrar struct {
// contains filtered or unexported fields
}
A Registrar collects information to support gRPC server reflection when building handlers. Registrars are valid HandlerOptions.
func (*Registrar) IsRegistered ¶
IsRegistered checks whether a fully-qualified protobuf service name is registered. It's safe to call concurrently.
type Specification ¶
type Specification struct { Type StreamType Package string // protobuf name, e.g. "acme.foo.v1" Service string // protobuf name, e.g. "FooService" Method string // protobuf name, e.g. "Bar" Path string ContentType string RequestCompression string ResponseCompression string ReadMaxBytes int64 }
Specification is a description of a client call or a handler invocation.
Note that the Method, Service, and Package are protobuf names, not Go import paths or identifiers.
type Stream ¶
type Stream interface { // Implementations must ensure that Context is safe to call concurrently. It // must not race with any other methods. Context() context.Context // Implementations must ensure that Send and CloseSend don't race with // Context, Receive, or CloseReceive. They may race with each other. Send(interface{}) error CloseSend(error) error // Implementations must ensure that Receive and CloseReceive don't race with // Context, Send, or CloseSend. They may race with each other. Receive(interface{}) error CloseReceive() error }
Stream is a bidirectional stream of protobuf messages.
Stream implementations must support a limited form of concurrency: one goroutine may call Send and CloseSend, and another may call Receive and CloseReceive. Either goroutine may call Context.
type StreamFunc ¶
StreamFunc is the generic signature of a streaming RPC. Interceptors wrap StreamFuncs.
func NewCall ¶
func NewCall( ctx context.Context, doer Doer, stype StreamType, baseURL, pkg, service, method string, opts ...CallOption, ) (context.Context, StreamFunc)
NewCall returns the context and StreamFunc required to call a remote procedure. It's the interface between the reRPC library and the client code generated by protoc-gen-go-rerpc; most users won't ever need to deal with it directly.
To see an example of how NewCall is used in the generated code, see the internal/ping/v1test package.
type StreamType ¶
type StreamType uint8
StreamType describes whether the client, server, neither, or both is streaming.
type UnaryInterceptorFunc ¶
A UnaryInterceptorFunc is a simple Interceptor implementation that only wraps unary RPCs. It has no effect on client, server, or bidirectional streaming RPCs. See CallMetadata for an example.
func (UnaryInterceptorFunc) Wrap ¶
func (f UnaryInterceptorFunc) Wrap(next Func) Func
Wrap implements Interceptor by applying the interceptor function.
func (UnaryInterceptorFunc) WrapStream ¶
func (f UnaryInterceptorFunc) WrapStream(next StreamFunc) StreamFunc
WrapStream implements Interceptor with a no-op.
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
cmd
|
|
protoc-gen-go-rerpc
protoc-gen-go-rerpc is a plugin for the protocol buffer compiler that generates Go code.
|
protoc-gen-go-rerpc is a plugin for the protocol buffer compiler that generates Go code. |
Package health offers support for gRPC's health-checking APIs.
|
Package health offers support for gRPC's health-checking APIs. |
internal
|
|
Package reflection offers support for gRPC's server reflection API.
|
Package reflection offers support for gRPC's server reflection API. |
Package rerpctest contains testing utilities for reRPC, including a replacement for httptest.Server.
|
Package rerpctest contains testing utilities for reRPC, including a replacement for httptest.Server. |