Path: blob/main/components/gitpod-protocol/go/websocket.go
2498 views
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.3/*---------------------------------------------------------------------------------------------4* Copyright (c) 2020 Jaime Pillora <[email protected]>. All rights reserved.5* Licensed under the MIT License. See https://github.com/jpillora/chisel/blob/7aa0da95db178b8bc4f20ab49128368348fd4410/LICENSE for license information.6*--------------------------------------------------------------------------------------------*/7// copied and modified from https://github.com/jpillora/chisel/blob/33fa2010abd42ec76ed9011995f5240642b1a3c5/share/cnet/conn_ws.go8package protocol910import (11"context"12"sync"13"time"1415"github.com/gorilla/websocket"16)1718type WebsocketConnection struct {19*websocket.Conn20buff []byte2122Ctx context.Context23cancel func()2425once sync.Once26closeErr error27waitDone chan struct{}28}2930var (31// Time allowed to write a message to the peer.32writeWait = 10 * time.Second3334// Time allowed to read the next pong message from the peer.35pongWait = 15 * time.Second3637// Send pings to peer with this period. Must be less than pongWait.38pingPeriod = (pongWait * 9) / 1039)4041// NewWebsocketConnection converts a websocket.Conn into a net.Conn42func NewWebsocketConnection(ctx context.Context, websocketConn *websocket.Conn, onStale func(staleErr error)) (*WebsocketConnection, error) {43ctx, cancel := context.WithCancel(ctx)44c := &WebsocketConnection{45Conn: websocketConn,46waitDone: make(chan struct{}),47Ctx: ctx,48cancel: cancel,49}50err := c.SetReadDeadline(time.Now().Add(pongWait))51if err != nil {52return nil, err53}54c.SetPongHandler(func(string) error { c.SetReadDeadline(time.Now().Add(pongWait)); return nil })5556go func() {57defer c.Close()58ticker := time.NewTicker(pingPeriod)59defer ticker.Stop()60for {61select {62case <-ctx.Done():63return64case <-ticker.C:65staleErr := c.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait))66if staleErr != nil {67onStale(staleErr)68return69}70}71}72}()73return c, nil74}7576// Close closes the connection77func (c *WebsocketConnection) Close() error {78c.once.Do(func() {79c.cancel()80c.closeErr = c.Conn.Close()81close(c.waitDone)82})83return c.closeErr84}8586// Wait waits till the connection is closed.87func (c *WebsocketConnection) Wait() error {88<-c.waitDone89return c.closeErr90}9192// Read is not threadsafe though thats okay since there93// should never be more than one reader94func (c *WebsocketConnection) Read(dst []byte) (int, error) {95ldst := len(dst)96//use buffer or read new message97var src []byte98if len(c.buff) > 0 {99src = c.buff100c.buff = nil101} else if _, msg, err := c.Conn.ReadMessage(); err == nil {102src = msg103} else {104return 0, err105}106//copy src->dest107var n int108if len(src) > ldst {109//copy as much as possible of src into dst110n = copy(dst, src[:ldst])111//copy remainder into buffer112r := src[ldst:]113lr := len(r)114c.buff = make([]byte, lr)115copy(c.buff, r)116} else {117//copy all of src into dst118n = copy(dst, src)119}120//return bytes copied121return n, nil122}123124func (c *WebsocketConnection) Write(b []byte) (int, error) {125err := c.Conn.WriteMessage(websocket.BinaryMessage, b)126if err != nil {127return 0, err128}129n := len(b)130return n, nil131}132133func (c *WebsocketConnection) SetDeadline(t time.Time) error {134if err := c.Conn.SetReadDeadline(t); err != nil {135return err136}137return c.Conn.SetWriteDeadline(t)138}139140141