diff options
Diffstat (limited to 'libgo/go/http/spdy/protocol.go')
-rw-r--r-- | libgo/go/http/spdy/protocol.go | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/libgo/go/http/spdy/protocol.go b/libgo/go/http/spdy/protocol.go new file mode 100644 index 00000000000..d584ea232ea --- /dev/null +++ b/libgo/go/http/spdy/protocol.go @@ -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 FrameFlags = 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() +} |