[Golang] WebSocket 사용해보기

32742 단어 typescriptgo

소개



이해를 위해 Pion examples 먼저 Golang에서 WebSocket을 사용해 봅니다.
이번에는 서버 측에서 gorilla / websocket을 사용하겠습니다.


  • 환경


  • Go ver.1.18.1 windows/amd64
  • Node.js 버전 18.0.0
  • TypeScript 버전 4.6.3

  • 기본 프로젝트



    index.html




    <!DOCTYPE html>
    <html>
        <head>
            <title>Go Sample</title>
            <meta charset="utf-8">
            <link href="css/site.css" rel="stylesheet" />
        </head>
        <body>
            <div id="sample_message"></div>
            <textarea id="input_message"></textarea>
            <button onclick="Page.connect('{{.}}')">Connect</button>
            <button onclick="Page.send()">Send</button>
            <button onclick="Page.close()">Close</button>
            <div id="received_text_area"></div>
            <script src="js/main.page.js"></script>
        </body>
    </html>
    


    main.page.ts




    import { WebsocketMessage } from "./websocket.type";
    
    let ws: WebSocket|null = null;
    export function connect(url: string): void {
        ws = new WebSocket(url);
        ws.onopen = () => sendMessage({
            messageType: "text",
            data: "connected",
        });
        ws.onmessage = data => {
            const message = <WebsocketMessage>JSON.parse(data.data);
            switch(message.messageType) {
                case "text":
                    if(typeof message.data === "string") {
                        addReceivedMessage(message.data);
                    } else {
                        console.error(message.data);                  
                    }
                    break;
                default:
                    console.log(data);
                    break;
            }
        };
    }
    export function send() {
        const messageArea = document.getElementById("input_message") as HTMLTextAreaElement;
        sendMessage({
            messageType: "text",
            data: messageArea.value,
        });
    }
    export function close() {
        if(ws == null) {
            return;
        }
        ws.close();
        ws = null;
    }
    function addReceivedMessage(message: string) {
        const receivedMessageArea = document.getElementById("received_text_area") as HTMLElement;
        const child = document.createElement("div");
        child.textContent = message;
        receivedMessageArea.appendChild(child);
    }
    function sendMessage(message: WebsocketMessage) {
        if (ws == null) {
            return;
        }
        ws.send(JSON.stringify(message));
    }
    


    websocket.type.ts




    export type WebsocketMessage = {
        messageType: "text"
        data: string|Blob|ArrayBuffer,
    };
    


    main.go




    package main
    
    import (
        "html/template"
        "log"
        "net/http"
        "path/filepath"
        "sync"
    )
    
    type templateHandler struct {
        once     sync.Once
        filename string
        templ    *template.Template
    }
    
    func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        t.once.Do(func() {
            t.templ = template.Must(template.ParseFiles(filepath.Join("templates", t.filename)))
        })
        t.templ.Execute(w, "Hello world")
    }
    
    func main() {
        http.Handle("/css/", http.FileServer(http.Dir("templates")))
        http.Handle("/js/", http.FileServer(http.Dir("templates")))
        http.HandleFunc("/websocket", websocketHandler)
        http.Handle("/", &templateHandler{filename: "index.html"})
        log.Fatal(http.ListenAndServe("localhost:8080", nil))
    }
    


    WebSocket을 통해 내 자신의 메시지 수신 및 전송



    룸고




    package main
    
    import (
        "encoding/json"
        "log"
        "net/http"
        "strconv"
    
        "github.com/gorilla/websocket"
    )
    
    var upgrader = websocket.Upgrader{
        ReadBufferSize:  1024,
        WriteBufferSize: 1024,
    }
    
    type websocketMessage struct {
        MessageType string `json:"messageType"`
        Data        string `json:"data"`
    }
    
    func websocketHandler(w http.ResponseWriter, r *http.Request) {
        conn, err := upgrader.Upgrade(w, r, nil)
        if err != nil {
            log.Println(err)
            return
        }
        // Close the connection when the for-loop operation is finished.
        defer conn.Close()
    
        message := &websocketMessage{}
        for {
            // the first message is "connected"
            messageType, raw, err := conn.ReadMessage()
            if err != nil {
                log.Println(err)
                return
            } else if err := json.Unmarshal(raw, &message); err != nil {
                log.Println(err)
                return
            }
            conn.WriteJSON(message)
        }
    }
    


    업그레이드



    WebSocket 연결을 설정하려면 설정된 연결의 프로토콜을 HTTP/1.1에서 WebSocket으로 변경해야 합니다.
  • Upgrade - HTTP | MDN
  • Protocol upgrade mechanism - HTTP | MDN

  • 클라이언트 측의 WebSocket API는 WebSocket 관련 헤더를 추가합니다.

    요청 헤더(Google Chrome)




    Accept-Encoding: gzip, deflate, br
    Accept-Language: en-US,en;q=0.9,ja-JP;q=0.8,ja;q=0.7,zh-CN;q=0.6,zh;q=0.5
    Cache-Control: no-cache
    Connection: Upgrade
    Host: localhost:8080
    Origin: http://localhost:8080
    Pragma: no-cache
    Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
    Sec-WebSocket-Key: loDo1/V2L+3izvedjmEt9A==
    Sec-WebSocket-Version: 13
    Upgrade: websocket
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36
    


    "websocket.Upgrader.Upgrade"는 요청 헤더를 검사하여 새로운 연결(net.Conn)을 생성하고 연결을 이어받을 수 있도록 합니다.

    룸고




    ...
        var upgrader = websocket.Upgrader{
        ReadBufferSize:  1024,
        WriteBufferSize: 1024,
    }
    ...
    func websocketHandler(w http.ResponseWriter, r *http.Request) {
        conn, err := upgrader.Upgrade(w, r, nil)
    ...
    }
    


  • server.go - gorilla / websocket - GitHub

  • 연결 후 아래와 같은 응답 헤더를 얻을 수 있습니다.

    응답 헤더




    Connection: Upgrade
    Sec-WebSocket-Accept: /CQqmm5VOeDDGblpvMXlK56DWFs=
    Upgrade: websocket
    


    연결 관리



    다른 유저들과 대화를 하려면 .

    연결을 유지하기 위한 공통 사양이 없기 때문에 모든 샘플은 이를 다른 방식으로 구현합니다.

    예를 들어 sfu-ws of Pion example은 PeerConnection과의 연결을 유지합니다.
    액세스할 때 먼저 잠급니다.

    반면 Chat Example of gorilla/websocket은 하나의 "허브"인스턴스를 생성하고 여기에 연결을 설정합니다.

    이번에는 Pion 예제와 같은 연결을 유지합니다.

    룸고




    package main
    
    import (
        "encoding/json"
        "log"
        "net/http"
        "sync"
    
        "github.com/gorilla/websocket"
    )
    
    var (
        upgrader = websocket.Upgrader{
            ReadBufferSize:  1024,
            WriteBufferSize: 1024,
        }
        listLock    sync.RWMutex
        connections []connectionState
    )
    
    type websocketMessage struct {
        MessageType string `json:"messageType"`
        Data        string `json:"data"`
    }
    type connectionState struct {
        websocket *threadSafeWriter
    }
    type threadSafeWriter struct {
        *websocket.Conn
        sync.Mutex
    }
    
    func websocketHandler(w http.ResponseWriter, r *http.Request) {
        unsafeConn, err := upgrader.Upgrade(w, r, nil)
        if err != nil {
            log.Println(err)
            return
        }
        conn := &threadSafeWriter{unsafeConn, sync.Mutex{}}
        // Close the connection when the for-loop operation is finished.
        defer conn.Close()
        listLock.Lock()
        connections = append(connections, connectionState{websocket: conn})
        listLock.Unlock()
    
        message := &websocketMessage{}
        for {
            _, raw, err := conn.ReadMessage()
            if err != nil {
                log.Println(err)
                return
            } else if err := json.Unmarshal(raw, &message); err != nil {
                log.Println(err)
                return
            }
            for _, c := range connections {
                c.websocket.WriteJSON(message)
            }
        }
    }
    func (t *threadSafeWriter) WriteJSON(v interface{}) error {
        t.Lock()
        defer t.Unlock()
    
        return t.Conn.WriteJSON(v)
    }
    


    자원


  • RFC6455: The WebSocket Protocol
  • Writing WebSocket client applications - Web APIs | MDN
  • gorilla / websocket - GitHub
  • WebSockets Standard
  • Upgrade - HTTP | MDN
  • Protocol upgrade mechanism - HTTP | MDN
  • ハイパフォーマンス ブラウザネットワーキング(https://www.oreilly.com/library/view/high-performance-browser/9781449344757/)
  • Go言語によるWebアプリケーション開発(Go Programming Blueprints)
  • 좋은 웹페이지 즐겨찾기