From 37c8e780bc4a720c0cd51829956ec8f6056db7d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ken-H=C3=A5vard=20Lieng?= Date: Sat, 15 Apr 2017 04:48:24 +0200 Subject: [PATCH] Add HTTP/2 push --- .travis.yml | 3 --- README.md | 7 +------ server/serve_files.go | 48 +++++++++++++++++++++++++++++++++++++++++-- server/server.go | 10 ++++----- server/tls.go | 32 ----------------------------- 5 files changed, 52 insertions(+), 48 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4f4dcafe..a1b203bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,6 @@ language: go go: - - 1.5.4 - - 1.6.4 - - 1.7.5 - 1.8.1 - tip diff --git a/README.md b/README.md index f0ddf4bb..1506bb47 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,7 @@ There is a few different ways of getting it: - [Other versions](https://github.com/khlieng/dispatch/releases) ### 2. Go -This requires a [Go environment](http://golang.org/doc/install). - -If running go 1.5 this environment variable is needed, versions <1.5 are not supported: -```bash -export GO15VENDOREXPERIMENT=1 -``` +This requires a [Go environment](http://golang.org/doc/install), version 1.8 or greater. Fetch, compile and run dispatch: ```bash diff --git a/server/serve_files.go b/server/serve_files.go index 468397a4..889c935c 100644 --- a/server/serve_files.go +++ b/server/serve_files.go @@ -28,6 +28,7 @@ type File struct { Path string Asset string GzipAsset []byte + Hash string ContentType string CacheControl string Compressed bool @@ -69,7 +70,8 @@ func initFileServer() { } hash := md5.Sum(data) - files[0].Path = "bundle." + base64.RawURLEncoding.EncodeToString(hash[:]) + ".js" + files[0].Hash = base64.RawURLEncoding.EncodeToString(hash[:]) + files[0].Path = "bundle." + files[0].Hash + ".js" br, err := brotli.NewReader(bytes.NewReader(data), nil) if err != nil { @@ -92,7 +94,8 @@ func initFileServer() { } hash = md5.Sum(data) - files[1].Path = "bundle." + base64.RawURLEncoding.EncodeToString(hash[:]) + ".css" + files[1].Hash = base64.RawURLEncoding.EncodeToString(hash[:]) + files[1].Path = "bundle." + files[1].Hash + ".css" br.Reset(bytes.NewReader(data)) buf = &bytes.Buffer{} @@ -196,6 +199,36 @@ func serveIndex(w http.ResponseWriter, r *http.Request) { w.Header().Set("Strict-Transport-Security", hstsHeader) } + if pusher, ok := w.(http.Pusher); ok { + options := &http.PushOptions{ + Header: http.Header{ + "Accept-Encoding": r.Header["Accept-Encoding"], + }, + } + + cookie, err := r.Cookie("push") + if err != nil { + err = pusher.Push("/"+files[1].Path, options) + err = pusher.Push("/"+files[0].Path, options) + setPushCookie(w, r) + } else { + pushed := false + + if files[1].Hash != cookie.Value[22:] { + pusher.Push("/"+files[1].Path, options) + pushed = true + } + if files[0].Hash != cookie.Value[:22] { + pusher.Push("/"+files[0].Path, options) + pushed = true + } + + if pushed { + setPushCookie(w, r) + } + } + } + if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { w.Header().Set("Content-Encoding", "gzip") @@ -207,6 +240,17 @@ func serveIndex(w http.ResponseWriter, r *http.Request) { } } +func setPushCookie(w http.ResponseWriter, r *http.Request) { + http.SetCookie(w, &http.Cookie{ + Name: "push", + Value: files[0].Hash + files[1].Hash, + Path: "/", + Expires: time.Now().AddDate(1, 0, 0), + HttpOnly: true, + Secure: r.TLS != nil, + }) +} + func serveFile(w http.ResponseWriter, r *http.Request, file *File) { info, err := assets.AssetInfo(file.Asset) if err != nil { diff --git a/server/server.go b/server/server.go index af2af061..e0c45538 100644 --- a/server/server.go +++ b/server/server.go @@ -1,6 +1,7 @@ package server import ( + "crypto/tls" "log" "net" "net/http" @@ -10,7 +11,6 @@ import ( "github.com/gorilla/websocket" "github.com/spf13/viper" - "golang.org/x/net/http2" "github.com/khlieng/dispatch/letsencrypt" "github.com/khlieng/dispatch/storage" @@ -59,8 +59,6 @@ func startHTTP() { Handler: http.HandlerFunc(serve), } - http2.ConfigureServer(server, nil) - if certExists() { log.Println("[HTTPS] Listening on port", portHTTPS) server.ListenAndServeTLS(viper.GetString("https.cert"), viper.GetString("https.key")) @@ -79,10 +77,12 @@ func startHTTP() { log.Fatal(err) } - server.TLSConfig.GetCertificate = letsEncrypt.GetCertificate + server.TLSConfig = &tls.Config{ + GetCertificate: letsEncrypt.GetCertificate, + } log.Println("[HTTPS] Listening on port", portHTTPS) - log.Fatal(listenAndServeTLS(server)) + log.Fatal(server.ListenAndServeTLS("", "")) } else { log.Fatal("Could not locate SSL certificate or private key") } diff --git a/server/tls.go b/server/tls.go index c3e231b5..edd87a54 100644 --- a/server/tls.go +++ b/server/tls.go @@ -1,43 +1,11 @@ package server import ( - "crypto/tls" - "net" - "net/http" "os" - "time" "github.com/spf13/viper" ) -func listenAndServeTLS(srv *http.Server) error { - if srv.TLSConfig.NextProtos == nil { - srv.TLSConfig.NextProtos = []string{"http/1.1"} - } - - ln, err := net.Listen("tcp", srv.Addr) - if err != nil { - return err - } - - tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig) - return srv.Serve(tlsListener) -} - -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 certExists() bool { cert := viper.GetString("https.cert") key := viper.GetString("https.key")