Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions header.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,21 +175,27 @@ type HeaderKeyValues struct {
// It's used as a pointer, so it can fit in a sort.Interface
// interface value without allocation.
type headerSorter struct {
kvs []HeaderKeyValues
order map[string]int
kvs []HeaderKeyValues
order map[string]int
lowerKeys []string
}

func (s *headerSorter) Len() int { return len(s.kvs) }
func (s *headerSorter) Swap(i, j int) { s.kvs[i], s.kvs[j] = s.kvs[j], s.kvs[i] }
func (s *headerSorter) Len() int { return len(s.kvs) }
func (s *headerSorter) Swap(i, j int) {
s.kvs[i], s.kvs[j] = s.kvs[j], s.kvs[i]
// lowerKeys is only populated by SortedKeyValuesBy; SortedKeyValues
// sorts without it.
if s.lowerKeys != nil {
s.lowerKeys[i], s.lowerKeys[j] = s.lowerKeys[j], s.lowerKeys[i]
}
}
func (s *headerSorter) Less(i, j int) bool {
// If the order isn't defined, sort lexicographically.
if s.order == nil {
return s.kvs[i].Key < s.kvs[j].Key
}
//idxi, iok := s.order[s.kvs[i].Key]
//idxj, jok := s.order[s.kvs[j].Key]
idxi, iok := s.order[strings.ToLower(s.kvs[i].Key)]
idxj, jok := s.order[strings.ToLower(s.kvs[j].Key)]
idxi, iok := s.order[s.lowerKeys[i]]
idxj, jok := s.order[s.lowerKeys[j]]
if !iok && !jok {
return s.kvs[i].Key < s.kvs[j].Key
} else if !iok && jok {
Expand Down Expand Up @@ -223,6 +229,11 @@ func (h Header) SortedKeyValues(exclude map[string]bool) (kvs []HeaderKeyValues,
mutex.RUnlock()
}
hs.kvs = kvs
// Reset state a pooled sorter may carry from a previous
// SortedKeyValuesBy call: a stale order would make Less sort by the
// wrong order (and index lowerKeys, which this path leaves empty).
hs.order = nil
hs.lowerKeys = nil
sort.Sort(hs)
return kvs, hs
}
Expand All @@ -242,6 +253,15 @@ func (h Header) SortedKeyValuesBy(order map[string]int, exclude map[string]bool)
}
hs.kvs = kvs
hs.order = order

if cap(hs.lowerKeys) < len(kvs) {
hs.lowerKeys = make([]string, len(kvs))
}
hs.lowerKeys = hs.lowerKeys[:len(kvs)]
for i, kv := range kvs {
hs.lowerKeys[i] = strings.ToLower(kv.Key)
}

sort.Sort(hs)

return kvs, hs
Expand Down
49 changes: 49 additions & 0 deletions header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package http

import (
"bytes"
"io"
"reflect"
"runtime"
"testing"
Expand Down Expand Up @@ -359,3 +360,51 @@ func TestHTTP1HeaderOrder(t *testing.T) {
t.Fatalf("got:\n%swant:\n%s", buf.String(), expected)
}
}

func TestHeaderWriteWithoutOrder(t *testing.T) {
// Sorting without a HeaderOrderKey goes through SortedKeyValues,
// which does not populate headerSorter.lowerKeys. Swap must not
// touch lowerKeys in that case.
h := Header{
"Zebra": {"1"},
"Apple": {"2"},
"Mango": {"3"},
"Banana": {"4"},
"Orange": {"5"},
"Kiwi": {"6"},
"Grape": {"7"},
"Lemon": {"8"},
"Peach": {"9"},
"Cherry": {"10"},
"Plum": {"11"},
"Apricot": {"12"},
}
if err := h.Write(io.Discard); err != nil {
t.Fatal(err)
}
}

func TestHeaderSorterPoolReuse(t *testing.T) {
// A sorter used by SortedKeyValuesBy carries order and lowerKeys.
// When it is pulled from the pool again by SortedKeyValues (no
// order), the stale state must not be used.
ordered := Header{
"Zebra": {"1"},
"Apple": {"2"},
}
// Deliberately the reverse of lexicographic order.
_, hs := ordered.SortedKeyValuesBy(map[string]int{"zebra": 0, "apple": 1}, nil)
headerSorterPool.Put(hs)

plain := Header{
"Apple": {"1"},
"Banana": {"2"},
"Zebra": {"3"},
}
kvs, _ := plain.SortedKeyValues(nil)
for i := 1; i < len(kvs); i++ {
if kvs[i-1].Key > kvs[i].Key {
t.Fatalf("keys not sorted lexicographically: %q before %q", kvs[i-1].Key, kvs[i].Key)
}
}
}
4 changes: 2 additions & 2 deletions http2/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -1914,7 +1914,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
return
}

name = strings.ToLower(name)
name = lowerHeader(name)
cc.writeHeader(name, value)
if traceHeaders {
traceWroteHeaderField(trace, name, value)
Expand Down Expand Up @@ -1964,7 +1964,7 @@ func (cc *ClientConn) encodeTrailers(req *http.Request) ([]byte, error) {
for k, vv := range req.Trailer {
// Transfer-Encoding, etc.. have already been filtered at the
// start of RoundTrip
lowKey := strings.ToLower(k)
lowKey := lowerHeader(k)
for _, v := range vv {
cc.writeHeader(lowKey, v)
}
Expand Down
4 changes: 2 additions & 2 deletions transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ package http
import (
"bufio"
"bytes"
"compress/flate"
"compress/gzip"
"github.com/klauspost/compress/flate"
"github.com/klauspost/compress/gzip"
"compress/zlib"
"container/list"
"context"
Expand Down