| // 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() |
| } |