Browse Source

go.net: initial code

Manual edits to README.
Moved from main Go repository, deleted Makefiles, ran gofix -r go1rename.

Tested with: go test code.google.com/p/go.net/...

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5574065
Russ Cox 14 years ago
parent
commit
3294cb5d6c
14 changed files with 4629 additions and 15 deletions
  1. 2 15
      README
  2. 210 0
      dict/dict.go
  3. 312 0
      spdy/read.go
  4. 497 0
      spdy/spdy_test.go
  5. 369 0
      spdy/types.go
  6. 285 0
      spdy/write.go
  7. 137 0
      websocket/client.go
  8. 695 0
      websocket/hixie.go
  9. 201 0
      websocket/hixie_test.go
  10. 549 0
      websocket/hybi.go
  11. 584 0
      websocket/hybi_test.go
  12. 102 0
      websocket/server.go
  13. 412 0
      websocket/websocket.go
  14. 274 0
      websocket/websocket_test.go

+ 2 - 15
README

@@ -1,16 +1,3 @@
-This is an empty Go subrepository.  To create a new subrepository,
-visit http://code.google.com/p/go/adminSource and under repositories,
-type the new name, check [x] Clone contents and select [empty] as
-the one you want to clone.
+This repository holds supplementary Go networking libraries.
 
-Then execute:
-	go get code.google.com/p/go.newrepo
-	cd $(go list -e -f '{{.Dir}}' code.google.com/p/go.newrepo)
-
-The go get will complain about not finding source code, but it will
-successfully check out the repository.
-
-Edit the README (this file) to describe the new subrepository, and then
-use the usual hg change, mail, submit to send in the change.
-You will need to follow http://golang.org/doc/contribute.html#Code_review
-to enable the Code Review extension (pointing into your Go root).
+To submit changes to this repository, see http://golang.org/doc/contribute.html.

+ 210 - 0
dict/dict.go

@@ -0,0 +1,210 @@
+// Copyright 2010 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 dict implements the Dictionary Server Protocol
+// as defined in RFC 2229.
+package dict
+
+import (
+	"net/textproto"
+	"strconv"
+	"strings"
+)
+
+// A Client represents a client connection to a dictionary server.
+type Client struct {
+	text *textproto.Conn
+}
+
+// Dial returns a new client connected to a dictionary server at
+// addr on the given network.
+func Dial(network, addr string) (*Client, error) {
+	text, err := textproto.Dial(network, addr)
+	if err != nil {
+		return nil, err
+	}
+	_, _, err = text.ReadCodeLine(220)
+	if err != nil {
+		text.Close()
+		return nil, err
+	}
+	return &Client{text: text}, nil
+}
+
+// Close closes the connection to the dictionary server.
+func (c *Client) Close() error {
+	return c.text.Close()
+}
+
+// A Dict represents a dictionary available on the server.
+type Dict struct {
+	Name string // short name of dictionary
+	Desc string // long description
+}
+
+// Dicts returns a list of the dictionaries available on the server.
+func (c *Client) Dicts() ([]Dict, error) {
+	id, err := c.text.Cmd("SHOW DB")
+	if err != nil {
+		return nil, err
+	}
+
+	c.text.StartResponse(id)
+	defer c.text.EndResponse(id)
+
+	_, _, err = c.text.ReadCodeLine(110)
+	if err != nil {
+		return nil, err
+	}
+	lines, err := c.text.ReadDotLines()
+	if err != nil {
+		return nil, err
+	}
+	_, _, err = c.text.ReadCodeLine(250)
+
+	dicts := make([]Dict, len(lines))
+	for i := range dicts {
+		d := &dicts[i]
+		a, _ := fields(lines[i])
+		if len(a) < 2 {
+			return nil, textproto.ProtocolError("invalid dictionary: " + lines[i])
+		}
+		d.Name = a[0]
+		d.Desc = a[1]
+	}
+	return dicts, err
+}
+
+// A Defn represents a definition.
+type Defn struct {
+	Dict Dict   // Dict where definition was found
+	Word string // Word being defined
+	Text []byte // Definition text, typically multiple lines
+}
+
+// Define requests the definition of the given word.
+// The argument dict names the dictionary to use,
+// the Name field of a Dict returned by Dicts.
+//
+// The special dictionary name "*" means to look in all the
+// server's dictionaries.
+// The special dictionary name "!" means to look in all the
+// server's dictionaries in turn, stopping after finding the word
+// in one of them.
+func (c *Client) Define(dict, word string) ([]*Defn, error) {
+	id, err := c.text.Cmd("DEFINE %s %q", dict, word)
+	if err != nil {
+		return nil, err
+	}
+
+	c.text.StartResponse(id)
+	defer c.text.EndResponse(id)
+
+	_, line, err := c.text.ReadCodeLine(150)
+	if err != nil {
+		return nil, err
+	}
+	a, _ := fields(line)
+	if len(a) < 1 {
+		return nil, textproto.ProtocolError("malformed response: " + line)
+	}
+	n, err := strconv.Atoi(a[0])
+	if err != nil {
+		return nil, textproto.ProtocolError("invalid definition count: " + a[0])
+	}
+	def := make([]*Defn, n)
+	for i := 0; i < n; i++ {
+		_, line, err = c.text.ReadCodeLine(151)
+		if err != nil {
+			return nil, err
+		}
+		a, _ := fields(line)
+		if len(a) < 3 {
+			// skip it, to keep protocol in sync
+			i--
+			n--
+			def = def[0:n]
+			continue
+		}
+		d := &Defn{Word: a[0], Dict: Dict{a[1], a[2]}}
+		d.Text, err = c.text.ReadDotBytes()
+		if err != nil {
+			return nil, err
+		}
+		def[i] = d
+	}
+	_, _, err = c.text.ReadCodeLine(250)
+	return def, err
+}
+
+// Fields returns the fields in s.
+// Fields are space separated unquoted words
+// or quoted with single or double quote.
+func fields(s string) ([]string, error) {
+	var v []string
+	i := 0
+	for {
+		for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
+			i++
+		}
+		if i >= len(s) {
+			break
+		}
+		if s[i] == '"' || s[i] == '\'' {
+			q := s[i]
+			// quoted string
+			var j int
+			for j = i + 1; ; j++ {
+				if j >= len(s) {
+					return nil, textproto.ProtocolError("malformed quoted string")
+				}
+				if s[j] == '\\' {
+					j++
+					continue
+				}
+				if s[j] == q {
+					j++
+					break
+				}
+			}
+			v = append(v, unquote(s[i+1:j-1]))
+			i = j
+		} else {
+			// atom
+			var j int
+			for j = i; j < len(s); j++ {
+				if s[j] == ' ' || s[j] == '\t' || s[j] == '\\' || s[j] == '"' || s[j] == '\'' {
+					break
+				}
+			}
+			v = append(v, s[i:j])
+			i = j
+		}
+		if i < len(s) {
+			c := s[i]
+			if c != ' ' && c != '\t' {
+				return nil, textproto.ProtocolError("quotes not on word boundaries")
+			}
+		}
+	}
+	return v, nil
+}
+
+func unquote(s string) string {
+	if strings.Index(s, "\\") < 0 {
+		return s
+	}
+	b := []byte(s)
+	w := 0
+	for r := 0; r < len(b); r++ {
+		c := b[r]
+		if c == '\\' {
+			r++
+			c = b[r]
+		}
+		b[w] = c
+		w++
+	}
+	return string(b[0:w])
+}

+ 312 - 0
spdy/read.go

@@ -0,0 +1,312 @@
+// 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 (
+	"compress/zlib"
+	"encoding/binary"
+	"io"
+	"net/http"
+	"strings"
+)
+
+func (frame *SynStreamFrame) read(h ControlFrameHeader, f *Framer) error {
+	return f.readSynStreamFrame(h, frame)
+}
+
+func (frame *SynReplyFrame) read(h ControlFrameHeader, f *Framer) error {
+	return f.readSynReplyFrame(h, frame)
+}
+
+func (frame *RstStreamFrame) read(h ControlFrameHeader, f *Framer) 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
+}
+
+func (frame *SettingsFrame) read(h ControlFrameHeader, f *Framer) 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
+}
+
+func (frame *NoopFrame) read(h ControlFrameHeader, f *Framer) error {
+	frame.CFHeader = h
+	return nil
+}
+
+func (frame *PingFrame) read(h ControlFrameHeader, f *Framer) error {
+	frame.CFHeader = h
+	if err := binary.Read(f.r, binary.BigEndian, &frame.Id); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (frame *GoAwayFrame) read(h ControlFrameHeader, f *Framer) error {
+	frame.CFHeader = h
+	if err := binary.Read(f.r, binary.BigEndian, &frame.LastGoodStreamId); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (frame *HeadersFrame) read(h ControlFrameHeader, f *Framer) error {
+	return f.readHeadersFrame(h, frame)
+}
+
+func newControlFrame(frameType ControlFrameType) (controlFrame, error) {
+	ctor, ok := cframeCtor[frameType]
+	if !ok {
+		return nil, &Error{Err: 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
+}
+
+func (f *Framer) uncorkHeaderDecompressor(payloadSize int64) error {
+	if f.headerDecompressor != nil {
+		f.headerReader.N = payloadSize
+		return nil
+	}
+	f.headerReader = io.LimitedReader{R: f.r, N: payloadSize}
+	decompressor, err := zlib.NewReaderDict(&f.headerReader, []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, 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, 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, streamId uint32) (http.Header, error) {
+	var numHeaders uint16
+	if err := binary.Read(r, binary.BigEndian, &numHeaders); err != nil {
+		return nil, err
+	}
+	var e error
+	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) {
+			e = &Error{UnlowercasedHeaderName, streamId}
+			name = strings.ToLower(name)
+		}
+		if h[name] != nil {
+			e = &Error{DuplicateHeaders, streamId}
+		}
+		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")
+		for _, v := range valueList {
+			h.Add(name, v)
+		}
+	}
+	if e != nil {
+		return h, e
+	}
+	return h, nil
+}
+
+func (f *Framer) readSynStreamFrame(h ControlFrameHeader, frame *SynStreamFrame) error {
+	frame.CFHeader = h
+	var err 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.uncorkHeaderDecompressor(int64(h.length - 10))
+		reader = f.headerDecompressor
+	}
+
+	frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
+	if !f.headerCompressionDisabled && ((err == io.EOF && f.headerReader.N == 0) || f.headerReader.N != 0) {
+		err = &Error{WrongCompressedPayloadSize, 0}
+	}
+	if err != nil {
+		return err
+	}
+	// Remove this condition when we bump Version to 3.
+	if Version >= 3 {
+		for h := range frame.Headers {
+			if invalidReqHeaders[h] {
+				return &Error{InvalidHeaderPresent, frame.StreamId}
+			}
+		}
+	}
+	return nil
+}
+
+func (f *Framer) readSynReplyFrame(h ControlFrameHeader, frame *SynReplyFrame) error {
+	frame.CFHeader = h
+	var err 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.uncorkHeaderDecompressor(int64(h.length - 6))
+		reader = f.headerDecompressor
+	}
+	frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
+	if !f.headerCompressionDisabled && ((err == io.EOF && f.headerReader.N == 0) || f.headerReader.N != 0) {
+		err = &Error{WrongCompressedPayloadSize, 0}
+	}
+	if err != nil {
+		return err
+	}
+	// Remove this condition when we bump Version to 3.
+	if Version >= 3 {
+		for h := range frame.Headers {
+			if invalidRespHeaders[h] {
+				return &Error{InvalidHeaderPresent, frame.StreamId}
+			}
+		}
+	}
+	return nil
+}
+
+func (f *Framer) readHeadersFrame(h ControlFrameHeader, frame *HeadersFrame) error {
+	frame.CFHeader = h
+	var err 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.uncorkHeaderDecompressor(int64(h.length - 6))
+		reader = f.headerDecompressor
+	}
+	frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
+	if !f.headerCompressionDisabled && ((err == io.EOF && f.headerReader.N == 0) || f.headerReader.N != 0) {
+		err = &Error{WrongCompressedPayloadSize, 0}
+	}
+	if err != nil {
+		return err
+	}
+
+	// Remove this condition when we bump Version to 3.
+	if Version >= 3 {
+		var invalidHeaders map[string]bool
+		if frame.StreamId%2 == 0 {
+			invalidHeaders = invalidReqHeaders
+		} else {
+			invalidHeaders = invalidRespHeaders
+		}
+		for h := range frame.Headers {
+			if invalidHeaders[h] {
+				return &Error{InvalidHeaderPresent, frame.StreamId}
+			}
+		}
+	}
+	return nil
+}
+
+func (f *Framer) parseDataFrame(streamId uint32) (*DataFrame, 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)
+	if _, err := io.ReadFull(f.r, frame.Data); err != nil {
+		return nil, err
+	}
+	return &frame, nil
+}

+ 497 - 0
spdy/spdy_test.go

@@ -0,0 +1,497 @@
+// 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"
+	"io"
+	"net/http"
+	"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)
+
+	const bogusStreamId = 1
+	newHeaders, err := parseHeaderValueBlock(&headerValueBlockBuf, bogusStreamId)
+	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.Fatalf("expected HeadersFrame; got %T %v", frame, 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.Fatalf("expected SynStreamFrame; got %T %v", frame, 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)
+	}
+}

+ 369 - 0
spdy/types.go

@@ -0,0 +1,369 @@
+// 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"
+	"io"
+	"net/http"
+)
+
+//  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
+
+// 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
+)
+
+// ControlFlags are the flags that can be set on a control frame.
+type ControlFlags uint8
+
+const (
+	ControlFlagFin ControlFlags = 0x01
+)
+
+// DataFlags are the flags that can be set on a data frame.
+type DataFlags uint8
+
+const (
+	DataFlagFin        DataFlags = 0x01
+	DataFlagCompressed           = 0x02
+)
+
+// MaxDataLength is the maximum number of bytes that can be stored in one frame.
+const MaxDataLength = 1<<24 - 1
+
+// 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) error
+}
+
+// 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
+}
+
+type controlFrame interface {
+	Frame
+	read(h ControlFrameHeader, f *Framer) error
+}
+
+// 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
+}
+
+// SynReplyFrame is the unpacked, in-memory representation of a SYN_REPLY frame.
+type SynReplyFrame struct {
+	CFHeader ControlFrameHeader
+	StreamId uint32
+	Headers  http.Header
+}
+
+// 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
+}
+
+// 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
+}
+
+// NoopFrame is the unpacked, in-memory representation of a NOOP frame.
+type NoopFrame struct {
+	CFHeader ControlFrameHeader
+}
+
+// PingFrame is the unpacked, in-memory representation of a PING frame.
+type PingFrame struct {
+	CFHeader ControlFrameHeader
+	Id       uint32
+}
+
+// GoAwayFrame is the unpacked, in-memory representation of a GOAWAY frame.
+type GoAwayFrame struct {
+	CFHeader         ControlFrameHeader
+	LastGoodStreamId uint32
+}
+
+// HeadersFrame is the unpacked, in-memory representation of a HEADERS frame.
+type HeadersFrame struct {
+	CFHeader ControlFrameHeader
+	StreamId uint32
+	Headers  http.Header
+}
+
+// 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
+}
+
+// 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"
+
+// A SPDY specific error.
+type ErrorCode string
+
+const (
+	UnlowercasedHeaderName     ErrorCode = "header was not lowercased"
+	DuplicateHeaders           ErrorCode = "multiple headers with same name"
+	WrongCompressedPayloadSize ErrorCode = "compressed payload size was incorrect"
+	UnknownFrameType           ErrorCode = "unknown frame type"
+	InvalidControlFrame        ErrorCode = "invalid control frame"
+	InvalidDataFrame           ErrorCode = "invalid data frame"
+	InvalidHeaderPresent       ErrorCode = "frame contained invalid header"
+)
+
+// Error contains both the type of error and additional values. StreamId is 0
+// if Error is not associated with a stream.
+type Error struct {
+	Err      ErrorCode
+	StreamId uint32
+}
+
+func (e *Error) Error() string {
+	return string(e.Err)
+}
+
+var invalidReqHeaders = map[string]bool{
+	"Connection":        true,
+	"Keep-Alive":        true,
+	"Proxy-Connection":  true,
+	"Transfer-Encoding": true,
+}
+
+var invalidRespHeaders = map[string]bool{
+	"Connection":        true,
+	"Keep-Alive":        true,
+	"Transfer-Encoding": true,
+}
+
+// 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
+	headerReader              io.LimitedReader
+	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, 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
+}

+ 285 - 0
spdy/write.go

@@ -0,0 +1,285 @@
+// 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 (
+	"encoding/binary"
+	"io"
+	"net/http"
+	"strings"
+)
+
+func (frame *SynStreamFrame) write(f *Framer) error {
+	return f.writeSynStreamFrame(frame)
+}
+
+func (frame *SynReplyFrame) write(f *Framer) error {
+	return f.writeSynReplyFrame(frame)
+}
+
+func (frame *RstStreamFrame) write(f *Framer) (err 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
+	}
+	if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
+		return
+	}
+	if err = binary.Write(f.w, binary.BigEndian, frame.Status); err != nil {
+		return
+	}
+	return
+}
+
+func (frame *SettingsFrame) write(f *Framer) (err 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
+		}
+		if err = binary.Write(f.w, binary.BigEndian, flagIdValue.Value); err != nil {
+			return
+		}
+	}
+	return
+}
+
+func (frame *NoopFrame) write(f *Framer) error {
+	frame.CFHeader.version = Version
+	frame.CFHeader.frameType = TypeNoop
+
+	// Serialize frame to Writer
+	return writeControlFrameHeader(f.w, frame.CFHeader)
+}
+
+func (frame *PingFrame) write(f *Framer) (err 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
+	}
+	if err = binary.Write(f.w, binary.BigEndian, frame.Id); err != nil {
+		return
+	}
+	return
+}
+
+func (frame *GoAwayFrame) write(f *Framer) (err 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 *HeadersFrame) write(f *Framer) error {
+	return f.writeHeadersFrame(frame)
+}
+
+func (frame *DataFrame) write(f *Framer) error {
+	return f.writeDataFrame(frame)
+}
+
+// WriteFrame writes a frame.
+func (f *Framer) WriteFrame(frame Frame) error {
+	return frame.write(f)
+}
+
+func writeControlFrameHeader(w io.Writer, h ControlFrameHeader) 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 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 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 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 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 error) {
+	// Validate DataFrame
+	if frame.StreamId&0x80000000 != 0 || len(frame.Data) >= 0x0f000000 {
+		return &Error{InvalidDataFrame, frame.StreamId}
+	}
+
+	// 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
+}

+ 137 - 0
websocket/client.go

@@ -0,0 +1,137 @@
+// Copyright 2009 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 websocket
+
+import (
+	"bufio"
+	"crypto/tls"
+	"io"
+	"net"
+	"net/url"
+)
+
+// DialError is an error that occurs while dialling a websocket server.
+type DialError struct {
+	*Config
+	Err error
+}
+
+func (e *DialError) Error() string {
+	return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error()
+}
+
+// NewConfig creates a new WebSocket config for client connection.
+func NewConfig(server, origin string) (config *Config, err error) {
+	config = new(Config)
+	config.Version = ProtocolVersionHybi13
+	config.Location, err = url.ParseRequest(server)
+	if err != nil {
+		return
+	}
+	config.Origin, err = url.ParseRequest(origin)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// NewClient creates a new WebSocket client connection over rwc.
+func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) {
+	br := bufio.NewReader(rwc)
+	bw := bufio.NewWriter(rwc)
+	switch config.Version {
+	case ProtocolVersionHixie75:
+		err = hixie75ClientHandshake(config, br, bw)
+	case ProtocolVersionHixie76, ProtocolVersionHybi00:
+		err = hixie76ClientHandshake(config, br, bw)
+	case ProtocolVersionHybi08, ProtocolVersionHybi13:
+		err = hybiClientHandshake(config, br, bw)
+	default:
+		err = ErrBadProtocolVersion
+	}
+	if err != nil {
+		return
+	}
+	buf := bufio.NewReadWriter(br, bw)
+	switch config.Version {
+	case ProtocolVersionHixie75, ProtocolVersionHixie76, ProtocolVersionHybi00:
+		ws = newHixieClientConn(config, buf, rwc)
+	case ProtocolVersionHybi08, ProtocolVersionHybi13:
+		ws = newHybiClientConn(config, buf, rwc)
+	}
+	return
+}
+
+/*
+Dial opens a new client connection to a WebSocket.
+
+A trivial example client:
+
+	package main
+
+	import (
+		"log"
+		"net/http"
+		"strings"
+		"websocket"
+	)
+
+	func main() {
+		origin := "http://localhost/"
+		url := "ws://localhost/ws" 
+		ws, err := websocket.Dial(url, "", origin)
+		if err != nil {
+			log.Fatal(err)
+		}
+		if _, err := ws.Write([]byte("hello, world!\n")); err != nil {
+			log.Fatal(err)
+		}
+		var msg = make([]byte, 512);
+		if n, err := ws.Read(msg); err != nil {
+			log.Fatal(err)
+		}
+		// use msg[0:n]
+	}
+*/
+func Dial(url_, protocol, origin string) (ws *Conn, err error) {
+	config, err := NewConfig(url_, origin)
+	if err != nil {
+		return nil, err
+	}
+	return DialConfig(config)
+}
+
+// DialConfig opens a new client connection to a WebSocket with a config.
+func DialConfig(config *Config) (ws *Conn, err error) {
+	var client net.Conn
+	if config.Location == nil {
+		return nil, &DialError{config, ErrBadWebSocketLocation}
+	}
+	if config.Origin == nil {
+		return nil, &DialError{config, ErrBadWebSocketOrigin}
+	}
+	switch config.Location.Scheme {
+	case "ws":
+		client, err = net.Dial("tcp", config.Location.Host)
+
+	case "wss":
+		client, err = tls.Dial("tcp", config.Location.Host, config.TlsConfig)
+
+	default:
+		err = ErrBadScheme
+	}
+	if err != nil {
+		goto Error
+	}
+
+	ws, err = NewClient(config, client)
+	if err != nil {
+		goto Error
+	}
+	return
+
+Error:
+	return nil, &DialError{config, err}
+}

+ 695 - 0
websocket/hixie.go

@@ -0,0 +1,695 @@
+// Copyright 2009 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 websocket
+
+// This file implements a protocol of Hixie draft version 75 and 76
+// (draft 76 equals to hybi 00)
+
+import (
+	"bufio"
+	"bytes"
+	"crypto/md5"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"math/rand"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+)
+
+// An aray of characters to be randomly inserted to construct Sec-WebSocket-Key
+// value. It holds characters from ranges U+0021 to U+002F and U+003A to U+007E.
+// See Step 21 in Section 4.1 Opening handshake.
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00#page-22
+var secKeyRandomChars [0x30 - 0x21 + 0x7F - 0x3A]byte
+
+func init() {
+	i := 0
+	for ch := byte(0x21); ch < 0x30; ch++ {
+		secKeyRandomChars[i] = ch
+		i++
+	}
+	for ch := byte(0x3a); ch < 0x7F; ch++ {
+		secKeyRandomChars[i] = ch
+		i++
+	}
+}
+
+type byteReader interface {
+	ReadByte() (byte, error)
+}
+
+// readHixieLength reads frame length for frame type 0x80-0xFF
+// as defined in Hixie draft.
+// See section 4.2 Data framing.
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00#section-4.2
+func readHixieLength(r byteReader) (length int64, lengthFields []byte, err error) {
+	for {
+		c, err := r.ReadByte()
+		if err != nil {
+			return 0, nil, err
+		}
+		lengthFields = append(lengthFields, c)
+		length = length*128 + int64(c&0x7f)
+		if c&0x80 == 0 {
+			break
+		}
+	}
+	return
+}
+
+// A hixieLengthFrameReader is a reader for frame type 0x80-0xFF
+// as defined in hixie draft.
+type hixieLengthFrameReader struct {
+	reader    io.Reader
+	FrameType byte
+	Length    int64
+	header    *bytes.Buffer
+	length    int
+}
+
+func (frame *hixieLengthFrameReader) Read(msg []byte) (n int, err error) {
+	return frame.reader.Read(msg)
+}
+
+func (frame *hixieLengthFrameReader) PayloadType() byte {
+	if frame.FrameType == '\xff' && frame.Length == 0 {
+		return CloseFrame
+	}
+	return UnknownFrame
+}
+
+func (frame *hixieLengthFrameReader) HeaderReader() io.Reader {
+	if frame.header == nil {
+		return nil
+	}
+	if frame.header.Len() == 0 {
+		frame.header = nil
+		return nil
+	}
+	return frame.header
+}
+
+func (frame *hixieLengthFrameReader) TrailerReader() io.Reader { return nil }
+
+func (frame *hixieLengthFrameReader) Len() (n int) { return frame.length }
+
+// A HixieSentinelFrameReader is a reader for frame type 0x00-0x7F
+// as defined in hixie draft.
+type hixieSentinelFrameReader struct {
+	reader      *bufio.Reader
+	FrameType   byte
+	header      *bytes.Buffer
+	data        []byte
+	seenTrailer bool
+	trailer     *bytes.Buffer
+}
+
+func (frame *hixieSentinelFrameReader) Read(msg []byte) (n int, err error) {
+	if len(frame.data) == 0 {
+		if frame.seenTrailer {
+			return 0, io.EOF
+		}
+		frame.data, err = frame.reader.ReadSlice('\xff')
+		if err == nil {
+			frame.seenTrailer = true
+			frame.data = frame.data[:len(frame.data)-1] // trim \xff
+			frame.trailer = bytes.NewBuffer([]byte{0xff})
+		}
+	}
+	n = copy(msg, frame.data)
+	frame.data = frame.data[n:]
+	return n, err
+}
+
+func (frame *hixieSentinelFrameReader) PayloadType() byte {
+	if frame.FrameType == 0 {
+		return TextFrame
+	}
+	return UnknownFrame
+}
+
+func (frame *hixieSentinelFrameReader) HeaderReader() io.Reader {
+	if frame.header == nil {
+		return nil
+	}
+	if frame.header.Len() == 0 {
+		frame.header = nil
+		return nil
+	}
+	return frame.header
+}
+
+func (frame *hixieSentinelFrameReader) TrailerReader() io.Reader {
+	if frame.trailer == nil {
+		return nil
+	}
+	if frame.trailer.Len() == 0 {
+		frame.trailer = nil
+		return nil
+	}
+	return frame.trailer
+}
+
+func (frame *hixieSentinelFrameReader) Len() int { return -1 }
+
+// A HixieFrameReaderFactory creates new frame reader based on its frame type.
+type hixieFrameReaderFactory struct {
+	*bufio.Reader
+}
+
+func (buf hixieFrameReaderFactory) NewFrameReader() (r frameReader, err error) {
+	var header []byte
+	var b byte
+	b, err = buf.ReadByte()
+	if err != nil {
+		return
+	}
+	header = append(header, b)
+	if b&0x80 == 0x80 {
+		length, lengthFields, err := readHixieLength(buf.Reader)
+		if err != nil {
+			return nil, err
+		}
+		if length == 0 {
+			return nil, io.EOF
+		}
+		header = append(header, lengthFields...)
+		return &hixieLengthFrameReader{
+			reader:    io.LimitReader(buf.Reader, length),
+			FrameType: b,
+			Length:    length,
+			header:    bytes.NewBuffer(header)}, err
+	}
+	return &hixieSentinelFrameReader{
+		reader:    buf.Reader,
+		FrameType: b,
+		header:    bytes.NewBuffer(header)}, err
+}
+
+type hixiFrameWriter struct {
+	writer *bufio.Writer
+}
+
+func (frame *hixiFrameWriter) Write(msg []byte) (n int, err error) {
+	frame.writer.WriteByte(0)
+	frame.writer.Write(msg)
+	frame.writer.WriteByte(0xff)
+	err = frame.writer.Flush()
+	return len(msg), err
+}
+
+func (frame *hixiFrameWriter) Close() error { return nil }
+
+type hixiFrameWriterFactory struct {
+	*bufio.Writer
+}
+
+func (buf hixiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) {
+	if payloadType != TextFrame {
+		return nil, ErrNotSupported
+	}
+	return &hixiFrameWriter{writer: buf.Writer}, nil
+}
+
+type hixiFrameHandler struct {
+	conn *Conn
+}
+
+func (handler *hixiFrameHandler) HandleFrame(frame frameReader) (r frameReader, err error) {
+	if header := frame.HeaderReader(); header != nil {
+		io.Copy(ioutil.Discard, header)
+	}
+	if frame.PayloadType() != TextFrame {
+		io.Copy(ioutil.Discard, frame)
+		return nil, nil
+	}
+	return frame, nil
+}
+
+func (handler *hixiFrameHandler) WriteClose(_ int) (err error) {
+	handler.conn.wio.Lock()
+	defer handler.conn.wio.Unlock()
+	closingFrame := []byte{'\xff', '\x00'}
+	handler.conn.buf.Write(closingFrame)
+	return handler.conn.buf.Flush()
+}
+
+// newHixiConn creates a new WebSocket connection speaking hixie draft protocol.
+func newHixieConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
+	if buf == nil {
+		br := bufio.NewReader(rwc)
+		bw := bufio.NewWriter(rwc)
+		buf = bufio.NewReadWriter(br, bw)
+	}
+	ws := &Conn{config: config, request: request, buf: buf, rwc: rwc,
+		frameReaderFactory: hixieFrameReaderFactory{buf.Reader},
+		frameWriterFactory: hixiFrameWriterFactory{buf.Writer},
+		PayloadType:        TextFrame}
+	ws.frameHandler = &hixiFrameHandler{ws}
+	return ws
+}
+
+// getChallengeResponse computes the expected response from the
+// challenge as described in section 5.1 Opening Handshake steps 42 to
+// 43 of http://www.whatwg.org/specs/web-socket-protocol/
+func getChallengeResponse(number1, number2 uint32, key3 []byte) (expected []byte, err error) {
+	// 41. Let /challenge/ be the concatenation of /number_1/, expressed
+	// a big-endian 32 bit integer, /number_2/, expressed in a big-
+	// endian 32 bit integer, and the eight bytes of /key_3/ in the
+	// order they were sent to the wire.
+	challenge := make([]byte, 16)
+	binary.BigEndian.PutUint32(challenge[0:], number1)
+	binary.BigEndian.PutUint32(challenge[4:], number2)
+	copy(challenge[8:], key3)
+
+	// 42. Let /expected/ be the MD5 fingerprint of /challenge/ as a big-
+	// endian 128 bit string.
+	h := md5.New()
+	if _, err = h.Write(challenge); err != nil {
+		return
+	}
+	expected = h.Sum(nil)
+	return
+}
+
+// Generates handshake key as described in 4.1 Opening handshake step 16 to 22.
+// cf. http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
+func generateKeyNumber() (key string, number uint32) {
+	// 16.  Let /spaces_n/ be a random integer from 1 to 12 inclusive.
+	spaces := rand.Intn(12) + 1
+
+	// 17. Let /max_n/ be the largest integer not greater than
+	//     4,294,967,295 divided by /spaces_n/
+	max := int(4294967295 / uint32(spaces))
+
+	// 18. Let /number_n/ be a random integer from 0 to /max_n/ inclusive.
+	number = uint32(rand.Intn(max + 1))
+
+	// 19. Let /product_n/ be the result of multiplying /number_n/ and
+	//     /spaces_n/ together.
+	product := number * uint32(spaces)
+
+	// 20. Let /key_n/ be a string consisting of /product_n/, expressed
+	// in base ten using the numerals in the range U+0030 DIGIT ZERO (0)
+	// to U+0039 DIGIT NINE (9).
+	key = fmt.Sprintf("%d", product)
+
+	// 21. Insert between one and twelve random characters from the ranges
+	//     U+0021 to U+002F and U+003A to U+007E into /key_n/ at random
+	//     positions.
+	n := rand.Intn(12) + 1
+	for i := 0; i < n; i++ {
+		pos := rand.Intn(len(key)) + 1
+		ch := secKeyRandomChars[rand.Intn(len(secKeyRandomChars))]
+		key = key[0:pos] + string(ch) + key[pos:]
+	}
+
+	// 22. Insert /spaces_n/ U+0020 SPACE characters into /key_n/ at random
+	//     positions other than the start or end of the string.
+	for i := 0; i < spaces; i++ {
+		pos := rand.Intn(len(key)-1) + 1
+		key = key[0:pos] + " " + key[pos:]
+	}
+
+	return
+}
+
+// Generates handshake key_3 as described in 4.1 Opening handshake step 26.
+// cf. http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
+func generateKey3() (key []byte) {
+	// 26. Let /key3/ be a string consisting of eight random bytes (or
+	//  equivalently, a random 64 bit integer encoded in big-endian order).
+	key = make([]byte, 8)
+	for i := 0; i < 8; i++ {
+		key[i] = byte(rand.Intn(256))
+	}
+	return
+}
+
+// Cilent handhake described in (soon obsolete)
+// draft-ietf-hybi-thewebsocket-protocol-00
+// (draft-hixie-thewebsocket-protocol-76) 
+func hixie76ClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
+	switch config.Version {
+	case ProtocolVersionHixie76, ProtocolVersionHybi00:
+	default:
+		panic("wrong protocol version.")
+	}
+	// 4.1. Opening handshake.
+	// Step 5.  send a request line.
+	bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
+
+	// Step 6-14. push request headers in fields.
+	fields := []string{
+		"Upgrade: WebSocket\r\n",
+		"Connection: Upgrade\r\n",
+		"Host: " + config.Location.Host + "\r\n",
+		"Origin: " + config.Origin.String() + "\r\n",
+	}
+	if len(config.Protocol) > 0 {
+		if len(config.Protocol) != 1 {
+			return ErrBadWebSocketProtocol
+		}
+		fields = append(fields, "Sec-WebSocket-Protocol: "+config.Protocol[0]+"\r\n")
+	}
+	// TODO(ukai): Step 15. send cookie if any.
+
+	// Step 16-23. generate keys and push Sec-WebSocket-Key<n> in fields.
+	key1, number1 := generateKeyNumber()
+	key2, number2 := generateKeyNumber()
+	if config.handshakeData != nil {
+		key1 = config.handshakeData["key1"]
+		n, err := strconv.ParseUint(config.handshakeData["number1"], 10, 32)
+		if err != nil {
+			panic(err)
+		}
+		number1 = uint32(n)
+		key2 = config.handshakeData["key2"]
+		n, err = strconv.ParseUint(config.handshakeData["number2"], 10, 32)
+		if err != nil {
+			panic(err)
+		}
+		number2 = uint32(n)
+	}
+	fields = append(fields, "Sec-WebSocket-Key1: "+key1+"\r\n")
+	fields = append(fields, "Sec-WebSocket-Key2: "+key2+"\r\n")
+
+	// Step 24. shuffle fields and send them out.
+	for i := 1; i < len(fields); i++ {
+		j := rand.Intn(i)
+		fields[i], fields[j] = fields[j], fields[i]
+	}
+	for i := 0; i < len(fields); i++ {
+		bw.WriteString(fields[i])
+	}
+	// Step 25. send CRLF.
+	bw.WriteString("\r\n")
+
+	// Step 26. generate 8 bytes random key.
+	key3 := generateKey3()
+	if config.handshakeData != nil {
+		key3 = []byte(config.handshakeData["key3"])
+	}
+	// Step 27. send it out.
+	bw.Write(key3)
+	if err = bw.Flush(); err != nil {
+		return
+	}
+
+	// Step 28-29, 32-40. read response from server.
+	resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
+	if err != nil {
+		return err
+	}
+	// Step 30. check response code is 101.
+	if resp.StatusCode != 101 {
+		return ErrBadStatus
+	}
+
+	// Step 41. check websocket headers.
+	if resp.Header.Get("Upgrade") != "WebSocket" ||
+		strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
+		return ErrBadUpgrade
+	}
+
+	if resp.Header.Get("Sec-Websocket-Origin") != config.Origin.String() {
+		return ErrBadWebSocketOrigin
+	}
+
+	if resp.Header.Get("Sec-Websocket-Location") != config.Location.String() {
+		return ErrBadWebSocketLocation
+	}
+
+	if len(config.Protocol) > 0 && resp.Header.Get("Sec-Websocket-Protocol") != config.Protocol[0] {
+		return ErrBadWebSocketProtocol
+	}
+
+	// Step 42-43. get expected data from challenge data.
+	expected, err := getChallengeResponse(number1, number2, key3)
+	if err != nil {
+		return err
+	}
+
+	// Step 44. read 16 bytes from server.
+	reply := make([]byte, 16)
+	if _, err = io.ReadFull(br, reply); err != nil {
+		return err
+	}
+
+	// Step 45. check the reply equals to expected data.
+	if !bytes.Equal(expected, reply) {
+		return ErrChallengeResponse
+	}
+	// WebSocket connection is established.
+	return
+}
+
+// Client Handshake described in (soon obsolete)
+// draft-hixie-thewebsocket-protocol-75.
+func hixie75ClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
+	if config.Version != ProtocolVersionHixie75 {
+		panic("wrong protocol version.")
+	}
+	bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
+	bw.WriteString("Upgrade: WebSocket\r\n")
+	bw.WriteString("Connection: Upgrade\r\n")
+	bw.WriteString("Host: " + config.Location.Host + "\r\n")
+	bw.WriteString("Origin: " + config.Origin.String() + "\r\n")
+	if len(config.Protocol) > 0 {
+		if len(config.Protocol) != 1 {
+			return ErrBadWebSocketProtocol
+		}
+		bw.WriteString("WebSocket-Protocol: " + config.Protocol[0] + "\r\n")
+	}
+	bw.WriteString("\r\n")
+	bw.Flush()
+	resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
+	if err != nil {
+		return
+	}
+	if resp.Status != "101 Web Socket Protocol Handshake" {
+		return ErrBadStatus
+	}
+	if resp.Header.Get("Upgrade") != "WebSocket" ||
+		resp.Header.Get("Connection") != "Upgrade" {
+		return ErrBadUpgrade
+	}
+	if resp.Header.Get("Websocket-Origin") != config.Origin.String() {
+		return ErrBadWebSocketOrigin
+	}
+	if resp.Header.Get("Websocket-Location") != config.Location.String() {
+		return ErrBadWebSocketLocation
+	}
+	if len(config.Protocol) > 0 && resp.Header.Get("Websocket-Protocol") != config.Protocol[0] {
+		return ErrBadWebSocketProtocol
+	}
+	return
+}
+
+// newHixieClientConn returns new WebSocket connection speaking hixie draft protocol.
+func newHixieClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
+	return newHixieConn(config, buf, rwc, nil)
+}
+
+// Gets key number from Sec-WebSocket-Key<n>: field as described
+// in 5.2 Sending the server's opening handshake, 4.
+func getKeyNumber(s string) (r uint32) {
+	// 4. Let /key-number_n/ be the digits (characters in the range
+	// U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_1/,
+	// interpreted as a base ten integer, ignoring all other characters
+	// in /key_n/.
+	r = 0
+	for i := 0; i < len(s); i++ {
+		if s[i] >= '0' && s[i] <= '9' {
+			r = r*10 + uint32(s[i]) - '0'
+		}
+	}
+	return
+}
+
+// A Hixie76ServerHandshaker performs a server handshake using
+// hixie draft 76 protocol.
+type hixie76ServerHandshaker struct {
+	*Config
+	challengeResponse []byte
+}
+
+func (c *hixie76ServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
+	c.Version = ProtocolVersionHybi00
+	if req.Method != "GET" {
+		return http.StatusMethodNotAllowed, ErrBadRequestMethod
+	}
+	// HTTP version can be safely ignored.
+
+	if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
+		strings.ToLower(req.Header.Get("Connection")) != "upgrade" {
+		return http.StatusBadRequest, ErrNotWebSocket
+	}
+
+	// TODO(ukai): check Host
+	c.Origin, err = url.ParseRequest(req.Header.Get("Origin"))
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+
+	key1 := req.Header.Get("Sec-Websocket-Key1")
+	if key1 == "" {
+		return http.StatusBadRequest, ErrChallengeResponse
+	}
+	key2 := req.Header.Get("Sec-Websocket-Key2")
+	if key2 == "" {
+		return http.StatusBadRequest, ErrChallengeResponse
+	}
+	key3 := make([]byte, 8)
+	if _, err := io.ReadFull(buf, key3); err != nil {
+		return http.StatusBadRequest, ErrChallengeResponse
+	}
+
+	var scheme string
+	if req.TLS != nil {
+		scheme = "wss"
+	} else {
+		scheme = "ws"
+	}
+	c.Location, err = url.ParseRequest(scheme + "://" + req.Host + req.URL.RequestURI())
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+
+	// Step 4. get key number in Sec-WebSocket-Key<n> fields.
+	keyNumber1 := getKeyNumber(key1)
+	keyNumber2 := getKeyNumber(key2)
+
+	// Step 5. get number of spaces in Sec-WebSocket-Key<n> fields.
+	space1 := uint32(strings.Count(key1, " "))
+	space2 := uint32(strings.Count(key2, " "))
+	if space1 == 0 || space2 == 0 {
+		return http.StatusBadRequest, ErrChallengeResponse
+	}
+
+	// Step 6. key number must be an integral multiple of spaces.
+	if keyNumber1%space1 != 0 || keyNumber2%space2 != 0 {
+		return http.StatusBadRequest, ErrChallengeResponse
+	}
+
+	// Step 7. let part be key number divided by spaces.
+	part1 := keyNumber1 / space1
+	part2 := keyNumber2 / space2
+
+	// Step 8. let challenge be concatenation of part1, part2 and key3.
+	// Step 9. get MD5 fingerprint of challenge.
+	c.challengeResponse, err = getChallengeResponse(part1, part2, key3)
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+	protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
+	protocols := strings.Split(protocol, ",")
+	for i := 0; i < len(protocols); i++ {
+		c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
+	}
+
+	return http.StatusSwitchingProtocols, nil
+}
+
+func (c *hixie76ServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
+	if len(c.Protocol) > 0 {
+		if len(c.Protocol) != 1 {
+			return ErrBadWebSocketProtocol
+		}
+	}
+
+	// Step 10. send response status line.
+	buf.WriteString("HTTP/1.1 101 WebSocket Protocol Handshake\r\n")
+	// Step 11. send response headers.
+	buf.WriteString("Upgrade: WebSocket\r\n")
+	buf.WriteString("Connection: Upgrade\r\n")
+	buf.WriteString("Sec-WebSocket-Origin: " + c.Origin.String() + "\r\n")
+	buf.WriteString("Sec-WebSocket-Location: " + c.Location.String() + "\r\n")
+	if len(c.Protocol) > 0 {
+		buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
+	}
+	// Step 12. send CRLF.
+	buf.WriteString("\r\n")
+	// Step 13. send response data.
+	buf.Write(c.challengeResponse)
+	return buf.Flush()
+}
+
+func (c *hixie76ServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn) {
+	return newHixieServerConn(c.Config, buf, rwc, request)
+}
+
+// A hixie75ServerHandshaker performs a server handshake using
+// hixie draft 75 protocol.
+type hixie75ServerHandshaker struct {
+	*Config
+}
+
+func (c *hixie75ServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
+	c.Version = ProtocolVersionHixie75
+	if req.Method != "GET" || req.Proto != "HTTP/1.1" {
+		return http.StatusMethodNotAllowed, ErrBadRequestMethod
+	}
+	if req.Header.Get("Upgrade") != "WebSocket" {
+		return http.StatusBadRequest, ErrNotWebSocket
+	}
+	if req.Header.Get("Connection") != "Upgrade" {
+		return http.StatusBadRequest, ErrNotWebSocket
+	}
+	c.Origin, err = url.ParseRequest(strings.TrimSpace(req.Header.Get("Origin")))
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+
+	var scheme string
+	if req.TLS != nil {
+		scheme = "wss"
+	} else {
+		scheme = "ws"
+	}
+	c.Location, err = url.ParseRequest(scheme + "://" + req.Host + req.URL.RequestURI())
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+	protocol := strings.TrimSpace(req.Header.Get("Websocket-Protocol"))
+	protocols := strings.Split(protocol, ",")
+	for i := 0; i < len(protocols); i++ {
+		c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
+	}
+
+	return http.StatusSwitchingProtocols, nil
+}
+
+func (c *hixie75ServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
+	if len(c.Protocol) > 0 {
+		if len(c.Protocol) != 1 {
+			return ErrBadWebSocketProtocol
+		}
+	}
+
+	buf.WriteString("HTTP/1.1 101 Web Socket Protocol Handshake\r\n")
+	buf.WriteString("Upgrade: WebSocket\r\n")
+	buf.WriteString("Connection: Upgrade\r\n")
+	buf.WriteString("WebSocket-Origin: " + c.Origin.String() + "\r\n")
+	buf.WriteString("WebSocket-Location: " + c.Location.String() + "\r\n")
+	if len(c.Protocol) > 0 {
+		buf.WriteString("WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
+	}
+	buf.WriteString("\r\n")
+	return buf.Flush()
+}
+
+func (c *hixie75ServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn) {
+	return newHixieServerConn(c.Config, buf, rwc, request)
+}
+
+// newHixieServerConn returns a new WebSocket connection speaking hixie draft protocol.
+func newHixieServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
+	return newHixieConn(config, buf, rwc, request)
+}

+ 201 - 0
websocket/hixie_test.go

@@ -0,0 +1,201 @@
+// 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 websocket
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+)
+
+// Test the getChallengeResponse function with values from section
+// 5.1 of the specification steps 18, 26, and 43 from
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
+func TestHixie76Challenge(t *testing.T) {
+	var part1 uint32 = 777007543
+	var part2 uint32 = 114997259
+	key3 := []byte{0x47, 0x30, 0x22, 0x2D, 0x5A, 0x3F, 0x47, 0x58}
+	expected := []byte("0st3Rl&q-2ZU^weu")
+
+	response, err := getChallengeResponse(part1, part2, key3)
+	if err != nil {
+		t.Errorf("getChallengeResponse: returned error %v", err)
+		return
+	}
+	if !bytes.Equal(expected, response) {
+		t.Errorf("getChallengeResponse: expected %q got %q", expected, response)
+	}
+}
+
+func TestHixie76ClientHandshake(t *testing.T) {
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+	br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 WebSocket Protocol Handshake
+Upgrade: WebSocket
+Connection: Upgrade
+Sec-WebSocket-Origin: http://example.com
+Sec-WebSocket-Location: ws://example.com/demo
+Sec-WebSocket-Protocol: sample
+
+8jKS'y:G*Co,Wxa-`))
+
+	var err error
+	config := new(Config)
+	config.Location, err = url.ParseRequest("ws://example.com/demo")
+	if err != nil {
+		t.Fatal("location url", err)
+	}
+	config.Origin, err = url.ParseRequest("http://example.com")
+	if err != nil {
+		t.Fatal("origin url", err)
+	}
+	config.Protocol = append(config.Protocol, "sample")
+	config.Version = ProtocolVersionHixie76
+
+	config.handshakeData = map[string]string{
+		"key1":    "4 @1  46546xW%0l 1 5",
+		"number1": "829309203",
+		"key2":    "12998 5 Y3 1  .P00",
+		"number2": "259970620",
+		"key3":    "^n:ds[4U",
+	}
+	err = hixie76ClientHandshake(config, br, bw)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	req, err := http.ReadRequest(bufio.NewReader(b))
+	if err != nil {
+		t.Fatalf("read request: %v", err)
+	}
+	if req.Method != "GET" {
+		t.Errorf("request method expected GET, but got %q", req.Method)
+	}
+	if req.URL.Path != "/demo" {
+		t.Errorf("request path expected /demo, but got %q", req.URL.Path)
+	}
+	if req.Proto != "HTTP/1.1" {
+		t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto)
+	}
+	if req.Host != "example.com" {
+		t.Errorf("request Host expected example.com, but got %v", req.Host)
+	}
+	var expectedHeader = map[string]string{
+		"Connection":             "Upgrade",
+		"Upgrade":                "WebSocket",
+		"Origin":                 "http://example.com",
+		"Sec-Websocket-Key1":     config.handshakeData["key1"],
+		"Sec-Websocket-Key2":     config.handshakeData["key2"],
+		"Sec-WebSocket-Protocol": config.Protocol[0],
+	}
+	for k, v := range expectedHeader {
+		if req.Header.Get(k) != v {
+			t.Errorf(fmt.Sprintf("%s expected %q but got %q", k, v, req.Header.Get(k)))
+		}
+	}
+}
+
+func TestHixie76ServerHandshake(t *testing.T) {
+	config := new(Config)
+	handshaker := &hixie76ServerHandshaker{Config: config}
+	br := bufio.NewReader(strings.NewReader(`GET /demo HTTP/1.1
+Host: example.com
+Connection: Upgrade
+Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
+Sec-WebSocket-Protocol: sample
+Upgrade: WebSocket
+Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
+Origin: http://example.com
+
+^n:ds[4U`))
+	req, err := http.ReadRequest(br)
+	if err != nil {
+		t.Fatal("request", err)
+	}
+	code, err := handshaker.ReadHandshake(br, req)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	if code != http.StatusSwitchingProtocols {
+		t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code)
+	}
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+
+	err = handshaker.AcceptHandshake(bw)
+	if err != nil {
+		t.Errorf("handshake response failed: %v", err)
+	}
+	expectedResponse := strings.Join([]string{
+		"HTTP/1.1 101 WebSocket Protocol Handshake",
+		"Upgrade: WebSocket",
+		"Connection: Upgrade",
+		"Sec-WebSocket-Origin: http://example.com",
+		"Sec-WebSocket-Location: ws://example.com/demo",
+		"Sec-WebSocket-Protocol: sample",
+		"", ""}, "\r\n") + "8jKS'y:G*Co,Wxa-"
+	if b.String() != expectedResponse {
+		t.Errorf("handshake expected %q but got %q", expectedResponse, b.String())
+	}
+}
+
+func TestHixie76SkipLengthFrame(t *testing.T) {
+	b := []byte{'\x80', '\x01', 'x', 0, 'h', 'e', 'l', 'l', 'o', '\xff'}
+	buf := bytes.NewBuffer(b)
+	br := bufio.NewReader(buf)
+	bw := bufio.NewWriter(buf)
+	config := newConfig(t, "/")
+	ws := newHixieConn(config, bufio.NewReadWriter(br, bw), nil, nil)
+	msg := make([]byte, 5)
+	n, err := ws.Read(msg)
+	if err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	if !bytes.Equal(b[4:9], msg[0:n]) {
+		t.Errorf("Read: expected %q got %q", b[4:9], msg[0:n])
+	}
+}
+
+func TestHixie76SkipNoUTF8Frame(t *testing.T) {
+	b := []byte{'\x01', 'n', '\xff', 0, 'h', 'e', 'l', 'l', 'o', '\xff'}
+	buf := bytes.NewBuffer(b)
+	br := bufio.NewReader(buf)
+	bw := bufio.NewWriter(buf)
+	config := newConfig(t, "/")
+	ws := newHixieConn(config, bufio.NewReadWriter(br, bw), nil, nil)
+	msg := make([]byte, 5)
+	n, err := ws.Read(msg)
+	if err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	if !bytes.Equal(b[4:9], msg[0:n]) {
+		t.Errorf("Read: expected %q got %q", b[4:9], msg[0:n])
+	}
+}
+
+func TestHixie76ClosingFrame(t *testing.T) {
+	b := []byte{0, 'h', 'e', 'l', 'l', 'o', '\xff'}
+	buf := bytes.NewBuffer(b)
+	br := bufio.NewReader(buf)
+	bw := bufio.NewWriter(buf)
+	config := newConfig(t, "/")
+	ws := newHixieConn(config, bufio.NewReadWriter(br, bw), nil, nil)
+	msg := make([]byte, 5)
+	n, err := ws.Read(msg)
+	if err != nil {
+		t.Errorf("read: %v", err)
+	}
+	if !bytes.Equal(b[1:6], msg[0:n]) {
+		t.Errorf("Read: expected %q got %q", b[1:6], msg[0:n])
+	}
+	n, err = ws.Read(msg)
+	if err != io.EOF {
+		t.Errorf("read: %v", err)
+	}
+}

+ 549 - 0
websocket/hybi.go

@@ -0,0 +1,549 @@
+// 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 websocket
+
+// This file implements a protocol of hybi draft.
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
+
+import (
+	"bufio"
+	"bytes"
+	"crypto/rand"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+const (
+	websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+	closeStatusNormal            = 1000
+	closeStatusGoingAway         = 1001
+	closeStatusProtocolError     = 1002
+	closeStatusUnsupportedData   = 1003
+	closeStatusFrameTooLarge     = 1004
+	closeStatusNoStatusRcvd      = 1005
+	closeStatusAbnormalClosure   = 1006
+	closeStatusBadMessageData    = 1007
+	closeStatusPolicyViolation   = 1008
+	closeStatusTooBigData        = 1009
+	closeStatusExtensionMismatch = 1010
+
+	maxControlFramePayloadLength = 125
+)
+
+var (
+	ErrBadMaskingKey         = &ProtocolError{"bad masking key"}
+	ErrBadPongMessage        = &ProtocolError{"bad pong message"}
+	ErrBadClosingStatus      = &ProtocolError{"bad closing status"}
+	ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"}
+	ErrNotImplemented        = &ProtocolError{"not implemented"}
+)
+
+// A hybiFrameHeader is a frame header as defined in hybi draft.
+type hybiFrameHeader struct {
+	Fin        bool
+	Rsv        [3]bool
+	OpCode     byte
+	Length     int64
+	MaskingKey []byte
+
+	data *bytes.Buffer
+}
+
+// A hybiFrameReader is a reader for hybi frame.
+type hybiFrameReader struct {
+	reader io.Reader
+
+	header hybiFrameHeader
+	pos    int64
+	length int
+}
+
+func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) {
+	n, err = frame.reader.Read(msg)
+	if err != nil {
+		return 0, err
+	}
+	if frame.header.MaskingKey != nil {
+		for i := 0; i < n; i++ {
+			msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4]
+			frame.pos++
+		}
+	}
+	return n, err
+}
+
+func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode }
+
+func (frame *hybiFrameReader) HeaderReader() io.Reader {
+	if frame.header.data == nil {
+		return nil
+	}
+	if frame.header.data.Len() == 0 {
+		return nil
+	}
+	return frame.header.data
+}
+
+func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil }
+
+func (frame *hybiFrameReader) Len() (n int) { return frame.length }
+
+// A hybiFrameReaderFactory creates new frame reader based on its frame type.
+type hybiFrameReaderFactory struct {
+	*bufio.Reader
+}
+
+// NewFrameReader reads a frame header from the connection, and creates new reader for the frame.
+// See Section 5.2 Base Frameing protocol for detail.
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2
+func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) {
+	hybiFrame := new(hybiFrameReader)
+	frame = hybiFrame
+	var header []byte
+	var b byte
+	// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
+	b, err = buf.ReadByte()
+	if err != nil {
+		return
+	}
+	header = append(header, b)
+	hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0
+	for i := 0; i < 3; i++ {
+		j := uint(6 - i)
+		hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0
+	}
+	hybiFrame.header.OpCode = header[0] & 0x0f
+
+	// Second byte. Mask/Payload len(7bits)
+	b, err = buf.ReadByte()
+	if err != nil {
+		return
+	}
+	header = append(header, b)
+	mask := (b & 0x80) != 0
+	b &= 0x7f
+	lengthFields := 0
+	switch {
+	case b <= 125: // Payload length 7bits.
+		hybiFrame.header.Length = int64(b)
+	case b == 126: // Payload length 7+16bits
+		lengthFields = 2
+	case b == 127: // Payload length 7+64bits
+		lengthFields = 8
+	}
+	for i := 0; i < lengthFields; i++ {
+		b, err = buf.ReadByte()
+		if err != nil {
+			return
+		}
+		header = append(header, b)
+		hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b)
+	}
+	if mask {
+		// Masking key. 4 bytes.
+		for i := 0; i < 4; i++ {
+			b, err = buf.ReadByte()
+			if err != nil {
+				return
+			}
+			header = append(header, b)
+			hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b)
+		}
+	}
+	hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length)
+	hybiFrame.header.data = bytes.NewBuffer(header)
+	hybiFrame.length = len(header) + int(hybiFrame.header.Length)
+	return
+}
+
+// A HybiFrameWriter is a writer for hybi frame.
+type hybiFrameWriter struct {
+	writer *bufio.Writer
+
+	header *hybiFrameHeader
+}
+
+func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) {
+	var header []byte
+	var b byte
+	if frame.header.Fin {
+		b |= 0x80
+	}
+	for i := 0; i < 3; i++ {
+		if frame.header.Rsv[i] {
+			j := uint(6 - i)
+			b |= 1 << j
+		}
+	}
+	b |= frame.header.OpCode
+	header = append(header, b)
+	if frame.header.MaskingKey != nil {
+		b = 0x80
+	} else {
+		b = 0
+	}
+	lengthFields := 0
+	length := len(msg)
+	switch {
+	case length <= 125:
+		b |= byte(length)
+	case length < 65536:
+		b |= 126
+		lengthFields = 2
+	default:
+		b |= 127
+		lengthFields = 8
+	}
+	header = append(header, b)
+	for i := 0; i < lengthFields; i++ {
+		j := uint((lengthFields - i - 1) * 8)
+		b = byte((length >> j) & 0xff)
+		header = append(header, b)
+	}
+	if frame.header.MaskingKey != nil {
+		if len(frame.header.MaskingKey) != 4 {
+			return 0, ErrBadMaskingKey
+		}
+		header = append(header, frame.header.MaskingKey...)
+		frame.writer.Write(header)
+		var data []byte
+
+		for i := 0; i < length; i++ {
+			data = append(data, msg[i]^frame.header.MaskingKey[i%4])
+		}
+		frame.writer.Write(data)
+		err = frame.writer.Flush()
+		return length, err
+	}
+	frame.writer.Write(header)
+	frame.writer.Write(msg)
+	err = frame.writer.Flush()
+	return length, err
+}
+
+func (frame *hybiFrameWriter) Close() error { return nil }
+
+type hybiFrameWriterFactory struct {
+	*bufio.Writer
+	needMaskingKey bool
+}
+
+func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) {
+	frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType}
+	if buf.needMaskingKey {
+		frameHeader.MaskingKey, err = generateMaskingKey()
+		if err != nil {
+			return nil, err
+		}
+	}
+	return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil
+}
+
+type hybiFrameHandler struct {
+	conn        *Conn
+	payloadType byte
+}
+
+func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (r frameReader, err error) {
+	if handler.conn.IsServerConn() {
+		// The client MUST mask all frames sent to the server.
+		if frame.(*hybiFrameReader).header.MaskingKey == nil {
+			handler.WriteClose(closeStatusProtocolError)
+			return nil, io.EOF
+		}
+	} else {
+		// The server MUST NOT mask all frames.
+		if frame.(*hybiFrameReader).header.MaskingKey != nil {
+			handler.WriteClose(closeStatusProtocolError)
+			return nil, io.EOF
+		}
+	}
+	if header := frame.HeaderReader(); header != nil {
+		io.Copy(ioutil.Discard, header)
+	}
+	switch frame.PayloadType() {
+	case ContinuationFrame:
+		frame.(*hybiFrameReader).header.OpCode = handler.payloadType
+	case TextFrame, BinaryFrame:
+		handler.payloadType = frame.PayloadType()
+	case CloseFrame:
+		return nil, io.EOF
+	case PingFrame:
+		pingMsg := make([]byte, maxControlFramePayloadLength)
+		n, err := io.ReadFull(frame, pingMsg)
+		if err != nil && err != io.ErrUnexpectedEOF {
+			return nil, err
+		}
+		io.Copy(ioutil.Discard, frame)
+		n, err = handler.WritePong(pingMsg[:n])
+		if err != nil {
+			return nil, err
+		}
+		return nil, nil
+	case PongFrame:
+		return nil, ErrNotImplemented
+	}
+	return frame, nil
+}
+
+func (handler *hybiFrameHandler) WriteClose(status int) (err error) {
+	handler.conn.wio.Lock()
+	defer handler.conn.wio.Unlock()
+	w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame)
+	if err != nil {
+		return err
+	}
+	msg := make([]byte, 2)
+	binary.BigEndian.PutUint16(msg, uint16(status))
+	_, err = w.Write(msg)
+	w.Close()
+	return err
+}
+
+func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) {
+	handler.conn.wio.Lock()
+	defer handler.conn.wio.Unlock()
+	w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame)
+	if err != nil {
+		return 0, err
+	}
+	n, err = w.Write(msg)
+	w.Close()
+	return n, err
+}
+
+// newHybiConn creates a new WebSocket connection speaking hybi draft protocol.
+func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
+	if buf == nil {
+		br := bufio.NewReader(rwc)
+		bw := bufio.NewWriter(rwc)
+		buf = bufio.NewReadWriter(br, bw)
+	}
+	ws := &Conn{config: config, request: request, buf: buf, rwc: rwc,
+		frameReaderFactory: hybiFrameReaderFactory{buf.Reader},
+		frameWriterFactory: hybiFrameWriterFactory{
+			buf.Writer, request == nil},
+		PayloadType:        TextFrame,
+		defaultCloseStatus: closeStatusNormal}
+	ws.frameHandler = &hybiFrameHandler{conn: ws}
+	return ws
+}
+
+// generateMaskingKey generates a masking key for a frame.
+func generateMaskingKey() (maskingKey []byte, err error) {
+	maskingKey = make([]byte, 4)
+	if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil {
+		return
+	}
+	return
+}
+
+// genetateNonce geneates a nonce consisting of a randomly selected 16-byte
+// value that has been base64-encoded.
+func generateNonce() (nonce []byte) {
+	key := make([]byte, 16)
+	if _, err := io.ReadFull(rand.Reader, key); err != nil {
+		panic(err)
+	}
+	nonce = make([]byte, 24)
+	base64.StdEncoding.Encode(nonce, key)
+	return
+}
+
+// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of
+// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string.
+func getNonceAccept(nonce []byte) (expected []byte, err error) {
+	h := sha1.New()
+	if _, err = h.Write(nonce); err != nil {
+		return
+	}
+	if _, err = h.Write([]byte(websocketGUID)); err != nil {
+		return
+	}
+	expected = make([]byte, 28)
+	base64.StdEncoding.Encode(expected, h.Sum(nil))
+	return
+}
+
+func isHybiVersion(version int) bool {
+	switch version {
+	case ProtocolVersionHybi08, ProtocolVersionHybi13:
+		return true
+	default:
+	}
+	return false
+}
+
+// Client handhake described in draft-ietf-hybi-thewebsocket-protocol-17
+func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
+	if !isHybiVersion(config.Version) {
+		panic("wrong protocol version.")
+	}
+
+	bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
+
+	bw.WriteString("Host: " + config.Location.Host + "\r\n")
+	bw.WriteString("Upgrade: websocket\r\n")
+	bw.WriteString("Connection: Upgrade\r\n")
+	nonce := generateNonce()
+	if config.handshakeData != nil {
+		nonce = []byte(config.handshakeData["key"])
+	}
+	bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n")
+	if config.Version == ProtocolVersionHybi13 {
+		bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n")
+	} else if config.Version == ProtocolVersionHybi08 {
+		bw.WriteString("Sec-WebSocket-Origin: " + strings.ToLower(config.Origin.String()) + "\r\n")
+	}
+	bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n")
+	if len(config.Protocol) > 0 {
+		bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n")
+	}
+	// TODO(ukai): send extensions.
+	// TODO(ukai): send cookie if any.
+
+	bw.WriteString("\r\n")
+	if err = bw.Flush(); err != nil {
+		return err
+	}
+
+	resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 101 {
+		return ErrBadStatus
+	}
+	if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" ||
+		strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
+		return ErrBadUpgrade
+	}
+	expectedAccept, err := getNonceAccept(nonce)
+	if err != nil {
+		return err
+	}
+	if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) {
+		return ErrChallengeResponse
+	}
+	if resp.Header.Get("Sec-WebSocket-Extensions") != "" {
+		return ErrUnsupportedExtensions
+	}
+	offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol")
+	if offeredProtocol != "" {
+		protocolMatched := false
+		for i := 0; i < len(config.Protocol); i++ {
+			if config.Protocol[i] == offeredProtocol {
+				protocolMatched = true
+				break
+			}
+		}
+		if !protocolMatched {
+			return ErrBadWebSocketProtocol
+		}
+		config.Protocol = []string{offeredProtocol}
+	}
+
+	return nil
+}
+
+// newHybiClientConn creates a client WebSocket connection after handshake.
+func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
+	return newHybiConn(config, buf, rwc, nil)
+}
+
+// A HybiServerHandshaker performs a server handshake using hybi draft protocol.
+type hybiServerHandshaker struct {
+	*Config
+	accept []byte
+}
+
+func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
+	c.Version = ProtocolVersionHybi13
+	if req.Method != "GET" {
+		return http.StatusMethodNotAllowed, ErrBadRequestMethod
+	}
+	// HTTP version can be safely ignored.
+
+	if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
+		!strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") {
+		return http.StatusBadRequest, ErrNotWebSocket
+	}
+
+	key := req.Header.Get("Sec-Websocket-Key")
+	if key == "" {
+		return http.StatusBadRequest, ErrChallengeResponse
+	}
+	version := req.Header.Get("Sec-Websocket-Version")
+	var origin string
+	switch version {
+	case "13":
+		c.Version = ProtocolVersionHybi13
+		origin = req.Header.Get("Origin")
+	case "8":
+		c.Version = ProtocolVersionHybi08
+		origin = req.Header.Get("Sec-Websocket-Origin")
+	default:
+		return http.StatusBadRequest, ErrBadWebSocketVersion
+	}
+	c.Origin, err = url.ParseRequest(origin)
+	if err != nil {
+		return http.StatusForbidden, err
+	}
+	var scheme string
+	if req.TLS != nil {
+		scheme = "wss"
+	} else {
+		scheme = "ws"
+	}
+	c.Location, err = url.ParseRequest(scheme + "://" + req.Host + req.URL.RequestURI())
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+	protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
+	protocols := strings.Split(protocol, ",")
+	for i := 0; i < len(protocols); i++ {
+		c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
+	}
+	c.accept, err = getNonceAccept([]byte(key))
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+	return http.StatusSwitchingProtocols, nil
+}
+
+func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
+	if len(c.Protocol) > 0 {
+		if len(c.Protocol) != 1 {
+			return ErrBadWebSocketProtocol
+		}
+	}
+	buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
+	buf.WriteString("Upgrade: websocket\r\n")
+	buf.WriteString("Connection: Upgrade\r\n")
+	buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n")
+	if len(c.Protocol) > 0 {
+		buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
+	}
+	// TODO(ukai): support extensions
+	buf.WriteString("\r\n")
+	return buf.Flush()
+}
+
+func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
+	return newHybiServerConn(c.Config, buf, rwc, request)
+}
+
+// newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol.
+func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
+	return newHybiConn(config, buf, rwc, request)
+}

+ 584 - 0
websocket/hybi_test.go

@@ -0,0 +1,584 @@
+// 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 websocket
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+)
+
+// Test the getNonceAccept function with values in
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
+func TestSecWebSocketAccept(t *testing.T) {
+	nonce := []byte("dGhlIHNhbXBsZSBub25jZQ==")
+	expected := []byte("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=")
+	accept, err := getNonceAccept(nonce)
+	if err != nil {
+		t.Errorf("getNonceAccept: returned error %v", err)
+		return
+	}
+	if !bytes.Equal(expected, accept) {
+		t.Errorf("getNonceAccept: expected %q got %q", expected, accept)
+	}
+}
+
+func TestHybiClientHandshake(t *testing.T) {
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+	br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
+Sec-WebSocket-Protocol: chat
+
+`))
+	var err error
+	config := new(Config)
+	config.Location, err = url.ParseRequest("ws://server.example.com/chat")
+	if err != nil {
+		t.Fatal("location url", err)
+	}
+	config.Origin, err = url.ParseRequest("http://example.com")
+	if err != nil {
+		t.Fatal("origin url", err)
+	}
+	config.Protocol = append(config.Protocol, "chat")
+	config.Protocol = append(config.Protocol, "superchat")
+	config.Version = ProtocolVersionHybi13
+
+	config.handshakeData = map[string]string{
+		"key": "dGhlIHNhbXBsZSBub25jZQ==",
+	}
+	err = hybiClientHandshake(config, br, bw)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	req, err := http.ReadRequest(bufio.NewReader(b))
+	if err != nil {
+		t.Fatalf("read request: %v", err)
+	}
+	if req.Method != "GET" {
+		t.Errorf("request method expected GET, but got %q", req.Method)
+	}
+	if req.URL.Path != "/chat" {
+		t.Errorf("request path expected /chat, but got %q", req.URL.Path)
+	}
+	if req.Proto != "HTTP/1.1" {
+		t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto)
+	}
+	if req.Host != "server.example.com" {
+		t.Errorf("request Host expected server.example.com, but got %v", req.Host)
+	}
+	var expectedHeader = map[string]string{
+		"Connection":             "Upgrade",
+		"Upgrade":                "websocket",
+		"Sec-Websocket-Key":      config.handshakeData["key"],
+		"Origin":                 config.Origin.String(),
+		"Sec-Websocket-Protocol": "chat, superchat",
+		"Sec-Websocket-Version":  fmt.Sprintf("%d", ProtocolVersionHybi13),
+	}
+	for k, v := range expectedHeader {
+		if req.Header.Get(k) != v {
+			t.Errorf(fmt.Sprintf("%s expected %q but got %q", k, v, req.Header.Get(k)))
+		}
+	}
+}
+
+func TestHybiClientHandshakeHybi08(t *testing.T) {
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+	br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
+Sec-WebSocket-Protocol: chat
+
+`))
+	var err error
+	config := new(Config)
+	config.Location, err = url.ParseRequest("ws://server.example.com/chat")
+	if err != nil {
+		t.Fatal("location url", err)
+	}
+	config.Origin, err = url.ParseRequest("http://example.com")
+	if err != nil {
+		t.Fatal("origin url", err)
+	}
+	config.Protocol = append(config.Protocol, "chat")
+	config.Protocol = append(config.Protocol, "superchat")
+	config.Version = ProtocolVersionHybi08
+
+	config.handshakeData = map[string]string{
+		"key": "dGhlIHNhbXBsZSBub25jZQ==",
+	}
+	err = hybiClientHandshake(config, br, bw)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	req, err := http.ReadRequest(bufio.NewReader(b))
+	if err != nil {
+		t.Fatalf("read request: %v", err)
+	}
+	if req.Method != "GET" {
+		t.Errorf("request method expected GET, but got %q", req.Method)
+	}
+	if req.URL.Path != "/chat" {
+		t.Errorf("request path expected /demo, but got %q", req.URL.Path)
+	}
+	if req.Proto != "HTTP/1.1" {
+		t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto)
+	}
+	if req.Host != "server.example.com" {
+		t.Errorf("request Host expected example.com, but got %v", req.Host)
+	}
+	var expectedHeader = map[string]string{
+		"Connection":             "Upgrade",
+		"Upgrade":                "websocket",
+		"Sec-Websocket-Key":      config.handshakeData["key"],
+		"Sec-Websocket-Origin":   config.Origin.String(),
+		"Sec-Websocket-Protocol": "chat, superchat",
+		"Sec-Websocket-Version":  fmt.Sprintf("%d", ProtocolVersionHybi08),
+	}
+	for k, v := range expectedHeader {
+		if req.Header.Get(k) != v {
+			t.Errorf(fmt.Sprintf("%s expected %q but got %q", k, v, req.Header.Get(k)))
+		}
+	}
+}
+
+func TestHybiServerHandshake(t *testing.T) {
+	config := new(Config)
+	handshaker := &hybiServerHandshaker{Config: config}
+	br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1
+Host: server.example.com
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Origin: http://example.com
+Sec-WebSocket-Protocol: chat, superchat
+Sec-WebSocket-Version: 13
+
+`))
+	req, err := http.ReadRequest(br)
+	if err != nil {
+		t.Fatal("request", err)
+	}
+	code, err := handshaker.ReadHandshake(br, req)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	if code != http.StatusSwitchingProtocols {
+		t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code)
+	}
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+
+	config.Protocol = []string{"chat"}
+
+	err = handshaker.AcceptHandshake(bw)
+	if err != nil {
+		t.Errorf("handshake response failed: %v", err)
+	}
+	expectedResponse := strings.Join([]string{
+		"HTTP/1.1 101 Switching Protocols",
+		"Upgrade: websocket",
+		"Connection: Upgrade",
+		"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
+		"Sec-WebSocket-Protocol: chat",
+		"", ""}, "\r\n")
+
+	if b.String() != expectedResponse {
+		t.Errorf("handshake expected %q but got %q", expectedResponse, b.String())
+	}
+}
+
+func TestHybiServerHandshakeHybi08(t *testing.T) {
+	config := new(Config)
+	handshaker := &hybiServerHandshaker{Config: config}
+	br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1
+Host: server.example.com
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Sec-WebSocket-Origin: http://example.com
+Sec-WebSocket-Protocol: chat, superchat
+Sec-WebSocket-Version: 8
+
+`))
+	req, err := http.ReadRequest(br)
+	if err != nil {
+		t.Fatal("request", err)
+	}
+	code, err := handshaker.ReadHandshake(br, req)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	if code != http.StatusSwitchingProtocols {
+		t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code)
+	}
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+
+	config.Protocol = []string{"chat"}
+
+	err = handshaker.AcceptHandshake(bw)
+	if err != nil {
+		t.Errorf("handshake response failed: %v", err)
+	}
+	expectedResponse := strings.Join([]string{
+		"HTTP/1.1 101 Switching Protocols",
+		"Upgrade: websocket",
+		"Connection: Upgrade",
+		"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
+		"Sec-WebSocket-Protocol: chat",
+		"", ""}, "\r\n")
+
+	if b.String() != expectedResponse {
+		t.Errorf("handshake expected %q but got %q", expectedResponse, b.String())
+	}
+}
+
+func TestHybiServerHandshakeHybiBadVersion(t *testing.T) {
+	config := new(Config)
+	handshaker := &hybiServerHandshaker{Config: config}
+	br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1
+Host: server.example.com
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Sec-WebSocket-Origin: http://example.com
+Sec-WebSocket-Protocol: chat, superchat
+Sec-WebSocket-Version: 9
+
+`))
+	req, err := http.ReadRequest(br)
+	if err != nil {
+		t.Fatal("request", err)
+	}
+	code, err := handshaker.ReadHandshake(br, req)
+	if err != ErrBadWebSocketVersion {
+		t.Errorf("handshake expected err %q but got %q", ErrBadWebSocketVersion, err)
+	}
+	if code != http.StatusBadRequest {
+		t.Errorf("status expected %q but got %q", http.StatusBadRequest, code)
+	}
+}
+
+func testHybiFrame(t *testing.T, testHeader, testPayload, testMaskedPayload []byte, frameHeader *hybiFrameHeader) {
+	b := bytes.NewBuffer([]byte{})
+	frameWriterFactory := &hybiFrameWriterFactory{bufio.NewWriter(b), false}
+	w, _ := frameWriterFactory.NewFrameWriter(TextFrame)
+	w.(*hybiFrameWriter).header = frameHeader
+	_, err := w.Write(testPayload)
+	w.Close()
+	if err != nil {
+		t.Errorf("Write error %q", err)
+	}
+	var expectedFrame []byte
+	expectedFrame = append(expectedFrame, testHeader...)
+	expectedFrame = append(expectedFrame, testMaskedPayload...)
+	if !bytes.Equal(expectedFrame, b.Bytes()) {
+		t.Errorf("frame expected %q got %q", expectedFrame, b.Bytes())
+	}
+	frameReaderFactory := &hybiFrameReaderFactory{bufio.NewReader(b)}
+	r, err := frameReaderFactory.NewFrameReader()
+	if err != nil {
+		t.Errorf("Read error %q", err)
+	}
+	if header := r.HeaderReader(); header == nil {
+		t.Errorf("no header")
+	} else {
+		actualHeader := make([]byte, r.Len())
+		n, err := header.Read(actualHeader)
+		if err != nil {
+			t.Errorf("Read header error %q", err)
+		} else {
+			if n < len(testHeader) {
+				t.Errorf("header too short %q got %q", testHeader, actualHeader[:n])
+			}
+			if !bytes.Equal(testHeader, actualHeader[:n]) {
+				t.Errorf("header expected %q got %q", testHeader, actualHeader[:n])
+			}
+		}
+	}
+	if trailer := r.TrailerReader(); trailer != nil {
+		t.Errorf("unexpected trailer %q", trailer)
+	}
+	frame := r.(*hybiFrameReader)
+	if frameHeader.Fin != frame.header.Fin ||
+		frameHeader.OpCode != frame.header.OpCode ||
+		len(testPayload) != int(frame.header.Length) {
+		t.Errorf("mismatch %v (%d) vs %v", frameHeader, len(testPayload), frame)
+	}
+	payload := make([]byte, len(testPayload))
+	_, err = r.Read(payload)
+	if err != nil {
+		t.Errorf("read %v", err)
+	}
+	if !bytes.Equal(testPayload, payload) {
+		t.Errorf("payload %q vs %q", testPayload, payload)
+	}
+}
+
+func TestHybiShortTextFrame(t *testing.T) {
+	frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame}
+	payload := []byte("hello")
+	testHybiFrame(t, []byte{0x81, 0x05}, payload, payload, frameHeader)
+
+	payload = make([]byte, 125)
+	testHybiFrame(t, []byte{0x81, 125}, payload, payload, frameHeader)
+}
+
+func TestHybiShortMaskedTextFrame(t *testing.T) {
+	frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame,
+		MaskingKey: []byte{0xcc, 0x55, 0x80, 0x20}}
+	payload := []byte("hello")
+	maskedPayload := []byte{0xa4, 0x30, 0xec, 0x4c, 0xa3}
+	header := []byte{0x81, 0x85}
+	header = append(header, frameHeader.MaskingKey...)
+	testHybiFrame(t, header, payload, maskedPayload, frameHeader)
+}
+
+func TestHybiShortBinaryFrame(t *testing.T) {
+	frameHeader := &hybiFrameHeader{Fin: true, OpCode: BinaryFrame}
+	payload := []byte("hello")
+	testHybiFrame(t, []byte{0x82, 0x05}, payload, payload, frameHeader)
+
+	payload = make([]byte, 125)
+	testHybiFrame(t, []byte{0x82, 125}, payload, payload, frameHeader)
+}
+
+func TestHybiControlFrame(t *testing.T) {
+	frameHeader := &hybiFrameHeader{Fin: true, OpCode: PingFrame}
+	payload := []byte("hello")
+	testHybiFrame(t, []byte{0x89, 0x05}, payload, payload, frameHeader)
+
+	frameHeader = &hybiFrameHeader{Fin: true, OpCode: PongFrame}
+	testHybiFrame(t, []byte{0x8A, 0x05}, payload, payload, frameHeader)
+
+	frameHeader = &hybiFrameHeader{Fin: true, OpCode: CloseFrame}
+	payload = []byte{0x03, 0xe8} // 1000
+	testHybiFrame(t, []byte{0x88, 0x02}, payload, payload, frameHeader)
+}
+
+func TestHybiLongFrame(t *testing.T) {
+	frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame}
+	payload := make([]byte, 126)
+	testHybiFrame(t, []byte{0x81, 126, 0x00, 126}, payload, payload, frameHeader)
+
+	payload = make([]byte, 65535)
+	testHybiFrame(t, []byte{0x81, 126, 0xff, 0xff}, payload, payload, frameHeader)
+
+	payload = make([]byte, 65536)
+	testHybiFrame(t, []byte{0x81, 127, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}, payload, payload, frameHeader)
+}
+
+func TestHybiClientRead(t *testing.T) {
+	wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o',
+		0x89, 0x05, 'h', 'e', 'l', 'l', 'o', // ping
+		0x81, 0x05, 'w', 'o', 'r', 'l', 'd'}
+	br := bufio.NewReader(bytes.NewBuffer(wireData))
+	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
+	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil)
+
+	msg := make([]byte, 512)
+	n, err := conn.Read(msg)
+	if err != nil {
+		t.Errorf("read 1st frame, error %q", err)
+	}
+	if n != 5 {
+		t.Errorf("read 1st frame, expect 5, got %d", n)
+	}
+	if !bytes.Equal(wireData[2:7], msg[:n]) {
+		t.Errorf("read 1st frame %v, got %v", wireData[2:7], msg[:n])
+	}
+	n, err = conn.Read(msg)
+	if err != nil {
+		t.Errorf("read 2nd frame, error %q", err)
+	}
+	if n != 5 {
+		t.Errorf("read 2nd frame, expect 5, got %d", n)
+	}
+	if !bytes.Equal(wireData[16:21], msg[:n]) {
+		t.Errorf("read 2nd frame %v, got %v", wireData[16:21], msg[:n])
+	}
+	n, err = conn.Read(msg)
+	if err == nil {
+		t.Errorf("read not EOF")
+	}
+	if n != 0 {
+		t.Errorf("expect read 0, got %d", n)
+	}
+}
+
+func TestHybiShortRead(t *testing.T) {
+	wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o',
+		0x89, 0x05, 'h', 'e', 'l', 'l', 'o', // ping
+		0x81, 0x05, 'w', 'o', 'r', 'l', 'd'}
+	br := bufio.NewReader(bytes.NewBuffer(wireData))
+	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
+	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil)
+
+	step := 0
+	pos := 0
+	expectedPos := []int{2, 5, 16, 19}
+	expectedLen := []int{3, 2, 3, 2}
+	for {
+		msg := make([]byte, 3)
+		n, err := conn.Read(msg)
+		if step >= len(expectedPos) {
+			if err == nil {
+				t.Errorf("read not EOF")
+			}
+			if n != 0 {
+				t.Errorf("expect read 0, got %d", n)
+			}
+			return
+		}
+		pos = expectedPos[step]
+		endPos := pos + expectedLen[step]
+		if err != nil {
+			t.Errorf("read from %d, got error %q", pos, err)
+			return
+		}
+		if n != endPos-pos {
+			t.Errorf("read from %d, expect %d, got %d", pos, endPos-pos, n)
+		}
+		if !bytes.Equal(wireData[pos:endPos], msg[:n]) {
+			t.Errorf("read from %d, frame %v, got %v", pos, wireData[pos:endPos], msg[:n])
+		}
+		step++
+	}
+}
+
+func TestHybiServerRead(t *testing.T) {
+	wireData := []byte{0x81, 0x85, 0xcc, 0x55, 0x80, 0x20,
+		0xa4, 0x30, 0xec, 0x4c, 0xa3, // hello
+		0x89, 0x85, 0xcc, 0x55, 0x80, 0x20,
+		0xa4, 0x30, 0xec, 0x4c, 0xa3, // ping: hello
+		0x81, 0x85, 0xed, 0x83, 0xb4, 0x24,
+		0x9a, 0xec, 0xc6, 0x48, 0x89, // world
+	}
+	br := bufio.NewReader(bytes.NewBuffer(wireData))
+	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
+	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, new(http.Request))
+
+	expected := [][]byte{[]byte("hello"), []byte("world")}
+
+	msg := make([]byte, 512)
+	n, err := conn.Read(msg)
+	if err != nil {
+		t.Errorf("read 1st frame, error %q", err)
+	}
+	if n != 5 {
+		t.Errorf("read 1st frame, expect 5, got %d", n)
+	}
+	if !bytes.Equal(expected[0], msg[:n]) {
+		t.Errorf("read 1st frame %q, got %q", expected[0], msg[:n])
+	}
+
+	n, err = conn.Read(msg)
+	if err != nil {
+		t.Errorf("read 2nd frame, error %q", err)
+	}
+	if n != 5 {
+		t.Errorf("read 2nd frame, expect 5, got %d", n)
+	}
+	if !bytes.Equal(expected[1], msg[:n]) {
+		t.Errorf("read 2nd frame %q, got %q", expected[1], msg[:n])
+	}
+
+	n, err = conn.Read(msg)
+	if err == nil {
+		t.Errorf("read not EOF")
+	}
+	if n != 0 {
+		t.Errorf("expect read 0, got %d", n)
+	}
+}
+
+func TestHybiServerReadWithoutMasking(t *testing.T) {
+	wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o'}
+	br := bufio.NewReader(bytes.NewBuffer(wireData))
+	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
+	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, new(http.Request))
+	// server MUST close the connection upon receiving a non-masked frame.
+	msg := make([]byte, 512)
+	_, err := conn.Read(msg)
+	if err != io.EOF {
+		t.Errorf("read 1st frame, expect %q, but got %q", io.EOF, err)
+	}
+}
+
+func TestHybiClientReadWithMasking(t *testing.T) {
+	wireData := []byte{0x81, 0x85, 0xcc, 0x55, 0x80, 0x20,
+		0xa4, 0x30, 0xec, 0x4c, 0xa3, // hello
+	}
+	br := bufio.NewReader(bytes.NewBuffer(wireData))
+	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
+	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil)
+
+	// client MUST close the connection upon receiving a masked frame.
+	msg := make([]byte, 512)
+	_, err := conn.Read(msg)
+	if err != io.EOF {
+		t.Errorf("read 1st frame, expect %q, but got %q", io.EOF, err)
+	}
+}
+
+// Test the hybiServerHandshaker supports firefox implementation and
+// checks Connection request header include (but it's not necessary 
+// equal to) "upgrade"   
+func TestHybiServerFirefoxHandshake(t *testing.T) {
+	config := new(Config)
+	handshaker := &hybiServerHandshaker{Config: config}
+	br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1
+Host: server.example.com
+Upgrade: websocket
+Connection: keep-alive, upgrade
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Origin: http://example.com
+Sec-WebSocket-Protocol: chat, superchat
+Sec-WebSocket-Version: 13
+
+`))
+	req, err := http.ReadRequest(br)
+	if err != nil {
+		t.Fatal("request", err)
+	}
+	code, err := handshaker.ReadHandshake(br, req)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	if code != http.StatusSwitchingProtocols {
+		t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code)
+	}
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+
+	config.Protocol = []string{"chat"}
+
+	err = handshaker.AcceptHandshake(bw)
+	if err != nil {
+		t.Errorf("handshake response failed: %v", err)
+	}
+	expectedResponse := strings.Join([]string{
+		"HTTP/1.1 101 Switching Protocols",
+		"Upgrade: websocket",
+		"Connection: Upgrade",
+		"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
+		"Sec-WebSocket-Protocol: chat",
+		"", ""}, "\r\n")
+
+	if b.String() != expectedResponse {
+		t.Errorf("handshake expected %q but got %q", expectedResponse, b.String())
+	}
+}

