mirror of https://github.com/portainer/portainer
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
3.4 KiB
146 lines
3.4 KiB
1 month ago
|
package ws
|
||
6 years ago
|
|
||
|
import (
|
||
7 months ago
|
"bufio"
|
||
2 years ago
|
"errors"
|
||
6 years ago
|
"fmt"
|
||
7 months ago
|
"io"
|
||
|
"net"
|
||
6 years ago
|
"net/http"
|
||
7 months ago
|
"sync"
|
||
|
"time"
|
||
4 years ago
|
|
||
|
"github.com/gorilla/websocket"
|
||
7 months ago
|
"github.com/rs/zerolog/log"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// Time allowed to write a message to the peer
|
||
1 month ago
|
WriteWait = 10 * time.Second
|
||
7 months ago
|
|
||
|
// Send pings to peer with this period
|
||
1 month ago
|
PingPeriod = 50 * time.Second
|
||
6 years ago
|
)
|
||
|
|
||
1 month ago
|
func HijackRequest(websocketConn *websocket.Conn, conn net.Conn, request *http.Request) error {
|
||
7 months ago
|
resp, err := sendHTTPRequest(conn, request)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer resp.Body.Close()
|
||
|
|
||
|
// Check if the response status code indicates an upgrade (101 Switching Protocols)
|
||
|
if resp.StatusCode != http.StatusSwitchingProtocols {
|
||
|
return fmt.Errorf("unexpected response status code: %d", resp.StatusCode)
|
||
|
}
|
||
|
|
||
1 month ago
|
var mu sync.Mutex
|
||
|
|
||
7 months ago
|
errorChan := make(chan error, 1)
|
||
1 month ago
|
go StreamFromWebsocketToWriter(websocketConn, conn, errorChan)
|
||
|
go WriteReaderToWebSocket(websocketConn, &mu, conn, errorChan)
|
||
7 months ago
|
|
||
|
err = <-errorChan
|
||
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) {
|
||
1 month ago
|
log.Debug().Err(err).Msg("unexpected close error")
|
||
|
|
||
7 months ago
|
return err
|
||
|
}
|
||
|
|
||
1 month ago
|
log.Info().Msg("session ended")
|
||
|
|
||
7 months ago
|
return nil
|
||
|
}
|
||
|
|
||
|
// sendHTTPRequest sends an HTTP request over the provided net.Conn and parses the response.
|
||
|
func sendHTTPRequest(conn net.Conn, req *http.Request) (*http.Response, error) {
|
||
|
// Send the HTTP request to the server
|
||
|
if err := req.Write(conn); err != nil {
|
||
|
return nil, fmt.Errorf("error writing request: %w", err)
|
||
|
}
|
||
|
|
||
|
// Read the response from the server
|
||
|
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("error reading response: %w", err)
|
||
|
}
|
||
|
|
||
|
return resp, nil
|
||
|
}
|
||
|
|
||
1 month ago
|
func WriteReaderToWebSocket(websocketConn *websocket.Conn, mu *sync.Mutex, reader io.Reader, errorChan chan error) {
|
||
|
out := make([]byte, ReaderBufferSize)
|
||
7 months ago
|
input := make(chan string)
|
||
1 month ago
|
pingTicker := time.NewTicker(PingPeriod)
|
||
7 months ago
|
defer pingTicker.Stop()
|
||
|
defer websocketConn.Close()
|
||
6 years ago
|
|
||
1 month ago
|
mu.Lock()
|
||
|
websocketConn.SetReadLimit(ReaderBufferSize)
|
||
7 months ago
|
websocketConn.SetPongHandler(func(string) error {
|
||
|
return nil
|
||
|
})
|
||
|
|
||
|
websocketConn.SetPingHandler(func(data string) error {
|
||
1 month ago
|
websocketConn.SetWriteDeadline(time.Now().Add(WriteWait))
|
||
|
|
||
7 months ago
|
return websocketConn.WriteMessage(websocket.PongMessage, []byte(data))
|
||
|
})
|
||
1 month ago
|
mu.Unlock()
|
||
6 years ago
|
|
||
7 months ago
|
go func() {
|
||
|
for {
|
||
|
n, err := reader.Read(out)
|
||
|
if err != nil {
|
||
|
errorChan <- err
|
||
1 month ago
|
|
||
7 months ago
|
if !errors.Is(err, io.EOF) {
|
||
1 month ago
|
log.Debug().Err(err).Msg("error reading from server")
|
||
7 months ago
|
}
|
||
1 month ago
|
|
||
7 months ago
|
return
|
||
|
}
|
||
1 year ago
|
|
||
1 month ago
|
processedOutput := ValidString(string(out[:n]))
|
||
6 months ago
|
input <- processedOutput
|
||
7 months ago
|
}
|
||
|
}()
|
||
|
|
||
|
for {
|
||
|
select {
|
||
|
case msg := <-input:
|
||
1 month ago
|
if err := wsWrite(websocketConn, mu, msg); err != nil {
|
||
|
log.Debug().Err(err).Msg("error writing to websocket")
|
||
7 months ago
|
errorChan <- err
|
||
1 month ago
|
|
||
7 months ago
|
return
|
||
|
}
|
||
|
case <-pingTicker.C:
|
||
1 month ago
|
if err := wsPing(websocketConn, mu); err != nil {
|
||
|
log.Debug().Err(err).Msg("error writing to websocket during pong response")
|
||
7 months ago
|
errorChan <- err
|
||
1 month ago
|
|
||
7 months ago
|
return
|
||
|
}
|
||
1 year ago
|
}
|
||
6 years ago
|
}
|
||
7 months ago
|
}
|
||
6 years ago
|
|
||
1 month ago
|
func wsWrite(websocketConn *websocket.Conn, mu *sync.Mutex, msg string) error {
|
||
7 months ago
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
|
||
1 month ago
|
websocketConn.SetWriteDeadline(time.Now().Add(WriteWait))
|
||
|
|
||
7 months ago
|
return websocketConn.WriteMessage(websocket.TextMessage, []byte(msg))
|
||
|
}
|
||
|
|
||
1 month ago
|
func wsPing(websocketConn *websocket.Conn, mu *sync.Mutex) error {
|
||
7 months ago
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
|
||
1 month ago
|
websocketConn.SetWriteDeadline(time.Now().Add(WriteWait))
|
||
|
|
||
7 months ago
|
return websocketConn.WriteMessage(websocket.PingMessage, nil)
|
||
6 years ago
|
}
|