19
19
package reverseproxy
20
20
21
21
import (
22
+ "bufio"
22
23
"context"
23
24
"errors"
24
25
"fmt"
@@ -33,8 +34,29 @@ import (
33
34
"go.uber.org/zap"
34
35
"go.uber.org/zap/zapcore"
35
36
"golang.org/x/net/http/httpguts"
37
+
38
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
36
39
)
37
40
41
+ type h2ReadWriteCloser struct {
42
+ io.ReadCloser
43
+ http.ResponseWriter
44
+ }
45
+
46
+ func (rwc h2ReadWriteCloser ) Write (p []byte ) (n int , err error ) {
47
+ n , err = rwc .ResponseWriter .Write (p )
48
+ if err != nil {
49
+ return 0 , err
50
+ }
51
+
52
+ //nolint:bodyclose
53
+ err = http .NewResponseController (rwc .ResponseWriter ).Flush ()
54
+ if err != nil {
55
+ return 0 , err
56
+ }
57
+ return n , nil
58
+ }
59
+
38
60
func (h * Handler ) handleUpgradeResponse (logger * zap.Logger , wg * sync.WaitGroup , rw http.ResponseWriter , req * http.Request , res * http.Response ) {
39
61
reqUpType := upgradeType (req .Header )
40
62
resUpType := upgradeType (res .Header )
@@ -67,24 +89,58 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup,
67
89
// like the rest of handler chain.
68
90
copyHeader (rw .Header (), res .Header )
69
91
normalizeWebsocketHeaders (rw .Header ())
70
- rw .WriteHeader (res .StatusCode )
71
92
72
- logger .Debug ("upgrading connection" )
93
+ var (
94
+ conn io.ReadWriteCloser
95
+ brw * bufio.ReadWriter
96
+ )
97
+ // websocket over http2, assuming backend doesn't support this, the request will be modified to http1.1 upgrade
98
+ // TODO: once we can reliably detect backend support this, it can be removed for those backends
99
+ if body , ok := caddyhttp .GetVar (req .Context (), "h2_websocket_body" ).(io.ReadCloser ); ok {
100
+ req .Body = body
101
+ rw .Header ().Del ("Upgrade" )
102
+ rw .Header ().Del ("Connection" )
103
+ delete (rw .Header (), "Sec-WebSocket-Accept" )
104
+ rw .WriteHeader (http .StatusOK )
105
+
106
+ if c := logger .Check (zap .DebugLevel , "upgrading connection" ); c != nil {
107
+ c .Write (zap .Int ("http_version" , 2 ))
108
+ }
73
109
74
- //nolint:bodyclose
75
- conn , brw , hijackErr := http .NewResponseController (rw ).Hijack ()
76
- if errors .Is (hijackErr , http .ErrNotSupported ) {
77
- if c := logger .Check (zapcore .ErrorLevel , "can't switch protocols using non-Hijacker ResponseWriter" ); c != nil {
78
- c .Write (zap .String ("type" , fmt .Sprintf ("%T" , rw )))
110
+ //nolint:bodyclose
111
+ flushErr := http .NewResponseController (rw ).Flush ()
112
+ if flushErr != nil {
113
+ if c := h .logger .Check (zap .ErrorLevel , "failed to flush http2 websocket response" ); c != nil {
114
+ c .Write (zap .Error (flushErr ))
115
+ }
116
+ return
79
117
}
80
- return
81
- }
118
+ conn = h2ReadWriteCloser {req .Body , rw }
119
+ // bufio is not needed, use minimal buffer
120
+ brw = bufio .NewReadWriter (bufio .NewReaderSize (conn , 1 ), bufio .NewWriterSize (conn , 1 ))
121
+ } else {
122
+ rw .WriteHeader (res .StatusCode )
82
123
83
- if hijackErr != nil {
84
- if c := logger .Check (zapcore .ErrorLevel , "hijack failed on protocol switch" ); c != nil {
85
- c .Write (zap .Error (hijackErr ))
124
+ if c := logger .Check (zap .DebugLevel , "upgrading connection" ); c != nil {
125
+ c .Write (zap .Int ("http_version" , req .ProtoMajor ))
126
+ }
127
+
128
+ var hijackErr error
129
+ //nolint:bodyclose
130
+ conn , brw , hijackErr = http .NewResponseController (rw ).Hijack ()
131
+ if errors .Is (hijackErr , http .ErrNotSupported ) {
132
+ if c := h .logger .Check (zap .ErrorLevel , "can't switch protocols using non-Hijacker ResponseWriter" ); c != nil {
133
+ c .Write (zap .String ("type" , fmt .Sprintf ("%T" , rw )))
134
+ }
135
+ return
136
+ }
137
+
138
+ if hijackErr != nil {
139
+ if c := h .logger .Check (zap .ErrorLevel , "hijack failed on protocol switch" ); c != nil {
140
+ c .Write (zap .Error (hijackErr ))
141
+ }
142
+ return
86
143
}
87
- return
88
144
}
89
145
90
146
// adopted from https://github.com/golang/go/commit/8bcf2834afdf6a1f7937390903a41518715ef6f5
0 commit comments