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 }