HTTP2 Client Connections

I’m attempting to verify that go1.10.2 HTTP2 clients correctly multiplex requests over a single HTTP2 connection. I’m using a current release NginX reverse proxy that is configured to support HTTP2. I’ve verified that it does support HTTP2 with h2c.

My go HTTP2 client is configured to use httptrace with all callbacks for all requests.

When this go HTTP2 client issues three concurrent requests (three successive go invocations of a simple request function), trace shows that it is creating and caching three separate connections.

Something seems to be wrong here. Either the client is not HTTP2 enabled or it is not correctly mux’ing its requests over a singe HTTP2 connection.

If use of multiple HTTP2 connections to communicate to a single HTTP2 service is the expected behavior of the http package, this would be a significant performance problem for clients that expect to efficiently use HTTP2 client streams.

Can anyone shed any light on this?

Here’s the client code if it’s of interest.

/*
Command simSwitch simulates the switch end of the dial-out switch config protocol.

This simSwitch assumes its controller service is at:

https://localhost:8443

The local directory of simSwitch must contain a cert directory that contains three files:

client.shivaram-1.tenants.servicefractal.com.cert.pem - The client's certificate
client.shivaram-1.tenants.servicefractal.com.key.pem - The client's certificate key
shivaram-1.tenants.servicefractal.com-ca-chain.cert.pem - The certificate chain required to validate the switch's controller certificate

*/
package main

import (
“crypto/tls”
"crypto/x509"
“fmt”
“io/ioutil”
“log”
“net/http”
“net/http/httptrace”
“net/url”
“os”
)

const (

//Controller service URL
controllerURL = "https://localhost:8443"

)

var (

//Mutual Auth HTTP Client
client *http.Client

//The tenant's client SSL certificate
clientCert tls.Certificate

//Trace Context
clientTrace = httptrace.ClientTrace{GetConn: getConn, GotConn: gotConn, PutIdleConn: putIdleConn, GotFirstResponseByte: gotFirstResponseByte, Got100Continue: got100Continue, DNSStart: dnsStart, DNSDone: dnsDone, ConnectStart: connectStart, ConnectDone: connectDone, TLSHandshakeStart: tlsHandshakeStart, TLSHandshakeDone: tlsHandshakeDone, WroteHeaders: wroteHeaders, Wait100Continue: wait100Continue, WroteRequest: wroteRequest}

)

//HTTP Trace hooks
func getConn(s string) {
log.Printf(“GetCon %s\n”, s)
}

func gotConn(p httptrace.GotConnInfo) {
log.Printf(“gotConn %+v\n”, p)
}

func putIdleConn(err error) {
log.Printf(“putIdleConn\n”)
}

func gotFirstResponseByte() {
log.Printf(“gotFirstResponseByte\n”)
}

func got100Continue() {
log.Printf(“got100Continue\n”)
}

func dnsStart(p httptrace.DNSStartInfo) {
log.Printf(“dnsStart %+v\n”, p)
}

func dnsDone(p httptrace.DNSDoneInfo) {
log.Printf(“dnsDone %+v\n”, p)
}

func connectStart(network, addr string) {
log.Printf(“connectStart %s: %s\n”, network, addr)
}

func connectDone(network, addr string, err error) {
log.Printf(“connectDone %s: %s\n”, network, addr)
}

func tlsHandshakeStart() {
log.Printf(“tlsHandshakeStart\n”)
}

func tlsHandshakeDone(conState tls.ConnectionState, err error) {
log.Printf(“tlsHandshakeDone %+v\n”, conState)
}

func wroteHeaders() {
log.Printf(“wroteHeaders\n”)
}

func wait100Continue() {
log.Printf(“wait100Continue\n”)
}

func wroteRequest(wri httptrace.WroteRequestInfo) {
log.Printf(“wroteRequest\n”)
}

//ping requests controller ping with delay interval
func ping(ch chan int, interval int) {
var (
req http.Request
rsp *http.Response
intervalS string
err error
)

log.Printf("Ping interval %d\n", interval)
intervalS = fmt.Sprintf("%d", interval)
req.Method = http.MethodGet
req.URL, _ = url.ParseRequestURI("https://controller-1.shivaram-1.tenants.servicefractal.com:8443/ping?interval=" + intervalS)
tracedReq := req.WithContext(httptrace.WithClientTrace(req.Context(), &clientTrace))
rsp, err = client.Do(tracedReq)
if err != nil {
	log.Printf("Ping error: %s\n", err)
	os.Exit(1)
}
if rsp.StatusCode != http.StatusOK {
	log.Printf("Ping status: %s\n", rsp.Status)
	os.Exit(1)
}

//Signal ping has completed
ch <- 1

log.Printf("Ping interval %d response received\n", interval)

} //ping

func main() {
var (
caCertBytes []byte
caCertPool *x509.CertPool
tlsConfig *tls.Config
transport *http.Transport
pingCh chan int
pingDone int
err error
)

//Load client cert
clientCert, err = tls.LoadX509KeyPair("cert/client.shivaram-1.tenants.servicefractal.com.cert.pem", "cert/client.shivaram-1.tenants.servicefractal.com.key.pem")
if err != nil {
	fmt.Printf("The simSwitch cert subdirectory must contain the switch's cert and key pem files: %s\n", err)
	os.Exit(1)
}

//Load CA cert bundle
caCertBytes, err = ioutil.ReadFile("cert/shivaram-1.tenants.servicefractal.com-ca-chain.cert.pem")
if err != nil {
	fmt.Println("The simSwitch cert subdirectory must contain the cert bundle required to verify its controller's certificate.")
	os.Exit(1)
}
caCertPool = x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCertBytes)

//Setup HTTPS mutual auth client
tlsConfig = &tls.Config{
	Certificates: []tls.Certificate{clientCert},
	RootCAs:      caCertPool,
}
tlsConfig.BuildNameToCertificate()
transport = &http.Transport{TLSClientConfig: tlsConfig}
client = &http.Client{Transport: transport}

//Start three concurrent Pings
pingCh = make(chan int, 100)
go ping(pingCh, 5)
go ping(pingCh, 10)
go ping(pingCh, 15)

gatherPings:
for {
select {
case <-pingCh:
pingDone += 1
if pingDone >= 3 {
break gatherPings
}
}
}

log.Println("simSwitch has finished.")

}

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.