1
0
mirror of https://github.com/golang/go synced 2024-11-24 17:00:01 -07:00

http/spdy: redo interfaces, flesh out implementation & frame types

Added a new Framer to handle reading/writing Frames. This is necessary since we have to maintain a compression context across streams.

TODO:
* Separate the types and read/write routines into different files.
* Improve error handling.

R=bradfitz, rsc
CC=golang-dev
https://golang.org/cl/4503042
This commit is contained in:
William Chan 2011-05-26 09:54:54 -07:00 committed by Brad Fitzpatrick
parent 4bb337e525
commit abb970ef57
5 changed files with 1317 additions and 540 deletions

View File

@ -6,6 +6,7 @@ include ../../../Make.inc
TARG=http/spdy
GOFILES=\
framer.go\
protocol.go\
include ../../../Make.pkg

422
src/pkg/http/spdy/framer.go Normal file
View File

@ -0,0 +1,422 @@
// 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"
"encoding/binary"
"compress/zlib"
"http"
"io"
"os"
"strconv"
"strings"
)
type FramerError int
const (
Internal FramerError = iota
InvalidControlFrame
UnlowercasedHeaderName
DuplicateHeaders
UnknownFrameType
InvalidDataFrame
)
func (e FramerError) String() string {
switch e {
case Internal:
return "Internal"
case InvalidControlFrame:
return "InvalidControlFrame"
case UnlowercasedHeaderName:
return "UnlowercasedHeaderName"
case DuplicateHeaders:
return "DuplicateHeaders"
case UnknownFrameType:
return "UnknownFrameType"
case InvalidDataFrame:
return "InvalidDataFrame"
}
return "Error(" + strconv.Itoa(int(e)) + ")"
}
// Framer handles serializing/deserializing SPDY frames, including compressing/
// decompressing payloads.
type Framer struct {
headerCompressionDisabled bool
w io.Writer
headerBuf *bytes.Buffer
headerCompressor *zlib.Writer
r io.Reader
headerDecompressor io.ReadCloser
}
// NewFramer allocates a new Framer for a given SPDY connection, repesented by
// a io.Writer and io.Reader. Note that Framer will read and write individual fields
// from/to the Reader and Writer, so the caller should pass in an appropriately
// buffered implementation to optimize performance.
func NewFramer(w io.Writer, r io.Reader) (*Framer, os.Error) {
compressBuf := new(bytes.Buffer)
compressor, err := zlib.NewWriterDict(compressBuf, zlib.BestCompression, []byte(HeaderDictionary))
if err != nil {
return nil, err
}
framer := &Framer{
w: w,
headerBuf: compressBuf,
headerCompressor: compressor,
r: r,
}
return framer, nil
}
func (f *Framer) initHeaderDecompression() os.Error {
if f.headerDecompressor != nil {
return nil
}
decompressor, err := zlib.NewReaderDict(f.r, []byte(HeaderDictionary))
if err != nil {
return err
}
f.headerDecompressor = decompressor
return nil
}
// ReadFrame reads SPDY encoded data and returns a decompressed Frame.
func (f *Framer) ReadFrame() (Frame, os.Error) {
var firstWord uint32
if err := binary.Read(f.r, binary.BigEndian, &firstWord); err != nil {
return nil, err
}
if (firstWord & 0x80000000) != 0 {
frameType := ControlFrameType(firstWord & 0xffff)
version := uint16(0x7fff & (firstWord >> 16))
return f.parseControlFrame(version, frameType)
}
return f.parseDataFrame(firstWord & 0x7fffffff)
}
func (f *Framer) parseControlFrame(version uint16, frameType ControlFrameType) (Frame, os.Error) {
var length uint32
if err := binary.Read(f.r, binary.BigEndian, &length); err != nil {
return nil, err
}
flags := ControlFlags((length & 0xff000000) >> 24)
length &= 0xffffff
header := ControlFrameHeader{version, frameType, flags, length}
cframe, err := newControlFrame(frameType)
if err != nil {
return nil, err
}
if err = cframe.read(header, f); err != nil {
return nil, err
}
return cframe, nil
}
func parseHeaderValueBlock(r io.Reader) (http.Header, os.Error) {
var numHeaders uint16
if err := binary.Read(r, binary.BigEndian, &numHeaders); err != nil {
return nil, err
}
h := make(http.Header, int(numHeaders))
for i := 0; i < int(numHeaders); i++ {
var length uint16
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
return nil, err
}
nameBytes := make([]byte, length)
if _, err := io.ReadFull(r, nameBytes); err != nil {
return nil, err
}
name := string(nameBytes)
if name != strings.ToLower(name) {
return nil, UnlowercasedHeaderName
}
if h[name] != nil {
return nil, DuplicateHeaders
}
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
return nil, err
}
value := make([]byte, length)
if _, err := io.ReadFull(r, value); err != nil {
return nil, err
}
valueList := strings.Split(string(value), "\x00", -1)
for _, v := range valueList {
h.Add(name, v)
}
}
return h, nil
}
func (f *Framer) readSynStreamFrame(h ControlFrameHeader, frame *SynStreamFrame) os.Error {
frame.CFHeader = h
var err os.Error
if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
return err
}
if err = binary.Read(f.r, binary.BigEndian, &frame.AssociatedToStreamId); err != nil {
return err
}
if err = binary.Read(f.r, binary.BigEndian, &frame.Priority); err != nil {
return err
}
frame.Priority >>= 14
reader := f.r
if !f.headerCompressionDisabled {
f.initHeaderDecompression()
reader = f.headerDecompressor
}
frame.Headers, err = parseHeaderValueBlock(reader)
if err != nil {
return err
}
return nil
}
func (f *Framer) readSynReplyFrame(h ControlFrameHeader, frame *SynReplyFrame) os.Error {
frame.CFHeader = h
var err os.Error
if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
return err
}
var unused uint16
if err = binary.Read(f.r, binary.BigEndian, &unused); err != nil {
return err
}
reader := f.r
if !f.headerCompressionDisabled {
f.initHeaderDecompression()
reader = f.headerDecompressor
}
frame.Headers, err = parseHeaderValueBlock(reader)
if err != nil {
return err
}
return nil
}
func (f *Framer) readHeadersFrame(h ControlFrameHeader, frame *HeadersFrame) os.Error {
frame.CFHeader = h
var err os.Error
if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
return err
}
var unused uint16
if err = binary.Read(f.r, binary.BigEndian, &unused); err != nil {
return err
}
reader := f.r
if !f.headerCompressionDisabled {
f.initHeaderDecompression()
reader = f.headerDecompressor
}
frame.Headers, err = parseHeaderValueBlock(reader)
if err != nil {
return err
}
return nil
}
func (f *Framer) parseDataFrame(streamId uint32) (*DataFrame, os.Error) {
var length uint32
if err := binary.Read(f.r, binary.BigEndian, &length); err != nil {
return nil, err
}
var frame DataFrame
frame.StreamId = streamId
frame.Flags = DataFlags(length >> 24)
length &= 0xffffff
frame.Data = make([]byte, length)
// TODO(willchan): Support compressed data frames.
if _, err := io.ReadFull(f.r, frame.Data); err != nil {
return nil, err
}
return &frame, nil
}
// WriteFrame writes a frame.
func (f *Framer) WriteFrame(frame Frame) os.Error {
return frame.write(f)
}
func writeControlFrameHeader(w io.Writer, h ControlFrameHeader) os.Error {
if err := binary.Write(w, binary.BigEndian, 0x8000|h.version); err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, h.frameType); err != nil {
return err
}
flagsAndLength := (uint32(h.Flags) << 24) | h.length
if err := binary.Write(w, binary.BigEndian, flagsAndLength); err != nil {
return err
}
return nil
}
func writeHeaderValueBlock(w io.Writer, h http.Header) (n int, err os.Error) {
n = 0
if err = binary.Write(w, binary.BigEndian, uint16(len(h))); err != nil {
return
}
n += 2
for name, values := range h {
if err = binary.Write(w, binary.BigEndian, uint16(len(name))); err != nil {
return
}
n += 2
name = strings.ToLower(name)
if _, err = io.WriteString(w, name); err != nil {
return
}
n += len(name)
v := strings.Join(values, "\x00")
if err = binary.Write(w, binary.BigEndian, uint16(len(v))); err != nil {
return
}
n += 2
if _, err = io.WriteString(w, v); err != nil {
return
}
n += len(v)
}
return
}
func (f *Framer) writeSynStreamFrame(frame *SynStreamFrame) (err os.Error) {
// Marshal the headers.
var writer io.Writer = f.headerBuf
if !f.headerCompressionDisabled {
writer = f.headerCompressor
}
if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil {
return
}
if !f.headerCompressionDisabled {
f.headerCompressor.Flush()
}
// Set ControlFrameHeader
frame.CFHeader.version = Version
frame.CFHeader.frameType = TypeSynStream
frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 10)
// Serialize frame to Writer
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
return err
}
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
return err
}
if err = binary.Write(f.w, binary.BigEndian, frame.AssociatedToStreamId); err != nil {
return err
}
if err = binary.Write(f.w, binary.BigEndian, frame.Priority<<14); err != nil {
return err
}
if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil {
return err
}
f.headerBuf.Reset()
return nil
}
func (f *Framer) writeSynReplyFrame(frame *SynReplyFrame) (err os.Error) {
// Marshal the headers.
var writer io.Writer = f.headerBuf
if !f.headerCompressionDisabled {
writer = f.headerCompressor
}
if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil {
return
}
if !f.headerCompressionDisabled {
f.headerCompressor.Flush()
}
// Set ControlFrameHeader
frame.CFHeader.version = Version
frame.CFHeader.frameType = TypeSynReply
frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 6)
// Serialize frame to Writer
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
return
}
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
return
}
if err = binary.Write(f.w, binary.BigEndian, uint16(0)); err != nil {
return
}
if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil {
return
}
f.headerBuf.Reset()
return
}
func (f *Framer) writeHeadersFrame(frame *HeadersFrame) (err os.Error) {
// Marshal the headers.
var writer io.Writer = f.headerBuf
if !f.headerCompressionDisabled {
writer = f.headerCompressor
}
if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil {
return
}
if !f.headerCompressionDisabled {
f.headerCompressor.Flush()
}
// Set ControlFrameHeader
frame.CFHeader.version = Version
frame.CFHeader.frameType = TypeHeaders
frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 6)
// Serialize frame to Writer
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
return
}
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
return
}
if err = binary.Write(f.w, binary.BigEndian, uint16(0)); err != nil {
return
}
if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil {
return
}
f.headerBuf.Reset()
return
}
func (f *Framer) writeDataFrame(frame *DataFrame) (err os.Error) {
// Validate DataFrame
if frame.StreamId&0x80000000 != 0 || len(frame.Data) >= 0x0f000000 {
return InvalidDataFrame
}
// TODO(willchan): Support data compression.
// Serialize frame to Writer
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
return
}
flagsAndLength := (uint32(frame.Flags) << 24) | uint32(len(frame.Data))
if err = binary.Write(f.w, binary.BigEndian, flagsAndLength); err != nil {
return
}
if _, err = f.w.Write(frame.Data); err != nil {
return
}
return nil
}

View File

@ -0,0 +1,496 @@
// 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"
"http"
"io"
"reflect"
"testing"
)
func TestHeaderParsing(t *testing.T) {
headers := http.Header{
"Url": []string{"http://www.google.com/"},
"Method": []string{"get"},
"Version": []string{"http/1.1"},
}
var headerValueBlockBuf bytes.Buffer
writeHeaderValueBlock(&headerValueBlockBuf, headers)
newHeaders, err := parseHeaderValueBlock(&headerValueBlockBuf)
if err != nil {
t.Fatal("parseHeaderValueBlock:", err)
}
if !reflect.DeepEqual(headers, newHeaders) {
t.Fatal("got: ", newHeaders, "\nwant: ", headers)
}
}
func TestCreateParseSynStreamFrame(t *testing.T) {
buffer := new(bytes.Buffer)
framer := &Framer{
headerCompressionDisabled: true,
w: buffer,
headerBuf: new(bytes.Buffer),
r: buffer,
}
synStreamFrame := SynStreamFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeSynStream,
},
Headers: http.Header{
"Url": []string{"http://www.google.com/"},
"Method": []string{"get"},
"Version": []string{"http/1.1"},
},
}
if err := framer.WriteFrame(&synStreamFrame); err != nil {
t.Fatal("WriteFrame without compression:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame without compression:", err)
}
parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
}
// Test again with compression
buffer.Reset()
framer, err = NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
if err := framer.WriteFrame(&synStreamFrame); err != nil {
t.Fatal("WriteFrame with compression:", err)
}
frame, err = framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame with compression:", err)
}
parsedSynStreamFrame, ok = frame.(*SynStreamFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
}
}
func TestCreateParseSynReplyFrame(t *testing.T) {
buffer := new(bytes.Buffer)
framer := &Framer{
headerCompressionDisabled: true,
w: buffer,
headerBuf: new(bytes.Buffer),
r: buffer,
}
synReplyFrame := SynReplyFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeSynReply,
},
Headers: http.Header{
"Url": []string{"http://www.google.com/"},
"Method": []string{"get"},
"Version": []string{"http/1.1"},
},
}
if err := framer.WriteFrame(&synReplyFrame); err != nil {
t.Fatal("WriteFrame without compression:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame without compression:", err)
}
parsedSynReplyFrame, ok := frame.(*SynReplyFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(synReplyFrame, *parsedSynReplyFrame) {
t.Fatal("got: ", *parsedSynReplyFrame, "\nwant: ", synReplyFrame)
}
// Test again with compression
buffer.Reset()
framer, err = NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
if err := framer.WriteFrame(&synReplyFrame); err != nil {
t.Fatal("WriteFrame with compression:", err)
}
frame, err = framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame with compression:", err)
}
parsedSynReplyFrame, ok = frame.(*SynReplyFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(synReplyFrame, *parsedSynReplyFrame) {
t.Fatal("got: ", *parsedSynReplyFrame, "\nwant: ", synReplyFrame)
}
}
func TestCreateParseRstStream(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
rstStreamFrame := RstStreamFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeRstStream,
},
StreamId: 1,
Status: InvalidStream,
}
if err := framer.WriteFrame(&rstStreamFrame); err != nil {
t.Fatal("WriteFrame:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame:", err)
}
parsedRstStreamFrame, ok := frame.(*RstStreamFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(rstStreamFrame, *parsedRstStreamFrame) {
t.Fatal("got: ", *parsedRstStreamFrame, "\nwant: ", rstStreamFrame)
}
}
func TestCreateParseSettings(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
settingsFrame := SettingsFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeSettings,
},
FlagIdValues: []SettingsFlagIdValue{
{FlagSettingsPersistValue, SettingsCurrentCwnd, 10},
{FlagSettingsPersisted, SettingsUploadBandwidth, 1},
},
}
if err := framer.WriteFrame(&settingsFrame); err != nil {
t.Fatal("WriteFrame:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame:", err)
}
parsedSettingsFrame, ok := frame.(*SettingsFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(settingsFrame, *parsedSettingsFrame) {
t.Fatal("got: ", *parsedSettingsFrame, "\nwant: ", settingsFrame)
}
}
func TestCreateParseNoop(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
noopFrame := NoopFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeNoop,
},
}
if err := framer.WriteFrame(&noopFrame); err != nil {
t.Fatal("WriteFrame:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame:", err)
}
parsedNoopFrame, ok := frame.(*NoopFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(noopFrame, *parsedNoopFrame) {
t.Fatal("got: ", *parsedNoopFrame, "\nwant: ", noopFrame)
}
}
func TestCreateParsePing(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
pingFrame := PingFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypePing,
},
Id: 31337,
}
if err := framer.WriteFrame(&pingFrame); err != nil {
t.Fatal("WriteFrame:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame:", err)
}
parsedPingFrame, ok := frame.(*PingFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(pingFrame, *parsedPingFrame) {
t.Fatal("got: ", *parsedPingFrame, "\nwant: ", pingFrame)
}
}
func TestCreateParseGoAway(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
goAwayFrame := GoAwayFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeGoAway,
},
LastGoodStreamId: 31337,
}
if err := framer.WriteFrame(&goAwayFrame); err != nil {
t.Fatal("WriteFrame:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame:", err)
}
parsedGoAwayFrame, ok := frame.(*GoAwayFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(goAwayFrame, *parsedGoAwayFrame) {
t.Fatal("got: ", *parsedGoAwayFrame, "\nwant: ", goAwayFrame)
}
}
func TestCreateParseHeadersFrame(t *testing.T) {
buffer := new(bytes.Buffer)
framer := &Framer{
headerCompressionDisabled: true,
w: buffer,
headerBuf: new(bytes.Buffer),
r: buffer,
}
headersFrame := HeadersFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeHeaders,
},
}
headersFrame.Headers = http.Header{
"Url": []string{"http://www.google.com/"},
"Method": []string{"get"},
"Version": []string{"http/1.1"},
}
if err := framer.WriteFrame(&headersFrame); err != nil {
t.Fatal("WriteFrame without compression:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame without compression:", err)
}
parsedHeadersFrame, ok := frame.(*HeadersFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
}
// Test again with compression
buffer.Reset()
framer, err = NewFramer(buffer, buffer)
if err := framer.WriteFrame(&headersFrame); err != nil {
t.Fatal("WriteFrame with compression:", err)
}
frame, err = framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame with compression:", err)
}
parsedHeadersFrame, ok = frame.(*HeadersFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
}
}
func TestCreateParseDataFrame(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
dataFrame := DataFrame{
StreamId: 1,
Data: []byte{'h', 'e', 'l', 'l', 'o'},
}
if err := framer.WriteFrame(&dataFrame); err != nil {
t.Fatal("WriteFrame:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame:", err)
}
parsedDataFrame, ok := frame.(*DataFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(dataFrame, *parsedDataFrame) {
t.Fatal("got: ", *parsedDataFrame, "\nwant: ", dataFrame)
}
}
func TestCompressionContextAcrossFrames(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
headersFrame := HeadersFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeHeaders,
},
Headers: http.Header{
"Url": []string{"http://www.google.com/"},
"Method": []string{"get"},
"Version": []string{"http/1.1"},
},
}
if err := framer.WriteFrame(&headersFrame); err != nil {
t.Fatal("WriteFrame (HEADERS):", err)
}
synStreamFrame := SynStreamFrame{ControlFrameHeader{Version, TypeSynStream, 0, 0}, 0, 0, 0, nil}
synStreamFrame.Headers = http.Header{
"Url": []string{"http://www.google.com/"},
"Method": []string{"get"},
"Version": []string{"http/1.1"},
}
if err := framer.WriteFrame(&synStreamFrame); err != nil {
t.Fatal("WriteFrame (SYN_STREAM):", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame (HEADERS):", err, buffer.Bytes())
}
parsedHeadersFrame, ok := frame.(*HeadersFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
}
frame, err = framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame (SYN_STREAM):", err, buffer.Bytes())
}
parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
}
}
func TestMultipleSPDYFrames(t *testing.T) {
// Initialize the framers.
pr1, pw1 := io.Pipe()
pr2, pw2 := io.Pipe()
writer, err := NewFramer(pw1, pr2)
if err != nil {
t.Fatal("Failed to create writer:", err)
}
reader, err := NewFramer(pw2, pr1)
if err != nil {
t.Fatal("Failed to create reader:", err)
}
// Set up the frames we're actually transferring.
headersFrame := HeadersFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeHeaders,
},
Headers: http.Header{
"Url": []string{"http://www.google.com/"},
"Method": []string{"get"},
"Version": []string{"http/1.1"},
},
}
synStreamFrame := SynStreamFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeSynStream,
},
Headers: http.Header{
"Url": []string{"http://www.google.com/"},
"Method": []string{"get"},
"Version": []string{"http/1.1"},
},
}
// Start the goroutines to write the frames.
go func() {
if err := writer.WriteFrame(&headersFrame); err != nil {
t.Fatal("WriteFrame (HEADERS): ", err)
}
if err := writer.WriteFrame(&synStreamFrame); err != nil {
t.Fatal("WriteFrame (SYN_STREAM): ", err)
}
}()
// Read the frames and verify they look as expected.
frame, err := reader.ReadFrame()
if err != nil {
t.Fatal("ReadFrame (HEADERS): ", err)
}
parsedHeadersFrame, ok := frame.(*HeadersFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
}
frame, err = reader.ReadFrame()
if err != nil {
t.Fatal("ReadFrame (SYN_STREAM):", err)
}
parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
if !ok {
t.Fatal("Parsed incorrect frame type.")
}
if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
}
}

