mirror of
https://github.com/golang/go
synced 2024-11-18 05:04:47 -07:00
vendor: update golang_org/x/net/http2 packages
Update the http2/hpack package to rev 05d3205. Introduce the following changes: - 05d3205 http2/hpack: fix memory leak in headerFieldTable lookup maps - bce15e7 http2/hpack: speedup Encoder.searchTable - dd2d9a6 http2/hpack: remove hpack's constant time string comparison - 357296a all: single space after period - 71a0359 x/net/http2: Fix various typos in doc comments. Updates #19967 Change-Id: Ie2c8edcaaf96abde515cb995dfa503b54776abfe Reviewed-on: https://go-review.googlesource.com/40833 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
157a230413
commit
3f2b2dea9f
@ -39,13 +39,14 @@ func NewEncoder(w io.Writer) *Encoder {
|
||||
tableSizeUpdate: false,
|
||||
w: w,
|
||||
}
|
||||
e.dynTab.table.init()
|
||||
e.dynTab.setMaxSize(initialHeaderTableSize)
|
||||
return e
|
||||
}
|
||||
|
||||
// WriteField encodes f into a single Write to e's underlying Writer.
|
||||
// This function may also produce bytes for "Header Table Size Update"
|
||||
// if necessary. If produced, it is done before encoding f.
|
||||
// if necessary. If produced, it is done before encoding f.
|
||||
func (e *Encoder) WriteField(f HeaderField) error {
|
||||
e.buf = e.buf[:0]
|
||||
|
||||
@ -88,29 +89,17 @@ func (e *Encoder) WriteField(f HeaderField) error {
|
||||
// only name matches, i points to that index and nameValueMatch
|
||||
// becomes false.
|
||||
func (e *Encoder) searchTable(f HeaderField) (i uint64, nameValueMatch bool) {
|
||||
for idx, hf := range staticTable {
|
||||
if !constantTimeStringCompare(hf.Name, f.Name) {
|
||||
continue
|
||||
}
|
||||
if i == 0 {
|
||||
i = uint64(idx + 1)
|
||||
}
|
||||
if f.Sensitive {
|
||||
continue
|
||||
}
|
||||
if !constantTimeStringCompare(hf.Value, f.Value) {
|
||||
continue
|
||||
}
|
||||
i = uint64(idx + 1)
|
||||
nameValueMatch = true
|
||||
return
|
||||
i, nameValueMatch = staticTable.search(f)
|
||||
if nameValueMatch {
|
||||
return i, true
|
||||
}
|
||||
|
||||
j, nameValueMatch := e.dynTab.search(f)
|
||||
j, nameValueMatch := e.dynTab.table.search(f)
|
||||
if nameValueMatch || (i == 0 && j != 0) {
|
||||
i = j + uint64(len(staticTable))
|
||||
return j + uint64(staticTable.len()), nameValueMatch
|
||||
}
|
||||
return
|
||||
|
||||
return i, false
|
||||
}
|
||||
|
||||
// SetMaxDynamicTableSize changes the dynamic header table size to v.
|
||||
|
@ -7,6 +7,8 @@ package hpack
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -101,17 +103,20 @@ func TestEncoderSearchTable(t *testing.T) {
|
||||
wantMatch bool
|
||||
}{
|
||||
// Name and Value match
|
||||
{pair("foo", "bar"), uint64(len(staticTable) + 3), true},
|
||||
{pair("blake", "miz"), uint64(len(staticTable) + 2), true},
|
||||
{pair("foo", "bar"), uint64(staticTable.len()) + 3, true},
|
||||
{pair("blake", "miz"), uint64(staticTable.len()) + 2, true},
|
||||
{pair(":method", "GET"), 2, true},
|
||||
|
||||
// Only name match because Sensitive == true
|
||||
{HeaderField{":method", "GET", true}, 2, false},
|
||||
// Only name match because Sensitive == true. This is allowed to match
|
||||
// any ":method" entry. The current implementation uses the last entry
|
||||
// added in newStaticTable.
|
||||
{HeaderField{":method", "GET", true}, 3, false},
|
||||
|
||||
// Only Name matches
|
||||
{pair("foo", "..."), uint64(len(staticTable) + 3), false},
|
||||
{pair("blake", "..."), uint64(len(staticTable) + 2), false},
|
||||
{pair(":method", "..."), 2, false},
|
||||
{pair("foo", "..."), uint64(staticTable.len()) + 3, false},
|
||||
{pair("blake", "..."), uint64(staticTable.len()) + 2, false},
|
||||
// As before, this is allowed to match any ":method" entry.
|
||||
{pair(":method", "..."), 3, false},
|
||||
|
||||
// None match
|
||||
{pair("foo-", "bar"), 0, false},
|
||||
@ -328,3 +333,54 @@ func TestEncoderSetMaxDynamicTableSizeLimit(t *testing.T) {
|
||||
func removeSpace(s string) string {
|
||||
return strings.Replace(s, " ", "", -1)
|
||||
}
|
||||
|
||||
func BenchmarkEncoderSearchTable(b *testing.B) {
|
||||
e := NewEncoder(nil)
|
||||
|
||||
// A sample of possible header fields.
|
||||
// This is not based on any actual data from HTTP/2 traces.
|
||||
var possible []HeaderField
|
||||
for _, f := range staticTable.ents {
|
||||
if f.Value == "" {
|
||||
possible = append(possible, f)
|
||||
continue
|
||||
}
|
||||
// Generate 5 random values, except for cookie and set-cookie,
|
||||
// which we know can have many values in practice.
|
||||
num := 5
|
||||
if f.Name == "cookie" || f.Name == "set-cookie" {
|
||||
num = 25
|
||||
}
|
||||
for i := 0; i < num; i++ {
|
||||
f.Value = fmt.Sprintf("%s-%d", f.Name, i)
|
||||
possible = append(possible, f)
|
||||
}
|
||||
}
|
||||
for k := 0; k < 10; k++ {
|
||||
f := HeaderField{
|
||||
Name: fmt.Sprintf("x-header-%d", k),
|
||||
Sensitive: rand.Int()%2 == 0,
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
f.Value = fmt.Sprintf("%s-%d", f.Name, i)
|
||||
possible = append(possible, f)
|
||||
}
|
||||
}
|
||||
|
||||
// Add a random sample to the dynamic table. This very loosely simulates
|
||||
// a history of 100 requests with 20 header fields per request.
|
||||
for r := 0; r < 100*20; r++ {
|
||||
f := possible[rand.Int31n(int32(len(possible)))]
|
||||
// Skip if this is in the staticTable verbatim.
|
||||
if _, has := staticTable.search(f); !has {
|
||||
e.dynTab.add(f)
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
for _, f := range possible {
|
||||
e.searchTable(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
106
src/vendor/golang_org/x/net/http2/hpack/hpack.go
vendored
106
src/vendor/golang_org/x/net/http2/hpack/hpack.go
vendored
@ -57,11 +57,11 @@ func (hf HeaderField) String() string {
|
||||
return fmt.Sprintf("header field %q = %q%s", hf.Name, hf.Value, suffix)
|
||||
}
|
||||
|
||||
// Size returns the size of an entry per RFC 7540 section 5.2.
|
||||
// Size returns the size of an entry per RFC 7541 section 4.1.
|
||||
func (hf HeaderField) Size() uint32 {
|
||||
// http://http2.github.io/http2-spec/compression.html#rfc.section.4.1
|
||||
// "The size of the dynamic table is the sum of the size of
|
||||
// its entries. The size of an entry is the sum of its name's
|
||||
// its entries. The size of an entry is the sum of its name's
|
||||
// length in octets (as defined in Section 5.2), its value's
|
||||
// length in octets (see Section 5.2), plus 32. The size of
|
||||
// an entry is calculated using the length of the name and
|
||||
@ -102,6 +102,7 @@ func NewDecoder(maxDynamicTableSize uint32, emitFunc func(f HeaderField)) *Decod
|
||||
emit: emitFunc,
|
||||
emitEnabled: true,
|
||||
}
|
||||
d.dynTab.table.init()
|
||||
d.dynTab.allowedMaxSize = maxDynamicTableSize
|
||||
d.dynTab.setMaxSize(maxDynamicTableSize)
|
||||
return d
|
||||
@ -154,12 +155,9 @@ func (d *Decoder) SetAllowedMaxDynamicTableSize(v uint32) {
|
||||
}
|
||||
|
||||
type dynamicTable struct {
|
||||
// ents is the FIFO described at
|
||||
// http://http2.github.io/http2-spec/compression.html#rfc.section.2.3.2
|
||||
// The newest (low index) is append at the end, and items are
|
||||
// evicted from the front.
|
||||
ents []HeaderField
|
||||
size uint32
|
||||
table headerFieldTable
|
||||
size uint32 // in bytes
|
||||
maxSize uint32 // current maxSize
|
||||
allowedMaxSize uint32 // maxSize may go up to this, inclusive
|
||||
}
|
||||
@ -169,95 +167,45 @@ func (dt *dynamicTable) setMaxSize(v uint32) {
|
||||
dt.evict()
|
||||
}
|
||||
|
||||
// TODO: change dynamicTable to be a struct with a slice and a size int field,
|
||||
// per http://http2.github.io/http2-spec/compression.html#rfc.section.4.1:
|
||||
//
|
||||
//
|
||||
// Then make add increment the size. maybe the max size should move from Decoder to
|
||||
// dynamicTable and add should return an ok bool if there was enough space.
|
||||
//
|
||||
// Later we'll need a remove operation on dynamicTable.
|
||||
|
||||
func (dt *dynamicTable) add(f HeaderField) {
|
||||
dt.ents = append(dt.ents, f)
|
||||
dt.table.addEntry(f)
|
||||
dt.size += f.Size()
|
||||
dt.evict()
|
||||
}
|
||||
|
||||
// If we're too big, evict old stuff (front of the slice)
|
||||
// If we're too big, evict old stuff.
|
||||
func (dt *dynamicTable) evict() {
|
||||
base := dt.ents // keep base pointer of slice
|
||||
for dt.size > dt.maxSize {
|
||||
dt.size -= dt.ents[0].Size()
|
||||
dt.ents = dt.ents[1:]
|
||||
var n int
|
||||
for dt.size > dt.maxSize && n < dt.table.len() {
|
||||
dt.size -= dt.table.ents[n].Size()
|
||||
n++
|
||||
}
|
||||
|
||||
// Shift slice contents down if we evicted things.
|
||||
if len(dt.ents) != len(base) {
|
||||
copy(base, dt.ents)
|
||||
dt.ents = base[:len(dt.ents)]
|
||||
}
|
||||
}
|
||||
|
||||
// constantTimeStringCompare compares string a and b in a constant
|
||||
// time manner.
|
||||
func constantTimeStringCompare(a, b string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
c := byte(0)
|
||||
|
||||
for i := 0; i < len(a); i++ {
|
||||
c |= a[i] ^ b[i]
|
||||
}
|
||||
|
||||
return c == 0
|
||||
}
|
||||
|
||||
// Search searches f in the table. The return value i is 0 if there is
|
||||
// no name match. If there is name match or name/value match, i is the
|
||||
// index of that entry (1-based). If both name and value match,
|
||||
// nameValueMatch becomes true.
|
||||
func (dt *dynamicTable) search(f HeaderField) (i uint64, nameValueMatch bool) {
|
||||
l := len(dt.ents)
|
||||
for j := l - 1; j >= 0; j-- {
|
||||
ent := dt.ents[j]
|
||||
if !constantTimeStringCompare(ent.Name, f.Name) {
|
||||
continue
|
||||
}
|
||||
if i == 0 {
|
||||
i = uint64(l - j)
|
||||
}
|
||||
if f.Sensitive {
|
||||
continue
|
||||
}
|
||||
if !constantTimeStringCompare(ent.Value, f.Value) {
|
||||
continue
|
||||
}
|
||||
i = uint64(l - j)
|
||||
nameValueMatch = true
|
||||
return
|
||||
}
|
||||
return
|
||||
dt.table.evictOldest(n)
|
||||
}
|
||||
|
||||
func (d *Decoder) maxTableIndex() int {
|
||||
return len(d.dynTab.ents) + len(staticTable)
|
||||
// This should never overflow. RFC 7540 Section 6.5.2 limits the size of
|
||||
// the dynamic table to 2^32 bytes, where each entry will occupy more than
|
||||
// one byte. Further, the staticTable has a fixed, small length.
|
||||
return d.dynTab.table.len() + staticTable.len()
|
||||
}
|
||||
|
||||
func (d *Decoder) at(i uint64) (hf HeaderField, ok bool) {
|
||||
if i < 1 {
|
||||
// See Section 2.3.3.
|
||||
if i == 0 {
|
||||
return
|
||||
}
|
||||
if i <= uint64(staticTable.len()) {
|
||||
return staticTable.ents[i-1], true
|
||||
}
|
||||
if i > uint64(d.maxTableIndex()) {
|
||||
return
|
||||
}
|
||||
if i <= uint64(len(staticTable)) {
|
||||
return staticTable[i-1], true
|
||||
}
|
||||
dents := d.dynTab.ents
|
||||
return dents[len(dents)-(int(i)-len(staticTable))], true
|
||||
// In the dynamic table, newer entries have lower indices.
|
||||
// However, dt.ents[0] is the oldest entry. Hence, dt.ents is
|
||||
// the reversed dynamic table.
|
||||
dt := d.dynTab.table
|
||||
return dt.ents[dt.len()-(int(i)-staticTable.len())], true
|
||||
}
|
||||
|
||||
// Decode decodes an entire block.
|
||||
@ -307,7 +255,7 @@ func (d *Decoder) Write(p []byte) (n int, err error) {
|
||||
err = d.parseHeaderFieldRepr()
|
||||
if err == errNeedMore {
|
||||
// Extra paranoia, making sure saveBuf won't
|
||||
// get too large. All the varint and string
|
||||
// get too large. All the varint and string
|
||||
// reading code earlier should already catch
|
||||
// overlong things and return ErrStringLength,
|
||||
// but keep this as a last resort.
|
||||
|
@ -5,117 +5,16 @@
|
||||
package hpack
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestStaticTable(t *testing.T) {
|
||||
fromSpec := `
|
||||
+-------+-----------------------------+---------------+
|
||||
| 1 | :authority | |
|
||||
| 2 | :method | GET |
|
||||
| 3 | :method | POST |
|
||||
| 4 | :path | / |
|
||||
| 5 | :path | /index.html |
|
||||
| 6 | :scheme | http |
|
||||
| 7 | :scheme | https |
|
||||
| 8 | :status | 200 |
|
||||
| 9 | :status | 204 |
|
||||
| 10 | :status | 206 |
|
||||
| 11 | :status | 304 |
|
||||
| 12 | :status | 400 |
|
||||
| 13 | :status | 404 |
|
||||
| 14 | :status | 500 |
|
||||
| 15 | accept-charset | |
|
||||
| 16 | accept-encoding | gzip, deflate |
|
||||
| 17 | accept-language | |
|
||||
| 18 | accept-ranges | |
|
||||
| 19 | accept | |
|
||||
| 20 | access-control-allow-origin | |
|
||||
| 21 | age | |
|
||||
| 22 | allow | |
|
||||
| 23 | authorization | |
|
||||
| 24 | cache-control | |
|
||||
| 25 | content-disposition | |
|
||||
| 26 | content-encoding | |
|
||||
| 27 | content-language | |
|
||||
| 28 | content-length | |
|
||||
| 29 | content-location | |
|
||||
| 30 | content-range | |
|
||||
| 31 | content-type | |
|
||||
| 32 | cookie | |
|
||||
| 33 | date | |
|
||||
| 34 | etag | |
|
||||
| 35 | expect | |
|
||||
| 36 | expires | |
|
||||
| 37 | from | |
|
||||
| 38 | host | |
|
||||
| 39 | if-match | |
|
||||
| 40 | if-modified-since | |
|
||||
| 41 | if-none-match | |
|
||||
| 42 | if-range | |
|
||||
| 43 | if-unmodified-since | |
|
||||
| 44 | last-modified | |
|
||||
| 45 | link | |
|
||||
| 46 | location | |
|
||||
| 47 | max-forwards | |
|
||||
| 48 | proxy-authenticate | |
|
||||
| 49 | proxy-authorization | |
|
||||
| 50 | range | |
|
||||
| 51 | referer | |
|
||||
| 52 | refresh | |
|
||||
| 53 | retry-after | |
|
||||
| 54 | server | |
|
||||
| 55 | set-cookie | |
|
||||
| 56 | strict-transport-security | |
|
||||
| 57 | transfer-encoding | |
|
||||
| 58 | user-agent | |
|
||||
| 59 | vary | |
|
||||
| 60 | via | |
|
||||
| 61 | www-authenticate | |
|
||||
+-------+-----------------------------+---------------+
|
||||
`
|
||||
bs := bufio.NewScanner(strings.NewReader(fromSpec))
|
||||
re := regexp.MustCompile(`\| (\d+)\s+\| (\S+)\s*\| (\S(.*\S)?)?\s+\|`)
|
||||
for bs.Scan() {
|
||||
l := bs.Text()
|
||||
if !strings.Contains(l, "|") {
|
||||
continue
|
||||
}
|
||||
m := re.FindStringSubmatch(l)
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
i, err := strconv.Atoi(m[1])
|
||||
if err != nil {
|
||||
t.Errorf("Bogus integer on line %q", l)
|
||||
continue
|
||||
}
|
||||
if i < 1 || i > len(staticTable) {
|
||||
t.Errorf("Bogus index %d on line %q", i, l)
|
||||
continue
|
||||
}
|
||||
if got, want := staticTable[i-1].Name, m[2]; got != want {
|
||||
t.Errorf("header index %d name = %q; want %q", i, got, want)
|
||||
}
|
||||
if got, want := staticTable[i-1].Value, m[3]; got != want {
|
||||
t.Errorf("header index %d value = %q; want %q", i, got, want)
|
||||
}
|
||||
}
|
||||
if err := bs.Err(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Decoder) mustAt(idx int) HeaderField {
|
||||
if hf, ok := d.at(uint64(idx)); !ok {
|
||||
panic(fmt.Sprintf("bogus index %d", idx))
|
||||
@ -132,10 +31,10 @@ func TestDynamicTableAt(t *testing.T) {
|
||||
}
|
||||
d.dynTab.add(pair("foo", "bar"))
|
||||
d.dynTab.add(pair("blake", "miz"))
|
||||
if got, want := at(len(staticTable)+1), (pair("blake", "miz")); got != want {
|
||||
if got, want := at(staticTable.len()+1), (pair("blake", "miz")); got != want {
|
||||
t.Errorf("at(dyn 1) = %v; want %v", got, want)
|
||||
}
|
||||
if got, want := at(len(staticTable)+2), (pair("foo", "bar")); got != want {
|
||||
if got, want := at(staticTable.len()+2), (pair("foo", "bar")); got != want {
|
||||
t.Errorf("at(dyn 2) = %v; want %v", got, want)
|
||||
}
|
||||
if got, want := at(3), (pair(":method", "POST")); got != want {
|
||||
@ -143,41 +42,6 @@ func TestDynamicTableAt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDynamicTableSearch(t *testing.T) {
|
||||
dt := dynamicTable{}
|
||||
dt.setMaxSize(4096)
|
||||
|
||||
dt.add(pair("foo", "bar"))
|
||||
dt.add(pair("blake", "miz"))
|
||||
dt.add(pair(":method", "GET"))
|
||||
|
||||
tests := []struct {
|
||||
hf HeaderField
|
||||
wantI uint64
|
||||
wantMatch bool
|
||||
}{
|
||||
// Name and Value match
|
||||
{pair("foo", "bar"), 3, true},
|
||||
{pair(":method", "GET"), 1, true},
|
||||
|
||||
// Only name match because of Sensitive == true
|
||||
{HeaderField{"blake", "miz", true}, 2, false},
|
||||
|
||||
// Only Name matches
|
||||
{pair("foo", "..."), 3, false},
|
||||
{pair("blake", "..."), 2, false},
|
||||
{pair(":method", "..."), 1, false},
|
||||
|
||||
// None match
|
||||
{pair("foo-", "bar"), 0, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if gotI, gotMatch := dt.search(tt.hf); gotI != tt.wantI || gotMatch != tt.wantMatch {
|
||||
t.Errorf("d.search(%+v) = %v, %v; want %v, %v", tt.hf, gotI, gotMatch, tt.wantI, tt.wantMatch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDynamicTableSizeEvict(t *testing.T) {
|
||||
d := NewDecoder(4096, nil)
|
||||
if want := uint32(0); d.dynTab.size != want {
|
||||
@ -196,7 +60,7 @@ func TestDynamicTableSizeEvict(t *testing.T) {
|
||||
if want := uint32(6 + 32); d.dynTab.size != want {
|
||||
t.Fatalf("after setMaxSize, size = %d; want %d", d.dynTab.size, want)
|
||||
}
|
||||
if got, want := d.mustAt(len(staticTable)+1), (pair("foo", "bar")); got != want {
|
||||
if got, want := d.mustAt(staticTable.len()+1), (pair("foo", "bar")); got != want {
|
||||
t.Errorf("at(dyn 1) = %v; want %v", got, want)
|
||||
}
|
||||
add(pair("long", strings.Repeat("x", 500)))
|
||||
@ -255,9 +119,9 @@ func TestDecoderDecode(t *testing.T) {
|
||||
}
|
||||
|
||||
func (dt *dynamicTable) reverseCopy() (hf []HeaderField) {
|
||||
hf = make([]HeaderField, len(dt.ents))
|
||||
hf = make([]HeaderField, len(dt.table.ents))
|
||||
for i := range hf {
|
||||
hf[i] = dt.ents[len(dt.ents)-1-i]
|
||||
hf[i] = dt.table.ents[len(dt.table.ents)-1-i]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
250
src/vendor/golang_org/x/net/http2/hpack/tables.go
vendored
250
src/vendor/golang_org/x/net/http2/hpack/tables.go
vendored
@ -4,73 +4,199 @@
|
||||
|
||||
package hpack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// headerFieldTable implements a list of HeaderFields.
|
||||
// This is used to implement the static and dynamic tables.
|
||||
type headerFieldTable struct {
|
||||
// For static tables, entries are never evicted.
|
||||
//
|
||||
// For dynamic tables, entries are evicted from ents[0] and added to the end.
|
||||
// Each entry has a unique id that starts at one and increments for each
|
||||
// entry that is added. This unique id is stable across evictions, meaning
|
||||
// it can be used as a pointer to a specific entry. As in hpack, unique ids
|
||||
// are 1-based. The unique id for ents[k] is k + evictCount + 1.
|
||||
//
|
||||
// Zero is not a valid unique id.
|
||||
//
|
||||
// evictCount should not overflow in any remotely practical situation. In
|
||||
// practice, we will have one dynamic table per HTTP/2 connection. If we
|
||||
// assume a very powerful server that handles 1M QPS per connection and each
|
||||
// request adds (then evicts) 100 entries from the table, it would still take
|
||||
// 2M years for evictCount to overflow.
|
||||
ents []HeaderField
|
||||
evictCount uint64
|
||||
|
||||
// byName maps a HeaderField name to the unique id of the newest entry with
|
||||
// the same name. See above for a definition of "unique id".
|
||||
byName map[string]uint64
|
||||
|
||||
// byNameValue maps a HeaderField name/value pair to the unique id of the newest
|
||||
// entry with the same name and value. See above for a definition of "unique id".
|
||||
byNameValue map[pairNameValue]uint64
|
||||
}
|
||||
|
||||
type pairNameValue struct {
|
||||
name, value string
|
||||
}
|
||||
|
||||
func (t *headerFieldTable) init() {
|
||||
t.byName = make(map[string]uint64)
|
||||
t.byNameValue = make(map[pairNameValue]uint64)
|
||||
}
|
||||
|
||||
// len reports the number of entries in the table.
|
||||
func (t *headerFieldTable) len() int {
|
||||
return len(t.ents)
|
||||
}
|
||||
|
||||
// addEntry adds a new entry.
|
||||
func (t *headerFieldTable) addEntry(f HeaderField) {
|
||||
id := uint64(t.len()) + t.evictCount + 1
|
||||
t.byName[f.Name] = id
|
||||
t.byNameValue[pairNameValue{f.Name, f.Value}] = id
|
||||
t.ents = append(t.ents, f)
|
||||
}
|
||||
|
||||
// evictOldest evicts the n oldest entries in the table.
|
||||
func (t *headerFieldTable) evictOldest(n int) {
|
||||
if n > t.len() {
|
||||
panic(fmt.Sprintf("evictOldest(%v) on table with %v entries", n, t.len()))
|
||||
}
|
||||
for k := 0; k < n; k++ {
|
||||
f := t.ents[k]
|
||||
id := t.evictCount + uint64(k) + 1
|
||||
if t.byName[f.Name] == id {
|
||||
delete(t.byName, f.Name)
|
||||
}
|
||||
if p := (pairNameValue{f.Name, f.Value}); t.byNameValue[p] == id {
|
||||
delete(t.byNameValue, p)
|
||||
}
|
||||
}
|
||||
copy(t.ents, t.ents[n:])
|
||||
for k := t.len() - n; k < t.len(); k++ {
|
||||
t.ents[k] = HeaderField{} // so strings can be garbage collected
|
||||
}
|
||||
t.ents = t.ents[:t.len()-n]
|
||||
if t.evictCount+uint64(n) < t.evictCount {
|
||||
panic("evictCount overflow")
|
||||
}
|
||||
t.evictCount += uint64(n)
|
||||
}
|
||||
|
||||
// search finds f in the table. If there is no match, i is 0.
|
||||
// If both name and value match, i is the matched index and nameValueMatch
|
||||
// becomes true. If only name matches, i points to that index and
|
||||
// nameValueMatch becomes false.
|
||||
//
|
||||
// The returned index is a 1-based HPACK index. For dynamic tables, HPACK says
|
||||
// that index 1 should be the newest entry, but t.ents[0] is the oldest entry,
|
||||
// meaning t.ents is reversed for dynamic tables. Hence, when t is a dynamic
|
||||
// table, the return value i actually refers to the entry t.ents[t.len()-i].
|
||||
//
|
||||
// All tables are assumed to be a dynamic tables except for the global
|
||||
// staticTable pointer.
|
||||
//
|
||||
// See Section 2.3.3.
|
||||
func (t *headerFieldTable) search(f HeaderField) (i uint64, nameValueMatch bool) {
|
||||
if !f.Sensitive {
|
||||
if id := t.byNameValue[pairNameValue{f.Name, f.Value}]; id != 0 {
|
||||
return t.idToIndex(id), true
|
||||
}
|
||||
}
|
||||
if id := t.byName[f.Name]; id != 0 {
|
||||
return t.idToIndex(id), false
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// idToIndex converts a unique id to an HPACK index.
|
||||
// See Section 2.3.3.
|
||||
func (t *headerFieldTable) idToIndex(id uint64) uint64 {
|
||||
if id <= t.evictCount {
|
||||
panic(fmt.Sprintf("id (%v) <= evictCount (%v)", id, t.evictCount))
|
||||
}
|
||||
k := id - t.evictCount - 1 // convert id to an index t.ents[k]
|
||||
if t != staticTable {
|
||||
return uint64(t.len()) - k // dynamic table
|
||||
}
|
||||
return k + 1
|
||||
}
|
||||
|
||||
func pair(name, value string) HeaderField {
|
||||
return HeaderField{Name: name, Value: value}
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
|
||||
var staticTable = [...]HeaderField{
|
||||
pair(":authority", ""), // index 1 (1-based)
|
||||
pair(":method", "GET"),
|
||||
pair(":method", "POST"),
|
||||
pair(":path", "/"),
|
||||
pair(":path", "/index.html"),
|
||||
pair(":scheme", "http"),
|
||||
pair(":scheme", "https"),
|
||||
pair(":status", "200"),
|
||||
pair(":status", "204"),
|
||||
pair(":status", "206"),
|
||||
pair(":status", "304"),
|
||||
pair(":status", "400"),
|
||||
pair(":status", "404"),
|
||||
pair(":status", "500"),
|
||||
pair("accept-charset", ""),
|
||||
pair("accept-encoding", "gzip, deflate"),
|
||||
pair("accept-language", ""),
|
||||
pair("accept-ranges", ""),
|
||||
pair("accept", ""),
|
||||
pair("access-control-allow-origin", ""),
|
||||
pair("age", ""),
|
||||
pair("allow", ""),
|
||||
pair("authorization", ""),
|
||||
pair("cache-control", ""),
|
||||
pair("content-disposition", ""),
|
||||
pair("content-encoding", ""),
|
||||
pair("content-language", ""),
|
||||
pair("content-length", ""),
|
||||
pair("content-location", ""),
|
||||
pair("content-range", ""),
|
||||
pair("content-type", ""),
|
||||
pair("cookie", ""),
|
||||
pair("date", ""),
|
||||
pair("etag", ""),
|
||||
pair("expect", ""),
|
||||
pair("expires", ""),
|
||||
pair("from", ""),
|
||||
pair("host", ""),
|
||||
pair("if-match", ""),
|
||||
pair("if-modified-since", ""),
|
||||
pair("if-none-match", ""),
|
||||
pair("if-range", ""),
|
||||
pair("if-unmodified-since", ""),
|
||||
pair("last-modified", ""),
|
||||
pair("link", ""),
|
||||
pair("location", ""),
|
||||
pair("max-forwards", ""),
|
||||
pair("proxy-authenticate", ""),
|
||||
pair("proxy-authorization", ""),
|
||||
pair("range", ""),
|
||||
pair("referer", ""),
|
||||
pair("refresh", ""),
|
||||
pair("retry-after", ""),
|
||||
pair("server", ""),
|
||||
pair("set-cookie", ""),
|
||||
pair("strict-transport-security", ""),
|
||||
pair("transfer-encoding", ""),
|
||||
pair("user-agent", ""),
|
||||
pair("vary", ""),
|
||||
pair("via", ""),
|
||||
pair("www-authenticate", ""),
|
||||
var staticTable = newStaticTable()
|
||||
|
||||
func newStaticTable() *headerFieldTable {
|
||||
t := &headerFieldTable{}
|
||||
t.init()
|
||||
t.addEntry(pair(":authority", ""))
|
||||
t.addEntry(pair(":method", "GET"))
|
||||
t.addEntry(pair(":method", "POST"))
|
||||
t.addEntry(pair(":path", "/"))
|
||||
t.addEntry(pair(":path", "/index.html"))
|
||||
t.addEntry(pair(":scheme", "http"))
|
||||
t.addEntry(pair(":scheme", "https"))
|
||||
t.addEntry(pair(":status", "200"))
|
||||
t.addEntry(pair(":status", "204"))
|
||||
t.addEntry(pair(":status", "206"))
|
||||
t.addEntry(pair(":status", "304"))
|
||||
t.addEntry(pair(":status", "400"))
|
||||
t.addEntry(pair(":status", "404"))
|
||||
t.addEntry(pair(":status", "500"))
|
||||
t.addEntry(pair("accept-charset", ""))
|
||||
t.addEntry(pair("accept-encoding", "gzip, deflate"))
|
||||
t.addEntry(pair("accept-language", ""))
|
||||
t.addEntry(pair("accept-ranges", ""))
|
||||
t.addEntry(pair("accept", ""))
|
||||
t.addEntry(pair("access-control-allow-origin", ""))
|
||||
t.addEntry(pair("age", ""))
|
||||
t.addEntry(pair("allow", ""))
|
||||
t.addEntry(pair("authorization", ""))
|
||||
t.addEntry(pair("cache-control", ""))
|
||||
t.addEntry(pair("content-disposition", ""))
|
||||
t.addEntry(pair("content-encoding", ""))
|
||||
t.addEntry(pair("content-language", ""))
|
||||
t.addEntry(pair("content-length", ""))
|
||||
t.addEntry(pair("content-location", ""))
|
||||
t.addEntry(pair("content-range", ""))
|
||||
t.addEntry(pair("content-type", ""))
|
||||
t.addEntry(pair("cookie", ""))
|
||||
t.addEntry(pair("date", ""))
|
||||
t.addEntry(pair("etag", ""))
|
||||
t.addEntry(pair("expect", ""))
|
||||
t.addEntry(pair("expires", ""))
|
||||
t.addEntry(pair("from", ""))
|
||||
t.addEntry(pair("host", ""))
|
||||
t.addEntry(pair("if-match", ""))
|
||||
t.addEntry(pair("if-modified-since", ""))
|
||||
t.addEntry(pair("if-none-match", ""))
|
||||
t.addEntry(pair("if-range", ""))
|
||||
t.addEntry(pair("if-unmodified-since", ""))
|
||||
t.addEntry(pair("last-modified", ""))
|
||||
t.addEntry(pair("link", ""))
|
||||
t.addEntry(pair("location", ""))
|
||||
t.addEntry(pair("max-forwards", ""))
|
||||
t.addEntry(pair("proxy-authenticate", ""))
|
||||
t.addEntry(pair("proxy-authorization", ""))
|
||||
t.addEntry(pair("range", ""))
|
||||
t.addEntry(pair("referer", ""))
|
||||
t.addEntry(pair("refresh", ""))
|
||||
t.addEntry(pair("retry-after", ""))
|
||||
t.addEntry(pair("server", ""))
|
||||
t.addEntry(pair("set-cookie", ""))
|
||||
t.addEntry(pair("strict-transport-security", ""))
|
||||
t.addEntry(pair("transfer-encoding", ""))
|
||||
t.addEntry(pair("user-agent", ""))
|
||||
t.addEntry(pair("vary", ""))
|
||||
t.addEntry(pair("via", ""))
|
||||
t.addEntry(pair("www-authenticate", ""))
|
||||
return t
|
||||
}
|
||||
|
||||
var huffmanCodes = [256]uint32{
|
||||
|
214
src/vendor/golang_org/x/net/http2/hpack/tables_test.go
vendored
Normal file
214
src/vendor/golang_org/x/net/http2/hpack/tables_test.go
vendored
Normal file
@ -0,0 +1,214 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
package hpack
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHeaderFieldTable(t *testing.T) {
|
||||
table := &headerFieldTable{}
|
||||
table.init()
|
||||
table.addEntry(pair("key1", "value1-1"))
|
||||
table.addEntry(pair("key2", "value2-1"))
|
||||
table.addEntry(pair("key1", "value1-2"))
|
||||
table.addEntry(pair("key3", "value3-1"))
|
||||
table.addEntry(pair("key4", "value4-1"))
|
||||
table.addEntry(pair("key2", "value2-2"))
|
||||
|
||||
// Tests will be run twice: once before evicting anything, and
|
||||
// again after evicting the three oldest entries.
|
||||
tests := []struct {
|
||||
f HeaderField
|
||||
beforeWantStaticI uint64
|
||||
beforeWantMatch bool
|
||||
afterWantStaticI uint64
|
||||
afterWantMatch bool
|
||||
}{
|
||||
{HeaderField{"key1", "value1-1", false}, 1, true, 0, false},
|
||||
{HeaderField{"key1", "value1-2", false}, 3, true, 0, false},
|
||||
{HeaderField{"key1", "value1-3", false}, 3, false, 0, false},
|
||||
{HeaderField{"key2", "value2-1", false}, 2, true, 3, false},
|
||||
{HeaderField{"key2", "value2-2", false}, 6, true, 3, true},
|
||||
{HeaderField{"key2", "value2-3", false}, 6, false, 3, false},
|
||||
{HeaderField{"key4", "value4-1", false}, 5, true, 2, true},
|
||||
// Name match only, because sensitive.
|
||||
{HeaderField{"key4", "value4-1", true}, 5, false, 2, false},
|
||||
// Key not found.
|
||||
{HeaderField{"key5", "value5-x", false}, 0, false, 0, false},
|
||||
}
|
||||
|
||||
staticToDynamic := func(i uint64) uint64 {
|
||||
if i == 0 {
|
||||
return 0
|
||||
}
|
||||
return uint64(table.len()) - i + 1 // dynamic is the reversed table
|
||||
}
|
||||
|
||||
searchStatic := func(f HeaderField) (uint64, bool) {
|
||||
old := staticTable
|
||||
staticTable = table
|
||||
defer func() { staticTable = old }()
|
||||
return staticTable.search(f)
|
||||
}
|
||||
|
||||
searchDynamic := func(f HeaderField) (uint64, bool) {
|
||||
return table.search(f)
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
gotI, gotMatch := searchStatic(test.f)
|
||||
if wantI, wantMatch := test.beforeWantStaticI, test.beforeWantMatch; gotI != wantI || gotMatch != wantMatch {
|
||||
t.Errorf("before evictions: searchStatic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
|
||||
}
|
||||
gotI, gotMatch = searchDynamic(test.f)
|
||||
wantDynamicI := staticToDynamic(test.beforeWantStaticI)
|
||||
if wantI, wantMatch := wantDynamicI, test.beforeWantMatch; gotI != wantI || gotMatch != wantMatch {
|
||||
t.Errorf("before evictions: searchDynamic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
|
||||
}
|
||||
}
|
||||
|
||||
table.evictOldest(3)
|
||||
|
||||
for _, test := range tests {
|
||||
gotI, gotMatch := searchStatic(test.f)
|
||||
if wantI, wantMatch := test.afterWantStaticI, test.afterWantMatch; gotI != wantI || gotMatch != wantMatch {
|
||||
t.Errorf("after evictions: searchStatic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
|
||||
}
|
||||
gotI, gotMatch = searchDynamic(test.f)
|
||||
wantDynamicI := staticToDynamic(test.afterWantStaticI)
|
||||
if wantI, wantMatch := wantDynamicI, test.afterWantMatch; gotI != wantI || gotMatch != wantMatch {
|
||||
t.Errorf("after evictions: searchDynamic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaderFieldTable_LookupMapEviction(t *testing.T) {
|
||||
table := &headerFieldTable{}
|
||||
table.init()
|
||||
table.addEntry(pair("key1", "value1-1"))
|
||||
table.addEntry(pair("key2", "value2-1"))
|
||||
table.addEntry(pair("key1", "value1-2"))
|
||||
table.addEntry(pair("key3", "value3-1"))
|
||||
table.addEntry(pair("key4", "value4-1"))
|
||||
table.addEntry(pair("key2", "value2-2"))
|
||||
|
||||
// evict all pairs
|
||||
table.evictOldest(table.len())
|
||||
|
||||
if l := table.len(); l > 0 {
|
||||
t.Errorf("table.len() = %d, want 0", l)
|
||||
}
|
||||
|
||||
if l := len(table.byName); l > 0 {
|
||||
t.Errorf("len(table.byName) = %d, want 0", l)
|
||||
}
|
||||
|
||||
if l := len(table.byNameValue); l > 0 {
|
||||
t.Errorf("len(table.byNameValue) = %d, want 0", l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStaticTable(t *testing.T) {
|
||||
fromSpec := `
|
||||
+-------+-----------------------------+---------------+
|
||||
| 1 | :authority | |
|
||||
| 2 | :method | GET |
|
||||
| 3 | :method | POST |
|
||||
| 4 | :path | / |
|
||||
| 5 | :path | /index.html |
|
||||
| 6 | :scheme | http |
|
||||
| 7 | :scheme | https |
|
||||
| 8 | :status | 200 |
|
||||
| 9 | :status | 204 |
|
||||
| 10 | :status | 206 |
|
||||
| 11 | :status | 304 |
|
||||
| 12 | :status | 400 |
|
||||
| 13 | :status | 404 |
|
||||
| 14 | :status | 500 |
|
||||
| 15 | accept-charset | |
|
||||
| 16 | accept-encoding | gzip, deflate |
|
||||
| 17 | accept-language | |
|
||||
| 18 | accept-ranges | |
|
||||
| 19 | accept | |
|
||||
| 20 | access-control-allow-origin | |
|
||||
| 21 | age | |
|
||||
| 22 | allow | |
|
||||
| 23 | authorization | |
|
||||
| 24 | cache-control | |
|
||||
| 25 | content-disposition | |
|
||||
| 26 | content-encoding | |
|
||||
| 27 | content-language | |
|
||||
| 28 | content-length | |
|
||||
| 29 | content-location | |
|
||||
| 30 | content-range | |
|
||||
| 31 | content-type | |
|
||||
| 32 | cookie | |
|
||||
| 33 | date | |
|
||||
| 34 | etag | |
|
||||
| 35 | expect | |
|
||||
| 36 | expires | |
|
||||
| 37 | from | |
|
||||
| 38 | host | |
|
||||
| 39 | if-match | |
|
||||
| 40 | if-modified-since | |
|
||||
| 41 | if-none-match | |
|
||||
| 42 | if-range | |
|
||||
| 43 | if-unmodified-since | |
|
||||
| 44 | last-modified | |
|
||||
| 45 | link | |
|
||||
| 46 | location | |
|
||||
| 47 | max-forwards | |
|
||||
| 48 | proxy-authenticate | |
|
||||
| 49 | proxy-authorization | |
|
||||
| 50 | range | |
|
||||
| 51 | referer | |
|
||||
| 52 | refresh | |
|
||||
| 53 | retry-after | |
|
||||
| 54 | server | |
|
||||
| 55 | set-cookie | |
|
||||
| 56 | strict-transport-security | |
|
||||
| 57 | transfer-encoding | |
|
||||
| 58 | user-agent | |
|
||||
| 59 | vary | |
|
||||
| 60 | via | |
|
||||
| 61 | www-authenticate | |
|
||||
+-------+-----------------------------+---------------+
|
||||
`
|
||||
bs := bufio.NewScanner(strings.NewReader(fromSpec))
|
||||
re := regexp.MustCompile(`\| (\d+)\s+\| (\S+)\s*\| (\S(.*\S)?)?\s+\|`)
|
||||
for bs.Scan() {
|
||||
l := bs.Text()
|
||||
if !strings.Contains(l, "|") {
|
||||
continue
|
||||
}
|
||||
m := re.FindStringSubmatch(l)
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
i, err := strconv.Atoi(m[1])
|
||||
if err != nil {
|
||||
t.Errorf("Bogus integer on line %q", l)
|
||||
continue
|
||||
}
|
||||
if i < 1 || i > staticTable.len() {
|
||||
t.Errorf("Bogus index %d on line %q", i, l)
|
||||
continue
|
||||
}
|
||||
if got, want := staticTable.ents[i-1].Name, m[2]; got != want {
|
||||
t.Errorf("header index %d name = %q; want %q", i, got, want)
|
||||
}
|
||||
if got, want := staticTable.ents[i-1].Value, m[3]; got != want {
|
||||
t.Errorf("header index %d value = %q; want %q", i, got, want)
|
||||
}
|
||||
}
|
||||
if err := bs.Err(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user