Skip to content

Ability to set http headers without allocations #2119

@rabbbit

Description

@rabbbit

Hello,

It seems not possible to add headers without allocations. Adding a header seems to be costing 6 (or 4) allocations.

Ideas

My initial thought was that the AddHeaders API is inefficient, but @jstroem below benchmarked this not be a problem.

#### First thought - No "AddHeader" API.
type ResponseWriter interface {
	io.Writer
        AddHeaders(Headers)

however, transport.Headers always allocated two maps

type Headers struct {
	items map[string]string
	originalItems map[string]string
}

yarpc canonical headers are lowercase, while http canonical headers are capitalized.

This leads to a very paradoxical situation where:

if I add lower-case http header

	resw.AddHeaders(transport.NewHeadersWithCapacity(1).With(
		"abc", "",
	))

I get:

BenchmarkRespWriter-12           2514664               462.6 ns/op           672 B/op          4 allocs/op

This means two allocations for the maps in transport.Headers, 1 allocation for addToMetadata, and possibly 1 https://pkg.go.dev/net/http#CanonicalHeaderKey - not sure if the last one is included in my benchmark though.

But if I do correct HTTP canonical headers, which should be better

	resw.AddHeaders(transport.NewHeadersWithCapacity(1).With(
		"Abc", "",
	))

I get

BenchmarkRespWriter-12           2390634               446.2 ns/op           678 B/op          6 allocs/op

so it's worse - yarpc will convert my correct header to lower-case first (extra alloc) and one another extra alloc that I don't understand.

Possible solutions

There are a few options - which one would you recommend?

#### Just add `AddHeader`

It would be relatively easy to add ResponseWriter.AddHeader(key, value string) - this would save me two allocations, leaving the remaining two.

AddRawHeader, or make AddSystemHeader public

In order to avoid Go triggering https://pkg.go.dev/net/http#CanonicalHeaderKey for me later on anyway, I would rather pass in HTTP canonical headers (Abc and not abc).

This means I would need something like ResponseWriter.AddRawHeader(key, value string) that wouldn't do transport.CanonicalizeHeaderKey for me. But I'm not sure about the other implications of doing this.

Anything else?

Please advise.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions