@@ -17,10 +17,12 @@ limitations under the License.
1717package roundtripper
1818
1919import (
20+ "bufio"
2021 "context"
2122 "crypto/tls"
2223 "crypto/x509"
2324 "encoding/json"
25+ "errors"
2426 "fmt"
2527 "io"
2628 "net"
@@ -29,9 +31,15 @@ import (
2931 "net/url"
3032 "regexp"
3133
34+ "golang.org/x/net/http2"
3235 "sigs.k8s.io/gateway-api/conformance/utils/config"
3336)
3437
38+ const (
39+ H2CUpgradeProtocol = "H2C_UPGRADE"
40+ H2CPriorKnowledgeProtocol = "H2C_PRIOR_KNOWLEDGE"
41+ )
42+
3543// RoundTripper is an interface used to make requests within conformance tests.
3644// This can be overridden with custom implementations whenever necessary.
3745type RoundTripper interface {
@@ -104,19 +112,7 @@ type DefaultRoundTripper struct {
104112 CustomDialContext func (context.Context , string , string ) (net.Conn , error )
105113}
106114
107- // CaptureRoundTrip makes a request with the provided parameters and returns the
108- // captured request and response from echoserver. An error will be returned if
109- // there is an error running the function but not if an HTTP error status code
110- // is received.
111- func (d * DefaultRoundTripper ) CaptureRoundTrip (request Request ) (* CapturedRequest , * CapturedResponse , error ) {
112- client := & http.Client {}
113-
114- if request .UnfollowRedirect {
115- client .CheckRedirect = func (req * http.Request , via []* http.Request ) error {
116- return http .ErrUseLastResponse
117- }
118- }
119-
115+ func (d * DefaultRoundTripper ) httpTransport (request Request ) (http.RoundTripper , error ) {
120116 transport := & http.Transport {
121117 DialContext : d .CustomDialContext ,
122118 // We disable keep-alives so that we don't leak established TCP connections.
@@ -131,10 +127,110 @@ func (d *DefaultRoundTripper) CaptureRoundTrip(request Request) (*CapturedReques
131127 if request .Server != "" && len (request .CertPem ) != 0 && len (request .KeyPem ) != 0 {
132128 tlsConfig , err := tlsClientConfig (request .Server , request .CertPem , request .KeyPem )
133129 if err != nil {
134- return nil , nil , err
130+ return nil , err
135131 }
136132 transport .TLSClientConfig = tlsConfig
137133 }
134+
135+ return transport , nil
136+ }
137+
138+ func (d * DefaultRoundTripper ) h2cPriorKnowledgeTransport (request Request ) (http.RoundTripper , error ) {
139+ if request .Server != "" && len (request .CertPem ) != 0 && len (request .KeyPem ) != 0 {
140+ return nil , errors .New ("request has configured cert and key but h2 prior knowledge is not encrypted" )
141+ }
142+
143+ transport := & http2.Transport {
144+ AllowHTTP : true ,
145+ DialTLSContext : func (ctx context.Context , network , addr string , cfg * tls.Config ) (net.Conn , error ) {
146+ var d net.Dialer
147+ return d .DialContext (ctx , network , addr )
148+ },
149+ }
150+
151+ return transport , nil
152+ }
153+
154+ // This actually makes a H2C upgrade request when dialing to establish
155+ // an HTTP/2 connection. Then the transport will re-send the same request
156+ //
157+ // This seemed like the easiest way to accomplish testing h2c upgrade flow
158+ // with golang. Open issue is here https://github.com/golang/go/issues/46249
159+ //
160+ // The alternative would be to re-implement parts of http2.Transport
161+ func (d * DefaultRoundTripper ) h2cUpgradeTransport (request Request ) (http.RoundTripper , error ) {
162+ if request .Server != "" && len (request .CertPem ) != 0 && len (request .KeyPem ) != 0 {
163+ return nil , errors .New ("request has configured cert and key but h2c is not encrypted" )
164+ }
165+ return & http2.Transport {
166+ AllowHTTP : true ,
167+ DialTLSContext : func (ctx context.Context , network , addr string , cfg * tls.Config ) (net.Conn , error ) {
168+ dialer := net.Dialer {}
169+ conn , err := dialer .DialContext (ctx , network , addr )
170+ if err != nil {
171+ return nil , err
172+ }
173+ bw := bufio .NewWriter (conn )
174+ br := bufio .NewReader (conn )
175+
176+ req , _ := http .NewRequestWithContext (ctx , request .Method , "http://" + addr , nil )
177+ req .Header .Set ("Connection" , "Upgrade, HTTP2-Settings" )
178+ req .Header .Set ("Upgrade" , "h2c" )
179+ req .Header .Set ("HTTP2-Settings" , "" )
180+
181+ if err = req .Write (bw ); err != nil {
182+ return nil , err
183+ }
184+ if err = bw .Flush (); err != nil {
185+ return nil , err
186+ }
187+
188+ resp , err := http .ReadResponse (br , req )
189+ if err != nil {
190+ return nil , err
191+ }
192+ defer resp .Body .Close ()
193+ if resp .StatusCode != http .StatusSwitchingProtocols {
194+ return nil , errors .New ("switching protocols failed" )
195+ }
196+ return conn , nil
197+ },
198+ }, nil
199+ }
200+
201+ // CaptureRoundTrip makes a request with the provided parameters and returns the
202+ // captured request and response from echoserver. An error will be returned if
203+ // there is an error running the function but not if an HTTP error status code
204+ // is received.
205+ func (d * DefaultRoundTripper ) CaptureRoundTrip (request Request ) (* CapturedRequest , * CapturedResponse , error ) {
206+ var transport http.RoundTripper
207+ var err error
208+
209+ switch request .Protocol {
210+ case H2CUpgradeProtocol :
211+ transport , err = d .h2cUpgradeTransport (request )
212+ case H2CPriorKnowledgeProtocol :
213+ transport , err = d .h2cPriorKnowledgeTransport (request )
214+ default :
215+ transport , err = d .httpTransport (request )
216+ }
217+
218+ if err != nil {
219+ return nil , nil , err
220+ }
221+
222+ return d .defaultRoundTrip (request , transport )
223+ }
224+
225+ func (d * DefaultRoundTripper ) defaultRoundTrip (request Request , transport http.RoundTripper ) (* CapturedRequest , * CapturedResponse , error ) {
226+ client := & http.Client {}
227+
228+ if request .UnfollowRedirect {
229+ client .CheckRedirect = func (req * http.Request , via []* http.Request ) error {
230+ return http .ErrUseLastResponse
231+ }
232+ }
233+
138234 client .Transport = transport
139235
140236 method := "GET"
0 commit comments