首页

    go http2 server meet cloudflare

    标签:go,HTTP2

    go http2 server meet cloudflare

    服务器上有个go写的http2 server,使用了cloudflare Proxy模式,但是访问时返回Error 520,Web server is returning an unknown error,日志打印

    server.go:640: http2: server: error reading preface from client 162.158.165.70:19046: bogus greeting "GET / HTTP/1.1\r\nHost: "

    代码如下

    package main
    
    import (
        "crypto/tls"
        "flag"
        "fmt"
        "io/ioutil"
        "log"
        "net"
        "net/http"
    
        "golang.org/x/net/http2"
    )
    
    var (
        certPath = flag.String("cer", "", "cer file path")
        keyPath  = flag.String("key", "", "key file path")
        handler  = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if err := r.ParseForm(); err != nil {
                fmt.Fprintf(w, "ParseForm() err: %v", err)
                return
            }
            log.Printf("form:%v", r.Form)
            log.Printf("post form:%v", r.PostForm)
    
            w.Write([]byte("hello http2"))
        })
    
        server   = &http.Server{}
        h2Server = &http2.Server{}
    )
    
    func init() {
        log.SetFlags(log.LstdFlags | log.Lshortfile)
    }
    
    func handleConn(conn net.Conn) {
        defer conn.Close()
        if tlsConn, ok := conn.(*tls.Conn); ok {
            e := tlsConn.Handshake()
            if e != nil {
                log.Printf("ip %s handshake err:%v", conn.RemoteAddr().String(), e)
                return
            }
            state := tlsConn.ConnectionState()
            log.Printf("HandshakeComplete:%v,CipherSuite:%v", state.HandshakeComplete, state.CipherSuite)
        }
        h2Server.ServeConn(conn, &http2.ServeConnOpts{
            BaseConfig: server,
            Handler:    handler,
        })
    }
    
    func main() {
        flag.Parse()
        http2.VerboseLogs = true
        certPEMBlock, err := ioutil.ReadFile(*certPath)
        if err != nil {
            panic(err)
        }
        keyPEMBlock, err := ioutil.ReadFile(*keyPath)
        if err != nil {
            panic(err)
        }
        cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
    
        cfg := &tls.Config{
            Certificates: []tls.Certificate{cert},
            NextProtos:   []string{http2.NextProtoTLS},
            MinVersion:   tls.VersionTLS12,
        }
    
        //http2.ConfigureServer(server, h2Server)
    
        l, err := tls.Listen("tcp", ":443", cfg)
        if err != nil {
            panic(err)
        }
    
        for {
            conn, err := l.Accept()
            if err != nil {
                return
            }
            handleConn(conn)
        }
    }
    

    如果不使用cloudflare Proxy模式,能正常访问.查看cloudflare文档,找到Understanding Cloudflare HTTP/2 and HTTP/3 Support,里面说了

    This article is in reference to eyeball requests (connections from clients to Cloudflare's Edge). Connections from Cloudflare's Edge to your origin server(s) only support HTTP/1.1.

    于是不使用cloudflare Proxy模式,用curl试试http1.1

    curl --http1.1 -kv https://domain
    

    果然是上面的错误
    试试http2

    curl --http2 -kv https://domain
    

    能正常访问.说明上面的http2 server只支持http2,不支持降级到http1.1

    我换了种http2 server常见的写法

    package main
    
    import (
        "flag"
        "fmt"
        "log"
        "net/http"
    
        "golang.org/x/net/http2"
    )
    
    var (
        certPath = flag.String("cer", "", "cer file path")
        keyPath  = flag.String("key", "", "key file path")
    )
    
    func init() {
        log.SetFlags(log.LstdFlags | log.Lshortfile)
    }
    
    func main() {
        flag.Parse()
        var srv http.Server
        http2.VerboseLogs = true
        srv.Addr = ":443"
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            if err := r.ParseForm(); err != nil {
                fmt.Fprintf(w, "ParseForm() err: %v", err)
                return
            }
            log.Printf("form:%v", r.Form)
            log.Printf("post form:%v", r.PostForm)
    
            w.Write([]byte("hello http2"))
        })
        http2.ConfigureServer(&srv, &http2.Server{})
        go func() {
            log.Fatal(srv.ListenAndServeTLS(*certPath, *keyPath))
        }()
        select {}
    }
    

    这种写法无论http2还是http1.1访问,都能正常访问

    查看源码,第二种方式里的http2.ConfigureServer(&srv, &http2.Server{})

    func ConfigureServer(s *http.Server, conf *Server) error {
        ...
        protoHandler := func(hs *http.Server, c *tls.Conn, h http.Handler) {
            if testHookOnConn != nil {
                testHookOnConn()
            }
            // The TLSNextProto interface predates contexts, so
            // the net/http package passes down its per-connection
            // base context via an exported but unadvertised
            // method on the Handler. This is for internal
            // net/http<=>http2 use only.
            var ctx context.Context
            type baseContexter interface {
                BaseContext() context.Context
            }
            if bc, ok := h.(baseContexter); ok {
                ctx = bc.BaseContext()
            }
            conf.ServeConn(c, &ServeConnOpts{
                Context:    ctx,
                Handler:    h,
                BaseConfig: hs,
            })
        }
        s.TLSNextProto[NextProtoTLS] = protoHandler
    }
    

    和第一种方式里的

        h2Server.ServeConn(conn, &http2.ServeConnOpts{
            BaseConfig: server,
            Handler:    handler,
        })
    

    都使用了https://godoc.org/golang.org/x/net/http2#Server.ServeConn ServeConn(c net.Conn, opts *ServeConnOpts)),这是http2处理conn的入口

    再看第二种方式,顺着srv.ListenAndServeTLS。在src/net/http/server.go里

    // validNextProto reports whether the proto is not a blacklisted ALPN
    // protocol name. Empty and built-in protocol types are blacklisted
    // and can't be overridden with alternate implementations.
    func validNextProto(proto string) bool {
        switch proto {
        case "", "http/1.1", "http/1.0":
            return false
        }
        return true
    }
    
    func (c *conn) serve(ctx context.Context) {
        ...
        if tlsConn, ok := c.rwc.(*tls.Conn); ok {
            if err := tlsConn.Handshake(); err != nil {
                // If the handshake failed due to the client not speaking
                // TLS, assume they're speaking plaintext HTTP and write a
                // 400 response on the TLS conn's underlying net.Conn.
                if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {
                    io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n")
                    re.Conn.Close()
                    return
                }
                c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
                return
            }
            c.tlsState = new(tls.ConnectionState)
            *c.tlsState = tlsConn.ConnectionState()
            proto := c.tlsState.NegotiatedProtocol
            validResult := validNextProto(proto)
            if validResult {
                if fn := c.server.TLSNextProto[proto]; fn != nil {
                    h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
                    fn(c.server, tlsConn, h)
                }
                return
            }
        }
    
        // HTTP/1.x from here on.
    
        ctx, cancelCtx := context.WithCancel(ctx)
        c.cancelCtx = cancelCtx
        defer cancelCtx()
        ...
    }
    

    如果是

    • http1.1访问,validResult是false,会继续执行后面http1.1的相关代码
    • http2访问,proto是"h2",validResult是true,会运行fn := c.server.TLSNextProto[proto]。fn对应的是上面func ConfigureServer(s http.Server, conf Server) error里的protoHandler,protoHandler里面有http2的处理逻辑

    所以第二种方式http1.1,http2均能正常访问 第一种方式只有http2的处理逻辑,所以使用http1.1.无法访问