+ 102 - 0
websocket/server.go

@@ -0,0 +1,102 @@
+// Copyright 2009 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 websocket
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"net/http"
+)
+
+func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request) (conn *Conn, err error) {
+	config := new(Config)
+	var hs serverHandshaker = &hybiServerHandshaker{Config: config}
+	code, err := hs.ReadHandshake(buf.Reader, req)
+	if err == ErrBadWebSocketVersion {
+		fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
+		fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion)
+		buf.WriteString("\r\n")
+		buf.WriteString(err.Error())
+		buf.Flush()
+		return
+	}
+	if err != nil {
+		hs = &hixie76ServerHandshaker{Config: config}
+		code, err = hs.ReadHandshake(buf.Reader, req)
+	}
+	if err != nil {
+		hs = &hixie75ServerHandshaker{Config: config}
+		code, err = hs.ReadHandshake(buf.Reader, req)
+	}
+	if err != nil {
+		fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
+		buf.WriteString("\r\n")
+		buf.WriteString(err.Error())
+		buf.Flush()
+		return
+	}
+	config.Protocol = nil
+
+	err = hs.AcceptHandshake(buf.Writer)
+	if err != nil {
+		code = http.StatusBadRequest
+		fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
+		buf.WriteString("\r\n")
+		buf.Flush()
+		return
+	}
+	conn = hs.NewServerConn(buf, rwc, req)
+	return
+}
+
+/*
+Handler is an interface to a WebSocket.
+
+A trivial example server:
+
+	package main
+
+	import (
+		"io"
+		"net/http"
+		"websocket"
+	)
+
+	// Echo the data received on the WebSocket.
+	func EchoServer(ws *websocket.Conn) {
+		io.Copy(ws, ws);
+	}
+
+	func main() {
+		http.Handle("/echo", websocket.Handler(EchoServer));
+		err := http.ListenAndServe(":12345", nil);
+		if err != nil {
+			panic("ListenAndServe: " + err.Error())
+		}
+	}
+*/
+type Handler func(*Conn)
+
+// ServeHTTP implements the http.Handler interface for a Web Socket
+func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	rwc, buf, err := w.(http.Hijacker).Hijack()
+	if err != nil {
+		panic("Hijack failed: " + err.Error())
+		return
+	}
+	// The server should abort the WebSocket connection if it finds
+	// the client did not send a handshake that matches with protocol
+	// specification.
+	defer rwc.Close()
+	conn, err := newServerConn(rwc, buf, req)
+	if err != nil {
+		return
+	}
+	if conn == nil {
+		panic("unepxected nil conn")
+	}
+	h(conn)
+}

+ 412 - 0
websocket/websocket.go

@@ -0,0 +1,412 @@
+// Copyright 2009 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 websocket implements a client and server for the WebSocket protocol.
+// The protocol is defined at http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol
+package websocket
+
+import (
+	"bufio"
+	"crypto/tls"
+	"encoding/json"
+	"errors"
+	"io"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/url"
+	"sync"
+	"time"
+)
+
+const (
+	ProtocolVersionHixie75   = -75
+	ProtocolVersionHixie76   = -76
+	ProtocolVersionHybi00    = 0
+	ProtocolVersionHybi08    = 8
+	ProtocolVersionHybi13    = 13
+	ProtocolVersionHybi      = ProtocolVersionHybi13
+	SupportedProtocolVersion = "13, 8"
+
+	ContinuationFrame = 0
+	TextFrame         = 1
+	BinaryFrame       = 2
+	CloseFrame        = 8
+	PingFrame         = 9
+	PongFrame         = 10
+	UnknownFrame      = 255
+)
+
+// WebSocket protocol errors.
+type ProtocolError struct {
+	ErrorString string
+}
+
+func (err *ProtocolError) Error() string { return err.ErrorString }
+
+var (
+	ErrBadProtocolVersion   = &ProtocolError{"bad protocol version"}
+	ErrBadScheme            = &ProtocolError{"bad scheme"}
+	ErrBadStatus            = &ProtocolError{"bad status"}
+	ErrBadUpgrade           = &ProtocolError{"missing or bad upgrade"}
+	ErrBadWebSocketOrigin   = &ProtocolError{"missing or bad WebSocket-Origin"}
+	ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"}
+	ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"}
+	ErrBadWebSocketVersion  = &ProtocolError{"missing or bad WebSocket Version"}
+	ErrChallengeResponse    = &ProtocolError{"mismatch challenge/response"}
+	ErrBadFrame             = &ProtocolError{"bad frame"}
+	ErrBadFrameBoundary     = &ProtocolError{"not on frame boundary"}
+	ErrNotWebSocket         = &ProtocolError{"not websocket protocol"}
+	ErrBadRequestMethod     = &ProtocolError{"bad method"}
+	ErrNotSupported         = &ProtocolError{"not supported"}
+)
+
+// Addr is an implementation of net.Addr for WebSocket.
+type Addr struct {
+	*url.URL
+}
+
+// Network returns the network type for a WebSocket, "websocket".
+func (addr *Addr) Network() string { return "websocket" }
+
+// Config is a WebSocket configuration
+type Config struct {
+	// A WebSocket server address.
+	Location *url.URL
+
+	// A Websocket client origin.
+	Origin *url.URL
+
+	// WebSocket subprotocols.
+	Protocol []string
+
+	// WebSocket protocol version.
+	Version int
+
+	// TLS config for secure WebSocket (wss).
+	TlsConfig *tls.Config
+
+	handshakeData map[string]string
+}
+
+// serverHandshaker is an interface to handle WebSocket server side handshake.
+type serverHandshaker interface {
+	// ReadHandshake reads handshake request message from client.
+	// Returns http response code and error if any.
+	ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error)
+
+	// AcceptHandshake accepts the client handshake request and sends
+	// handshake response back to client.
+	AcceptHandshake(buf *bufio.Writer) (err error)
+
+	// NewServerConn creates a new WebSocket connection.
+	NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn)
+}
+
+// frameReader is an interface to read a WebSocket frame.
+type frameReader interface {
+	// Reader is to read payload of the frame.
+	io.Reader
+
+	// PayloadType returns payload type.
+	PayloadType() byte
+
+	// HeaderReader returns a reader to read header of the frame.
+	HeaderReader() io.Reader
+
+	// TrailerReader returns a reader to read trailer of the frame.
+	// If it returns nil, there is no trailer in the frame.
+	TrailerReader() io.Reader
+
+	// Len returns total length of the frame, including header and trailer.
+	Len() int
+}
+
+// frameReaderFactory is an interface to creates new frame reader.
+type frameReaderFactory interface {
+	NewFrameReader() (r frameReader, err error)
+}
+
+// frameWriter is an interface to write a WebSocket frame.
+type frameWriter interface {
+	// Writer is to write playload of the frame.
+	io.WriteCloser
+}
+
+// frameWriterFactory is an interface to create new frame writer.
+type frameWriterFactory interface {
+	NewFrameWriter(payloadType byte) (w frameWriter, err error)
+}
+
+type frameHandler interface {
+	HandleFrame(frame frameReader) (r frameReader, err error)
+	WriteClose(status int) (err error)
+}
+
+// Conn represents a WebSocket connection.
+type Conn struct {
+	config  *Config
+	request *http.Request
+
+	buf *bufio.ReadWriter
+	rwc io.ReadWriteCloser
+
+	rio sync.Mutex
+	frameReaderFactory
+	frameReader
+
+	wio sync.Mutex
+	frameWriterFactory
+
+	frameHandler
+	PayloadType        byte
+	defaultCloseStatus int
+}
+
+// Read implements the io.Reader interface:
+// it reads data of a frame from the WebSocket connection.
+// if msg is not large enough for the frame data, it fills the msg and next Read
+// will read the rest of the frame data.
+// it reads Text frame or Binary frame.
+func (ws *Conn) Read(msg []byte) (n int, err error) {
+	ws.rio.Lock()
+	defer ws.rio.Unlock()
+again:
+	if ws.frameReader == nil {
+		frame, err := ws.frameReaderFactory.NewFrameReader()
+		if err != nil {
+			return 0, err
+		}
+		ws.frameReader, err = ws.frameHandler.HandleFrame(frame)
+		if err != nil {
+			return 0, err
+		}
+		if ws.frameReader == nil {
+			goto again
+		}
+	}
+	n, err = ws.frameReader.Read(msg)
+	if err == io.EOF {
+		if trailer := ws.frameReader.TrailerReader(); trailer != nil {
+			io.Copy(ioutil.Discard, trailer)
+		}
+		ws.frameReader = nil
+		goto again
+	}
+	return n, err
+}
+
+// Write implements the io.Writer interface:
+// it writes data as a frame to the WebSocket connection.
+func (ws *Conn) Write(msg []byte) (n int, err error) {
+	ws.wio.Lock()
+	defer ws.wio.Unlock()
+	w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType)
+	if err != nil {
+		return 0, err
+	}
+	n, err = w.Write(msg)
+	w.Close()
+	if err != nil {
+		return n, err
+	}
+	return n, err
+}
+
+// Close implements the io.Closer interface.
+func (ws *Conn) Close() error {
+	err := ws.frameHandler.WriteClose(ws.defaultCloseStatus)
+	if err != nil {
+		return err
+	}
+	return ws.rwc.Close()
+}
+
+func (ws *Conn) IsClientConn() bool { return ws.request == nil }
+func (ws *Conn) IsServerConn() bool { return ws.request != nil }
+
+// LocalAddr returns the WebSocket Origin for the connection for client, or
+// the WebSocket location for server.
+func (ws *Conn) LocalAddr() net.Addr {
+	if ws.IsClientConn() {
+		return &Addr{ws.config.Origin}
+	}
+	return &Addr{ws.config.Location}
+}
+
+// RemoteAddr returns the WebSocket location for the connection for client, or
+// the Websocket Origin for server.
+func (ws *Conn) RemoteAddr() net.Addr {
+	if ws.IsClientConn() {
+		return &Addr{ws.config.Location}
+	}
+	return &Addr{ws.config.Origin}
+}
+
+var errSetDeadline = errors.New("websocket: cannot set deadline: not using a net.Conn")
+
+// SetDeadline sets the connection's network read & write deadlines.
+func (ws *Conn) SetDeadline(t time.Time) error {
+	if conn, ok := ws.rwc.(net.Conn); ok {
+		return conn.SetDeadline(t)
+	}
+	return errSetDeadline
+}
+
+// SetReadDeadline sets the connection's network read deadline.
+func (ws *Conn) SetReadDeadline(t time.Time) error {
+	if conn, ok := ws.rwc.(net.Conn); ok {
+		return conn.SetReadDeadline(t)
+	}
+	return errSetDeadline
+}
+
+// SetWriteDeadline sets the connection's network write deadline.
+func (ws *Conn) SetWriteDeadline(t time.Time) error {
+	if conn, ok := ws.rwc.(net.Conn); ok {
+		return conn.SetWriteDeadline(t)
+	}
+	return errSetDeadline
+}
+
+// Config returns the WebSocket config.
+func (ws *Conn) Config() *Config { return ws.config }
+
+// Request returns the http request upgraded to the WebSocket.
+// It is nil for client side.
+func (ws *Conn) Request() *http.Request { return ws.request }
+
+// Codec represents a symmetric pair of functions that implement a codec.
+type Codec struct {
+	Marshal   func(v interface{}) (data []byte, payloadType byte, err error)
+	Unmarshal func(data []byte, payloadType byte, v interface{}) (err error)
+}
+
+// Send sends v marshaled by cd.Marshal as single frame to ws.
+func (cd Codec) Send(ws *Conn, v interface{}) (err error) {
+	if err != nil {
+		return err
+	}
+	data, payloadType, err := cd.Marshal(v)
+	if err != nil {
+		return err
+	}
+	ws.wio.Lock()
+	defer ws.wio.Unlock()
+	w, err := ws.frameWriterFactory.NewFrameWriter(payloadType)
+	_, err = w.Write(data)
+	w.Close()
+	return err
+}
+
+// Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores in v.
+func (cd Codec) Receive(ws *Conn, v interface{}) (err error) {
+	ws.rio.Lock()
+	defer ws.rio.Unlock()
+	if ws.frameReader != nil {
+		_, err = io.Copy(ioutil.Discard, ws.frameReader)
+		if err != nil {
+			return err
+		}
+		ws.frameReader = nil
+	}
+again:
+	frame, err := ws.frameReaderFactory.NewFrameReader()
+	if err != nil {
+		return err
+	}
+	frame, err = ws.frameHandler.HandleFrame(frame)
+	if err != nil {
+		return err
+	}
+	if frame == nil {
+		goto again
+	}
+	payloadType := frame.PayloadType()
+	data, err := ioutil.ReadAll(frame)
+	if err != nil {
+		return err
+	}
+	return cd.Unmarshal(data, payloadType, v)
+}
+
+func marshal(v interface{}) (msg []byte, payloadType byte, err error) {
+	switch data := v.(type) {
+	case string:
+		return []byte(data), TextFrame, nil
+	case []byte:
+		return data, BinaryFrame, nil
+	}
+	return nil, UnknownFrame, ErrNotSupported
+}
+
+func unmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
+	switch data := v.(type) {
+	case *string:
+		*data = string(msg)
+		return nil
+	case *[]byte:
+		*data = msg
+		return nil
+	}
+	return ErrNotSupported
+}
+
+/*
+Message is a codec to send/receive text/binary data in a frame on WebSocket connection.
+To send/receive text frame, use string type.
+To send/receive binary frame, use []byte type.
+
+Trivial usage:
+
+	import "websocket"
+
+	// receive text frame
+	var message string
+	websocket.Message.Receive(ws, &message)
+
+	// send text frame
+	message = "hello"
+	websocket.Message.Send(ws, message)
+
+	// receive binary frame
+	var data []byte
+	websocket.Message.Receive(ws, &data)
+
+	// send binary frame
+	data = []byte{0, 1, 2}
+	websocket.Message.Send(ws, data)
+
+*/
+var Message = Codec{marshal, unmarshal}
+
+func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err error) {
+	msg, err = json.Marshal(v)
+	return msg, TextFrame, err
+}
+
+func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
+	return json.Unmarshal(msg, v)
+}
+
+/*
+JSON is a codec to send/receive JSON data in a frame from a WebSocket connection.
+
+Trival usage:
+
+	import "websocket"
+
+	type T struct {
+		Msg string
+		Count int
+	}
+
+	// receive JSON type T
+	var data T
+	websocket.JSON.Receive(ws, &data)
+
+	// send JSON type T
+	websocket.JSON.Send(ws, data)
+*/
+var JSON = Codec{jsonMarshal, jsonUnmarshal}

