forked from mirror/go-ethereum
It is no longer imported by any package in our tree.release/1.0.1
parent
bdba044a80
commit
f9c6bc63df
@ -1,112 +0,0 @@ |
|||||||
// 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/http" |
|
||||||
"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.ParseRequestURI(server) |
|
||||||
if err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
config.Origin, err = url.ParseRequestURI(origin) |
|
||||||
if err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
config.Header = http.Header(make(map[string][]string)) |
|
||||||
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) |
|
||||||
err = hybiClientHandshake(config, br, bw) |
|
||||||
if err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
buf := bufio.NewReadWriter(br, bw) |
|
||||||
ws = newHybiClientConn(config, buf, rwc) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// Dial opens a new client connection to a WebSocket.
|
|
||||||
func Dial(url_, protocol, origin string) (ws *Conn, err error) { |
|
||||||
config, err := NewConfig(url_, origin) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if protocol != "" { |
|
||||||
config.Protocol = []string{protocol} |
|
||||||
} |
|
||||||
return DialConfig(config) |
|
||||||
} |
|
||||||
|
|
||||||
var portMap = map[string]string{ |
|
||||||
"ws": "80", |
|
||||||
"wss": "443", |
|
||||||
} |
|
||||||
|
|
||||||
func parseAuthority(location *url.URL) string { |
|
||||||
if _, ok := portMap[location.Scheme]; ok { |
|
||||||
if _, _, err := net.SplitHostPort(location.Host); err != nil { |
|
||||||
return net.JoinHostPort(location.Host, portMap[location.Scheme]) |
|
||||||
} |
|
||||||
} |
|
||||||
return location.Host |
|
||||||
} |
|
||||||
|
|
||||||
// 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", parseAuthority(config.Location)) |
|
||||||
|
|
||||||
case "wss": |
|
||||||
client, err = tls.Dial("tcp", parseAuthority(config.Location), 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} |
|
||||||
} |
|
@ -1,31 +0,0 @@ |
|||||||
// Copyright 2012 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_test |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"log" |
|
||||||
|
|
||||||
"golang.org/x/net/websocket" |
|
||||||
) |
|
||||||
|
|
||||||
// This example demonstrates a trivial client.
|
|
||||||
func ExampleDial() { |
|
||||||
origin := "http://localhost/" |
|
||||||
url := "ws://localhost:12345/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) |
|
||||||
var n int |
|
||||||
if n, err = ws.Read(msg); err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
fmt.Printf("Received: %s.\n", msg[:n]) |
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
// Copyright 2012 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_test |
|
||||||
|
|
||||||
import ( |
|
||||||
"io" |
|
||||||
"net/http" |
|
||||||
|
|
||||||
"golang.org/x/net/websocket" |
|
||||||
) |
|
||||||
|
|
||||||
// Echo the data received on the WebSocket.
|
|
||||||
func EchoServer(ws *websocket.Conn) { |
|
||||||
io.Copy(ws, ws) |
|
||||||
} |
|
||||||
|
|
||||||
// This example demonstrates a trivial echo server.
|
|
||||||
func ExampleHandler() { |
|
||||||
http.Handle("/echo", websocket.Handler(EchoServer)) |
|
||||||
err := http.ListenAndServe(":12345", nil) |
|
||||||
if err != nil { |
|
||||||
panic("ListenAndServe: " + err.Error()) |
|
||||||
} |
|
||||||
} |
|
@ -1,564 +0,0 @@ |
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package 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"} |
|
||||||
|
|
||||||
handshakeHeader = map[string]bool{ |
|
||||||
"Host": true, |
|
||||||
"Upgrade": true, |
|
||||||
"Connection": true, |
|
||||||
"Sec-Websocket-Key": true, |
|
||||||
"Sec-Websocket-Origin": true, |
|
||||||
"Sec-Websocket-Version": true, |
|
||||||
"Sec-Websocket-Protocol": true, |
|
||||||
"Sec-Websocket-Accept": true, |
|
||||||
} |
|
||||||
) |
|
||||||
|
|
||||||
// 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 Framing 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) |
|
||||||
data := make([]byte, length) |
|
||||||
for i := range data { |
|
||||||
data[i] = 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 |
|
||||||
} |
|
||||||
|
|
||||||
// generateNonce generates 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 |
|
||||||
} |
|
||||||
|
|
||||||
// Client handshake described in draft-ietf-hybi-thewebsocket-protocol-17
|
|
||||||
func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) { |
|
||||||
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") |
|
||||||
bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n") |
|
||||||
|
|
||||||
if config.Version != ProtocolVersionHybi13 { |
|
||||||
return ErrBadProtocolVersion |
|
||||||
} |
|
||||||
|
|
||||||
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 Sec-WebSocket-Extensions.
|
|
||||||
err = config.Header.WriteSubset(bw, handshakeHeader) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
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") |
|
||||||
switch version { |
|
||||||
case "13": |
|
||||||
c.Version = ProtocolVersionHybi13 |
|
||||||
default: |
|
||||||
return http.StatusBadRequest, ErrBadWebSocketVersion |
|
||||||
} |
|
||||||
var scheme string |
|
||||||
if req.TLS != nil { |
|
||||||
scheme = "wss" |
|
||||||
} else { |
|
||||||
scheme = "ws" |
|
||||||
} |
|
||||||
c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI()) |
|
||||||
if err != nil { |
|
||||||
return http.StatusBadRequest, err |
|
||||||
} |
|
||||||
protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol")) |
|
||||||
if 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 |
|
||||||
} |
|
||||||
|
|
||||||
// Origin parses Origin header in "req".
|
|
||||||
// If origin is "null", returns (nil, nil).
|
|
||||||
func Origin(config *Config, req *http.Request) (*url.URL, error) { |
|
||||||
var origin string |
|
||||||
switch config.Version { |
|
||||||
case ProtocolVersionHybi13: |
|
||||||
origin = req.Header.Get("Origin") |
|
||||||
} |
|
||||||
if origin == "null" { |
|
||||||
return nil, nil |
|
||||||
} |
|
||||||
return url.ParseRequestURI(origin) |
|
||||||
} |
|
||||||
|
|
||||||
func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) { |
|
||||||
if len(c.Protocol) > 0 { |
|
||||||
if len(c.Protocol) != 1 { |
|
||||||
// You need choose a Protocol in Handshake func in Server.
|
|
||||||
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): send Sec-WebSocket-Extensions.
|
|
||||||
if c.Header != nil { |
|
||||||
err := c.Header.WriteSubset(buf, handshakeHeader) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
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) |
|
||||||
} |
|
@ -1,590 +0,0 @@ |
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package 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.ParseRequestURI("ws://server.example.com/chat") |
|
||||||
if err != nil { |
|
||||||
t.Fatal("location url", err) |
|
||||||
} |
|
||||||
config.Origin, err = url.ParseRequestURI("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 TestHybiClientHandshakeWithHeader(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.ParseRequestURI("ws://server.example.com/chat") |
|
||||||
if err != nil { |
|
||||||
t.Fatal("location url", err) |
|
||||||
} |
|
||||||
config.Origin, err = url.ParseRequestURI("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.Header = http.Header(make(map[string][]string)) |
|
||||||
config.Header.Add("User-Agent", "test") |
|
||||||
|
|
||||||
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), |
|
||||||
"User-Agent": "test", |
|
||||||
} |
|
||||||
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) |
|
||||||
} |
|
||||||
expectedProtocols := []string{"chat", "superchat"} |
|
||||||
if fmt.Sprintf("%v", config.Protocol) != fmt.Sprintf("%v", expectedProtocols) { |
|
||||||
t.Errorf("protocol expected %q but got %q", expectedProtocols, config.Protocol) |
|
||||||
} |
|
||||||
b := bytes.NewBuffer([]byte{}) |
|
||||||
bw := bufio.NewWriter(b) |
|
||||||
|
|
||||||
config.Protocol = config.Protocol[:1] |
|
||||||
|
|
||||||
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 TestHybiServerHandshakeNoSubProtocol(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-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) |
|
||||||
} |
|
||||||
if len(config.Protocol) != 0 { |
|
||||||
t.Errorf("len(config.Protocol) expected 0, but got %q", len(config.Protocol)) |
|
||||||
} |
|
||||||
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 Switching Protocols", |
|
||||||
"Upgrade: websocket", |
|
||||||
"Connection: Upgrade", |
|
||||||
"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", |
|
||||||
"", ""}, "\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()) |
|
||||||
} |
|
||||||
} |
|
@ -1,114 +0,0 @@ |
|||||||
// 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, config *Config, handshake func(*Config, *http.Request) error) (conn *Conn, err error) { |
|
||||||
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 { |
|
||||||
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 |
|
||||||
} |
|
||||||
if handshake != nil { |
|
||||||
err = handshake(config, req) |
|
||||||
if err != nil { |
|
||||||
code = http.StatusForbidden |
|
||||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) |
|
||||||
buf.WriteString("\r\n") |
|
||||||
buf.Flush() |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
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 |
|
||||||
} |
|
||||||
|
|
||||||
// Server represents a server of a WebSocket.
|
|
||||||
type Server struct { |
|
||||||
// Config is a WebSocket configuration for new WebSocket connection.
|
|
||||||
Config |
|
||||||
|
|
||||||
// Handshake is an optional function in WebSocket handshake.
|
|
||||||
// For example, you can check, or don't check Origin header.
|
|
||||||
// Another example, you can select config.Protocol.
|
|
||||||
Handshake func(*Config, *http.Request) error |
|
||||||
|
|
||||||
// Handler handles a WebSocket connection.
|
|
||||||
Handler |
|
||||||
} |
|
||||||
|
|
||||||
// ServeHTTP implements the http.Handler interface for a WebSocket
|
|
||||||
func (s Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
|
||||||
s.serveWebSocket(w, req) |
|
||||||
} |
|
||||||
|
|
||||||
func (s Server) serveWebSocket(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, &s.Config, s.Handshake) |
|
||||||
if err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
if conn == nil { |
|
||||||
panic("unexpected nil conn") |
|
||||||
} |
|
||||||
s.Handler(conn) |
|
||||||
} |
|
||||||
|
|
||||||
// Handler is a simple interface to a WebSocket browser client.
|
|
||||||
// It checks if Origin header is valid URL by default.
|
|
||||||
// You might want to verify websocket.Conn.Config().Origin in the func.
|
|
||||||
// If you use Server instead of Handler, you could call websocket.Origin and
|
|
||||||
// check the origin in your Handshake func. So, if you want to accept
|
|
||||||
// non-browser client, which doesn't send Origin header, you could use Server
|
|
||||||
//. that doesn't check origin in its Handshake.
|
|
||||||
type Handler func(*Conn) |
|
||||||
|
|
||||||
func checkOrigin(config *Config, req *http.Request) (err error) { |
|
||||||
config.Origin, err = Origin(config, req) |
|
||||||
if err == nil && config.Origin == nil { |
|
||||||
return fmt.Errorf("null origin") |
|
||||||
} |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// ServeHTTP implements the http.Handler interface for a WebSocket
|
|
||||||
func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
|
||||||
s := Server{Handler: h, Handshake: checkOrigin} |
|
||||||
s.serveWebSocket(w, req) |
|
||||||
} |
|
@ -1,411 +0,0 @@ |
|||||||
// 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
|
|
||||||
// as specified in RFC 6455.
|
|
||||||
package websocket |
|
||||||
|
|
||||||
import ( |
|
||||||
"bufio" |
|
||||||
"crypto/tls" |
|
||||||
"encoding/json" |
|
||||||
"errors" |
|
||||||
"io" |
|
||||||
"io/ioutil" |
|
||||||
"net" |
|
||||||
"net/http" |
|
||||||
"net/url" |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
ProtocolVersionHybi13 = 13 |
|
||||||
ProtocolVersionHybi = ProtocolVersionHybi13 |
|
||||||
SupportedProtocolVersion = "13" |
|
||||||
|
|
||||||
ContinuationFrame = 0 |
|
||||||
TextFrame = 1 |
|
||||||
BinaryFrame = 2 |
|
||||||
CloseFrame = 8 |
|
||||||
PingFrame = 9 |
|
||||||
PongFrame = 10 |
|
||||||
UnknownFrame = 255 |
|
||||||
) |
|
||||||
|
|
||||||
// ProtocolError represents 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 |
|
||||||
|
|
||||||
// Additional header fields to be sent in WebSocket opening handshake.
|
|
||||||
Header http.Header |
|
||||||
|
|
||||||
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 payload 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) { |
|
||||||
data, payloadType, err := cd.Marshal(v) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
ws.wio.Lock() |
|
||||||
defer ws.wio.Unlock() |
|
||||||
w, err := ws.frameWriterFactory.NewFrameWriter(payloadType) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
_, 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. |
|
||||||
|
|
||||||
Trivial 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} |
|
@ -1,414 +0,0 @@ |
|||||||
// 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 subProtocolHandshake(config *Config, req *http.Request) error { |
|
||||||
for _, proto := range config.Protocol { |
|
||||||
if proto == "chat" { |
|
||||||
config.Protocol = []string{proto} |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
return ErrBadWebSocketProtocol |
|
||||||
} |
|
||||||
|
|
||||||
func subProtoServer(ws *Conn) { |
|
||||||
for _, proto := range ws.Config().Protocol { |
|
||||||
io.WriteString(ws, proto) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func startServer() { |
|
||||||
http.Handle("/echo", Handler(echoServer)) |
|
||||||
http.Handle("/count", Handler(countServer)) |
|
||||||
subproto := Server{ |
|
||||||
Handshake: subProtocolHandshake, |
|
||||||
Handler: Handler(subProtoServer), |
|
||||||
} |
|
||||||
http.Handle("/subproto", subproto) |
|
||||||
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.ParseRequestURI(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, subproto []string) (string, error) { |
|
||||||
once.Do(startServer) |
|
||||||
|
|
||||||
client, err := net.Dial("tcp", serverAddr) |
|
||||||
if err != nil { |
|
||||||
t.Fatal("dialing", err) |
|
||||||
} |
|
||||||
|
|
||||||
config := newConfig(t, "/subproto") |
|
||||||
config.Protocol = subproto |
|
||||||
|
|
||||||
ws, err := NewClient(config, client) |
|
||||||
if err != nil { |
|
||||||
return "", err |
|
||||||
} |
|
||||||
msg := make([]byte, 16) |
|
||||||
n, err := ws.Read(msg) |
|
||||||
if err != nil { |
|
||||||
return "", err |
|
||||||
} |
|
||||||
ws.Close() |
|
||||||
return string(msg[:n]), nil |
|
||||||
} |
|
||||||
|
|
||||||
func TestWithProtocol(t *testing.T) { |
|
||||||
proto, err := testWithProtocol(t, []string{"chat"}) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("SubProto: unexpected error: %v", err) |
|
||||||
} |
|
||||||
if proto != "chat" { |
|
||||||
t.Errorf("SubProto: expected %q, got %q", "chat", proto) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestWithTwoProtocol(t *testing.T) { |
|
||||||
proto, err := testWithProtocol(t, []string{"test", "chat"}) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("SubProto: unexpected error: %v", err) |
|
||||||
} |
|
||||||
if proto != "chat" { |
|
||||||
t.Errorf("SubProto: expected %q, got %q", "chat", proto) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestWithBadProtocol(t *testing.T) { |
|
||||||
_, err := testWithProtocol(t, []string{"test"}) |
|
||||||
if err != ErrBadStatus { |
|
||||||
t.Errorf("SubProto: expected %v, got %v", ErrBadStatus, err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
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 response 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 TestDialConfigBadVersion(t *testing.T) { |
|
||||||
once.Do(startServer) |
|
||||||
config := newConfig(t, "/echo") |
|
||||||
config.Version = 1234 |
|
||||||
|
|
||||||
_, err := DialConfig(config) |
|
||||||
|
|
||||||
if dialerr, ok := err.(*DialError); ok { |
|
||||||
if dialerr.Err != ErrBadProtocolVersion { |
|
||||||
t.Errorf("dial expected err %q but got %q", ErrBadProtocolVersion, dialerr.Err) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
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() |
|
||||||
} |
|
||||||
|
|
||||||
var parseAuthorityTests = []struct { |
|
||||||
in *url.URL |
|
||||||
out string |
|
||||||
}{ |
|
||||||
{ |
|
||||||
&url.URL{ |
|
||||||
Scheme: "ws", |
|
||||||
Host: "www.google.com", |
|
||||||
}, |
|
||||||
"www.google.com:80", |
|
||||||
}, |
|
||||||
{ |
|
||||||
&url.URL{ |
|
||||||
Scheme: "wss", |
|
||||||
Host: "www.google.com", |
|
||||||
}, |
|
||||||
"www.google.com:443", |
|
||||||
}, |
|
||||||
{ |
|
||||||
&url.URL{ |
|
||||||
Scheme: "ws", |
|
||||||
Host: "www.google.com:80", |
|
||||||
}, |
|
||||||
"www.google.com:80", |
|
||||||
}, |
|
||||||
{ |
|
||||||
&url.URL{ |
|
||||||
Scheme: "wss", |
|
||||||
Host: "www.google.com:443", |
|
||||||
}, |
|
||||||
"www.google.com:443", |
|
||||||
}, |
|
||||||
// some invalid ones for parseAuthority. parseAuthority doesn't
|
|
||||||
// concern itself with the scheme unless it actually knows about it
|
|
||||||
{ |
|
||||||
&url.URL{ |
|
||||||
Scheme: "http", |
|
||||||
Host: "www.google.com", |
|
||||||
}, |
|
||||||
"www.google.com", |
|
||||||
}, |
|
||||||
{ |
|
||||||
&url.URL{ |
|
||||||
Scheme: "http", |
|
||||||
Host: "www.google.com:80", |
|
||||||
}, |
|
||||||
"www.google.com:80", |
|
||||||
}, |
|
||||||
{ |
|
||||||
&url.URL{ |
|
||||||
Scheme: "asdf", |
|
||||||
Host: "127.0.0.1", |
|
||||||
}, |
|
||||||
"127.0.0.1", |
|
||||||
}, |
|
||||||
{ |
|
||||||
&url.URL{ |
|
||||||
Scheme: "asdf", |
|
||||||
Host: "www.google.com", |
|
||||||
}, |
|
||||||
"www.google.com", |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
func TestParseAuthority(t *testing.T) { |
|
||||||
for _, tt := range parseAuthorityTests { |
|
||||||
out := parseAuthority(tt.in) |
|
||||||
if out != tt.out { |
|
||||||
t.Errorf("got %v; want %v", out, tt.out) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue