go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/grpc/prpc/client.go (about)

     1  // Copyright 2016 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package prpc
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"encoding/base64"
    21  	"fmt"
    22  	"io"
    23  	"net/http"
    24  	"net/url"
    25  	"os"
    26  	"strconv"
    27  	"strings"
    28  	"sync"
    29  	"time"
    30  
    31  	"golang.org/x/sync/semaphore"
    32  
    33  	"github.com/golang/protobuf/jsonpb"
    34  	"github.com/golang/protobuf/proto"
    35  	spb "google.golang.org/genproto/googleapis/rpc/status"
    36  	"google.golang.org/protobuf/types/known/anypb"
    37  
    38  	"google.golang.org/grpc"
    39  	"google.golang.org/grpc/codes"
    40  	"google.golang.org/grpc/metadata"
    41  	"google.golang.org/grpc/status"
    42  
    43  	"go.chromium.org/luci/common/clock"
    44  	"go.chromium.org/luci/common/errors"
    45  	"go.chromium.org/luci/common/logging"
    46  	"go.chromium.org/luci/common/retry"
    47  	"go.chromium.org/luci/common/retry/transient"
    48  	"go.chromium.org/luci/grpc/grpcutil"
    49  )
    50  
    51  const (
    52  	// HeaderGRPCCode is a name of the HTTP header that specifies the
    53  	// gRPC code in the response.
    54  	// A pRPC server must always specify it.
    55  	HeaderGRPCCode = "X-Prpc-Grpc-Code"
    56  
    57  	// HeaderStatusDetail is a name of the HTTP header that contains
    58  	// elements of google.rpc.Status.details field, one value per element,
    59  	// in the same order.
    60  	// The header value is a standard-base64 string of the encoded google.protobuf.Any,
    61  	// where the message encoding is the same as the response message encoding,
    62  	// i.e. depends on Accept request header.
    63  	HeaderStatusDetail = "X-Prpc-Status-Details-Bin"
    64  
    65  	// HeaderTimeout is HTTP header used to set pRPC request timeout.
    66  	// The single value should match regexp `\d+[HMSmun]`.
    67  	HeaderTimeout = "X-Prpc-Grpc-Timeout"
    68  
    69  	// DefaultMaxContentLength is the default maximum content length (in bytes)
    70  	// for a Client. It is 32MiB.
    71  	DefaultMaxContentLength = 32 * 1024 * 1024
    72  )
    73  
    74  var (
    75  	// DefaultUserAgent is default User-Agent HTTP header for pRPC requests.
    76  	DefaultUserAgent = "pRPC Client 1.4"
    77  
    78  	// ErrResponseTooBig is returned by Call when the Response's body size exceeds
    79  	// the Client's MaxContentLength limit.
    80  	ErrResponseTooBig = status.Error(codes.Unavailable, "prpc: response too big")
    81  
    82  	// ErrNoStreamingSupport is returned if a pRPC client is used to start a
    83  	// streaming RPC. They are not supported.
    84  	ErrNoStreamingSupport = status.Error(codes.Unimplemented, "prpc: no streaming support")
    85  )
    86  
    87  // Client can make pRPC calls.
    88  //
    89  // Changing fields after the first Call(...) is undefined behavior.
    90  type Client struct {
    91  	C       *http.Client // if nil, uses http.DefaultClient
    92  	Host    string       // host and optionally a port number of the target server
    93  	Options *Options     // if nil, DefaultOptions() are used
    94  
    95  	// ErrBodySize is the number of bytes to truncate error messages from HTTP
    96  	// responses to.
    97  	//
    98  	// If non-positive, defaults to 256.
    99  	ErrBodySize int
   100  
   101  	// MaxContentLength, if > 0, is the maximum content length, in bytes, that a
   102  	// pRPC is willing to read from the server. If a larger content length is
   103  	// present in the response, ErrResponseTooBig will be returned.
   104  	//
   105  	// If <= 0, DefaultMaxContentLength will be used.
   106  	MaxContentLength int
   107  
   108  	// MaxConcurrentRequests, if > 0, limits how many requests to the server can
   109  	// execute at the same time. If 0 (default), there's no limit.
   110  	//
   111  	// If there are more concurrent Call(...) calls than the limit, excessive ones
   112  	// will block until there are execution "slots" available or the context is
   113  	// canceled. This waiting does not count towards PerRPCTimeout.
   114  	//
   115  	// The primary purpose of this mechanism is to reduce strain on the local
   116  	// network resources such as number of HTTP connections and HTTP2 streams.
   117  	// Note that it will not help with OOM problems, since blocked calls (and
   118  	// their bodies) all queue up in memory anyway.
   119  	MaxConcurrentRequests int
   120  
   121  	// EnableRequestCompression allows the client to compress requests if they
   122  	// are larger than a certain threshold.
   123  	//
   124  	// This is false by default. Use this option only with servers that understand
   125  	// compressed requests! These are Go servers built after Aug 15 2022. Python
   126  	// servers and olders Go servers would fail to parse the request with
   127  	// INVALID_ARGUMENT error.
   128  	//
   129  	// The response compression is configured independently on the server. The
   130  	// client always accepts compressed responses.
   131  	EnableRequestCompression bool
   132  
   133  	// PathPrefix is the prefix of the URL path, "<PathPrefix>/<service>/<method>"
   134  	// when making HTTP requests. If not set, defaults to "/prpc".
   135  	PathPrefix string
   136  
   137  	// Semaphore to limit concurrency, initialized in concurrencySem().
   138  	semOnce sync.Once
   139  	sem     *semaphore.Weighted
   140  
   141  	// testPostHTTP is a test-installed callback that is invoked after an HTTP
   142  	// request finishes.
   143  	testPostHTTP func(context.Context, error) error
   144  }
   145  
   146  var _ grpc.ClientConnInterface = (*Client)(nil)
   147  
   148  // Invoke performs a unary RPC and returns after the response is received
   149  // into reply.
   150  //
   151  // It is a part of grpc.ClientConnInterface.
   152  func (c *Client) Invoke(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error {
   153  	// 'method' looks like "/service.Name/MethodName".
   154  	parts := strings.Split(method, "/")
   155  	if len(parts) != 3 || parts[0] != "" {
   156  		return status.Errorf(codes.Internal, "prpc: not a valid method name %q", method)
   157  	}
   158  	serviceName, methodName := parts[1], parts[2]
   159  
   160  	// Inputs and outputs must be proto messages.
   161  	in, ok := args.(proto.Message)
   162  	if !ok {
   163  		return status.Errorf(codes.Internal, "prpc: bad argument type %T, not a proto", args)
   164  	}
   165  	out, ok := reply.(proto.Message)
   166  	if !ok {
   167  		return status.Errorf(codes.Internal, "prpc: bad reply type %T, not a proto", reply)
   168  	}
   169  
   170  	return c.Call(ctx, serviceName, methodName, in, out, opts...)
   171  }
   172  
   173  // NewStream begins a streaming RPC.
   174  //
   175  // It is a part of grpc.ClientConnInterface.
   176  func (c *Client) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
   177  	return nil, ErrNoStreamingSupport
   178  }
   179  
   180  // prepareOptions copies client options and applies opts.
   181  func (c *Client) prepareOptions(opts []grpc.CallOption, serviceName, methodName string) *Options {
   182  	var options *Options
   183  	if c.Options != nil {
   184  		cpy := *c.Options
   185  		options = &cpy
   186  	} else {
   187  		options = DefaultOptions()
   188  	}
   189  	options.apply(opts)
   190  	options.host = c.Host
   191  	options.serviceName = serviceName
   192  	options.methodName = methodName
   193  	if options.UserAgent == "" {
   194  		options.UserAgent = DefaultUserAgent
   195  	}
   196  	return options
   197  }
   198  
   199  // Call performs a remote procedure call.
   200  //
   201  // Used by the generated code. Calling from multiple goroutines concurrently
   202  // is safe.
   203  //
   204  // `opts` must be created by this package. Options from google.golang.org/grpc
   205  // package are not supported. Panics if they are used.
   206  //
   207  // Propagates outgoing gRPC metadata provided via metadata.NewOutgoingContext.
   208  // It will be available via metadata.FromIncomingContext on the other side.
   209  // Similarly, if there is a deadline in the Context, it is be propagated
   210  // to the server and applied to the context of the request handler there.
   211  //
   212  // Retries on internal transient errors and on gRPC codes considered transient
   213  // by grpcutil.IsTransientCode. Logs unexpected errors (see ExpectedCode call
   214  // option).
   215  //
   216  // Returns gRPC errors, perhaps with extra structured details if the server
   217  // provided them. Context errors are converted into gRPC errors as well.
   218  // See google.golang.org/grpc/status package.
   219  func (c *Client) Call(ctx context.Context, serviceName, methodName string, in, out proto.Message, opts ...grpc.CallOption) error {
   220  	options := c.prepareOptions(opts, serviceName, methodName)
   221  
   222  	// Due to https://github.com/golang/protobuf/issues/745 bug
   223  	// in jsonpb handling of FieldMask, which are typically present in the
   224  	// request, not the response, do request via binary format.
   225  	options.inFormat = FormatBinary
   226  	reqBody, err := proto.Marshal(in)
   227  	if err != nil {
   228  		return status.Errorf(codes.Internal, "prpc: failed to marshal the request: %s", err)
   229  	}
   230  
   231  	switch options.AcceptContentSubtype {
   232  	case "", mtPRPCEncodingBinary:
   233  		options.outFormat = FormatBinary
   234  	case mtPRPCEncodingJSONPB:
   235  		options.outFormat = FormatJSONPB
   236  	case mtPRPCEncodingText:
   237  		return status.Errorf(codes.Internal, "prpc: text encoding for pRPC calls is not implemented")
   238  	default:
   239  		return status.Errorf(codes.Internal, "prpc: unrecognized contentSubtype %q of CallAcceptContentSubtype", options.AcceptContentSubtype)
   240  	}
   241  
   242  	resp, err := c.call(ctx, options, reqBody)
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	switch options.outFormat {
   248  	case FormatBinary:
   249  		err = proto.Unmarshal(resp, out)
   250  	case FormatJSONPB:
   251  		// We AllowUnknownFields because otherwise all prpc clients become tightly
   252  		// coupled to the server implementation and will break with codes.Internal
   253  		// when the server adds a new field to the response.
   254  		//
   255  		// We presume here that decoding a partial response will be easier to
   256  		// recover from in a deployment scenario than breaking all callers.
   257  		err = (&jsonpb.Unmarshaler{AllowUnknownFields: true}).Unmarshal(bytes.NewReader(resp), out)
   258  	default:
   259  		err = errors.Reason("unsupported outFormat: %s", options.outFormat).Err()
   260  	}
   261  	if err != nil {
   262  		return status.Errorf(codes.Internal, "prpc: failed to unmarshal the response: %s", err)
   263  	}
   264  
   265  	return nil
   266  }
   267  
   268  // CallWithFormats is like Call, but sends and returns raw data without
   269  // marshaling it.
   270  //
   271  // Trims JSONPBPrefix from the response if necessary.
   272  func (c *Client) CallWithFormats(ctx context.Context, serviceName, methodName string, in []byte, inf, outf Format, opts ...grpc.CallOption) ([]byte, error) {
   273  	options := c.prepareOptions(opts, serviceName, methodName)
   274  	if options.AcceptContentSubtype != "" {
   275  		return nil, status.Errorf(codes.Internal,
   276  			"prpc: CallAcceptContentSubtype option is not allowed with CallWithFormats "+
   277  				"because input/output formats are already specified")
   278  	}
   279  	options.inFormat = inf
   280  	options.outFormat = outf
   281  	return c.call(ctx, options, in)
   282  }
   283  
   284  // call implements Call and CallWithFormats.
   285  func (c *Client) call(ctx context.Context, options *Options, in []byte) ([]byte, error) {
   286  	md, _ := metadata.FromOutgoingContext(ctx)
   287  	req, err := c.prepareRequest(options, md, in)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  	ctx = logging.SetFields(ctx, logging.Fields{
   292  		"host":    options.host,
   293  		"service": options.serviceName,
   294  		"method":  options.methodName,
   295  	})
   296  
   297  	// These are populated below based on the response.
   298  	buf := &bytes.Buffer{}
   299  	contentType := ""
   300  
   301  	// Send the request in a retry loop. Use transient.Tag to propagate the retry
   302  	// signal from the loop body.
   303  	err = retry.Retry(ctx, transient.Only(options.Retry), func() (err error) {
   304  		// Note: `buf` is reset inside, it is safe to reuse it across attempts.
   305  		contentType, err = c.attemptCall(ctx, options, req, buf)
   306  		// Retry on regular transient errors and on per-RPC deadline. If this is
   307  		// a global deadline (i.e. `ctx` expired), the retry loop will just exit.
   308  		return grpcutil.WrapIfTransientOr(err, codes.DeadlineExceeded)
   309  	}, func(err error, sleepTime time.Duration) {
   310  		logging.Fields{
   311  			"sleepTime": sleepTime,
   312  		}.Warningf(ctx, "RPC failed transiently (retry in %s): %s", sleepTime, err)
   313  	})
   314  
   315  	// Parse the response content type, verify it is what we expect.
   316  	if err == nil {
   317  		switch f, formatErr := FormatFromContentType(contentType); {
   318  		case formatErr != nil:
   319  			err = status.Errorf(codes.Internal, "prpc: bad response content type %q: %s", contentType, formatErr)
   320  		case f != options.outFormat:
   321  			err = status.Errorf(codes.Internal, "prpc: output format (%q) doesn't match expected format (%q)",
   322  				f.MediaType(), options.outFormat.MediaType())
   323  		}
   324  	}
   325  
   326  	if err != nil {
   327  		// The context error is more interesting if it is present.
   328  		switch cerr := ctx.Err(); {
   329  		case cerr == context.DeadlineExceeded:
   330  			err = status.Error(codes.DeadlineExceeded, "prpc: overall deadline exceeded")
   331  		case cerr == context.Canceled:
   332  			err = status.Error(codes.Canceled, "prpc: call canceled")
   333  		case cerr != nil:
   334  			err = status.Error(codes.Unknown, cerr.Error())
   335  		}
   336  
   337  		// Unwrap the error since we wrap it in retry.Retry exclusively to attach
   338  		// a retry signal. call(...) **must** return standard unwrapped gRPC errors.
   339  		err = errors.Unwrap(err)
   340  
   341  		// Convert the error into status.Error (with Unknown code) if it wasn't
   342  		// a status before.
   343  		if status, ok := status.FromError(err); !ok {
   344  			err = status.Err()
   345  		}
   346  
   347  		// Log only on unexpected codes.
   348  		if code := status.Code(err); code != codes.Canceled {
   349  			ignore := false
   350  			for _, expected := range options.expectedCodes {
   351  				if code == expected {
   352  					ignore = true
   353  					break
   354  				}
   355  			}
   356  			if !ignore {
   357  				logging.Warningf(ctx, "RPC failed permanently: %s", err)
   358  				if options.Debug {
   359  					if code == codes.InvalidArgument && strings.Contains(err.Error(), "could not decode body") {
   360  						logging.Warningf(ctx, "Original request size: %d", len(in))
   361  						logging.Warningf(ctx, "Content-type: %s", options.inFormat.MediaType())
   362  						b64 := base64.StdEncoding.EncodeToString(in)
   363  						logging.Warningf(ctx, "Original request in base64 encoding: %s", b64)
   364  					}
   365  				}
   366  			}
   367  		}
   368  
   369  		// Do not return metadata from failed attempts.
   370  		options.resetResponseMetadata()
   371  		return nil, err
   372  	}
   373  
   374  	out := buf.Bytes()
   375  	if options.outFormat == FormatJSONPB {
   376  		out = bytes.TrimPrefix(out, bytesJSONPBPrefix)
   377  	}
   378  	return out, nil
   379  }
   380  
   381  // concurrencySem returns a semaphore to use to limit concurrency or nil if
   382  // the concurrency is unlimited.
   383  func (c *Client) concurrencySem() *semaphore.Weighted {
   384  	c.semOnce.Do(func() {
   385  		if c.MaxConcurrentRequests > 0 {
   386  			c.sem = semaphore.NewWeighted(int64(c.MaxConcurrentRequests))
   387  		}
   388  	})
   389  	return c.sem
   390  }
   391  
   392  // attemptCall makes one attempt at performing an RPC.
   393  //
   394  // Writes the raw response to the provided buffer, returns its content type.
   395  //
   396  // Returns gRPC errors.
   397  func (c *Client) attemptCall(ctx context.Context, options *Options, req *http.Request, buf *bytes.Buffer) (contentType string, err error) {
   398  	// Wait until there's an execution slot available.
   399  	if sem := c.concurrencySem(); sem != nil {
   400  		if err := sem.Acquire(ctx, 1); err != nil {
   401  			return "", status.FromContextError(err).Err()
   402  		}
   403  		defer sem.Release(1)
   404  	}
   405  
   406  	// Respect PerRPCTimeout option.
   407  	now := clock.Now(ctx)
   408  	var requestDeadline time.Time
   409  	if options.PerRPCTimeout > 0 {
   410  		requestDeadline = now.Add(options.PerRPCTimeout)
   411  	}
   412  
   413  	// Does our parent Context have a deadline?
   414  	if deadline, ok := ctx.Deadline(); ok && (requestDeadline.IsZero() || deadline.Before(requestDeadline)) {
   415  		// Outer Context has a shorter deadline than our per-RPC deadline, so
   416  		// use it.
   417  		requestDeadline = deadline
   418  	} else if !requestDeadline.IsZero() {
   419  		// We have a shorter request deadline. Create a context for this attempt.
   420  		var cancel context.CancelFunc
   421  		ctx, cancel = clock.WithDeadline(ctx, requestDeadline)
   422  		defer cancel()
   423  	}
   424  
   425  	// On errors prefer the context error if the per-RPC context expired. It is
   426  	// a more consistent representation of what is happening. The other error is
   427  	// more chaotic, depending on when exactly the context expires.
   428  	defer func() {
   429  		if err != nil {
   430  			switch cerr := ctx.Err(); {
   431  			case cerr == context.DeadlineExceeded:
   432  				err = status.Error(codes.DeadlineExceeded, "prpc: attempt deadline exceeded")
   433  			case cerr == context.Canceled:
   434  				err = status.Error(codes.Canceled, "prpc: attempt canceled")
   435  			case cerr != nil:
   436  				err = status.Error(codes.Unknown, cerr.Error())
   437  			}
   438  		}
   439  	}()
   440  
   441  	// If we have a request deadline, propagate it to the server.
   442  	if !requestDeadline.IsZero() {
   443  		delta := requestDeadline.Sub(now)
   444  		if delta <= 0 {
   445  			// The request has already expired. This will likely never happen, since
   446  			// the outer Retry loop will have expired, but there is a very slight
   447  			// possibility of a race.
   448  			return "", status.Error(codes.DeadlineExceeded, "prpc: attempt deadline exceeded")
   449  		}
   450  		logging.Debugf(ctx, "RPC %s/%s.%s [deadline %s]", options.host, options.serviceName, options.methodName, delta)
   451  		req.Header.Set(HeaderTimeout, EncodeTimeout(delta))
   452  	} else {
   453  		logging.Debugf(ctx, "RPC %s/%s.%s", options.host, options.serviceName, options.methodName)
   454  		req.Header.Del(HeaderTimeout)
   455  	}
   456  
   457  	client := c.C
   458  	if client == nil {
   459  		client = http.DefaultClient
   460  	}
   461  
   462  	// Send the request.
   463  	req.Body, _ = req.GetBody()
   464  	res, err := client.Do(req.WithContext(ctx))
   465  	if err == nil {
   466  		defer func() {
   467  			// Drain the body before closing it to enable HTTP connection reuse. This
   468  			// is all best effort cleanup, don't check errors.
   469  			io.Copy(io.Discard, res.Body)
   470  			res.Body.Close()
   471  		}()
   472  	}
   473  	if c.testPostHTTP != nil {
   474  		err = c.testPostHTTP(ctx, err)
   475  	}
   476  	if err != nil {
   477  		return "", status.Errorf(codeForErr(err), "prpc: sending request: %s", err)
   478  	}
   479  
   480  	if options.resHeaderMetadata != nil {
   481  		md, err := headersIntoMetadata(res.Header)
   482  		if err != nil {
   483  			return "", status.Errorf(codes.Internal, "prpc: decoding headers: %s", err)
   484  		}
   485  		*options.resHeaderMetadata = md
   486  	}
   487  	if err := c.readResponseBody(ctx, buf, res); err != nil {
   488  		return "", err
   489  	}
   490  	if options.resTrailerMetadata != nil {
   491  		md, err := headersIntoMetadata(res.Trailer)
   492  		if err != nil {
   493  			return "", status.Errorf(codes.Internal, "prpc: decoding trailers: %s", err)
   494  		}
   495  		*options.resTrailerMetadata = md
   496  	}
   497  
   498  	// Read the RPC status (perhaps with details). This is nil on success.
   499  	err = c.readStatus(res, buf)
   500  
   501  	return res.Header.Get("Content-Type"), err
   502  }
   503  
   504  // readResponseBody copies the response body into dest.
   505  //
   506  // Returns gRPC errors. If the response body size exceeds the limits or the
   507  // declared size, returns ErrResponseTooBig (which is also a gRPC error).
   508  func (c *Client) readResponseBody(ctx context.Context, dest *bytes.Buffer, r *http.Response) error {
   509  	limit := c.MaxContentLength
   510  	if limit <= 0 {
   511  		limit = DefaultMaxContentLength
   512  	}
   513  
   514  	dest.Reset()
   515  	if l := r.ContentLength; l > 0 {
   516  		if l > int64(limit) {
   517  			logging.Errorf(ctx, "ContentLength header exceeds response body limit: %d > %d.", l, limit)
   518  			return ErrResponseTooBig
   519  		}
   520  		limit = int(l)
   521  		dest.Grow(limit)
   522  	}
   523  
   524  	limitedBody := io.LimitReader(r.Body, int64(limit))
   525  	if _, err := dest.ReadFrom(limitedBody); err != nil {
   526  		return status.Errorf(codeForErr(err), "prpc: reading response: %s", err)
   527  	}
   528  
   529  	// If there is more data in the body Reader, it means that the response
   530  	// size has exceeded our limit.
   531  	var probeB [1]byte
   532  	if n, err := r.Body.Read(probeB[:]); n > 0 || err != io.EOF {
   533  		logging.Errorf(ctx, "Response body limit %d exceeded.", limit)
   534  		return ErrResponseTooBig
   535  	}
   536  
   537  	return nil
   538  }
   539  
   540  // codeForErr decided a gRPC status code based on an http.Client error.
   541  //
   542  // In particular it recognizes IO timeouts and returns them as DeadlineExceeded
   543  // code. This is necessary since it appears http.Client can sometimes fail with
   544  // an IO timeout error even before the parent context.Context expires (probably
   545  // has something to do with converting the context deadline into a timeout
   546  // duration for the IO calls). When this happens, we still need to return
   547  // DeadlineExceeded error.
   548  func codeForErr(err error) codes.Code {
   549  	if os.IsTimeout(err) {
   550  		return codes.DeadlineExceeded
   551  	}
   552  	return codes.Internal
   553  }
   554  
   555  // readStatus retrieves the detailed status from the response.
   556  //
   557  // Puts it into a status.New(...).Err() error.
   558  func (c *Client) readStatus(r *http.Response, bodyBuf *bytes.Buffer) error {
   559  	codeHeader := r.Header.Get(HeaderGRPCCode)
   560  	if codeHeader == "" {
   561  		if r.StatusCode >= 500 {
   562  			// It is possible that the request did not reach the pRPC server and
   563  			// that's why we don't have the code header. It's preferable to convert it
   564  			// to a gRPC status so that the client code treats the response
   565  			// appropriately.
   566  			code := codes.Internal
   567  			if r.StatusCode == http.StatusServiceUnavailable {
   568  				code = codes.Unavailable
   569  			}
   570  			return status.New(code, c.readErrorMessage(bodyBuf)).Err()
   571  		}
   572  
   573  		// Not a valid pRPC response.
   574  		return status.Errorf(codes.Internal,
   575  			"prpc: no %s header, HTTP status %d, body: %q",
   576  			HeaderGRPCCode, r.StatusCode, c.readErrorMessage(bodyBuf))
   577  	}
   578  
   579  	code, err := strconv.Atoi(codeHeader)
   580  	if err != nil {
   581  		return status.Errorf(codes.Internal, "prpc: invalid %s header value %q", HeaderGRPCCode, codeHeader)
   582  	}
   583  
   584  	if codes.Code(code) == codes.OK {
   585  		return nil
   586  	}
   587  
   588  	sp := &spb.Status{
   589  		Code:    int32(code),
   590  		Message: strings.TrimSuffix(c.readErrorMessage(bodyBuf), "\n"),
   591  	}
   592  	if sp.Details, err = c.readStatusDetails(r); err != nil {
   593  		return err
   594  	}
   595  	return status.FromProto(sp).Err()
   596  }
   597  
   598  // readErrorMessage reads an error message from a body buffer.
   599  //
   600  // Respects c.ErrBodySize. If the error message is too long, trims it and
   601  // appends "...".
   602  func (c *Client) readErrorMessage(bodyBuf *bytes.Buffer) string {
   603  	ret := bodyBuf.Bytes()
   604  
   605  	// Apply limits.
   606  	limit := c.ErrBodySize
   607  	if limit <= 0 {
   608  		limit = 256
   609  	}
   610  	if len(ret) > limit {
   611  		return strings.ToValidUTF8(string(ret[:limit]), "") + "..."
   612  	}
   613  
   614  	return string(ret)
   615  }
   616  
   617  // readStatusDetails reads google.rpc.Status.details from the response headers.
   618  //
   619  // Returns gRPC errors.
   620  func (c *Client) readStatusDetails(r *http.Response) ([]*anypb.Any, error) {
   621  	values := r.Header[HeaderStatusDetail]
   622  	if len(values) == 0 {
   623  		return nil, nil
   624  	}
   625  
   626  	ret := make([]*anypb.Any, len(values))
   627  	var buf []byte
   628  	for i, v := range values {
   629  		sz := base64.StdEncoding.DecodedLen(len(v))
   630  		if cap(buf) < sz {
   631  			buf = make([]byte, sz)
   632  		}
   633  
   634  		n, err := base64.StdEncoding.Decode(buf[:sz], []byte(v))
   635  		if err != nil {
   636  			return nil, status.Errorf(codes.Internal, "prpc: invalid header %s: %q", HeaderStatusDetail, v)
   637  		}
   638  
   639  		msg := &anypb.Any{}
   640  		if err := proto.Unmarshal(buf[:n], msg); err != nil {
   641  			return nil, status.Errorf(codes.Internal, "prpc: failed to unmarshal status detail: %s", err)
   642  		}
   643  		ret[i] = msg
   644  	}
   645  
   646  	return ret, nil
   647  }
   648  
   649  // prepareRequest creates an HTTP request for an RPC.
   650  //
   651  // Initializes GetBody, so that the request can be resent multiple times when
   652  // retrying.
   653  func (c *Client) prepareRequest(options *Options, md metadata.MD, requestMessage []byte) (*http.Request, error) {
   654  	// Convert metadata into HTTP headers in canonical form (i.e. Title-Case).
   655  	// Extract Host header, it is special and must be passed via
   656  	// http.Request.Host. Preallocate 5 more slots (for 4 headers below and for
   657  	// the RPC deadline header).
   658  	headers := make(http.Header, len(md)+5)
   659  	if err := metaIntoHeaders(md, headers); err != nil {
   660  		return nil, status.Errorf(codes.Internal, "prpc: headers: %s", err)
   661  	}
   662  	hostHdr := headers.Get("Host")
   663  	headers.Del("Host")
   664  
   665  	// Add protocol-related headers.
   666  	headers.Set("Content-Type", options.inFormat.MediaType())
   667  	headers.Set("Accept", options.outFormat.MediaType())
   668  	headers.Set("User-Agent", options.UserAgent)
   669  
   670  	body := requestMessage
   671  	if c.EnableRequestCompression && len(requestMessage) > gzipThreshold {
   672  		headers.Set("Content-Encoding", "gzip")
   673  		var err error
   674  		if body, err = compressBlob(requestMessage); err != nil {
   675  			return nil, status.Error(codes.Internal, err.Error())
   676  		}
   677  
   678  		// Do not add "Accept-Encoding: gzip". The http package does this
   679  		// automatically, and also decompresses the response.
   680  	}
   681  
   682  	headers.Set("Content-Length", strconv.Itoa(len(body)))
   683  
   684  	scheme := "https"
   685  	if options.Insecure {
   686  		scheme = "http"
   687  	}
   688  
   689  	pathPrefix := c.PathPrefix
   690  	if c.PathPrefix == "" {
   691  		pathPrefix = "/prpc"
   692  	}
   693  	return &http.Request{
   694  		Method: "POST",
   695  		URL: &url.URL{
   696  			Scheme: scheme,
   697  			Host:   options.host,
   698  			Path:   fmt.Sprintf("%s/%s/%s", pathPrefix, options.serviceName, options.methodName),
   699  		},
   700  		Host:          hostHdr,
   701  		Header:        headers,
   702  		ContentLength: int64(len(body)),
   703  		GetBody: func() (io.ReadCloser, error) {
   704  			return io.NopCloser(bytes.NewReader(body)), nil
   705  		},
   706  	}, nil
   707  }