Oxy with gorilla for websocket(+integration tests)
This commit is contained in:
parent
a09a8b1235
commit
888e6dcbc8
7 changed files with 184 additions and 81 deletions
90
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
90
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
|
@ -4,18 +4,16 @@
|
|||
package forward
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
|
||||
|
@ -255,77 +253,47 @@ func (f *httpForwarder) copyRequest(req *http.Request, u *url.URL) *http.Request
|
|||
// serveHTTP forwards websocket traffic
|
||||
func (f *websocketForwarder) serveHTTP(w http.ResponseWriter, req *http.Request, ctx *handlerContext) {
|
||||
outReq := f.copyRequest(req, req.URL)
|
||||
host := outReq.URL.Host
|
||||
dial := net.Dial
|
||||
|
||||
// if host does not specify a port, use the default http port
|
||||
if !strings.Contains(host, ":") {
|
||||
if outReq.URL.Scheme == "wss" {
|
||||
host = host + ":443"
|
||||
} else {
|
||||
host = host + ":80"
|
||||
}
|
||||
dialer := websocket.DefaultDialer
|
||||
if outReq.URL.Scheme == "wss" && f.TLSClientConfig != nil {
|
||||
dialer.TLSClientConfig = f.TLSClientConfig
|
||||
}
|
||||
|
||||
if outReq.URL.Scheme == "wss" {
|
||||
if f.TLSClientConfig == nil {
|
||||
f.TLSClientConfig = http.DefaultTransport.(*http.Transport).TLSClientConfig
|
||||
}
|
||||
dial = func(network, address string) (net.Conn, error) {
|
||||
return tls.Dial("tcp", host, f.TLSClientConfig)
|
||||
}
|
||||
}
|
||||
|
||||
targetConn, err := dial("tcp", host)
|
||||
targetConn, resp, err := dialer.Dial(outReq.URL.String(), outReq.Header)
|
||||
if err != nil {
|
||||
ctx.log.Errorf("Error dialing `%v`: %v", host, err)
|
||||
ctx.log.Errorf("Error dialing `%v`: %v", outReq.Host, err)
|
||||
ctx.errHandler.ServeHTTP(w, req, err)
|
||||
return
|
||||
}
|
||||
hijacker, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
ctx.log.Errorf("Unable to hijack the connection: %v", reflect.TypeOf(w))
|
||||
ctx.errHandler.ServeHTTP(w, req, nil)
|
||||
return
|
||||
}
|
||||
underlyingConn, _, err := hijacker.Hijack()
|
||||
upgrader := websocket.Upgrader{}
|
||||
utils.RemoveHeaders(resp.Header, WebsocketUpgradeHeaders...)
|
||||
underlyingConn, err := upgrader.Upgrade(w, req, resp.Header)
|
||||
if err != nil {
|
||||
ctx.log.Errorf("Unable to hijack the connection: %v %v", reflect.TypeOf(w), err)
|
||||
ctx.errHandler.ServeHTTP(w, req, err)
|
||||
ctx.log.Errorf("Error while upgrading connection : %v", err)
|
||||
return
|
||||
}
|
||||
// it is now caller's responsibility to Close the underlying connection
|
||||
defer underlyingConn.Close()
|
||||
defer targetConn.Close()
|
||||
|
||||
ctx.log.Infof("Writing outgoing Websocket request to target connection: %+v", outReq)
|
||||
|
||||
// write the modified incoming request to the dialed connection
|
||||
if err = outReq.Write(targetConn); err != nil {
|
||||
ctx.log.Errorf("Unable to copy request to target: %v", err)
|
||||
ctx.errHandler.ServeHTTP(w, req, err)
|
||||
return
|
||||
errc := make(chan error, 2)
|
||||
replicate := func(dst io.Writer, src io.Reader) {
|
||||
_, err := io.Copy(dst, src)
|
||||
errc <- err
|
||||
}
|
||||
|
||||
br := bufio.NewReader(targetConn)
|
||||
resp, err := http.ReadResponse(br, req)
|
||||
resp.Write(underlyingConn)
|
||||
defer resp.Body.Close()
|
||||
go replicate(targetConn.UnderlyingConn(), underlyingConn.UnderlyingConn())
|
||||
|
||||
// We connect the conn only if the switching protocol has not failed
|
||||
if resp.StatusCode == http.StatusSwitchingProtocols {
|
||||
ctx.log.Infof("Switching protocol success")
|
||||
errc := make(chan error, 2)
|
||||
replicate := func(dst io.Writer, src io.Reader) {
|
||||
_, err := io.Copy(dst, src)
|
||||
errc <- err
|
||||
}
|
||||
go replicate(targetConn, underlyingConn)
|
||||
go replicate(underlyingConn, targetConn)
|
||||
<-errc
|
||||
// Try to read the first message
|
||||
t, msg, err := targetConn.ReadMessage()
|
||||
if err != nil {
|
||||
ctx.log.Errorf("Couldn't read first message : %v", err)
|
||||
} else {
|
||||
ctx.log.Infof("Switching protocol failed")
|
||||
underlyingConn.WriteMessage(t, msg)
|
||||
}
|
||||
|
||||
go replicate(underlyingConn.UnderlyingConn(), targetConn.UnderlyingConn())
|
||||
<-errc
|
||||
|
||||
}
|
||||
|
||||
// copyRequest makes a copy of the specified request.
|
||||
|
@ -335,6 +303,7 @@ func (f *websocketForwarder) copyRequest(req *http.Request, u *url.URL) (outReq
|
|||
|
||||
outReq.URL = utils.CopyURL(req.URL)
|
||||
outReq.URL.Scheme = u.Scheme
|
||||
outReq.URL.Path = outReq.RequestURI
|
||||
|
||||
//sometimes backends might be registered as HTTP/HTTPS servers so translate URLs to websocket URLs.
|
||||
switch u.Scheme {
|
||||
|
@ -345,19 +314,12 @@ func (f *websocketForwarder) copyRequest(req *http.Request, u *url.URL) (outReq
|
|||
}
|
||||
|
||||
outReq.URL.Host = u.Host
|
||||
outReq.URL.Opaque = req.RequestURI
|
||||
// raw query is already included in RequestURI, so ignore it to avoid dupes
|
||||
outReq.URL.RawQuery = ""
|
||||
|
||||
outReq.Proto = "HTTP/1.1"
|
||||
outReq.ProtoMajor = 1
|
||||
outReq.ProtoMinor = 1
|
||||
|
||||
// Overwrite close flag so we can keep persistent connection for the backend servers
|
||||
outReq.Close = false
|
||||
|
||||
outReq.Header = make(http.Header)
|
||||
utils.CopyHeaders(outReq.Header, req.Header)
|
||||
utils.RemoveHeaders(outReq.Header, WebsocketDialHeaders...)
|
||||
|
||||
if f.rewriter != nil {
|
||||
f.rewriter.Rewrite(outReq)
|
||||
|
|
49
vendor/github.com/vulcand/oxy/forward/headers.go
generated
vendored
49
vendor/github.com/vulcand/oxy/forward/headers.go
generated
vendored
|
@ -1,20 +1,25 @@
|
|||
package forward
|
||||
|
||||
const (
|
||||
XForwardedProto = "X-Forwarded-Proto"
|
||||
XForwardedFor = "X-Forwarded-For"
|
||||
XForwardedHost = "X-Forwarded-Host"
|
||||
XForwardedServer = "X-Forwarded-Server"
|
||||
Connection = "Connection"
|
||||
KeepAlive = "Keep-Alive"
|
||||
ProxyAuthenticate = "Proxy-Authenticate"
|
||||
ProxyAuthorization = "Proxy-Authorization"
|
||||
Te = "Te" // canonicalized version of "TE"
|
||||
Trailers = "Trailers"
|
||||
TransferEncoding = "Transfer-Encoding"
|
||||
Upgrade = "Upgrade"
|
||||
ContentLength = "Content-Length"
|
||||
ContentType = "Content-Type"
|
||||
XForwardedProto = "X-Forwarded-Proto"
|
||||
XForwardedFor = "X-Forwarded-For"
|
||||
XForwardedHost = "X-Forwarded-Host"
|
||||
XForwardedServer = "X-Forwarded-Server"
|
||||
Connection = "Connection"
|
||||
KeepAlive = "Keep-Alive"
|
||||
ProxyAuthenticate = "Proxy-Authenticate"
|
||||
ProxyAuthorization = "Proxy-Authorization"
|
||||
Te = "Te" // canonicalized version of "TE"
|
||||
Trailers = "Trailers"
|
||||
TransferEncoding = "Transfer-Encoding"
|
||||
Upgrade = "Upgrade"
|
||||
ContentLength = "Content-Length"
|
||||
ContentType = "Content-Type"
|
||||
SecWebsocketKey = "Sec-Websocket-Key"
|
||||
SecWebsocketVersion = "Sec-Websocket-Version"
|
||||
SecWebsocketExtensions = "Sec-Websocket-Extensions"
|
||||
SecWebsocketProtocol = "Sec-Websocket-Protocol"
|
||||
SecWebsocketAccept = "Sec-Websocket-Accept"
|
||||
)
|
||||
|
||||
// Hop-by-hop headers. These are removed when sent to the backend.
|
||||
|
@ -30,3 +35,19 @@ var HopHeaders = []string{
|
|||
TransferEncoding,
|
||||
Upgrade,
|
||||
}
|
||||
|
||||
var WebsocketDialHeaders = []string{
|
||||
Upgrade,
|
||||
Connection,
|
||||
SecWebsocketKey,
|
||||
SecWebsocketVersion,
|
||||
SecWebsocketExtensions,
|
||||
SecWebsocketProtocol,
|
||||
SecWebsocketAccept,
|
||||
}
|
||||
|
||||
var WebsocketUpgradeHeaders = []string{
|
||||
Upgrade,
|
||||
Connection,
|
||||
SecWebsocketAccept,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue