javascript를 통해 http통신을 구현해본 것 처럼 이번에는 golang을 통해 websocket을 구현해볼 예정입니다.
main.go
package main
import (
"fmt"
"net/http"
)
func main() {
http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir("public"))))
fmt.Println("open web server: 8000")
http.ListenAndServe(":8000", nil)
}
위 코드를 통하여 웹서버 실행
public/test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
hello golang
</body>
</html>
실행 결과

브라우저에서 websocket 요청을 어떤식으로 보내는지 확인을 위한 handler 추가
main.go
func main() {
http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir("public"))))
http.HandleFunc("/websocket", handlerWebSocket)
fmt.Println("open web server: 8000")
http.ListenAndServe(":8000", nil)
}
func handlerWebSocket(w http.ResponseWriter, r *http.Request) {
dump, err := httputil.DumpRequest(r, true)
if err != nil {
http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
return
}
fmt.Println(string(dump))
}
test.html에 추가
<script>
document.addEventListener('DOMContentLoaded', event => {
const socket = new WebSocket('ws://localhost:8000/websocket');
})
</script>
실행 결과

웹소켓의 핸드쉐이크 방식은
https://www.rfc-editor.org/rfc/rfc6455#section-1.3
에 나타나 있습니다.
Globally Unique Identifier (GUID, [RFC4122]) "258EAFA5-E914-47DA- 95CA-C5AB0DC85B11" in string form, which is
unlikely to be used by network endpoints that do not understand the WebSocket Protocol. A SHA-1 hash (160 bits)
[FIPS.180-3], base64-encoded (see Section 4 of [RFC4648]), of this concatenation is then returned in the server's
handshake.
Concretely, if as in the example above, the |Sec-WebSocket-Key|
header field had the value "dGhlIHNhbXBsZSBub25jZQ==", the server
would concatenate the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
to form the string "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-
C5AB0DC85B11". The server would then take the SHA-1 hash of this,
giving the value 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6
0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea. This value is
then base64-encoded (see Section 4 of [RFC4648]), to give the value
"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=". This value would then be echoed in
the |Sec-WebSocket-Accept| header field.
1. Sec-Websocket-Key에 "258EAFA5-E914-47DA- 95CA-C5AB0DC85B11" 값을 추가한다.
2. 문자열을 SHA-1 암호화 후 base64 인코딩
3. Sec-WebSocket-Accept 값으로 해당 값 리턴
++ Sec-WebSocket-Protocol 해당 프로토콜은 애플리케이션 수준의 서브 프로토콜을 나타내고
main.go
func handlerWebSocket(w http.ResponseWriter, r *http.Request) {
hijacker := w.(http.Hijacker)
conn, readWriter, err := hijacker.Hijack()
if err != nil {
panic(err)
}
defer conn.Close()
key := r.Header.Get("Sec-Websocket-Key")
acceptKey := createWebSocketAcceptKey(key)
readWriter.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
readWriter.WriteString("Upgrade: websocket\r\n")
readWriter.WriteString("Connection: Upgrade\r\n")
readWriter.WriteString("Sec-WebSocket-Accept: " + acceptKey + "\r\n")
readWriter.WriteString("\r\n")
readWriter.Flush()
}
func createWebSocketAcceptKey(key string) string {
hash := sha1.New()
hash.Write([]byte(key))
hash.Write([]byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B12"))
return base64.StdEncoding.EncodeToString(hash.Sum(nil))
}
정상적으로 핸드쉐이크가 성공 했을 때

RFC 6455: The WebSocket Protocol
www.rfc-editor.org