Skip to content
This repository has been archived by the owner on Sep 1, 2023. It is now read-only.

Commit

Permalink
Minimize allocations writing User-Agent header (connectrpc#446)
Browse files Browse the repository at this point in the history
The fmt.Sprintf stood out again in my profiling as a relatively hot
path, and through the lifetime of an application, this value will not
change. So we can generate this header value at init() time and avoid
the extra memory allocation as well on each request.

**Before submitting your PR:** Please read through the contribution
guide at
https://github.com/bufbuild/connect-go/blob/main/.github/CONTRIBUTING.md
  • Loading branch information
mattrobenolt authored Jan 27, 2023
1 parent cb460f1 commit 4c2c9a9
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 20 deletions.
12 changes: 6 additions & 6 deletions protocol_connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ const (
connectStreamingContentTypePrefix = "application/connect+"
)

// defaultConnectUserAgent returns a User-Agent string similar to those used in gRPC.
//
//nolint:gochecknoglobals
var defaultConnectUserAgent = fmt.Sprintf("connect-go/%s (%s)", Version, runtime.Version())

type protocolConnect struct{}

// NewHandler implements protocol, so it must return an interface.
Expand Down Expand Up @@ -243,7 +248,7 @@ func (c *connectClient) WriteRequestHeader(streamType StreamType, header http.He
// We know these header keys are in canonical form, so we can bypass all the
// checks in Header.Set.
if header.Get(headerUserAgent) == "" {
header[headerUserAgent] = []string{connectUserAgent()}
header[headerUserAgent] = []string{defaultConnectUserAgent}
}
header[connectHeaderProtocolVersion] = []string{connectProtocolVersion}
header[headerContentType] = []string{
Expand Down Expand Up @@ -1028,11 +1033,6 @@ func connectHTTPToCode(httpCode int) Code {
}
}

// connectUserAgent returns a User-Agent string similar to those used in gRPC.
func connectUserAgent() string {
return fmt.Sprintf("connect-go/%s (%s)", Version, runtime.Version())
}

func connectCodecFromContentType(streamType StreamType, contentType string) string {
if streamType == StreamTypeUnary {
return strings.TrimPrefix(contentType, connectUnaryContentTypePrefix)
Expand Down
26 changes: 12 additions & 14 deletions protocol_grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ var (
}
grpcTimeoutUnitLookup = make(map[byte]time.Duration)
errTrailersWithoutGRPCStatus = fmt.Errorf("gRPC protocol error: no %s trailer", grpcHeaderStatus)

// defaultGrpcUserAgent follows
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#user-agents:
//
// While the protocol does not require a user-agent to function it is recommended
// that clients provide a structured user-agent string that provides a basic
// description of the calling library, version & platform to facilitate issue diagnosis
// in heterogeneous environments. The following structure is recommended to library developers:
//
// User-Agent → "grpc-" Language ?("-" Variant) "/" Version ?( " (" *(AdditionalProperty ";") ")" )
defaultGrpcUserAgent = fmt.Sprintf("grpc-go-connect/%s (%s)", Version, runtime.Version())
)

func init() {
Expand Down Expand Up @@ -227,7 +238,7 @@ func (g *grpcClient) WriteRequestHeader(_ StreamType, header http.Header) {
// We know these header keys are in canonical form, so we can bypass all the
// checks in Header.Set.
if header.Get(headerUserAgent) == "" {
header[headerUserAgent] = []string{grpcUserAgent()}
header[headerUserAgent] = []string{defaultGrpcUserAgent}
}
header[headerContentType] = []string{grpcContentTypeFromCodecName(g.web, g.Codec.Name())}
// gRPC handles compression on a per-message basis, so we don't want to
Expand Down Expand Up @@ -762,19 +773,6 @@ func grpcEncodeTimeout(timeout time.Duration) (string, error) {
return "", errNoTimeout
}

// grpcUserAgent follows
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#user-agents:
//
// While the protocol does not require a user-agent to function it is recommended
// that clients provide a structured user-agent string that provides a basic
// description of the calling library, version & platform to facilitate issue diagnosis
// in heterogeneous environments. The following structure is recommended to library developers:
//
// User-Agent → "grpc-" Language ?("-" Variant) "/" Version ?( " (" *(AdditionalProperty ";") ")" )
func grpcUserAgent() string {
return fmt.Sprintf("grpc-go-connect/%s (%s)", Version, runtime.Version())
}

func grpcCodecFromContentType(web bool, contentType string) string {
if (!web && contentType == grpcContentTypeDefault) || (web && contentType == grpcWebContentTypeDefault) {
// implicitly protobuf
Expand Down

0 comments on commit 4c2c9a9

Please sign in to comment.