traefik/vendor/github.com/vulcand/oxy/forward/fwd.go

582 lines
16 KiB
Go
Raw Normal View History

2018-08-20 08:38:03 +00:00
// Package forward implements http handler that forwards requests to remote server
2017-02-07 21:33:23 +00:00
// and serves back the response
// websocket proxying support based on https://github.com/yhat/wsutil
package forward
import (
2018-08-20 08:38:03 +00:00
"bytes"
2017-02-07 21:33:23 +00:00
"crypto/tls"
2018-02-12 16:24:03 +00:00
"errors"
2018-01-22 11:16:03 +00:00
"fmt"
2018-08-20 08:38:03 +00:00
"io"
"net"
2017-02-07 21:33:23 +00:00
"net/http"
2017-11-22 17:20:03 +00:00
"net/http/httptest"
"net/http/httputil"
2017-02-07 21:33:23 +00:00
"net/url"
"os"
2017-09-29 19:04:03 +00:00
"reflect"
2017-02-07 21:33:23 +00:00
"strings"
"time"
"github.com/gorilla/websocket"
2018-01-22 11:16:03 +00:00
log "github.com/sirupsen/logrus"
2017-02-07 21:33:23 +00:00
"github.com/vulcand/oxy/utils"
)
2018-08-20 08:38:03 +00:00
// OxyLogger interface of the internal
2018-02-12 16:24:03 +00:00
type OxyLogger interface {
log.FieldLogger
GetLevel() log.Level
}
type internalLogger struct {
*log.Logger
}
func (i *internalLogger) GetLevel() log.Level {
return i.Level
}
2017-02-07 21:33:23 +00:00
// ReqRewriter can alter request headers and body
type ReqRewriter interface {
Rewrite(r *http.Request)
}
type optSetter func(f *Forwarder) error
2018-08-20 08:38:03 +00:00
// PassHostHeader specifies if a client's Host header field should be delegated
2017-02-07 21:33:23 +00:00
func PassHostHeader(b bool) optSetter {
return func(f *Forwarder) error {
2017-11-22 17:20:03 +00:00
f.httpForwarder.passHost = b
2017-02-07 21:33:23 +00:00
return nil
}
}
// RoundTripper sets a new http.RoundTripper
// Forwarder will use http.DefaultTransport as a default round tripper
func RoundTripper(r http.RoundTripper) optSetter {
return func(f *Forwarder) error {
2017-11-22 17:20:03 +00:00
f.httpForwarder.roundTripper = r
2017-02-07 21:33:23 +00:00
return nil
}
}
// Rewriter defines a request rewriter for the HTTP forwarder
func Rewriter(r ReqRewriter) optSetter {
return func(f *Forwarder) error {
f.httpForwarder.rewriter = r
return nil
}
}
2018-08-20 08:38:03 +00:00
// WebsocketTLSClientConfig define the websocker client TLS configuration
2017-11-22 17:20:03 +00:00
func WebsocketTLSClientConfig(tcc *tls.Config) optSetter {
2017-02-07 21:33:23 +00:00
return func(f *Forwarder) error {
2017-11-22 17:20:03 +00:00
f.httpForwarder.tlsClientConfig = tcc
2017-02-07 21:33:23 +00:00
return nil
}
}
// ErrorHandler is a functional argument that sets error handler of the server
func ErrorHandler(h utils.ErrorHandler) optSetter {
return func(f *Forwarder) error {
f.errHandler = h
return nil
}
}
2018-06-07 07:46:03 +00:00
// BufferPool specifies a buffer pool for httputil.ReverseProxy.
func BufferPool(pool httputil.BufferPool) optSetter {
return func(f *Forwarder) error {
f.bufferPool = pool
return nil
}
}
2017-11-22 17:20:03 +00:00
// Stream specifies if HTTP responses should be streamed.
func Stream(stream bool) optSetter {
return func(f *Forwarder) error {
f.stream = stream
return nil
}
}
// Logger defines the logger the forwarder will use.
//
// It defaults to logrus.StandardLogger(), the global logger used by logrus.
2018-02-12 16:24:03 +00:00
func Logger(l log.FieldLogger) optSetter {
2017-02-07 21:33:23 +00:00
return func(f *Forwarder) error {
2018-02-12 16:24:03 +00:00
if logger, ok := l.(OxyLogger); ok {
f.log = logger
return nil
}
if logger, ok := l.(*log.Logger); ok {
f.log = &internalLogger{Logger: logger}
return nil
}
return errors.New("the type of the logger must be OxyLogger or logrus.Logger")
2017-02-07 21:33:23 +00:00
}
}
2018-08-20 08:38:03 +00:00
// StateListener defines a state listener for the HTTP forwarder
2017-11-22 17:20:03 +00:00
func StateListener(stateListener UrlForwardingStateListener) optSetter {
return func(f *Forwarder) error {
f.stateListener = stateListener
return nil
}
}
2018-08-20 08:38:03 +00:00
// WebsocketConnectionClosedHook defines a hook called when websocket connection is closed
func WebsocketConnectionClosedHook(hook func(req *http.Request, conn net.Conn)) optSetter {
return func(f *Forwarder) error {
f.httpForwarder.websocketConnectionClosedHook = hook
return nil
}
}
// ResponseModifier defines a response modifier for the HTTP forwarder
2017-11-22 17:20:03 +00:00
func ResponseModifier(responseModifier func(*http.Response) error) optSetter {
return func(f *Forwarder) error {
f.httpForwarder.modifyResponse = responseModifier
return nil
}
}
2018-08-20 08:38:03 +00:00
// StreamingFlushInterval defines a streaming flush interval for the HTTP forwarder
2017-11-22 17:20:03 +00:00
func StreamingFlushInterval(flushInterval time.Duration) optSetter {
return func(f *Forwarder) error {
f.httpForwarder.flushInterval = flushInterval
return nil
}
}
2018-08-20 08:38:03 +00:00
// ErrorHandlingRoundTripper a error handling round tripper
2017-11-22 17:20:03 +00:00
type ErrorHandlingRoundTripper struct {
http.RoundTripper
errorHandler utils.ErrorHandler
}
2018-08-20 08:38:03 +00:00
// RoundTrip executes the round trip
2017-11-22 17:20:03 +00:00
func (rt ErrorHandlingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
res, err := rt.RoundTripper.RoundTrip(req)
if err != nil {
// We use the recorder from httptest because there isn't another `public` implementation of a recorder.
recorder := httptest.NewRecorder()
rt.errorHandler.ServeHTTP(recorder, req, err)
res = recorder.Result()
err = nil
}
return res, err
}
2017-02-07 21:33:23 +00:00
// Forwarder wraps two traffic forwarding implementations: HTTP and websockets.
// It decides based on the specified request which implementation to use
type Forwarder struct {
*httpForwarder
*handlerContext
2017-11-22 17:20:03 +00:00
stateListener UrlForwardingStateListener
stream bool
2017-02-07 21:33:23 +00:00
}
// handlerContext defines a handler context for error reporting and logging
type handlerContext struct {
errHandler utils.ErrorHandler
}
// httpForwarder is a handler that can reverse proxy
// HTTP traffic
type httpForwarder struct {
roundTripper http.RoundTripper
rewriter ReqRewriter
passHost bool
2017-11-22 17:20:03 +00:00
flushInterval time.Duration
modifyResponse func(*http.Response) error
2017-02-07 21:33:23 +00:00
2017-11-22 17:20:03 +00:00
tlsClientConfig *tls.Config
2018-02-12 16:24:03 +00:00
log OxyLogger
2018-06-07 07:46:03 +00:00
2018-08-20 08:38:03 +00:00
bufferPool httputil.BufferPool
websocketConnectionClosedHook func(req *http.Request, conn net.Conn)
2017-02-07 21:33:23 +00:00
}
2018-08-20 08:38:03 +00:00
const defaultFlushInterval = time.Duration(100) * time.Millisecond
// Connection states
2017-11-22 17:20:03 +00:00
const (
2018-08-20 08:38:03 +00:00
StateConnected = iota
2017-11-22 17:20:03 +00:00
StateDisconnected
)
2018-08-20 08:38:03 +00:00
// UrlForwardingStateListener URL forwarding state listener
2017-11-22 17:20:03 +00:00
type UrlForwardingStateListener func(*url.URL, int)
2017-02-07 21:33:23 +00:00
// New creates an instance of Forwarder based on the provided list of configuration options
func New(setters ...optSetter) (*Forwarder, error) {
f := &Forwarder{
2018-02-12 16:24:03 +00:00
httpForwarder: &httpForwarder{log: &internalLogger{Logger: log.StandardLogger()}},
2017-11-22 17:20:03 +00:00
handlerContext: &handlerContext{},
2017-02-07 21:33:23 +00:00
}
for _, s := range setters {
if err := s(f); err != nil {
return nil, err
}
}
2017-11-22 17:20:03 +00:00
if !f.stream {
f.flushInterval = 0
} else if f.flushInterval == 0 {
f.flushInterval = defaultFlushInterval
2017-02-07 21:33:23 +00:00
}
2017-11-22 17:20:03 +00:00
2017-02-07 21:33:23 +00:00
if f.httpForwarder.rewriter == nil {
h, err := os.Hostname()
if err != nil {
h = "localhost"
}
f.httpForwarder.rewriter = &HeaderRewriter{TrustForwardHeader: true, Hostname: h}
}
2017-11-22 17:20:03 +00:00
if f.httpForwarder.roundTripper == nil {
f.httpForwarder.roundTripper = http.DefaultTransport
2017-02-07 21:33:23 +00:00
}
2017-11-22 17:20:03 +00:00
2017-02-07 21:33:23 +00:00
if f.errHandler == nil {
f.errHandler = utils.DefaultHandler
}
2017-11-22 17:20:03 +00:00
if f.tlsClientConfig == nil {
if ht, ok := f.httpForwarder.roundTripper.(*http.Transport); ok {
f.tlsClientConfig = ht.TLSClientConfig
}
2017-11-22 17:20:03 +00:00
}
f.httpForwarder.roundTripper = ErrorHandlingRoundTripper{
RoundTripper: f.httpForwarder.roundTripper,
errorHandler: f.errHandler,
}
2018-08-27 16:10:03 +00:00
f.postConfig()
2017-02-07 21:33:23 +00:00
return f, nil
}
// ServeHTTP decides which forwarder to use based on the specified
// request and delegates to the proper implementation
func (f *Forwarder) ServeHTTP(w http.ResponseWriter, req *http.Request) {
2018-02-12 16:24:03 +00:00
if f.log.GetLevel() >= log.DebugLevel {
2017-11-22 17:20:03 +00:00
logEntry := f.log.WithField("Request", utils.DumpHttpRequest(req))
logEntry.Debug("vulcand/oxy/forward: begin ServeHttp on request")
defer logEntry.Debug("vulcand/oxy/forward: completed ServeHttp on request")
2017-02-07 21:33:23 +00:00
}
2017-11-22 17:20:03 +00:00
if f.stateListener != nil {
f.stateListener(req.URL, StateConnected)
defer f.stateListener(req.URL, StateDisconnected)
2017-02-07 21:33:23 +00:00
}
2017-11-22 17:20:03 +00:00
if IsWebsocketRequest(req) {
f.httpForwarder.serveWebSocket(w, req, f.handlerContext)
} else {
f.httpForwarder.serveHTTP(w, req, f.handlerContext)
2017-06-29 14:41:44 +00:00
}
2017-11-22 17:20:03 +00:00
}
2017-06-29 14:41:44 +00:00
2017-11-22 17:20:03 +00:00
func (f *httpForwarder) getUrlFromRequest(req *http.Request) *url.URL {
// If the Request was created by Go via a real HTTP request, RequestURI will
// contain the original query string. If the Request was created in code, RequestURI
// will be empty, and we will use the URL object instead
u := req.URL
if req.RequestURI != "" {
parsedURL, err := url.ParseRequestURI(req.RequestURI)
2017-02-07 21:33:23 +00:00
if err == nil {
2017-11-22 17:20:03 +00:00
u = parsedURL
} else {
f.log.Warnf("vulcand/oxy/forward: error when parsing RequestURI: %s", err)
2017-02-07 21:33:23 +00:00
}
}
2017-11-22 17:20:03 +00:00
return u
}
2017-10-24 12:38:02 +00:00
2017-11-22 17:20:03 +00:00
// Modify the request to handle the target URL
func (f *httpForwarder) modifyRequest(outReq *http.Request, target *url.URL) {
outReq.URL = utils.CopyURL(outReq.URL)
outReq.URL.Scheme = target.Scheme
outReq.URL.Host = target.Host
2017-02-07 21:33:23 +00:00
2017-11-22 17:20:03 +00:00
u := f.getUrlFromRequest(outReq)
2017-02-07 21:33:23 +00:00
2017-11-22 17:20:03 +00:00
outReq.URL.Path = u.Path
outReq.URL.RawPath = u.RawPath
outReq.URL.RawQuery = u.RawQuery
outReq.RequestURI = "" // Outgoing request should not have RequestURI
2017-02-07 21:33:23 +00:00
outReq.Proto = "HTTP/1.1"
outReq.ProtoMajor = 1
outReq.ProtoMinor = 1
if f.rewriter != nil {
f.rewriter.Rewrite(outReq)
}
2018-08-20 08:38:03 +00:00
// Do not pass client Host header unless optsetter PassHostHeader is set.
if !f.passHost {
outReq.Host = target.Host
}
2017-02-07 21:33:23 +00:00
}
// serveHTTP forwards websocket traffic
2017-11-22 17:20:03 +00:00
func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request, ctx *handlerContext) {
2018-02-12 16:24:03 +00:00
if f.log.GetLevel() >= log.DebugLevel {
2017-11-22 17:20:03 +00:00
logEntry := f.log.WithField("Request", utils.DumpHttpRequest(req))
logEntry.Debug("vulcand/oxy/forward/websocket: begin ServeHttp on request")
defer logEntry.Debug("vulcand/oxy/forward/websocket: completed ServeHttp on request")
2017-11-22 17:20:03 +00:00
}
outReq := f.copyWebSocketRequest(req)
2017-02-07 21:33:23 +00:00
dialer := websocket.DefaultDialer
2018-01-22 11:16:03 +00:00
2017-11-22 17:20:03 +00:00
if outReq.URL.Scheme == "wss" && f.tlsClientConfig != nil {
dialer.TLSClientConfig = f.tlsClientConfig.Clone()
// WebSocket is only in http/1.1
2017-10-20 15:38:04 +00:00
dialer.TLSClientConfig.NextProtos = []string{"http/1.1"}
2017-02-07 21:33:23 +00:00
}
2018-08-27 16:10:03 +00:00
targetConn, resp, err := dialer.DialContext(outReq.Context(), outReq.URL.String(), outReq.Header)
2017-02-07 21:33:23 +00:00
if err != nil {
2017-09-29 19:04:03 +00:00
if resp == nil {
ctx.errHandler.ServeHTTP(w, req, err)
} else {
2018-09-24 08:04:03 +00:00
f.log.Errorf("vulcand/oxy/forward/websocket: Error dialing %q: %v with resp: %d %s", outReq.Host, err, resp.StatusCode, resp.Status)
2017-09-29 19:04:03 +00:00
hijacker, ok := w.(http.Hijacker)
if !ok {
2018-09-24 08:04:03 +00:00
f.log.Errorf("vulcand/oxy/forward/websocket: %s can not be hijack", reflect.TypeOf(w))
2017-09-29 19:04:03 +00:00
ctx.errHandler.ServeHTTP(w, req, err)
return
}
2017-11-22 17:20:03 +00:00
conn, _, errHijack := hijacker.Hijack()
if errHijack != nil {
2018-09-24 08:04:03 +00:00
f.log.Errorf("vulcand/oxy/forward/websocket: Failed to hijack responseWriter")
2017-11-22 17:20:03 +00:00
ctx.errHandler.ServeHTTP(w, req, errHijack)
2017-09-29 19:04:03 +00:00
return
}
defer func() {
conn.Close()
if f.websocketConnectionClosedHook != nil {
f.websocketConnectionClosedHook(req, conn)
}
}()
2017-09-29 19:04:03 +00:00
2017-11-22 17:20:03 +00:00
errWrite := resp.Write(conn)
if errWrite != nil {
2018-09-24 08:04:03 +00:00
f.log.Errorf("vulcand/oxy/forward/websocket: Failed to forward response")
2017-11-22 17:20:03 +00:00
ctx.errHandler.ServeHTTP(w, req, errWrite)
2017-09-29 19:04:03 +00:00
return
}
}
2017-02-07 21:33:23 +00:00
return
}
2017-11-22 17:20:03 +00:00
// Only the targetConn choose to CheckOrigin or not
upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
return true
}}
utils.RemoveHeaders(resp.Header, WebsocketUpgradeHeaders...)
utils.CopyHeaders(resp.Header, w.Header())
2018-01-22 11:16:03 +00:00
underlyingConn, err := upgrader.Upgrade(w, req, resp.Header)
2017-02-07 21:33:23 +00:00
if err != nil {
2018-09-24 08:04:03 +00:00
f.log.Errorf("vulcand/oxy/forward/websocket: Error while upgrading connection : %v", err)
2017-02-07 21:33:23 +00:00
return
}
2018-08-20 08:38:03 +00:00
defer func() {
underlyingConn.Close()
targetConn.Close()
if f.websocketConnectionClosedHook != nil {
f.websocketConnectionClosedHook(req, underlyingConn.UnderlyingConn())
}
}()
2017-02-07 21:33:23 +00:00
2018-02-12 16:24:03 +00:00
errClient := make(chan error, 1)
errBackend := make(chan error, 1)
2018-01-22 11:16:03 +00:00
replicateWebsocketConn := func(dst, src *websocket.Conn, errc chan error) {
2018-08-20 08:38:03 +00:00
forward := func(messageType int, reader io.Reader) error {
writer, err := dst.NextWriter(messageType)
if err != nil {
return err
}
_, err = io.Copy(writer, reader)
if err != nil {
return err
}
return writer.Close()
}
src.SetPingHandler(func(data string) error {
return forward(websocket.PingMessage, bytes.NewReader([]byte(data)))
})
src.SetPongHandler(func(data string) error {
return forward(websocket.PongMessage, bytes.NewReader([]byte(data)))
})
for {
2018-08-20 08:38:03 +00:00
msgType, reader, err := src.NextReader()
2018-01-22 11:16:03 +00:00
if err != nil {
2018-01-22 11:16:03 +00:00
m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err))
if e, ok := err.(*websocket.CloseError); ok {
if e.Code != websocket.CloseNoStatusReceived {
m = nil
// Following codes are not valid on the wire so just close the
// underlying TCP connection without sending a close frame.
if e.Code != websocket.CloseAbnormalClosure &&
e.Code != websocket.CloseTLSHandshake {
m = websocket.FormatCloseMessage(e.Code, e.Text)
}
2018-01-22 11:16:03 +00:00
}
}
errc <- err
if m != nil {
2018-08-20 08:38:03 +00:00
forward(websocket.CloseMessage, bytes.NewReader([]byte(m)))
}
break
}
2018-08-20 08:38:03 +00:00
err = forward(msgType, reader)
if err != nil {
2018-01-22 11:16:03 +00:00
errc <- err
break
}
2017-11-22 17:20:03 +00:00
}
2017-02-07 21:33:23 +00:00
}
2018-01-22 11:16:03 +00:00
go replicateWebsocketConn(underlyingConn, targetConn, errClient)
go replicateWebsocketConn(targetConn, underlyingConn, errBackend)
2018-01-22 11:16:03 +00:00
var message string
select {
case err = <-errClient:
message = "vulcand/oxy/forward/websocket: Error when copying from backend to client: %v"
case err = <-errBackend:
message = "vulcand/oxy/forward/websocket: Error when copying from client to backend: %v"
}
if e, ok := err.(*websocket.CloseError); !ok || e.Code == websocket.CloseAbnormalClosure {
f.log.Errorf(message, err)
}
2017-02-07 21:33:23 +00:00
}
2017-11-22 17:20:03 +00:00
// copyWebsocketRequest makes a copy of the specified request.
func (f *httpForwarder) copyWebSocketRequest(req *http.Request) (outReq *http.Request) {
2017-02-07 21:33:23 +00:00
outReq = new(http.Request)
*outReq = *req // includes shallow copies of maps, but we handle this below
outReq.URL = utils.CopyURL(req.URL)
2017-11-22 17:20:03 +00:00
outReq.URL.Scheme = req.URL.Scheme
2017-11-22 17:20:03 +00:00
// sometimes backends might be registered as HTTP/HTTPS servers so translate URLs to websocket URLs.
switch req.URL.Scheme {
case "https":
outReq.URL.Scheme = "wss"
case "http":
outReq.URL.Scheme = "ws"
}
2017-11-22 17:20:03 +00:00
u := f.getUrlFromRequest(outReq)
outReq.URL.Path = u.Path
outReq.URL.RawPath = u.RawPath
outReq.URL.RawQuery = u.RawQuery
outReq.RequestURI = "" // Outgoing request should not have RequestURI
2017-08-20 17:02:02 +00:00
2017-11-22 17:20:03 +00:00
outReq.URL.Host = req.URL.Host
2018-10-10 16:20:05 +00:00
if !f.passHost {
outReq.Host = req.URL.Host
}
2017-02-07 21:33:23 +00:00
outReq.Header = make(http.Header)
2017-11-22 17:20:03 +00:00
// gorilla websocket use this header to set the request.Host tested in checkSameOrigin
outReq.Header.Set("Host", outReq.Host)
2017-02-07 21:33:23 +00:00
utils.CopyHeaders(outReq.Header, req.Header)
utils.RemoveHeaders(outReq.Header, WebsocketDialHeaders...)
2017-02-07 21:33:23 +00:00
if f.rewriter != nil {
f.rewriter.Rewrite(outReq)
}
return outReq
}
2017-11-22 17:20:03 +00:00
// serveHTTP forwards HTTP traffic using the configured transport
func (f *httpForwarder) serveHTTP(w http.ResponseWriter, inReq *http.Request, ctx *handlerContext) {
2018-02-12 16:24:03 +00:00
if f.log.GetLevel() >= log.DebugLevel {
2017-11-22 17:20:03 +00:00
logEntry := f.log.WithField("Request", utils.DumpHttpRequest(inReq))
logEntry.Debug("vulcand/oxy/forward/http: begin ServeHttp on request")
defer logEntry.Debug("vulcand/oxy/forward/http: completed ServeHttp on request")
}
start := time.Now().UTC()
outReq := new(http.Request)
*outReq = *inReq // includes shallow copies of maps, but we handle this in Director
revproxy := httputil.ReverseProxy{
Director: func(req *http.Request) {
f.modifyRequest(req, inReq.URL)
},
Transport: f.roundTripper,
FlushInterval: f.flushInterval,
ModifyResponse: f.modifyResponse,
2018-06-07 07:46:03 +00:00
BufferPool: f.bufferPool,
2017-11-22 17:20:03 +00:00
}
if f.log.GetLevel() >= log.DebugLevel {
pw := utils.NewProxyWriter(w)
revproxy.ServeHTTP(pw, outReq)
if inReq.TLS != nil {
f.log.Debugf("vulcand/oxy/forward/http: Round trip: %v, code: %v, Length: %v, duration: %v tls:version: %x, tls:resume:%t, tls:csuite:%x, tls:server:%v",
inReq.URL, pw.StatusCode(), pw.GetLength(), time.Now().UTC().Sub(start),
inReq.TLS.Version,
inReq.TLS.DidResume,
inReq.TLS.CipherSuite,
inReq.TLS.ServerName)
} else {
f.log.Debugf("vulcand/oxy/forward/http: Round trip: %v, code: %v, Length: %v, duration: %v",
inReq.URL, pw.StatusCode(), pw.GetLength(), time.Now().UTC().Sub(start))
}
2017-11-22 17:20:03 +00:00
} else {
revproxy.ServeHTTP(w, outReq)
2017-11-22 17:20:03 +00:00
}
for key := range w.Header() {
if strings.HasPrefix(key, http.TrailerPrefix) {
if fl, ok := w.(http.Flusher); ok {
fl.Flush()
}
break
}
}
2017-11-22 17:20:03 +00:00
}
2018-08-20 08:38:03 +00:00
// IsWebsocketRequest determines if the specified HTTP request is a
2017-02-07 21:33:23 +00:00
// websocket handshake request
2017-11-22 17:20:03 +00:00
func IsWebsocketRequest(req *http.Request) bool {
2017-02-07 21:33:23 +00:00
containsHeader := func(name, value string) bool {
items := strings.Split(req.Header.Get(name), ",")
for _, item := range items {
if value == strings.ToLower(strings.TrimSpace(item)) {
return true
}
}
return false
}
return containsHeader(Connection, "upgrade") && containsHeader(Upgrade, "websocket")
}