mirror of
https://github.com/golang/go
synced 2024-11-20 01:14:40 -07:00
http/spdy: new package
R=bradfitz, agl1, rsc CC=golang-dev https://golang.org/cl/4435055
This commit is contained in:
parent
6f88288a13
commit
1801972b30
@ -103,6 +103,7 @@ DIRS=\
|
||||
http/fcgi\
|
||||
http/pprof\
|
||||
http/httptest\
|
||||
http/spdy\
|
||||
image\
|
||||
image/jpeg\
|
||||
image/png\
|
||||
|
11
src/pkg/http/spdy/Makefile
Normal file
11
src/pkg/http/spdy/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
# Copyright 2011 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.
|
||||
|
||||
include ../../../Make.inc
|
||||
|
||||
TARG=http/spdy
|
||||
GOFILES=\
|
||||
protocol.go\
|
||||
|
||||
include ../../../Make.pkg
|
367
src/pkg/http/spdy/protocol.go
Normal file
367
src/pkg/http/spdy/protocol.go
Normal file
@ -0,0 +1,367 @@
|
||||
// Copyright 2011 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 spdy is an incomplete implementation of the SPDY protocol.
|
||||
//
|
||||
// The implementation follows draft 2 of the spec:
|
||||
// https://sites.google.com/a/chromium.org/dev/spdy/spdy-protocol/spdy-protocol-draft2
|
||||
package spdy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"encoding/binary"
|
||||
"http"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Version is the protocol version number that this package implements.
|
||||
const Version = 2
|
||||
|
||||
// ControlFrameType stores the type field in a control frame header.
|
||||
type ControlFrameType uint16
|
||||
|
||||
// Control frame type constants
|
||||
const (
|
||||
TypeSynStream ControlFrameType = 0x0001
|
||||
TypeSynReply = 0x0002
|
||||
TypeRstStream = 0x0003
|
||||
TypeSettings = 0x0004
|
||||
TypeNoop = 0x0005
|
||||
TypePing = 0x0006
|
||||
TypeGoaway = 0x0007
|
||||
TypeHeaders = 0x0008
|
||||
TypeWindowUpdate = 0x0009
|
||||
)
|
||||
|
||||
func (t ControlFrameType) String() string {
|
||||
switch t {
|
||||
case TypeSynStream:
|
||||
return "SYN_STREAM"
|
||||
case TypeSynReply:
|
||||
return "SYN_REPLY"
|
||||
case TypeRstStream:
|
||||
return "RST_STREAM"
|
||||
case TypeSettings:
|
||||
return "SETTINGS"
|
||||
case TypeNoop:
|
||||
return "NOOP"
|
||||
case TypePing:
|
||||
return "PING"
|
||||
case TypeGoaway:
|
||||
return "GOAWAY"
|
||||
case TypeHeaders:
|
||||
return "HEADERS"
|
||||
case TypeWindowUpdate:
|
||||
return "WINDOW_UPDATE"
|
||||
}
|
||||
return "Type(" + strconv.Itoa(int(t)) + ")"
|
||||
}
|
||||
|
||||
type FrameFlags uint8
|
||||
|
||||
// Stream frame flags
|
||||
const (
|
||||
FlagFin FrameFlags = 0x01
|
||||
FlagUnidirectional = 0x02
|
||||
)
|
||||
|
||||
// SETTINGS frame flags
|
||||
const (
|
||||
FlagClearPreviouslyPersistedSettings = 0x01
|
||||
)
|
||||
|
||||
// MaxDataLength is the maximum number of bytes that can be stored in one frame.
|
||||
const MaxDataLength = 1<<24 - 1
|
||||
|
||||
// A Frame is a framed message as sent between clients and servers.
|
||||
// There are two types of frames: control frames and data frames.
|
||||
type Frame struct {
|
||||
Header [4]byte
|
||||
Flags FrameFlags
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// ControlFrame creates a control frame with the given information.
|
||||
func ControlFrame(t ControlFrameType, f FrameFlags, data []byte) Frame {
|
||||
return Frame{
|
||||
Header: [4]byte{
|
||||
(Version&0xff00)>>8 | 0x80,
|
||||
(Version & 0x00ff),
|
||||
byte((t & 0xff00) >> 8),
|
||||
byte((t & 0x00ff) >> 0),
|
||||
},
|
||||
Flags: f,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// DataFrame creates a data frame with the given information.
|
||||
func DataFrame(streamId uint32, f FrameFlags, data []byte) Frame {
|
||||
return Frame{
|
||||
Header: [4]byte{
|
||||
byte(streamId & 0x7f000000 >> 24),
|
||||
byte(streamId & 0x00ff0000 >> 16),
|
||||
byte(streamId & 0x0000ff00 >> 8),
|
||||
byte(streamId & 0x000000ff >> 0),
|
||||
},
|
||||
Flags: f,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFrame reads an entire frame into memory.
|
||||
func ReadFrame(r io.Reader) (f Frame, err os.Error) {
|
||||
_, err = io.ReadFull(r, f.Header[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Read(r, binary.BigEndian, &f.Flags)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var lengthField [3]byte
|
||||
_, err = io.ReadFull(r, lengthField[:])
|
||||
if err != nil {
|
||||
if err == os.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return
|
||||
}
|
||||
var length uint32
|
||||
length |= uint32(lengthField[0]) << 16
|
||||
length |= uint32(lengthField[1]) << 8
|
||||
length |= uint32(lengthField[2]) << 0
|
||||
if length > 0 {
|
||||
f.Data = make([]byte, int(length))
|
||||
_, err = io.ReadFull(r, f.Data)
|
||||
if err == os.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
} else {
|
||||
f.Data = []byte{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IsControl returns whether the frame holds a control frame.
|
||||
func (f Frame) IsControl() bool {
|
||||
return f.Header[0]&0x80 != 0
|
||||
}
|
||||
|
||||
// Type obtains the type field if the frame is a control frame, otherwise it returns zero.
|
||||
func (f Frame) Type() ControlFrameType {
|
||||
if !f.IsControl() {
|
||||
return 0
|
||||
}
|
||||
return (ControlFrameType(f.Header[2])<<8 | ControlFrameType(f.Header[3]))
|
||||
}
|
||||
|
||||
// StreamId returns the stream ID field if the frame is a data frame, otherwise it returns zero.
|
||||
func (f Frame) StreamId() (id uint32) {
|
||||
if f.IsControl() {
|
||||
return 0
|
||||
}
|
||||
id |= uint32(f.Header[0]) << 24
|
||||
id |= uint32(f.Header[1]) << 16
|
||||
id |= uint32(f.Header[2]) << 8
|
||||
id |= uint32(f.Header[3]) << 0
|
||||
return
|
||||
}
|
||||
|
||||
// WriteTo writes the frame in the SPDY format.
|
||||
func (f Frame) WriteTo(w io.Writer) (n int64, err os.Error) {
|
||||
var nn int
|
||||
// Header
|
||||
nn, err = w.Write(f.Header[:])
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Flags
|
||||
nn, err = w.Write([]byte{byte(f.Flags)})
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Length
|
||||
nn, err = w.Write([]byte{
|
||||
byte(len(f.Data) & 0x00ff0000 >> 16),
|
||||
byte(len(f.Data) & 0x0000ff00 >> 8),
|
||||
byte(len(f.Data) & 0x000000ff),
|
||||
})
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Data
|
||||
if len(f.Data) > 0 {
|
||||
nn, err = w.Write(f.Data)
|
||||
n += int64(nn)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// headerDictionary is the dictionary sent to the zlib compressor/decompressor.
|
||||
// Even though the specification states there is no null byte at the end, Chrome sends it.
|
||||
const headerDictionary = "optionsgetheadpostputdeletetrace" +
|
||||
"acceptaccept-charsetaccept-encodingaccept-languageauthorizationexpectfromhost" +
|
||||
"if-modified-sinceif-matchif-none-matchif-rangeif-unmodifiedsince" +
|
||||
"max-forwardsproxy-authorizationrangerefererteuser-agent" +
|
||||
"100101200201202203204205206300301302303304305306307400401402403404405406407408409410411412413414415416417500501502503504505" +
|
||||
"accept-rangesageetaglocationproxy-authenticatepublicretry-after" +
|
||||
"servervarywarningwww-authenticateallowcontent-basecontent-encodingcache-control" +
|
||||
"connectiondatetrailertransfer-encodingupgradeviawarning" +
|
||||
"content-languagecontent-lengthcontent-locationcontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookie" +
|
||||
"MondayTuesdayWednesdayThursdayFridaySaturdaySunday" +
|
||||
"JanFebMarAprMayJunJulAugSepOctNovDec" +
|
||||
"chunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplication/xhtmltext/plainpublicmax-age" +
|
||||
"charset=iso-8859-1utf-8gzipdeflateHTTP/1.1statusversionurl\x00"
|
||||
|
||||
// hrSource is a reader that passes through reads from another reader.
|
||||
// When the underlying reader reaches EOF, Read will block until another reader is added via change.
|
||||
type hrSource struct {
|
||||
r io.Reader
|
||||
m sync.RWMutex
|
||||
c *sync.Cond
|
||||
}
|
||||
|
||||
func (src *hrSource) Read(p []byte) (n int, err os.Error) {
|
||||
src.m.RLock()
|
||||
for src.r == nil {
|
||||
src.c.Wait()
|
||||
}
|
||||
n, err = src.r.Read(p)
|
||||
src.m.RUnlock()
|
||||
if err == os.EOF {
|
||||
src.change(nil)
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (src *hrSource) change(r io.Reader) {
|
||||
src.m.Lock()
|
||||
defer src.m.Unlock()
|
||||
src.r = r
|
||||
src.c.Broadcast()
|
||||
}
|
||||
|
||||
// A HeaderReader reads zlib-compressed headers.
|
||||
type HeaderReader struct {
|
||||
source hrSource
|
||||
decompressor io.ReadCloser
|
||||
}
|
||||
|
||||
// NewHeaderReader creates a HeaderReader with the initial dictionary.
|
||||
func NewHeaderReader() (hr *HeaderReader) {
|
||||
hr = new(HeaderReader)
|
||||
hr.source.c = sync.NewCond(hr.source.m.RLocker())
|
||||
return
|
||||
}
|
||||
|
||||
// ReadHeader reads a set of headers from a reader.
|
||||
func (hr *HeaderReader) ReadHeader(r io.Reader) (h http.Header, err os.Error) {
|
||||
hr.source.change(r)
|
||||
h, err = hr.read()
|
||||
return
|
||||
}
|
||||
|
||||
// Decode reads a set of headers from a block of bytes.
|
||||
func (hr *HeaderReader) Decode(data []byte) (h http.Header, err os.Error) {
|
||||
hr.source.change(bytes.NewBuffer(data))
|
||||
h, err = hr.read()
|
||||
return
|
||||
}
|
||||
|
||||
func (hr *HeaderReader) read() (h http.Header, err os.Error) {
|
||||
var count uint16
|
||||
if hr.decompressor == nil {
|
||||
hr.decompressor, err = zlib.NewReaderDict(&hr.source, []byte(headerDictionary))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = binary.Read(hr.decompressor, binary.BigEndian, &count)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h = make(http.Header, int(count))
|
||||
for i := 0; i < int(count); i++ {
|
||||
var name, value string
|
||||
name, err = readHeaderString(hr.decompressor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
value, err = readHeaderString(hr.decompressor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
valueList := strings.Split(string(value), "\x00", -1)
|
||||
for _, v := range valueList {
|
||||
h.Add(name, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readHeaderString(r io.Reader) (s string, err os.Error) {
|
||||
var length uint16
|
||||
err = binary.Read(r, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data := make([]byte, int(length))
|
||||
_, err = io.ReadFull(r, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// HeaderWriter will write zlib-compressed headers on different streams.
|
||||
type HeaderWriter struct {
|
||||
compressor *zlib.Writer
|
||||
buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
// NewHeaderWriter creates a HeaderWriter ready to compress headers.
|
||||
func NewHeaderWriter(level int) (hw *HeaderWriter) {
|
||||
hw = &HeaderWriter{buffer: new(bytes.Buffer)}
|
||||
hw.compressor, _ = zlib.NewWriterDict(hw.buffer, level, []byte(headerDictionary))
|
||||
return
|
||||
}
|
||||
|
||||
// WriteHeader writes a header block directly to an output.
|
||||
func (hw *HeaderWriter) WriteHeader(w io.Writer, h http.Header) (err os.Error) {
|
||||
hw.write(h)
|
||||
_, err = io.Copy(w, hw.buffer)
|
||||
hw.buffer.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
// Encode returns a compressed header block.
|
||||
func (hw *HeaderWriter) Encode(h http.Header) (data []byte) {
|
||||
hw.write(h)
|
||||
data = make([]byte, hw.buffer.Len())
|
||||
hw.buffer.Read(data)
|
||||
return
|
||||
}
|
||||
|
||||
func (hw *HeaderWriter) write(h http.Header) {
|
||||
binary.Write(hw.compressor, binary.BigEndian, uint16(len(h)))
|
||||
for k, vals := range h {
|
||||
k = strings.ToLower(k)
|
||||
binary.Write(hw.compressor, binary.BigEndian, uint16(len(k)))
|
||||
binary.Write(hw.compressor, binary.BigEndian, []byte(k))
|
||||
v := strings.Join(vals, "\x00")
|
||||
binary.Write(hw.compressor, binary.BigEndian, uint16(len(v)))
|
||||
binary.Write(hw.compressor, binary.BigEndian, []byte(v))
|
||||
}
|
||||
hw.compressor.Flush()
|
||||
}
|
259
src/pkg/http/spdy/protocol_test.go
Normal file
259
src/pkg/http/spdy/protocol_test.go
Normal file
@ -0,0 +1,259 @@
|
||||
// Copyright 2011 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 spdy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"http"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type frameIoTest struct {
|
||||
desc string
|
||||
data []byte
|
||||
frame Frame
|
||||
readError os.Error
|
||||
readOnly bool
|
||||
}
|
||||
|
||||
var frameIoTests = []frameIoTest{
|
||||
{
|
||||
"noop frame",
|
||||
[]byte{
|
||||
0x80, 0x02, 0x00, 0x05,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
ControlFrame(
|
||||
TypeNoop,
|
||||
0x00,
|
||||
[]byte{},
|
||||
),
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"ping frame",
|
||||
[]byte{
|
||||
0x80, 0x02, 0x00, 0x06,
|
||||
0x00, 0x00, 0x00, 0x04,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
},
|
||||
ControlFrame(
|
||||
TypePing,
|
||||
0x00,
|
||||
[]byte{0x00, 0x00, 0x00, 0x01},
|
||||
),
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"syn_stream frame",
|
||||
[]byte{
|
||||
0x80, 0x02, 0x00, 0x01,
|
||||
0x01, 0x00, 0x00, 0x53,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x78, 0xbb,
|
||||
0xdf, 0xa2, 0x51, 0xb2,
|
||||
0x62, 0x60, 0x66, 0x60,
|
||||
0xcb, 0x4d, 0x2d, 0xc9,
|
||||
0xc8, 0x4f, 0x61, 0x60,
|
||||
0x4e, 0x4f, 0x2d, 0x61,
|
||||
0x60, 0x2e, 0x2d, 0xca,
|
||||
0x61, 0x10, 0xcb, 0x28,
|
||||
0x29, 0x29, 0xb0, 0xd2,
|
||||
0xd7, 0x2f, 0x2f, 0x2f,
|
||||
0xd7, 0x4b, 0xcf, 0xcf,
|
||||
0x4f, 0xcf, 0x49, 0xd5,
|
||||
0x4b, 0xce, 0xcf, 0xd5,
|
||||
0x67, 0x60, 0x2f, 0x4b,
|
||||
0x2d, 0x2a, 0xce, 0xcc,
|
||||
0xcf, 0x63, 0xe0, 0x00,
|
||||
0x29, 0xd0, 0x37, 0xd4,
|
||||
0x33, 0x04, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff,
|
||||
},
|
||||
ControlFrame(
|
||||
TypeSynStream,
|
||||
0x01,
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x78, 0xbb,
|
||||
0xdf, 0xa2, 0x51, 0xb2,
|
||||
0x62, 0x60, 0x66, 0x60,
|
||||
0xcb, 0x4d, 0x2d, 0xc9,
|
||||
0xc8, 0x4f, 0x61, 0x60,
|
||||
0x4e, 0x4f, 0x2d, 0x61,
|
||||
0x60, 0x2e, 0x2d, 0xca,
|
||||
0x61, 0x10, 0xcb, 0x28,
|
||||
0x29, 0x29, 0xb0, 0xd2,
|
||||
0xd7, 0x2f, 0x2f, 0x2f,
|
||||
0xd7, 0x4b, 0xcf, 0xcf,
|
||||
0x4f, 0xcf, 0x49, 0xd5,
|
||||
0x4b, 0xce, 0xcf, 0xd5,
|
||||
0x67, 0x60, 0x2f, 0x4b,
|
||||
0x2d, 0x2a, 0xce, 0xcc,
|
||||
0xcf, 0x63, 0xe0, 0x00,
|
||||
0x29, 0xd0, 0x37, 0xd4,
|
||||
0x33, 0x04, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff,
|
||||
},
|
||||
),
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"data frame",
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x05,
|
||||
0x01, 0x00, 0x00, 0x04,
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
},
|
||||
DataFrame(
|
||||
5,
|
||||
0x01,
|
||||
[]byte{0x01, 0x02, 0x03, 0x04},
|
||||
),
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"too much data",
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x05,
|
||||
0x01, 0x00, 0x00, 0x04,
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
0x05, 0x06, 0x07, 0x08,
|
||||
},
|
||||
DataFrame(
|
||||
5,
|
||||
0x01,
|
||||
[]byte{0x01, 0x02, 0x03, 0x04},
|
||||
),
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"not enough data",
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x05,
|
||||
},
|
||||
Frame{},
|
||||
os.EOF,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
func TestReadFrame(t *testing.T) {
|
||||
for _, tt := range frameIoTests {
|
||||
f, err := ReadFrame(bytes.NewBuffer(tt.data))
|
||||
if err != tt.readError {
|
||||
t.Errorf("%s: ReadFrame: %s", tt.desc, err)
|
||||
continue
|
||||
}
|
||||
if err == nil {
|
||||
if !bytes.Equal(f.Header[:], tt.frame.Header[:]) {
|
||||
t.Errorf("%s: header %q != %q", tt.desc, string(f.Header[:]), string(tt.frame.Header[:]))
|
||||
}
|
||||
if f.Flags != tt.frame.Flags {
|
||||
t.Errorf("%s: flags %#02x != %#02x", tt.desc, f.Flags, tt.frame.Flags)
|
||||
}
|
||||
if !bytes.Equal(f.Data, tt.frame.Data) {
|
||||
t.Errorf("%s: data %q != %q", tt.desc, string(f.Data), string(tt.frame.Data))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteTo(t *testing.T) {
|
||||
for _, tt := range frameIoTests {
|
||||
if tt.readOnly {
|
||||
continue
|
||||
}
|
||||
b := new(bytes.Buffer)
|
||||
_, err := tt.frame.WriteTo(b)
|
||||
if err != nil {
|
||||
t.Errorf("%s: WriteTo: %s", tt.desc, err)
|
||||
}
|
||||
if !bytes.Equal(b.Bytes(), tt.data) {
|
||||
t.Errorf("%s: data %q != %q", tt.desc, string(b.Bytes()), string(tt.data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var headerDataTest = []byte{
|
||||
0x78, 0xbb, 0xdf, 0xa2,
|
||||
0x51, 0xb2, 0x62, 0x60,
|
||||
0x66, 0x60, 0xcb, 0x4d,
|
||||
0x2d, 0xc9, 0xc8, 0x4f,
|
||||
0x61, 0x60, 0x4e, 0x4f,
|
||||
0x2d, 0x61, 0x60, 0x2e,
|
||||
0x2d, 0xca, 0x61, 0x10,
|
||||
0xcb, 0x28, 0x29, 0x29,
|
||||
0xb0, 0xd2, 0xd7, 0x2f,
|
||||
0x2f, 0x2f, 0xd7, 0x4b,
|
||||
0xcf, 0xcf, 0x4f, 0xcf,
|
||||
0x49, 0xd5, 0x4b, 0xce,
|
||||
0xcf, 0xd5, 0x67, 0x60,
|
||||
0x2f, 0x4b, 0x2d, 0x2a,
|
||||
0xce, 0xcc, 0xcf, 0x63,
|
||||
0xe0, 0x00, 0x29, 0xd0,
|
||||
0x37, 0xd4, 0x33, 0x04,
|
||||
0x00, 0x00, 0x00, 0xff,
|
||||
0xff,
|
||||
}
|
||||
|
||||
func TestReadHeader(t *testing.T) {
|
||||
r := NewHeaderReader()
|
||||
h, err := r.Decode(headerDataTest)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %v", err)
|
||||
return
|
||||
}
|
||||
if len(h) != 3 {
|
||||
t.Errorf("Header count = %d (expected 3)", len(h))
|
||||
}
|
||||
if h.Get("Url") != "http://www.google.com/" {
|
||||
t.Errorf("Url: %q != %q", h.Get("Url"), "http://www.google.com/")
|
||||
}
|
||||
if h.Get("Method") != "get" {
|
||||
t.Errorf("Method: %q != %q", h.Get("Method"), "get")
|
||||
}
|
||||
if h.Get("Version") != "http/1.1" {
|
||||
t.Errorf("Version: %q != %q", h.Get("Version"), "http/1.1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteHeader(t *testing.T) {
|
||||
for level := zlib.NoCompression; level <= zlib.BestCompression; level++ {
|
||||
r := NewHeaderReader()
|
||||
w := NewHeaderWriter(level)
|
||||
for i := 0; i < 100; i++ {
|
||||
b := new(bytes.Buffer)
|
||||
gold := http.Header{
|
||||
"Url": []string{"http://www.google.com/"},
|
||||
"Method": []string{"get"},
|
||||
"Version": []string{"http/1.1"},
|
||||
}
|
||||
w.WriteHeader(b, gold)
|
||||
h, err := r.Decode(b.Bytes())
|
||||
if err != nil {
|
||||
t.Errorf("(level=%d i=%d) Error: %v", level, i, err)
|
||||
return
|
||||
}
|
||||
if len(h) != len(gold) {
|
||||
t.Errorf("(level=%d i=%d) Header count = %d (expected %d)", level, i, len(h), len(gold))
|
||||
}
|
||||
for k, _ := range h {
|
||||
if h.Get(k) != gold.Get(k) {
|
||||
t.Errorf("(level=%d i=%d) %s: %q != %q", level, i, k, h.Get(k), gold.Get(k))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user