View File

@ -9,17 +9,120 @@
package spdy
import (
"bytes"
"compress/zlib"
"encoding/binary"
"http"
"io"
"os"
"strconv"
"strings"
"sync"
)
// Data Frame Format
// +----------------------------------+
// |0| Stream-ID (31bits) |
// +----------------------------------+
// | flags (8) | Length (24 bits) |
// +----------------------------------+
// | Data |
// +----------------------------------+
//
// Control Frame Format
// +----------------------------------+
// |1| Version(15bits) | Type(16bits) |
// +----------------------------------+
// | flags (8) | Length (24 bits) |
// +----------------------------------+
// | Data |
// +----------------------------------+
//
// Control Frame: SYN_STREAM
// +----------------------------------+
// |1|000000000000001|0000000000000001|
// +----------------------------------+
// | flags (8) | Length (24 bits) | >= 12
// +----------------------------------+
// |X| Stream-ID(31bits) |
// +----------------------------------+
// |X|Associated-To-Stream-ID (31bits)|
// +----------------------------------+
// |Pri| unused | Length (16bits)|
// +----------------------------------+
//
// Control Frame: SYN_REPLY
// +----------------------------------+
// |1|000000000000001|0000000000000010|
// +----------------------------------+
// | flags (8) | Length (24 bits) | >= 8
// +----------------------------------+
// |X| Stream-ID(31bits) |
// +----------------------------------+
// | unused (16 bits)| Length (16bits)|
// +----------------------------------+
//
// Control Frame: RST_STREAM
// +----------------------------------+
// |1|000000000000001|0000000000000011|
// +----------------------------------+
// | flags (8) | Length (24 bits) | >= 4
// +----------------------------------+
// |X| Stream-ID(31bits) |
// +----------------------------------+
// | Status code (32 bits) |
// +----------------------------------+
//
// Control Frame: SETTINGS
// +----------------------------------+
// |1|000000000000001|0000000000000100|
// +----------------------------------+
// | flags (8) | Length (24 bits) |
// +----------------------------------+
// | # of entries (32) |
// +----------------------------------+
//
// Control Frame: NOOP
// +----------------------------------+
// |1|000000000000001|0000000000000101|
// +----------------------------------+
// | flags (8) | Length (24 bits) | = 0
// +----------------------------------+
//
// Control Frame: PING
// +----------------------------------+
// |1|000000000000001|0000000000000110|
// +----------------------------------+
// | flags (8) | Length (24 bits) | = 4
// +----------------------------------+
// | Unique id (32 bits) |
// +----------------------------------+
//
// Control Frame: GOAWAY
// +----------------------------------+
// |1|000000000000001|0000000000000111|
// +----------------------------------+
// | flags (8) | Length (24 bits) | = 4
// +----------------------------------+
// |X| Last-accepted-stream-id |
// +----------------------------------+
//
// Control Frame: HEADERS
// +----------------------------------+
// |1|000000000000001|0000000000001000|
// +----------------------------------+
// | flags (8) | Length (24 bits) | >= 8
// +----------------------------------+
// |X| Stream-ID (31 bits) |
// +----------------------------------+
// | unused (16 bits)| Length (16bits)|
// +----------------------------------+
//
// Control Frame: WINDOW_UPDATE
// +----------------------------------+
// |1|000000000000001|0000000000001001|
// +----------------------------------+
// | flags (8) | Length (24 bits) | = 8
// +----------------------------------+
// |X| Stream-ID (31 bits) |
// +----------------------------------+
// | Delta-Window-Size (32 bits) |
// +----------------------------------+
// Version is the protocol version number that this package implements.
const Version = 2
@ -34,182 +137,339 @@ const (
TypeSettings = 0x0004
TypeNoop = 0x0005
TypePing = 0x0006
TypeGoaway = 0x0007
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)) + ")"
}
// ControlFlags are the flags that can be set on a control frame.
type ControlFlags uint8
type FrameFlags uint8
// Stream frame flags
const (
FlagFin FrameFlags = 0x01
FlagUnidirectional = 0x02
ControlFlagFin ControlFlags = 0x01
)
// SETTINGS frame flags
// DataFlags are the flags that can be set on a data frame.
type DataFlags uint8
const (
FlagClearPreviouslyPersistedSettings FrameFlags = 0x01
DataFlagFin DataFlags = 0x01
DataFlagCompressed = 0x02
)
// 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
// Frame is a single SPDY frame in its unpacked in-memory representation. Use
// Framer to read and write it.
type Frame interface {
write(f *Framer) os.Error
}
// 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,
}
// ControlFrameHeader contains all the fields in a control frame header,
// in its unpacked in-memory representation.
type ControlFrameHeader struct {
// Note, high bit is the "Control" bit.
version uint16
frameType ControlFrameType
Flags ControlFlags
length uint32
}
// 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,
}
type controlFrame interface {
Frame
read(h ControlFrameHeader, f *Framer) os.Error
}
// 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 {
// SynStreamFrame is the unpacked, in-memory representation of a SYN_STREAM
// frame.
type SynStreamFrame struct {
CFHeader ControlFrameHeader
StreamId uint32
AssociatedToStreamId uint32
// Note, only 2 highest bits currently used
// Rest of Priority is unused.
Priority uint16
Headers http.Header
}
func (frame *SynStreamFrame) write(f *Framer) os.Error {
return f.writeSynStreamFrame(frame)
}
func (frame *SynStreamFrame) read(h ControlFrameHeader, f *Framer) os.Error {
return f.readSynStreamFrame(h, frame)
}
// SynReplyFrame is the unpacked, in-memory representation of a SYN_REPLY frame.
type SynReplyFrame struct {
CFHeader ControlFrameHeader
StreamId uint32
Headers http.Header
}
func (frame *SynReplyFrame) write(f *Framer) os.Error {
return f.writeSynReplyFrame(frame)
}
func (frame *SynReplyFrame) read(h ControlFrameHeader, f *Framer) os.Error {
return f.readSynReplyFrame(h, frame)
}
// StatusCode represents the status that led to a RST_STREAM
type StatusCode uint32
const (
ProtocolError StatusCode = 1
InvalidStream = 2
RefusedStream = 3
UnsupportedVersion = 4
Cancel = 5
InternalError = 6
FlowControlError = 7
)
// RstStreamFrame is the unpacked, in-memory representation of a RST_STREAM
// frame.
type RstStreamFrame struct {
CFHeader ControlFrameHeader
StreamId uint32
Status StatusCode
}
func (frame *RstStreamFrame) write(f *Framer) (err os.Error) {
frame.CFHeader.version = Version
frame.CFHeader.frameType = TypeRstStream
frame.CFHeader.length = 8
// Serialize frame to Writer
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
return
}
err = binary.Read(r, binary.BigEndian, &f.Flags)
if err != nil {
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
return
}
var lengthField [3]byte
_, err = io.ReadFull(r, lengthField[:])
if err != nil {
if err == os.EOF {
err = io.ErrUnexpectedEOF
if err = binary.Write(f.w, binary.BigEndian, frame.Status); err != nil {
return
}
return
}
func (frame *RstStreamFrame) read(h ControlFrameHeader, f *Framer) os.Error {
frame.CFHeader = h
if err := binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
return err
}
if err := binary.Read(f.r, binary.BigEndian, &frame.Status); err != nil {
return err
}
return nil
}
// SettingsFlag represents a flag in a SETTINGS frame.
type SettingsFlag uint8
const (
FlagSettingsPersistValue SettingsFlag = 0x1
FlagSettingsPersisted = 0x2
)
// SettingsFlag represents the id of an id/value pair in a SETTINGS frame.
type SettingsId uint32
const (
SettingsUploadBandwidth SettingsId = 1
SettingsDownloadBandwidth = 2
SettingsRoundTripTime = 3
SettingsMaxConcurrentStreams = 4
SettingsCurrentCwnd = 5
)
// SettingsFlagIdValue is the unpacked, in-memory representation of the
// combined flag/id/value for a setting in a SETTINGS frame.
type SettingsFlagIdValue struct {
Flag SettingsFlag
Id SettingsId
Value uint32
}
// SettingsFrame is the unpacked, in-memory representation of a SPDY
// SETTINGS frame.
type SettingsFrame struct {
CFHeader ControlFrameHeader
FlagIdValues []SettingsFlagIdValue
}
func (frame *SettingsFrame) write(f *Framer) (err os.Error) {
frame.CFHeader.version = Version
frame.CFHeader.frameType = TypeSettings
frame.CFHeader.length = uint32(len(frame.FlagIdValues)*8 + 4)
// Serialize frame to Writer
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
return
}
if err = binary.Write(f.w, binary.BigEndian, uint32(len(frame.FlagIdValues))); err != nil {
return
}
for _, flagIdValue := range frame.FlagIdValues {
flagId := (uint32(flagIdValue.Flag) << 24) | uint32(flagIdValue.Id)
if err = binary.Write(f.w, binary.BigEndian, flagId); err != nil {
return
}
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
if err = binary.Write(f.w, binary.BigEndian, flagIdValue.Value); err != nil {
return
}
} 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
func (frame *SettingsFrame) read(h ControlFrameHeader, f *Framer) os.Error {
frame.CFHeader = h
var numSettings uint32
if err := binary.Read(f.r, binary.BigEndian, &numSettings); err != nil {
return err
}
frame.FlagIdValues = make([]SettingsFlagIdValue, numSettings)
for i := uint32(0); i < numSettings; i++ {
if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Id); err != nil {
return err
}
frame.FlagIdValues[i].Flag = SettingsFlag((frame.FlagIdValues[i].Id & 0xff000000) >> 24)
frame.FlagIdValues[i].Id &= 0xffffff
if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Value); err != nil {
return err
}
}
return nil
}
// 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]))
// NoopFrame is the unpacked, in-memory representation of a NOOP frame.
type NoopFrame struct {
CFHeader ControlFrameHeader
}
// 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
func (frame *NoopFrame) write(f *Framer) os.Error {
frame.CFHeader.version = Version
frame.CFHeader.frameType = TypeNoop
// Serialize frame to Writer
return writeControlFrameHeader(f.w, frame.CFHeader)
}
// 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 {
func (frame *NoopFrame) read(h ControlFrameHeader, f *Framer) os.Error {
frame.CFHeader = h
return nil
}
// PingFrame is the unpacked, in-memory representation of a PING frame.
type PingFrame struct {
CFHeader ControlFrameHeader
Id uint32
}
func (frame *PingFrame) write(f *Framer) (err os.Error) {
frame.CFHeader.version = Version
frame.CFHeader.frameType = TypePing
frame.CFHeader.length = 4
// Serialize frame to Writer
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
return
}
// Flags
nn, err = w.Write([]byte{byte(f.Flags)})
n += int64(nn)
if err != nil {
if err = binary.Write(f.w, binary.BigEndian, frame.Id); 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.
func (frame *PingFrame) read(h ControlFrameHeader, f *Framer) os.Error {
frame.CFHeader = h
if err := binary.Read(f.r, binary.BigEndian, &frame.Id); err != nil {
return err
}
return nil
}
// GoAwayFrame is the unpacked, in-memory representation of a GOAWAY frame.
type GoAwayFrame struct {
CFHeader ControlFrameHeader
LastGoodStreamId uint32
}
func (frame *GoAwayFrame) write(f *Framer) (err os.Error) {
frame.CFHeader.version = Version
frame.CFHeader.frameType = TypeGoAway
frame.CFHeader.length = 4
// Serialize frame to Writer
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
return
}
if err = binary.Write(f.w, binary.BigEndian, frame.LastGoodStreamId); err != nil {
return
}
return nil
}
func (frame *GoAwayFrame) read(h ControlFrameHeader, f *Framer) os.Error {
frame.CFHeader = h
if err := binary.Read(f.r, binary.BigEndian, &frame.LastGoodStreamId); err != nil {
return err
}
return nil
}
// HeadersFrame is the unpacked, in-memory representation of a HEADERS frame.
type HeadersFrame struct {
CFHeader ControlFrameHeader
StreamId uint32
Headers http.Header
}
func (frame *HeadersFrame) write(f *Framer) os.Error {
return f.writeHeadersFrame(frame)
}
func (frame *HeadersFrame) read(h ControlFrameHeader, f *Framer) os.Error {
return f.readHeadersFrame(h, frame)
}
func newControlFrame(frameType ControlFrameType) (controlFrame, os.Error) {
ctor, ok := cframeCtor[frameType]
if !ok {
return nil, InvalidControlFrame
}
return ctor(), nil
}
var cframeCtor = map[ControlFrameType]func() controlFrame{
TypeSynStream: func() controlFrame { return new(SynStreamFrame) },
TypeSynReply: func() controlFrame { return new(SynReplyFrame) },
TypeRstStream: func() controlFrame { return new(RstStreamFrame) },
TypeSettings: func() controlFrame { return new(SettingsFrame) },
TypeNoop: func() controlFrame { return new(NoopFrame) },
TypePing: func() controlFrame { return new(PingFrame) },
TypeGoAway: func() controlFrame { return new(GoAwayFrame) },
TypeHeaders: func() controlFrame { return new(HeadersFrame) },
// TODO(willchan): Add TypeWindowUpdate
}
// DataFrame is the unpacked, in-memory representation of a DATA frame.
type DataFrame struct {
// Note, high bit is the "Control" bit. Should be 0 for data frames.
StreamId uint32
Flags DataFlags
Data []byte
}
func (frame *DataFrame) write(f *Framer) os.Error {
return f.writeDataFrame(frame)
}
// 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" +
const HeaderDictionary = "optionsgetheadpostputdeletetrace" +
"acceptaccept-charsetaccept-encodingaccept-languageauthorizationexpectfromhost" +
"if-modified-sinceif-matchif-none-matchif-rangeif-unmodifiedsince" +
"max-forwardsproxy-authorizationrangerefererteuser-agent" +
@ -222,146 +482,3 @@ const headerDictionary = "optionsgetheadpostputdeletetrace" +
"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()
}

View File

@ -1,259 +0,0 @@
// 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))
}
}
}
}
}