Documentation ¶
Overview ¶
Package prpc (provisional RPC) implements an RPC client over HTTP 1.x.
Like gRPC:
- services are defined in .proto files
- service implementation does not depend on pRPC.
Unlike gRPC:
- supports HTTP 1.x and AppEngine 1.x.
- does not support streams.
Server ¶
Type Server implements a pRPC server.
Example of usage: https://github.com/luci/luci-go/tree/master/examples/appengine/helloworld_standard
Package discovery implements service discovery.
Compile service definitions ¶
Use cproto tool to compile .proto files to .go files with gRPC and pRPC support. It runs protoc and does some additional postprocessing.
//go:generate cproto
Install cproto:
go install go.chromium.org/luci/grpc/cmd/cproto
Protocol ¶
## v1.4
v1.4 hides some leaking HTTP v1 transport implementation details from gRPC services and clients by stopping exposing values of the following headers in metadata.MD: "Accept", "Accept-Encoding", "Content-Encoding", "Content-Length", "Content-Type", "X-Content-Type-Options", all "X-Prpc-*" headers.
Note that "X-Prpc-Grpc-Timeout", "X-Prpc-Status-Details-Bin", "Content-Type" and "Accept" headers were already hidden in the previous version of the protocol.
Also note that such commonly present headers as "Host" and "User-Agent" are still exposed as metadata, since they are already used in the wild and the protocol does not depend on them significantly.
## v1.3
v1.3 adds request/response body compression support using GZIP (RFC-1952).
- A request MAY have a header "Content-Encoding: gzip". If it is present, then the server MUST decompress the request body before unmarshaling the request message.
- A request MAY have a header "Accept-Encoding: gzip". If the header is present, then the server response body MAY be compressed; and the server SHOULD decide based on the response message size. If the response body is compressed, then the response MUST include a header "Content-Encoding: gzip".
See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding and https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
## v1.2
v1.2 is small, backward-compatible amendment to v1.1 that adds support for error details.
Response header "X-Prpc-Status-Details-Bin" contains elements of google.rpc.Status.details field, one value per element, in the same order. The header value is a standard base64 string with padding of the encoded google.protobuf.Any, where the message encoding is the same as the response message encoding, i.e. depends on Accept request header.
## v1.1
v1.1 is small, backward-compatible amendment to v1.0 to address a security issue. Since it is backward compatible, it does not introduce a formal protocol version header at this time.
Changes:
- Requests/responses must use "application/json" media type instead of "application/prpc; encoding=json".
- Responses must include "X-Content-Type-Options: nosniff" HTTP header.
This enables CORB protection (which mitigates spectre) and disables content sniffing. For CORB, see https://chromium.googlesource.com/chromium/src/+/master/services/network/cross_origin_read_blocking_explainer.md.
## v1.0
This section describes the pRPC protocol. It is based on HTTP 1.x and employs gRPC codes.
A pRPC server MUST handle HTTP POST requests at `/prpc/{service}/{method}` where service is a full service name including full package name. The handler must decode an input message from an HTTP request, call the service method implementation and encode the returned output message or error to the HTTP response.
pRPC protocol defines three protocol buffer encodings and media types.
- Binary: "application/prpc; encoding=binary" (default).
- JSON: "application/prpc; encoding=json" or "application/json" A response body MUST have `)]}'\n` prefix to prevent XSSI.
- Text: "application/prpc; encoding=text"
A pRPC server MUST support Binary and SHOULD support JSON and Text.
Request headers:
- "X-Prpc-Grpc-Timeout": specifies request timeout. A client MAY specify it. If a service hits the timeout, a server MUST respond with HTTP 503 and DeadlineExceed gRPC code. Value format: `\d+[HMSmun]` (regular expression), where
- H - hour
- M - minute
- S - second
- m - millisecond
- u - microsecond
- n - nanosecond
- "Content-Type": specifies input message encoding in the body. A client SHOULD specify it. If not present, a server MUST treat the input message as Binary.
- "Accept": specifies the output message encoding for the response. A client MAY specify it, a server MUST support it.
- Any other headers MUST be added to metadata.MD in the context that is passed to the service method implementation (note this behavior was amended in v1.4).
- If a header name has "-Bin" suffix, the server must treat it as standard-base64-encoded with padding and put the decoded binary blob into the metadata under the original key (i.e. the one ending with "-bin").
Response headers:
- "X-Prpc-Grpc-Code": specifies the gRPC code. A server MUST specify it in all cases.
- If it is present, the client MUST ignore the HTTP status code.
- If OK, the client MUST return the output message decoded from the body.
- Otherwise, the client MUST return a gRPC error with the code and message read from the response body.
- If not present, the client MUST return a non-gRPC error with message read from the response body. A client MAY read a portion of the response body.
- "Content-Type": specifies the output message encoding. A server SHOULD specify it. If not specified, a client MUST treat it is as Binary.
- Any metadata returned by a service method implementation MUST go into http headers, unless metadata key starts with "X-Prpc-".
A server MUST always specify "X-Prpc-Grpc-Code". The server SHOULD specify HTTP status corresponding to the gRPC code.
If the "X-Prpc-Grpc-Code" response header value is not 0, the response body MUST contain a description of the error.
If a service/method is not found, the server MUST respond with Unimplemented gRPC code and SHOULD specify HTTP 501 status.
Index ¶
- Constants
- Variables
- func DecodeTimeout(s string) (time.Duration, error)
- func EncodeTimeout(t time.Duration) string
- func RegisterDefaultAuth(a Authenticator)
- func SetHeader(ctx context.Context, md metadata.MD) error
- type AccessControlDecision
- type Authenticator
- type CallOption
- type Client
- func (c *Client) Call(ctx context.Context, serviceName, methodName string, in, out proto.Message, ...) error
- func (c *Client) CallWithFormats(ctx context.Context, serviceName, methodName string, in []byte, ...) ([]byte, error)
- func (c *Client) Invoke(ctx context.Context, method string, args interface{}, reply interface{}, ...) error
- func (c *Client) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, ...) (grpc.ClientStream, error)
- type Format
- type Options
- type Override
- type Registrar
- type Server
Constants ¶
const ( // HeaderGRPCCode is a name of the HTTP header that specifies the // gRPC code in the response. // A pRPC server must always specify it. HeaderGRPCCode = "X-Prpc-Grpc-Code" // HeaderStatusDetail is a name of the HTTP header that contains // elements of google.rpc.Status.details field, one value per element, // in the same order. // The header value is a standard-base64 string of the encoded google.protobuf.Any, // where the message encoding is the same as the response message encoding, // i.e. depends on Accept request header. HeaderStatusDetail = "X-Prpc-Status-Details-Bin" // HeaderTimeout is HTTP header used to set pRPC request timeout. // The single value should match regexp `\d+[HMSmun]`. HeaderTimeout = "X-Prpc-Grpc-Timeout" // DefaultMaxContentLength is the default maximum content length (in bytes) // for a Client. It is 32MiB. DefaultMaxContentLength = 32 * 1024 * 1024 )
const ( // ContentTypePRPC is the formal MIME content type for pRPC messages. ContentTypePRPC = "application/prpc" // ContentTypeJSON is the JSON content type. ContentTypeJSON = "application/json" // JSONPBPrefix is prepended to a message in JSONPB format to avoid CSRF. JSONPBPrefix = ")]}'\n" )
Variables ¶
var ( // DefaultUserAgent is default User-Agent HTTP header for pRPC requests. DefaultUserAgent = "pRPC Client 1.4" // ErrResponseTooBig is returned by Call when the Response's body size exceeds // the Client's MaxContentLength limit. ErrResponseTooBig = status.Error(codes.Unavailable, "prpc: response too big") // ErrNoStreamingSupport is returned if a pRPC client is used to start a // streaming RPC. They are not supported. ErrNoStreamingSupport = status.Error(codes.Unimplemented, "prpc: no streaming support") )
Functions ¶
func DecodeTimeout ¶
DecodeTimeout decodes a gRPC/pRPC timeout header string into a time.Duration.
func EncodeTimeout ¶
EncodeTimeout encodes a gRPC/pRPC timeout into a timeout header time.Duration.
This rounds the time.Duration to the smallest time unit that can represent it.
func RegisterDefaultAuth ¶
func RegisterDefaultAuth(a Authenticator)
RegisterDefaultAuth sets a default authenticator that is used unless Server.Authenticator is provided.
It is supposed to be used during init() time, to configure the default authentication method used by the program.
For example, see luci-go/appengine/gaeauth/server/prpc.go, that gets imported by LUCI GAE apps. It sets up authentication based on LUCI auth framework.
Panics if 'a' is nil or a default authenticator is already set.
Types ¶
type AccessControlDecision ¶
type AccessControlDecision int
AccessControlDecision describes what access control headers to add.
const ( // AccessDefault does not write any CORS headers at all. AccessDefault AccessControlDecision = iota // AccessAllowWithoutCredentials adds usual CORS headers except for 'Access-Control-Allow-Credentials'. // This may be used with cookie-based authentication. AccessAllowWithoutCredentials // AccessAllowWithCredentials adds all usual CORS headers. // This must NOT be used with cookie-based authentication. AccessAllowWithCredentials )
func AllowOriginAll ¶
func AllowOriginAll(ctx context.Context, origin string) AccessControlDecision
AllowOriginAll returns AccessAllowWithCredentials unconditionally. It can be used as Server.AccessControl. It must NOT be used in combination with cookie-based authentication.
type Authenticator ¶
type Authenticator interface { // Authenticate returns a context populated with authentication related // values. // // If the request is authenticated, 'Authenticate' returns a derived context // that gets passed to the RPC handler. // // If the request cannot be authenticated, 'Authenticate' returns nil context // and an error. A non-transient error triggers Unauthenticated grpc error, // a transient error triggers Internal grpc error. In both cases the error // message is set to err.Error(). Authenticate(context.Context, *http.Request) (context.Context, error) }
Authenticator authenticates incoming pRPC requests.
var ( // NoAuthentication can be used in place of an Authenticator to explicitly // specify that your Server will skip authentication. // // Use it with Server.Authenticator or RegisterDefaultAuth. NoAuthentication Authenticator = nullAuthenticator{} )
func GetDefaultAuth ¶
func GetDefaultAuth() Authenticator
GetDefaultAuth returns the default authenticator set by RegisterDefaultAuth or nil if not registered.
type CallOption ¶
type CallOption struct { grpc.CallOption // contains filtered or unexported fields }
CallOption mutates Options.
func CallAcceptContentSubtype ¶
func CallAcceptContentSubtype(contentSubtype string) *CallOption
CallAcceptContentSubtype returns a CallOption that sets Content-Type. For example, if content-subtype is "json", the Content-Type over the wire will be "application/json". Unlike that of the grpc.CallContentSubtype, sets Content-Type only for response, not for the request.
func ExpectedCode ¶
func ExpectedCode(codes ...codes.Code) *CallOption
ExpectedCode can be used to indicate that given non-OK codes may appear during normal successful call flow, and thus they must not be logged as erroneous.
Only affects local logging, nothing else.
type Client ¶
type Client struct { C *http.Client // if nil, uses http.DefaultClient Host string // host and optionally a port number of the target server Options *Options // if nil, DefaultOptions() are used // ErrBodySize is the number of bytes to truncate error messages from HTTP // responses to. // // If non-positive, defaults to 256. ErrBodySize int // MaxContentLength, if > 0, is the maximum content length, in bytes, that a // pRPC is willing to read from the server. If a larger content length is // present in the response, ErrResponseTooBig will be returned. // // If <= 0, DefaultMaxContentLength will be used. MaxContentLength int // MaxConcurrentRequests, if > 0, limits how many requests to the server can // execute at the same time. If 0 (default), there's no limit. // // If there are more concurrent Call(...) calls than the limit, excessive ones // will block until there are execution "slots" available or the context is // canceled. This waiting does not count towards PerRPCTimeout. // // The primary purpose of this mechanism is to reduce strain on the local // network resources such as number of HTTP connections and HTTP2 streams. // Note that it will not help with OOM problems, since blocked calls (and // their bodies) all queue up in memory anyway. MaxConcurrentRequests int // EnableRequestCompression allows the client to compress the request // message. Use this option only with servers that implement pRPC v1.3+. // Olders servers would fail to parse the request. // // The response message compression is configured on the server. EnableRequestCompression bool // PathPrefix is the prefix of the URL path, "<PathPrefix>/<service>/<method>" // when making HTTP requests. If not set, defaults to "/prpc". PathPrefix string // contains filtered or unexported fields }
Client can make pRPC calls.
Changing fields after the first Call(...) is undefined behavior.
func (*Client) Call ¶
func (c *Client) Call(ctx context.Context, serviceName, methodName string, in, out proto.Message, opts ...grpc.CallOption) error
Call performs a remote procedure call.
Used by the generated code. Calling from multiple goroutines concurrently is safe.
`opts` must be created by this package. Options from google.golang.org/grpc package are not supported. Panics if they are used.
Propagates outgoing gRPC metadata provided via metadata.NewOutgoingContext. It will be available via metadata.FromIncomingContext on the other side. Similarly, if there is a deadline in the Context, it is be propagated to the server and applied to the context of the request handler there.
Retries on internal transient errors and on gRPC codes considered transient by grpcutil.IsTransientCode. Logs unexpected errors (see ExpectedCode call option).
Returns gRPC errors, perhaps with extra structured details if the server provided them. Context errors are converted into gRPC errors as well. See google.golang.org/grpc/status package.
func (*Client) CallWithFormats ¶
func (c *Client) CallWithFormats(ctx context.Context, serviceName, methodName string, in []byte, inf, outf Format, opts ...grpc.CallOption) ([]byte, error)
CallWithFormats is like Call, but sends and returns raw data without marshaling it.
Trims JSONPBPrefix from the response if necessary.
func (*Client) Invoke ¶
func (c *Client) Invoke(ctx context.Context, method string, args interface{}, reply interface{}, opts ...grpc.CallOption) error
Invoke performs a unary RPC and returns after the response is received into reply.
It is a part of grpc.ClientConnInterface.
func (*Client) NewStream ¶
func (c *Client) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error)
NewStream begins a streaming RPC.
It is a part of grpc.ClientConnInterface.
type Format ¶
type Format int
Format is the pRPC protobuf wire format specification.
const ( // FormatBinary indicates that a message is encoded as a raw binary protobuf. FormatBinary Format = iota // FormatJSONPB indicates that a message is encoded as a JSON-serialized // protobuf. FormatJSONPB // FormatText indicates that a message is encoded as a text protobuf. FormatText )
(Ordered by preference).
func FormatFromContentType ¶
FormatFromContentType converts Content-Type header value from a request to a format. Can return only FormatBinary, FormatJSONPB or FormatText. In case of an error, format is undefined.
func FormatFromEncoding ¶
FormatFromEncoding converts a media type encoding parameter into a pRPC Format. Can return only FormatBinary, FormatJSONPB or FormatText. In case of an error, format is undefined.
func FormatFromMediaType ¶
FormatFromMediaType converts a media type ContentType and its parameters into a pRPC Format. Can return only FormatBinary, FormatJSONPB or FormatText. In case of an error, format is undefined.
type Options ¶
type Options struct { Retry retry.Factory // RPC retrial. // UserAgent is the value of User-Agent HTTP header. // If empty, DefaultUserAgent is used. UserAgent string Insecure bool // if true, use HTTP instead of HTTPS. // PerRPCTimeout, if > 0, is a timeout that is applied to each RPC. If the // client Context has a shorter deadline, this timeout will not be applied. // Otherwise, if this timeout is hit, the RPC round will be considered // transient. PerRPCTimeout time.Duration // AcceptContentSubtype defines Content-Type over the wire for accepting // responses. // Valid values are "binary" and "json". Empty value defaults to "binary". // It can be overridden on per-call basis via CallAcceptContentSubtype(). AcceptContentSubtype string // contains filtered or unexported fields }
Options controls how RPC requests are sent.
func DefaultOptions ¶
func DefaultOptions() *Options
DefaultOptions are used if no options are specified in Client.
type Override ¶
Override is a pRPC method-specific override which may optionally handle the entire pRPC method call. If it returns true, the override is assumed to have fully handled the pRPC method call and processing of the request does not continue. In this case it's the override's responsibility to adhere to all pRPC semantics. However if it returns false, processing continues as normal, allowing the override to act as a preprocessor. In this case it's the override's responsibility to ensure it hasn't done anything that will be incompatible with pRPC semantics (such as writing garbage to the response writer in the router context).
type Registrar ¶
type Registrar interface { // RegisterService registers a service and its implementation. // Called from the generated code. RegisterService(desc *grpc.ServiceDesc, impl interface{}) }
Registrar can register a service. It is implemented by *grpc.Server and used instead of grpc.Server in the code generated by cproto.
type Server ¶
type Server struct { // Authenticator, if not nil, specifies how to authenticate requests. // // If nil, the default authenticator set by RegisterDefaultAuth will be used. // If the default authenticator is also nil, all request handlers will panic. // // If you want to disable the authentication (e.g for tests), explicitly set // Authenticator to NoAuthentication. Authenticator Authenticator // AccessControl, if not nil, is a callback that is invoked per request to // determine what permissive access control headers, if any, should be added to // the response. // // This callback includes the request Context and the origin header supplied // by the client. If nil, no headers will be written. // Otherwise, see AccessControlDecision for what access control headers will be // included in the response for the specified origin. AccessControl func(ctx context.Context, origin string) AccessControlDecision // HackFixFieldMasksForJSON indicates whether to attempt a workaround for // https://github.com/golang/protobuf/issues/745 when the request has // Content-Type: application/json. This hack is scheduled for removal. // TODO(crbug/1082369): Remove this workaround once field masks can be decoded. HackFixFieldMasksForJSON bool // UnaryServerInterceptor provides a hook to intercept the execution of // a unary RPC on the server. It is the responsibility of the interceptor to // invoke handler to complete the RPC. UnaryServerInterceptor grpc.UnaryServerInterceptor // EnableCompression tells the Server to respect "Accept-Encoding: gzip" // request header. If false (default), responses are never compressed. EnableCompression bool // contains filtered or unexported fields }
Server is a pRPC server to serve RPC requests. Zero value is valid.
func (*Server) InstallHandlers ¶
func (s *Server) InstallHandlers(r *router.Router, base router.MiddlewareChain)
InstallHandlers installs HTTP handlers at /prpc/:service/:method.
See https://godoc.org/go.chromium.org/luci/grpc/prpc#hdr-Protocol for pRPC protocol.
The authenticator in 'base' is always replaced by pRPC specific one. For more details about the authentication see Server.Authenticator doc.
func (*Server) RegisterOverride ¶
RegisterOverride registers an overriding function.
Panics if an override for the given service method is already registered.
func (*Server) RegisterService ¶
func (s *Server) RegisterService(desc *grpc.ServiceDesc, impl interface{})
RegisterService registers a service implementation. Called from the generated code.
desc must contain description of the service, its message types and all transitive dependencies.
Panics if a service of the same name is already registered.
func (*Server) ServiceNames ¶
ServiceNames returns a sorted list of full names of all registered services.