| // 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" |
| "crypto/md5" |
| "encoding/binary" |
| "http" |
| "io" |
| "strings" |
| ) |
| |
| /* |
| Handler is an interface to a WebSocket. |
| A trivial example server is: |
| |
| package main |
| |
| import ( |
| "http" |
| "io" |
| "websocket" |
| ) |
| |
| // Echo the data received on the Web Socket. |
| 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.String()) |
| } |
| } |
| */ |
| type Handler func(*Conn) |
| |
| /* |
| 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 |
| } |
| |
| // ServeHTTP implements the http.Handler interface for a Web Socket |
| func (f Handler) ServeHTTP(c *http.Conn, req *http.Request) { |
| rwc, buf, err := c.Hijack() |
| if err != nil { |
| panic("Hijack failed: ", err.String()) |
| 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() |
| |
| if req.Method != "GET" { |
| return |
| } |
| // HTTP version can be safely ignored. |
| |
| if v, found := req.Header["Upgrade"]; !found || |
| strings.ToLower(v) != "websocket" { |
| return |
| } |
| if v, found := req.Header["Connection"]; !found || |
| strings.ToLower(v) != "upgrade" { |
| return |
| } |
| // TODO(ukai): check Host |
| origin, found := req.Header["Origin"] |
| if !found { |
| return |
| } |
| |
| key1, found := req.Header["Sec-Websocket-Key1"] |
| if !found { |
| return |
| } |
| key2, found := req.Header["Sec-Websocket-Key2"] |
| if !found { |
| return |
| } |
| key3 := make([]byte, 8) |
| if _, err := io.ReadFull(buf, key3); err != nil { |
| return |
| } |
| |
| location := "ws://" + req.Host + req.URL.RawPath |
| |
| // 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 |
| } |
| |
| // Step 6. key number must be an integral multiple of spaces. |
| if keyNumber1%space1 != 0 || keyNumber2%space2 != 0 { |
| return |
| } |
| |
| // Step 7. let part be key number divided by spaces. |
| part1 := keyNumber1 / space1 |
| part2 := keyNumber2 / space2 |
| |
| // Step 8. let challenge to be concatination of part1, part2 and key3. |
| challenge := make([]byte, 16) |
| challengeBuf := bytes.NewBuffer(challenge) |
| err = binary.Write(challengeBuf, binary.BigEndian, part1) |
| if err != nil { |
| return |
| } |
| err = binary.Write(challengeBuf, binary.BigEndian, part2) |
| if err != nil { |
| return |
| } |
| if n := copy(challenge[8:], key3); n != 8 { |
| return |
| } |
| // Step 9. get MD5 fingerprint of challenge. |
| h := md5.New() |
| if _, err = h.Write(challenge); err != nil { |
| return |
| } |
| response := h.Sum() |
| |
| // 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-Location: " + location + "\r\n") |
| buf.WriteString("Sec-WebSocket-Origin: " + origin + "\r\n") |
| protocol, found := req.Header["Sec-WebSocket-Protocol"] |
| if found { |
| buf.WriteString("Sec-WebSocket-Protocol: " + protocol + "\r\n") |
| } |
| // Step 12. send CRLF. |
| buf.WriteString("\r\n") |
| // Step 13. send response data. |
| buf.Write(response) |
| if err := buf.Flush(); err != nil { |
| return |
| } |
| ws := newConn(origin, location, protocol, buf, rwc) |
| f(ws) |
| } |
| |
| |
| /* |
| Draft75Handler is an interface to a WebSocket based on |
| (soon obsolete) draft-hixie-thewebsocketprotocol-75. |
| */ |
| type Draft75Handler func(*Conn) |
| |
| // ServeHTTP implements the http.Handler interface for a Web Socket. |
| func (f Draft75Handler) ServeHTTP(c *http.Conn, req *http.Request) { |
| if req.Method != "GET" || req.Proto != "HTTP/1.1" { |
| c.WriteHeader(http.StatusBadRequest) |
| io.WriteString(c, "Unexpected request") |
| return |
| } |
| if v, found := req.Header["Upgrade"]; !found || v != "WebSocket" { |
| c.WriteHeader(http.StatusBadRequest) |
| io.WriteString(c, "missing Upgrade: WebSocket header") |
| return |
| } |
| if v, found := req.Header["Connection"]; !found || v != "Upgrade" { |
| c.WriteHeader(http.StatusBadRequest) |
| io.WriteString(c, "missing Connection: Upgrade header") |
| return |
| } |
| origin, found := req.Header["Origin"] |
| if !found { |
| c.WriteHeader(http.StatusBadRequest) |
| io.WriteString(c, "missing Origin header") |
| return |
| } |
| |
| rwc, buf, err := c.Hijack() |
| if err != nil { |
| panic("Hijack failed: ", err.String()) |
| return |
| } |
| defer rwc.Close() |
| location := "ws://" + req.Host + req.URL.RawPath |
| |
| // TODO(ukai): verify origin,location,protocol. |
| |
| 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: " + origin + "\r\n") |
| buf.WriteString("WebSocket-Location: " + location + "\r\n") |
| protocol, found := req.Header["Websocket-Protocol"] |
| // canonical header key of WebSocket-Protocol. |
| if found { |
| buf.WriteString("WebSocket-Protocol: " + protocol + "\r\n") |
| } |
| buf.WriteString("\r\n") |
| if err := buf.Flush(); err != nil { |
| return |
| } |
| ws := newConn(origin, location, protocol, buf, rwc) |
| f(ws) |
| } |