Commit 1801972b authored by Ross Light's avatar Ross Light Committed by Brad Fitzpatrick

http/spdy: new package

R=bradfitz, agl1, rsc
CC=golang-dev
https://golang.org/cl/4435055
parent 6f88288a
......@@ -103,6 +103,7 @@ DIRS=\
http/fcgi\
http/pprof\
http/httptest\
http/spdy\
image\
image/jpeg\
image/png\
......
# 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
// 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()
}
// 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))
}
}
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment