]> Cypherpunks repositories - gostls13.git/commitdiff
http/spdy: new package
authorRoss Light <rlight2@gmail.com>
Thu, 28 Apr 2011 20:11:37 +0000 (13:11 -0700)
committerBrad Fitzpatrick <bradfitz@golang.org>
Thu, 28 Apr 2011 20:11:37 +0000 (13:11 -0700)
R=bradfitz, agl1, rsc
CC=golang-dev
https://golang.org/cl/4435055

src/pkg/Makefile
src/pkg/http/spdy/Makefile [new file with mode: 0644]
src/pkg/http/spdy/protocol.go [new file with mode: 0644]
src/pkg/http/spdy/protocol_test.go [new file with mode: 0644]

index b046064a6fa534f3116a6f366f36eb7a7da9bf50..df84fc890ace46056bea454a18b42fc85924f95e 100644 (file)
@@ -103,6 +103,7 @@ DIRS=\
        http/fcgi\
        http/pprof\
        http/httptest\
+       http/spdy\
        image\
        image/jpeg\
        image/png\
diff --git a/src/pkg/http/spdy/Makefile b/src/pkg/http/spdy/Makefile
new file mode 100644 (file)
index 0000000..ff70d04
--- /dev/null
@@ -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
diff --git a/src/pkg/http/spdy/protocol.go b/src/pkg/http/spdy/protocol.go
new file mode 100644 (file)
index 0000000..09519d6
--- /dev/null
@@ -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()
+}
diff --git a/src/pkg/http/spdy/protocol_test.go b/src/pkg/http/spdy/protocol_test.go
new file mode 100644 (file)
index 0000000..998ff99
--- /dev/null
@@ -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))
+                               }
+                       }
+               }
+       }
+}