Skip to content

Commit 4c2c9a9

Browse files
authored
Minimize allocations writing User-Agent header (connectrpc#446)
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
1 parent cb460f1 commit 4c2c9a9

File tree

2 files changed

+18
-20
lines changed

2 files changed

+18
-20
lines changed

protocol_connect.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ const (
4949
connectStreamingContentTypePrefix = "application/connect+"
5050
)
5151

52+
// defaultConnectUserAgent returns a User-Agent string similar to those used in gRPC.
53+
//
54+
//nolint:gochecknoglobals
55+
var defaultConnectUserAgent = fmt.Sprintf("connect-go/%s (%s)", Version, runtime.Version())
56+
5257
type protocolConnect struct{}
5358

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

1031-
// connectUserAgent returns a User-Agent string similar to those used in gRPC.
1032-
func connectUserAgent() string {
1033-
return fmt.Sprintf("connect-go/%s (%s)", Version, runtime.Version())
1034-
}
1035-
10361036
func connectCodecFromContentType(streamType StreamType, contentType string) string {
10371037
if streamType == StreamTypeUnary {
10381038
return strings.TrimPrefix(contentType, connectUnaryContentTypePrefix)

protocol_grpc.go

+12-14
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ var (
6565
}
6666
grpcTimeoutUnitLookup = make(map[byte]time.Duration)
6767
errTrailersWithoutGRPCStatus = fmt.Errorf("gRPC protocol error: no %s trailer", grpcHeaderStatus)
68+
69+
// defaultGrpcUserAgent follows
70+
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#user-agents:
71+
//
72+
// While the protocol does not require a user-agent to function it is recommended
73+
// that clients provide a structured user-agent string that provides a basic
74+
// description of the calling library, version & platform to facilitate issue diagnosis
75+
// in heterogeneous environments. The following structure is recommended to library developers:
76+
//
77+
// User-Agent → "grpc-" Language ?("-" Variant) "/" Version ?( " (" *(AdditionalProperty ";") ")" )
78+
defaultGrpcUserAgent = fmt.Sprintf("grpc-go-connect/%s (%s)", Version, runtime.Version())
6879
)
6980

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

765-
// grpcUserAgent follows
766-
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#user-agents:
767-
//
768-
// While the protocol does not require a user-agent to function it is recommended
769-
// that clients provide a structured user-agent string that provides a basic
770-
// description of the calling library, version & platform to facilitate issue diagnosis
771-
// in heterogeneous environments. The following structure is recommended to library developers:
772-
//
773-
// User-Agent → "grpc-" Language ?("-" Variant) "/" Version ?( " (" *(AdditionalProperty ";") ")" )
774-
func grpcUserAgent() string {
775-
return fmt.Sprintf("grpc-go-connect/%s (%s)", Version, runtime.Version())
776-
}
777-
778776
func grpcCodecFromContentType(web bool, contentType string) string {
779777
if (!web && contentType == grpcContentTypeDefault) || (web && contentType == grpcWebContentTypeDefault) {
780778
// implicitly protobuf

0 commit comments

Comments
 (0)