+ 274 - 0
websocket/websocket_test.go

@@ -0,0 +1,274 @@
+// Copyright 2009 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 websocket
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"log"
+	"net"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"strings"
+	"sync"
+	"testing"
+)
+
+var serverAddr string
+var once sync.Once
+
+func echoServer(ws *Conn) { io.Copy(ws, ws) }
+
+type Count struct {
+	S string
+	N int
+}
+
+func countServer(ws *Conn) {
+	for {
+		var count Count
+		err := JSON.Receive(ws, &count)
+		if err != nil {
+			return
+		}
+		count.N++
+		count.S = strings.Repeat(count.S, count.N)
+		err = JSON.Send(ws, count)
+		if err != nil {
+			return
+		}
+	}
+}
+
+func startServer() {
+	http.Handle("/echo", Handler(echoServer))
+	http.Handle("/count", Handler(countServer))
+	server := httptest.NewServer(nil)
+	serverAddr = server.Listener.Addr().String()
+	log.Print("Test WebSocket server listening on ", serverAddr)
+}
+
+func newConfig(t *testing.T, path string) *Config {
+	config, _ := NewConfig(fmt.Sprintf("ws://%s%s", serverAddr, path), "http://localhost")
+	return config
+}
+
+func TestEcho(t *testing.T) {
+	once.Do(startServer)
+
+	// websocket.Dial()
+	client, err := net.Dial("tcp", serverAddr)
+	if err != nil {
+		t.Fatal("dialing", err)
+	}
+	conn, err := NewClient(newConfig(t, "/echo"), client)
+	if err != nil {
+		t.Errorf("WebSocket handshake error: %v", err)
+		return
+	}
+
+	msg := []byte("hello, world\n")
+	if _, err := conn.Write(msg); err != nil {
+		t.Errorf("Write: %v", err)
+	}
+	var actual_msg = make([]byte, 512)
+	n, err := conn.Read(actual_msg)
+	if err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	actual_msg = actual_msg[0:n]
+	if !bytes.Equal(msg, actual_msg) {
+		t.Errorf("Echo: expected %q got %q", msg, actual_msg)
+	}
+	conn.Close()
+}
+
+func TestAddr(t *testing.T) {
+	once.Do(startServer)
+
+	// websocket.Dial()
+	client, err := net.Dial("tcp", serverAddr)
+	if err != nil {
+		t.Fatal("dialing", err)
+	}
+	conn, err := NewClient(newConfig(t, "/echo"), client)
+	if err != nil {
+		t.Errorf("WebSocket handshake error: %v", err)
+		return
+	}
+
+	ra := conn.RemoteAddr().String()
+	if !strings.HasPrefix(ra, "ws://") || !strings.HasSuffix(ra, "/echo") {
+		t.Errorf("Bad remote addr: %v", ra)
+	}
+	la := conn.LocalAddr().String()
+	if !strings.HasPrefix(la, "http://") {
+		t.Errorf("Bad local addr: %v", la)
+	}
+	conn.Close()
+}
+
+func TestCount(t *testing.T) {
+	once.Do(startServer)
+
+	// websocket.Dial()
+	client, err := net.Dial("tcp", serverAddr)
+	if err != nil {
+		t.Fatal("dialing", err)
+	}
+	conn, err := NewClient(newConfig(t, "/count"), client)
+	if err != nil {
+		t.Errorf("WebSocket handshake error: %v", err)
+		return
+	}
+
+	var count Count
+	count.S = "hello"
+	if err := JSON.Send(conn, count); err != nil {
+		t.Errorf("Write: %v", err)
+	}
+	if err := JSON.Receive(conn, &count); err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	if count.N != 1 {
+		t.Errorf("count: expected %d got %d", 1, count.N)
+	}
+	if count.S != "hello" {
+		t.Errorf("count: expected %q got %q", "hello", count.S)
+	}
+	if err := JSON.Send(conn, count); err != nil {
+		t.Errorf("Write: %v", err)
+	}
+	if err := JSON.Receive(conn, &count); err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	if count.N != 2 {
+		t.Errorf("count: expected %d got %d", 2, count.N)
+	}
+	if count.S != "hellohello" {
+		t.Errorf("count: expected %q got %q", "hellohello", count.S)
+	}
+	conn.Close()
+}
+
+func TestWithQuery(t *testing.T) {
+	once.Do(startServer)
+
+	client, err := net.Dial("tcp", serverAddr)
+	if err != nil {
+		t.Fatal("dialing", err)
+	}
+
+	config := newConfig(t, "/echo")
+	config.Location, err = url.ParseRequest(fmt.Sprintf("ws://%s/echo?q=v", serverAddr))
+	if err != nil {
+		t.Fatal("location url", err)
+	}
+
+	ws, err := NewClient(config, client)
+	if err != nil {
+		t.Errorf("WebSocket handshake: %v", err)
+		return
+	}
+	ws.Close()
+}
+
+func TestWithProtocol(t *testing.T) {
+	once.Do(startServer)
+
+	client, err := net.Dial("tcp", serverAddr)
+	if err != nil {
+		t.Fatal("dialing", err)
+	}
+
+	config := newConfig(t, "/echo")
+	config.Protocol = append(config.Protocol, "test")
+
+	ws, err := NewClient(config, client)
+	if err != nil {
+		t.Errorf("WebSocket handshake: %v", err)
+		return
+	}
+	ws.Close()
+}
+
+func TestHTTP(t *testing.T) {
+	once.Do(startServer)
+
+	// If the client did not send a handshake that matches the protocol
+	// specification, the server MUST return an HTTP respose with an
+	// appropriate error code (such as 400 Bad Request)
+	resp, err := http.Get(fmt.Sprintf("http://%s/echo", serverAddr))
+	if err != nil {
+		t.Errorf("Get: error %#v", err)
+		return
+	}
+	if resp == nil {
+		t.Error("Get: resp is null")
+		return
+	}
+	if resp.StatusCode != http.StatusBadRequest {
+		t.Errorf("Get: expected %q got %q", http.StatusBadRequest, resp.StatusCode)
+	}
+}
+
+func TestTrailingSpaces(t *testing.T) {
+	// http://code.google.com/p/go/issues/detail?id=955
+	// The last runs of this create keys with trailing spaces that should not be
+	// generated by the client.
+	once.Do(startServer)
+	config := newConfig(t, "/echo")
+	for i := 0; i < 30; i++ {
+		// body
+		ws, err := DialConfig(config)
+		if err != nil {
+			t.Errorf("Dial #%d failed: %v", i, err)
+			break
+		}
+		ws.Close()
+	}
+}
+
+func TestSmallBuffer(t *testing.T) {
+	// http://code.google.com/p/go/issues/detail?id=1145
+	// Read should be able to handle reading a fragment of a frame.
+	once.Do(startServer)
+
+	// websocket.Dial()
+	client, err := net.Dial("tcp", serverAddr)
+	if err != nil {
+		t.Fatal("dialing", err)
+	}
+	conn, err := NewClient(newConfig(t, "/echo"), client)
+	if err != nil {
+		t.Errorf("WebSocket handshake error: %v", err)
+		return
+	}
+
+	msg := []byte("hello, world\n")
+	if _, err := conn.Write(msg); err != nil {
+		t.Errorf("Write: %v", err)
+	}
+	var small_msg = make([]byte, 8)
+	n, err := conn.Read(small_msg)
+	if err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	if !bytes.Equal(msg[:len(small_msg)], small_msg) {
+		t.Errorf("Echo: expected %q got %q", msg[:len(small_msg)], small_msg)
+	}
+	var second_msg = make([]byte, len(msg))
+	n, err = conn.Read(second_msg)
+	if err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	second_msg = second_msg[0:n]
+	if !bytes.Equal(msg[len(small_msg):], second_msg) {
+		t.Errorf("Echo: expected %q got %q", msg[len(small_msg):], second_msg)
+	}
+	conn.Close()
+}