forked from bitly/oauth2_proxy
-
Notifications
You must be signed in to change notification settings - Fork 1
/
websocket.go
138 lines (122 loc) · 4.08 KB
/
websocket.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package main
import (
"io"
"log"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/gorilla/websocket"
)
var (
ConnectionHeaderKey = http.CanonicalHeaderKey("connection")
SetCookieHeaderKey = http.CanonicalHeaderKey("set-cookie")
UpgradeHeaderKey = http.CanonicalHeaderKey("upgrade")
WSKeyHeaderKey = http.CanonicalHeaderKey("sec-websocket-key")
WSProtocolHeaderKey = http.CanonicalHeaderKey("sec-websocket-protocol")
WSVersionHeaderKey = http.CanonicalHeaderKey("sec-websocket-version")
WSExtensionsHeaderKey = http.CanonicalHeaderKey("sec-websocket-extensions")
ConnectionHeaderValue = "Upgrade"
UpgradeHeaderValue = "websocket"
HandshakeHeaders = []string{ConnectionHeaderKey, UpgradeHeaderKey, WSVersionHeaderKey, WSKeyHeaderKey, WSExtensionsHeaderKey}
UpgradeHeaders = []string{SetCookieHeaderKey, WSProtocolHeaderKey}
)
func (u *UpstreamProxy) handleWebsocket(w http.ResponseWriter, r *http.Request) {
// Copy request headers and remove websocket handshaking headers
// before submitting to the upstream server
upstreamHeader := http.Header{}
for key, _ := range r.Header {
copyHeader(&upstreamHeader, r.Header, key)
}
for _, header := range HandshakeHeaders {
delete(upstreamHeader, header)
}
upstreamHeader.Set("Host", r.Host)
// Connect upstream
upstreamAddr := u.upstreamWSURL(*r.URL).String()
upstream, upstreamResp, err := u.wsd.Dial(upstreamAddr, upstreamHeader)
if err != nil {
if upstreamResp != nil {
log.Printf("dialing upstream websocket failed with code %d: %v", upstreamResp.StatusCode, err)
} else {
log.Printf("dialing upstream websocket failed: %v", err)
}
http.Error(w, "websocket unavailable", http.StatusServiceUnavailable)
return
}
defer upstream.Close()
// Pass websocket handshake response headers to the upgrader
upgradeHeader := http.Header{}
copyHeaders(&upgradeHeader, upstreamResp.Header, UpgradeHeaders)
// Upgrade the client connection without validating the origin
upgrader := websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
client, err := upgrader.Upgrade(w, r, upgradeHeader)
if err != nil {
log.Printf("couldn't upgrade websocket request: %v", err)
http.Error(w, "websocket upgrade failed", http.StatusServiceUnavailable)
return
}
// Wire both sides together and close when finished
var wg sync.WaitGroup
cp := func(dst, src *websocket.Conn) {
defer wg.Done()
_, err := io.Copy(dst.UnderlyingConn(), src.UnderlyingConn())
var closeMessage []byte
if err != nil {
closeMessage = websocket.FormatCloseMessage(websocket.CloseProtocolError, err.Error())
} else {
closeMessage = websocket.FormatCloseMessage(websocket.CloseNormalClosure, "bye")
}
// Attempt to close the connection properly
dst.WriteControl(websocket.CloseMessage, closeMessage, time.Now().Add(2*time.Second))
src.WriteControl(websocket.CloseMessage, closeMessage, time.Now().Add(2*time.Second))
}
wg.Add(2)
go cp(upstream, client)
go cp(client, upstream)
wg.Wait()
}
// Create a websocket URL from the request URL
func (u *UpstreamProxy) upstreamWSURL(r url.URL) *url.URL {
ws := r
ws.User = r.User
ws.Host = u.upstream.Host
ws.Fragment = ""
switch u.upstream.Scheme {
case "http":
ws.Scheme = "ws"
case "https":
ws.Scheme = "wss"
}
return &ws
}
func isWebsocketRequest(req *http.Request) bool {
return isHeaderValuePresent(req.Header, UpgradeHeaderKey, UpgradeHeaderValue) &&
isHeaderValuePresent(req.Header, ConnectionHeaderKey, ConnectionHeaderValue)
}
func isHeaderValuePresent(headers http.Header, key string, value string) bool {
for _, header := range headers[key] {
for _, v := range strings.Split(header, ",") {
if strings.EqualFold(value, strings.TrimSpace(v)) {
return true
}
}
}
return false
}
func copyHeaders(dst *http.Header, src http.Header, headers []string) {
for _, header := range headers {
copyHeader(dst, src, header)
}
}
// Copy any non-empty and non-blank header values
func copyHeader(dst *http.Header, src http.Header, header string) {
for _, value := range src[header] {
if value != "" {
dst.Add(header, value)
}
}
}