Skip to content

Commit

Permalink
http2/hpack: build static table with go generate
Browse files Browse the repository at this point in the history
As the static version of headerFieldTable in hpack is generated in runtime,
we may use the go:generate to prepare the struct before the initialization phase.
This is supposed to save init time and allocations for many binaries,
as net/http imports hpack.

Before:
init golang.org/x/net/http2/hpack @1.1 ms, 0.097 ms clock, 21240 bytes, 29 allocs
After:
init golang.org/x/net/http2/hpack @0.67 ms, 0.015 ms clock, 8120 bytes, 9 allocs

Fixes golang/go#55881

Change-Id: Ia6575f67ffcba7cc4d75899b24a9c56deb58ccac
Reviewed-on: https://go-review.googlesource.com/c/net/ /434875
Run-TryBot: Damien Neil <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
Reviewed-by: Damien Neil <[email protected]>
Reviewed-by: Bryan Mills <[email protected]>
  • Loading branch information
Thorleon authored and neild committed Oct 27, 2022
1 parent 430a433 commit 84c13af
Show file tree
Hide file tree
Showing 4 changed files with 379 additions and 77 deletions.
184 changes: 184 additions & 0 deletions http2/hpack/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 1,184 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build ignore
// build ignore

package main

import (
"bytes"
"fmt"
"go/format"
"io/ioutil"
"os"
"sort"

"golang.org/x/net/http2/hpack"
)

// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
var staticTableEntries = [...]hpack.HeaderField{
{Name: ":authority"},
{Name: ":method", Value: "GET"},
{Name: ":method", Value: "POST"},
{Name: ":path", Value: "/"},
{Name: ":path", Value: "/index.html"},
{Name: ":scheme", Value: "http"},
{Name: ":scheme", Value: "https"},
{Name: ":status", Value: "200"},
{Name: ":status", Value: "204"},
{Name: ":status", Value: "206"},
{Name: ":status", Value: "304"},
{Name: ":status", Value: "400"},
{Name: ":status", Value: "404"},
{Name: ":status", Value: "500"},
{Name: "accept-charset"},
{Name: "accept-encoding", Value: "gzip, deflate"},
{Name: "accept-language"},
{Name: "accept-ranges"},
{Name: "accept"},
{Name: "access-control-allow-origin"},
{Name: "age"},
{Name: "allow"},
{Name: "authorization"},
{Name: "cache-control"},
{Name: "content-disposition"},
{Name: "content-encoding"},
{Name: "content-language"},
{Name: "content-length"},
{Name: "content-location"},
{Name: "content-range"},
{Name: "content-type"},
{Name: "cookie"},
{Name: "date"},
{Name: "etag"},
{Name: "expect"},
{Name: "expires"},
{Name: "from"},
{Name: "host"},
{Name: "if-match"},
{Name: "if-modified-since"},
{Name: "if-none-match"},
{Name: "if-range"},
{Name: "if-unmodified-since"},
{Name: "last-modified"},
{Name: "link"},
{Name: "location"},
{Name: "max-forwards"},
{Name: "proxy-authenticate"},
{Name: "proxy-authorization"},
{Name: "range"},
{Name: "referer"},
{Name: "refresh"},
{Name: "retry-after"},
{Name: "server"},
{Name: "set-cookie"},
{Name: "strict-transport-security"},
{Name: "transfer-encoding"},
{Name: "user-agent"},
{Name: "vary"},
{Name: "via"},
{Name: "www-authenticate"},
}

type pairNameValue struct {
name, value string
}

type byNameItem struct {
name string
id uint64
}

type byNameValueItem struct {
pairNameValue
id uint64
}

func headerFieldToString(f hpack.HeaderField) string {
return fmt.Sprintf("{Name: \"%s\", Value:\"%s\", Sensitive: %t}", f.Name, f.Value, f.Sensitive)
}

func pairNameValueToString(v pairNameValue) string {
return fmt.Sprintf("{name: \"%s\", value:\"%s\"}", v.name, v.value)
}

const header = `
// go generate gen.go
// Code generated by the command above; DO NOT EDIT.
package hpack
var staticTable = &headerFieldTable{
evictCount: 0,
byName: map[string]uint64{
`

//go:generate go run gen.go
func main() {
var bb bytes.Buffer
fmt.Fprintf(&bb, header)
byName := make(map[string]uint64)
byNameValue := make(map[pairNameValue]uint64)
for index, entry := range staticTableEntries {
id := uint64(index) 1
byName[entry.Name] = id
byNameValue[pairNameValue{entry.Name, entry.Value}] = id
}
// Sort maps for deterministic generation.
byNameItems := sortByName(byName)
byNameValueItems := sortByNameValue(byNameValue)

for _, item := range byNameItems {
fmt.Fprintf(&bb, "\"%s\":%d,\n", item.name, item.id)
}
fmt.Fprintf(&bb, "},\n")
fmt.Fprintf(&bb, "byNameValue: map[pairNameValue]uint64{\n")
for _, item := range byNameValueItems {
fmt.Fprintf(&bb, "%s:%d,\n", pairNameValueToString(item.pairNameValue), item.id)
}
fmt.Fprintf(&bb, "},\n")
fmt.Fprintf(&bb, "ents: []HeaderField{\n")
for _, value := range staticTableEntries {
fmt.Fprintf(&bb, "%s,\n", headerFieldToString(value))
}
fmt.Fprintf(&bb, "},\n")
fmt.Fprintf(&bb, "}\n")
genFile("static_table.go", &bb)
}

func sortByNameValue(byNameValue map[pairNameValue]uint64) []byNameValueItem {
var byNameValueItems []byNameValueItem
for k, v := range byNameValue {
byNameValueItems = append(byNameValueItems, byNameValueItem{k, v})
}
sort.Slice(byNameValueItems, func(i, j int) bool {
return byNameValueItems[i].id < byNameValueItems[j].id
})
return byNameValueItems
}

func sortByName(byName map[string]uint64) []byNameItem {
var byNameItems []byNameItem
for k, v := range byName {
byNameItems = append(byNameItems, byNameItem{k, v})
}
sort.Slice(byNameItems, func(i, j int) bool {
return byNameItems[i].id < byNameItems[j].id
})
return byNameItems
}

func genFile(name string, buf *bytes.Buffer) {
b, err := format.Source(buf.Bytes())
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err := ioutil.WriteFile(name, b, 0644); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
188 changes: 188 additions & 0 deletions http2/hpack/static_table.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 84c13af

Please sign in to comment.