Skip to content

Commit

Permalink
Update Router to serve HTTP/2 traffic
Browse files Browse the repository at this point in the history
- Add integration test.
- We were not able to get a router/router test working because making
HTTP/2 requests to the test Server resulted in broken pipes. We believe
this is due to differences in the test Server setup and the main.go
setup used in the integration tests. This will require additional
investigation.
- Add router/router test to confirm that Gorouter can still server
HTTP/1.1 traffic
- Update protocol checking middleware to accept HTTP/2

[#177586561]
[cloudfoundry/routing-release#200]

Co-authored-by: Weyman Fung <[email protected]>
Co-authored-by: Greg Cobb <[email protected]>
Co-authored-by: Weyman Fung <[email protected]>
  • Loading branch information
Gerg and weymanf committed Apr 6, 2021
1 parent 379860d commit 62ff230
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 13 deletions.
2 changes: 1 addition & 1 deletion handlers/protocolcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@ func (p *protocolCheck) hijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWri
}

func isProtocolSupported(request *http.Request) bool {
return request.ProtoMajor == 1 && (request.ProtoMinor == 0 || request.ProtoMinor == 1)
return request.ProtoMajor == 2 || (request.ProtoMajor == 1 && (request.ProtoMinor == 0 || request.ProtoMinor == 1))
}
26 changes: 14 additions & 12 deletions handlers/protocolcheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ var _ = Describe("Protocolcheck", func() {
server.Close()
})

Context("http 1.1", func() {
Context("http2", func() {
It("passes the request through", func() {
conn, err := net.Dial("tcp", server.Addr())
defer conn.Close()
Expect(err).ToNot(HaveOccurred())
respReader := bufio.NewReader(conn)

conn.Write([]byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"))
conn.Write([]byte("PRI * HTTP/2.0\r\nHost: example.com\r\n\r\n"))
resp, err := http.ReadResponse(respReader, nil)
Expect(err).ToNot(HaveOccurred())

Expand All @@ -64,14 +64,14 @@ var _ = Describe("Protocolcheck", func() {
})
})

Context("http 1.0", func() {
Context("http 1.1", func() {
It("passes the request through", func() {
conn, err := net.Dial("tcp", server.Addr())
defer conn.Close()
Expect(err).ToNot(HaveOccurred())
respReader := bufio.NewReader(conn)

conn.Write([]byte("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"))
conn.Write([]byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"))
resp, err := http.ReadResponse(respReader, nil)
Expect(err).ToNot(HaveOccurred())

Expand All @@ -80,32 +80,34 @@ var _ = Describe("Protocolcheck", func() {
})
})

Context("unsupported versions of http", func() {
It("returns a 400 bad request", func() {
Context("http 1.0", func() {
It("passes the request through", func() {
conn, err := net.Dial("tcp", server.Addr())
defer conn.Close()
Expect(err).ToNot(HaveOccurred())
respReader := bufio.NewReader(conn)

conn.Write([]byte("GET / HTTP/1.5\r\nHost: example.com\r\n\r\n"))
conn.Write([]byte("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"))
resp, err := http.ReadResponse(respReader, nil)
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(http.StatusBadRequest))

Expect(nextCalled).To(BeFalse())
Expect(resp.StatusCode).To(Equal(200))
Expect(nextCalled).To(BeTrue())
})
})

Context("http2", func() {
Context("unsupported versions of http", func() {
It("returns a 400 bad request", func() {
conn, err := net.Dial("tcp", server.Addr())
Expect(err).ToNot(HaveOccurred())
respReader := bufio.NewReader(conn)

conn.Write([]byte("PRI * HTTP/2.0\r\nHost: example.com\r\n\r\n"))

conn.Write([]byte("GET / HTTP/1.5\r\nHost: example.com\r\n\r\n"))
resp, err := http.ReadResponse(respReader, nil)
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(http.StatusBadRequest))

Expect(nextCalled).To(BeFalse())
})
})
})
48 changes: 48 additions & 0 deletions integration/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"syscall"
"time"

"golang.org/x/net/http2"

tls_helpers "code.cloudfoundry.org/cf-routing-test-helpers/tls"
"code.cloudfoundry.org/gorouter/config"
"code.cloudfoundry.org/gorouter/mbus"
Expand Down Expand Up @@ -248,6 +250,52 @@ var _ = Describe("Router Integration", func() {
})
})

Describe("HTTP/2 traffic", func() {
var (
clientTLSConfig *tls.Config
mbusClient *nats.Conn
)
BeforeEach(func() {
cfg, clientTLSConfig = createSSLConfig(statusPort, proxyPort, sslPort, natsPort)

})
JustBeforeEach(func() {
var err error
writeConfig(cfg, cfgFile)
mbusClient, err = newMessageBus(cfg)
Expect(err).ToNot(HaveOccurred())
})

It("serves HTTP/2 traffic", func() {
gorouterSession = startGorouterSession(cfgFile)
runningApp1 := test.NewGreetApp([]route.Uri{"test." + test_util.LocalhostDNS}, proxyPort, mbusClient, nil)
runningApp1.Register()
runningApp1.Listen()
routesUri := fmt.Sprintf("http://%s:%s@%s:%d/routes", cfg.Status.User, cfg.Status.Pass, localIP, statusPort)

heartbeatInterval := 200 * time.Millisecond
runningTicker := time.NewTicker(heartbeatInterval)
done := make(chan bool, 1)
defer func() { done <- true }()
go func() {
for {
select {
case <-runningTicker.C:
runningApp1.Register()
case <-done:
return
}
}
}()
Eventually(func() bool { return appRegistered(routesUri, runningApp1) }).Should(BeTrue())
client := &http.Client{Transport: &http2.Transport{TLSClientConfig: clientTLSConfig}}
resp, err := client.Get(fmt.Sprintf("https://test.%s:%d", test_util.LocalhostDNS, cfg.SSLPort))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(http.StatusOK))
Expect(resp.Proto).To(Equal("HTTP/2.0"))
})
})

Context("Drain", func() {

BeforeEach(func() {
Expand Down
1 change: 1 addition & 0 deletions router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ func (r *Router) serveHTTPS(server *http.Server, errChan chan error) error {
}

tlsConfig := &tls.Config{
NextProtos: []string{"h2"},
Certificates: r.config.SSLCertificates,
CipherSuites: r.config.CipherSuites,
MinVersion: r.config.MinTLSVersion,
Expand Down
44 changes: 44 additions & 0 deletions router/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,50 @@ var _ = Describe("Router", func() {

})

Context("serving multiple http versions", func() {
var (
rootCAs *x509.CertPool
)
BeforeEach(func() {
certChain := test_util.CreateSignedCertWithRootCA(test_util.CertNames{CommonName: "test." + test_util.LocalhostDNS})
config.CACerts = string(certChain.CACertPEM)
config.SSLCertificates = append(config.SSLCertificates, certChain.TLSCert())

rootCAs = x509.NewCertPool()
rootCAs.AddCert(certChain.CACert)
})

It("can serve HTTP/1.1 requests", func() {
tlsClientConfig := &tls.Config{
RootCAs: rootCAs,
}
client := &http.Client{Transport: &http.Transport{
TLSClientConfig: tlsClientConfig,
}}

app := test.NewGreetApp([]route.Uri{"test." + test_util.LocalhostDNS}, config.Port, mbusClient, nil)
app.RegisterAndListen()
Eventually(func() bool {
return appRegistered(registry, app)
}).Should(BeTrue())

uri := fmt.Sprintf("https://test.%s:%d/", test_util.LocalhostDNS, config.SSLPort)
req, _ := http.NewRequest("GET", uri, nil)

resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
Expect(resp).ToNot(BeNil())

Expect(resp.Proto).To(Equal("HTTP/1.1"))
Expect(resp.StatusCode).To(Equal(http.StatusOK))

bytes, err := ioutil.ReadAll(resp.Body)
Expect(err).ToNot(HaveOccurred())
Expect(bytes).To(ContainSubstring("Hello"))
defer resp.Body.Close()
})
})

Context("serving https", func() {
var (
cert []byte
Expand Down

0 comments on commit 62ff230

Please sign in to comment.