istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/echo/server/forwarder/websocket.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package forwarder 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "net" 22 "strings" 23 "time" 24 25 "github.com/gorilla/websocket" 26 27 "istio.io/istio/pkg/test/echo" 28 "istio.io/istio/pkg/test/echo/common" 29 "istio.io/istio/pkg/test/echo/proto" 30 ) 31 32 var _ protocol = &websocketProtocol{} 33 34 type websocketProtocol struct { 35 e *executor 36 } 37 38 func newWebsocketProtocol(e *executor) protocol { 39 return &websocketProtocol{e: e} 40 } 41 42 func (c *websocketProtocol) ForwardEcho(ctx context.Context, cfg *Config) (*proto.ForwardEchoResponse, error) { 43 return doForward(ctx, cfg, c.e, c.makeRequest) 44 } 45 46 func (c *websocketProtocol) Close() error { 47 return nil 48 } 49 50 func (c *websocketProtocol) makeRequest(ctx context.Context, cfg *Config, requestID int) (string, error) { 51 req := cfg.Request 52 var outBuffer bytes.Buffer 53 echo.ForwarderURLField.WriteForRequest(&outBuffer, requestID, req.Url) 54 55 // Set the special header to trigger the upgrade to WebSocket. 56 wsReq := cfg.headers.Clone() 57 if len(cfg.hostHeader) > 0 { 58 echo.HostField.WriteForRequest(&outBuffer, requestID, hostHeader) 59 } 60 writeForwardedHeaders(&outBuffer, requestID, wsReq) 61 common.SetWebSocketHeader(wsReq) 62 63 if req.Message != "" { 64 echo.ForwarderMessageField.WriteForRequest(&outBuffer, requestID, req.Message) 65 } 66 67 dialContext := func(network, addr string) (net.Conn, error) { 68 return newDialer(cfg).Dial(network, addr) 69 } 70 if len(cfg.UDS) > 0 { 71 dialContext = func(network, addr string) (net.Conn, error) { 72 return newDialer(cfg).Dial("unix", cfg.UDS) 73 } 74 } 75 76 dialer := &websocket.Dialer{ 77 TLSClientConfig: cfg.tlsConfig, 78 NetDial: dialContext, 79 HandshakeTimeout: cfg.timeout, 80 } 81 82 conn, _, err := dialer.Dial(req.Url, wsReq) 83 if err != nil { 84 // timeout or bad handshake 85 return outBuffer.String(), err 86 } 87 defer func() { 88 _ = conn.Close() 89 }() 90 91 // Apply per-request timeout to calculate deadline for reads/writes. 92 ctx, cancel := context.WithTimeout(ctx, cfg.timeout) 93 defer cancel() 94 95 // Apply the deadline to the connection. 96 deadline, _ := ctx.Deadline() 97 if err := conn.SetWriteDeadline(deadline); err != nil { 98 return outBuffer.String(), err 99 } 100 if err := conn.SetReadDeadline(deadline); err != nil { 101 return outBuffer.String(), err 102 } 103 104 start := time.Now() 105 err = conn.WriteMessage(websocket.TextMessage, []byte(req.Message)) 106 if err != nil { 107 return outBuffer.String(), err 108 } 109 110 _, resp, err := conn.ReadMessage() 111 if err != nil { 112 return outBuffer.String(), err 113 } 114 115 echo.LatencyField.WriteForRequest(&outBuffer, requestID, fmt.Sprintf("%v", time.Since(start))) 116 echo.ActiveRequestsField.WriteForRequest(&outBuffer, requestID, fmt.Sprintf("%d", c.e.ActiveRequests())) 117 for _, line := range strings.Split(string(resp), "\n") { 118 if line != "" { 119 echo.WriteBodyLine(&outBuffer, requestID, line) 120 } 121 } 122 123 return outBuffer.String(), nil 124 }