Add HTTP/2 push

This commit is contained in:
Ken-Håvard Lieng 2017-04-15 04:48:24 +02:00
parent 8d4b707757
commit 37c8e780bc
5 changed files with 52 additions and 48 deletions

View File

@ -1,9 +1,6 @@
language: go language: go
go: go:
- 1.5.4
- 1.6.4
- 1.7.5
- 1.8.1 - 1.8.1
- tip - tip

View File

@ -21,12 +21,7 @@ There is a few different ways of getting it:
- [Other versions](https://github.com/khlieng/dispatch/releases) - [Other versions](https://github.com/khlieng/dispatch/releases)
### 2. Go ### 2. Go
This requires a [Go environment](http://golang.org/doc/install). This requires a [Go environment](http://golang.org/doc/install), version 1.8 or greater.
If running go 1.5 this environment variable is needed, versions <1.5 are not supported:
```bash
export GO15VENDOREXPERIMENT=1
```
Fetch, compile and run dispatch: Fetch, compile and run dispatch:
```bash ```bash

View File

@ -28,6 +28,7 @@ type File struct {
Path string Path string
Asset string Asset string
GzipAsset []byte GzipAsset []byte
Hash string
ContentType string ContentType string
CacheControl string CacheControl string
Compressed bool Compressed bool
@ -69,7 +70,8 @@ func initFileServer() {
} }
hash := md5.Sum(data) 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) br, err := brotli.NewReader(bytes.NewReader(data), nil)
if err != nil { if err != nil {
@ -92,7 +94,8 @@ func initFileServer() {
} }
hash = md5.Sum(data) 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)) br.Reset(bytes.NewReader(data))
buf = &bytes.Buffer{} buf = &bytes.Buffer{}
@ -196,6 +199,36 @@ func serveIndex(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Strict-Transport-Security", hstsHeader) 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") { if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
w.Header().Set("Content-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) { func serveFile(w http.ResponseWriter, r *http.Request, file *File) {
info, err := assets.AssetInfo(file.Asset) info, err := assets.AssetInfo(file.Asset)
if err != nil { if err != nil {

View File

@ -1,6 +1,7 @@
package server package server
import ( import (
"crypto/tls"
"log" "log"
"net" "net"
"net/http" "net/http"
@ -10,7 +11,6 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/spf13/viper" "github.com/spf13/viper"
"golang.org/x/net/http2"
"github.com/khlieng/dispatch/letsencrypt" "github.com/khlieng/dispatch/letsencrypt"
"github.com/khlieng/dispatch/storage" "github.com/khlieng/dispatch/storage"
@ -59,8 +59,6 @@ func startHTTP() {
Handler: http.HandlerFunc(serve), Handler: http.HandlerFunc(serve),
} }
http2.ConfigureServer(server, nil)
if certExists() { if certExists() {
log.Println("[HTTPS] Listening on port", portHTTPS) log.Println("[HTTPS] Listening on port", portHTTPS)
server.ListenAndServeTLS(viper.GetString("https.cert"), viper.GetString("https.key")) server.ListenAndServeTLS(viper.GetString("https.cert"), viper.GetString("https.key"))
@ -79,10 +77,12 @@ func startHTTP() {
log.Fatal(err) log.Fatal(err)
} }
server.TLSConfig.GetCertificate = letsEncrypt.GetCertificate server.TLSConfig = &tls.Config{
GetCertificate: letsEncrypt.GetCertificate,
}
log.Println("[HTTPS] Listening on port", portHTTPS) log.Println("[HTTPS] Listening on port", portHTTPS)
log.Fatal(listenAndServeTLS(server)) log.Fatal(server.ListenAndServeTLS("", ""))
} else { } else {
log.Fatal("Could not locate SSL certificate or private key") log.Fatal("Could not locate SSL certificate or private key")
} }

View File

@ -1,43 +1,11 @@
package server package server
import ( import (
"crypto/tls"
"net"
"net/http"
"os" "os"
"time"
"github.com/spf13/viper" "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 { func certExists() bool {
cert := viper.GetString("https.cert") cert := viper.GetString("https.cert")
key := viper.GetString("https.key") key := viper.GetString("https.key")