-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Closed
Labels
Description
- What version of Go are you using (go version)?
1.6
- What operating system and processor architecture are you using?
Darwin, AMD64
- What did you do?
I'm trying to pass a tls net.Listener
to http.Server.Serve
but the server didn't run on http2.
See below code:
package main
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"time"
)
func main() {
crt := "YOUR_CRT"
key := "YOUR_KEY"
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello\n")
})
ss := &http.Server{
Addr: ":8080",
Handler: mux,
}
err := listenAndServeTLS(ss, []byte(crt), []byte(key))
if err != nil {
log.Fatal(err)
}
}
// listenAndServeTLS is equivalent to http.Server.ListenAndServeTLS
// but loads cert and key as []byte instead of files
func listenAndServeTLS(srv *http.Server, cert, key []byte) error {
addr := srv.Addr
if addr == "" {
addr = ":https"
}
config := cloneTLSConfig(srv.TLSConfig)
if !strSliceContains(config.NextProtos, "http/1.1") {
config.NextProtos = append(config.NextProtos, "http/1.1")
}
var err error
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.X509KeyPair(cert, key)
if err != nil {
return err
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
return srv.Serve(tlsListener)
}
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}
func strSliceContains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}
// cloneTLSConfig returns a shallow clone of the exported
// fields of cfg, ignoring the unexported sync.Once, which
// contains a mutex and must not be copied.
//
// The cfg must not be in active use by tls.Server, or else
// there can still be a race with tls.Server updating SessionTicketKey
// and our copying it, and also a race with the server setting
// SessionTicketsDisabled=false on failure to set the random
// ticket key.
//
// If cfg is nil, a new zero tls.Config is returned.
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return &tls.Config{
Rand: cfg.Rand,
Time: cfg.Time,
Certificates: cfg.Certificates,
NameToCertificate: cfg.NameToCertificate,
GetCertificate: cfg.GetCertificate,
RootCAs: cfg.RootCAs,
NextProtos: cfg.NextProtos,
ServerName: cfg.ServerName,
ClientAuth: cfg.ClientAuth,
ClientCAs: cfg.ClientCAs,
InsecureSkipVerify: cfg.InsecureSkipVerify,
CipherSuites: cfg.CipherSuites,
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
SessionTicketsDisabled: cfg.SessionTicketsDisabled,
SessionTicketKey: cfg.SessionTicketKey,
ClientSessionCache: cfg.ClientSessionCache,
MinVersion: cfg.MinVersion,
MaxVersion: cfg.MaxVersion,
CurvePreferences: cfg.CurvePreferences,
}
}
When I curled it:
$ curl https://localhost:8080 -k -v --http2
* Rebuilt URL to: https://localhost:8080/
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
* CAfile: /opt/boxen/homebrew/etc/openssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: C=US; ST=California; L=San Francisco; O=Heroku; OU=Heroku API; CN=Midgard; emailAddress=api@heroku.com
* start date: Oct 30 17:30:09 2015 GMT
* expire date: Mar 13 17:30:09 2017 GMT
* issuer: C=US; ST=California; L=San Francisco; O=Heroku; OU=Heroku API; CN=Midgard; emailAddress=api@heroku.com
* SSL certificate verify result: self signed certificate (18), continuing anyway.
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.47.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Thu, 18 Feb 2016 02:09:44 GMT
< Content-Length: 5
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact
hello%
I compared line by line my implementation of listenAndServeTLS
with http.Sever.ListenAndServeTLS
. The difference is I didn't call srv.setupHTTP2()
. But it should be called in http.Server.Serve
again. However, if I changed the code to http.Server.ListenAndServeTLS
, the server ran on http2.
- What did you expect to see?
I expected the server to run on http2 with http.Sever.Serve
.
- What did you see instead?
The server ran on http1/1