Commit ac409d67 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Add caching and serving precompiled assets

parent afcae9ed
...@@ -86,11 +86,15 @@ var httpRoutes = [...]httpRoute{ ...@@ -86,11 +86,15 @@ var httpRoutes = [...]httpRoute{
httpRoute{"", regexp.MustCompile(ciAPIPattern), proxyRequest}, httpRoute{"", regexp.MustCompile(ciAPIPattern), proxyRequest},
// Serve static files or forward the requests // Serve static files or forward the requests
httpRoute{"", nil, handleServeFile(documentRoot, httpRoute{"", nil,
handleServeFile(documentRoot, CacheDisabled,
handleDeployPage(documentRoot, handleDeployPage(documentRoot,
handleRailsError(documentRoot, handleRailsError(documentRoot,
proxyRequest, proxyRequest,
)))}, ),
),
),
},
} }
func main() { func main() {
......
...@@ -6,9 +6,17 @@ import ( ...@@ -6,9 +6,17 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
) )
func handleServeFile(documentRoot *string, notFoundHandler serviceHandleFunc) serviceHandleFunc { type CacheMode int
const (
CacheDisabled CacheMode = iota
CacheExpireMax
)
func handleServeFile(documentRoot *string, cache CacheMode, notFoundHandler serviceHandleFunc) serviceHandleFunc {
return func(w http.ResponseWriter, r *gitRequest) { return func(w http.ResponseWriter, r *gitRequest) {
file := filepath.Join(*documentRoot, r.relativeURIPath) file := filepath.Join(*documentRoot, r.relativeURIPath)
...@@ -22,7 +30,22 @@ func handleServeFile(documentRoot *string, notFoundHandler serviceHandleFunc) se ...@@ -22,7 +30,22 @@ func handleServeFile(documentRoot *string, notFoundHandler serviceHandleFunc) se
return return
} }
content, fi, err := openFile(file) var content *os.File
var fi os.FileInfo
var err error
// Serve pre-gzipped assets
if acceptEncoding := r.Header.Get("Accept-Encoding"); strings.Contains(acceptEncoding, "gzip") {
content, fi, err = openFile(file + ".gz")
if err == nil {
w.Header().Set("Content-Encoding", "gzip")
}
}
// If not found open the file
if content == nil || err != nil {
content, fi, err = openFile(file)
}
if err != nil { if err != nil {
if notFoundHandler != nil { if notFoundHandler != nil {
notFoundHandler(w, r) notFoundHandler(w, r)
...@@ -33,7 +56,15 @@ func handleServeFile(documentRoot *string, notFoundHandler serviceHandleFunc) se ...@@ -33,7 +56,15 @@ func handleServeFile(documentRoot *string, notFoundHandler serviceHandleFunc) se
} }
defer content.Close() defer content.Close()
log.Printf("StaticFile: serving %q", file) switch cache {
case CacheExpireMax:
// Cache statically served files for 1 year
cacheUntil := time.Now().AddDate(1, 0, 0).Format(http.TimeFormat)
w.Header().Set("Cache-Control", "public")
w.Header().Set("Expires", cacheUntil)
}
log.Printf("StaticFile: serving %q %q", file, w.Header().Get("Content-Encoding"))
http.ServeContent(w, r.Request, filepath.Base(file), fi.ModTime(), content) http.ServeContent(w, r.Request, filepath.Base(file), fi.ModTime(), content)
} }
} }
package main package main
import ( import (
"bytes"
"compress/gzip"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
...@@ -11,12 +13,14 @@ import ( ...@@ -11,12 +13,14 @@ import (
func TestServingNonExistingFile(t *testing.T) { func TestServingNonExistingFile(t *testing.T) {
dir := "/path/to/non/existing/directory" dir := "/path/to/non/existing/directory"
httpRequest, _ := http.NewRequest("GET", "/file", nil)
request := &gitRequest{ request := &gitRequest{
Request: httpRequest,
relativeURIPath: "/static/file", relativeURIPath: "/static/file",
} }
w := httptest.NewRecorder() w := httptest.NewRecorder()
handleServeFile(&dir, nil)(w, request) handleServeFile(&dir, CacheDisabled, nil)(w, request)
assertResponseCode(t, w, 404) assertResponseCode(t, w, 404)
} }
...@@ -27,34 +31,40 @@ func TestServingDirectory(t *testing.T) { ...@@ -27,34 +31,40 @@ func TestServingDirectory(t *testing.T) {
} }
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
httpRequest, _ := http.NewRequest("GET", "/file", nil)
request := &gitRequest{ request := &gitRequest{
Request: httpRequest,
relativeURIPath: "/", relativeURIPath: "/",
} }
w := httptest.NewRecorder() w := httptest.NewRecorder()
handleServeFile(&dir, nil)(w, request) handleServeFile(&dir, CacheDisabled, nil)(w, request)
assertResponseCode(t, w, 404) assertResponseCode(t, w, 404)
} }
func TestServingMalformedUri(t *testing.T) { func TestServingMalformedUri(t *testing.T) {
dir := "/path/to/non/existing/directory" dir := "/path/to/non/existing/directory"
httpRequest, _ := http.NewRequest("GET", "/file", nil)
request := &gitRequest{ request := &gitRequest{
Request: httpRequest,
relativeURIPath: "/../../../static/file", relativeURIPath: "/../../../static/file",
} }
w := httptest.NewRecorder() w := httptest.NewRecorder()
handleServeFile(&dir, nil)(w, request) handleServeFile(&dir, CacheDisabled, nil)(w, request)
assertResponseCode(t, w, 500) assertResponseCode(t, w, 500)
} }
func TestExecutingHandlerWhenNoFileFound(t *testing.T) { func TestExecutingHandlerWhenNoFileFound(t *testing.T) {
dir := "/path/to/non/existing/directory" dir := "/path/to/non/existing/directory"
httpRequest, _ := http.NewRequest("GET", "/file", nil)
request := &gitRequest{ request := &gitRequest{
Request: httpRequest,
relativeURIPath: "/static/file", relativeURIPath: "/static/file",
} }
executed := false executed := false
handleServeFile(&dir, func(w http.ResponseWriter, r *gitRequest) { handleServeFile(&dir, CacheDisabled, func(w http.ResponseWriter, r *gitRequest) {
executed = (r == request) executed = (r == request)
})(nil, request) })(nil, request)
if !executed { if !executed {
...@@ -79,9 +89,61 @@ func TestServingTheActualFile(t *testing.T) { ...@@ -79,9 +89,61 @@ func TestServingTheActualFile(t *testing.T) {
ioutil.WriteFile(filepath.Join(dir, "file"), []byte(fileContent), 0600) ioutil.WriteFile(filepath.Join(dir, "file"), []byte(fileContent), 0600)
w := httptest.NewRecorder() w := httptest.NewRecorder()
handleServeFile(&dir, nil)(w, request) handleServeFile(&dir, CacheDisabled, nil)(w, request)
assertResponseCode(t, w, 200) assertResponseCode(t, w, 200)
if w.Body.String() != fileContent { if w.Body.String() != fileContent {
t.Error("We should serve the file: ", w.Body.String()) t.Error("We should serve the file: ", w.Body.String())
} }
} }
func testServingThePregzippedFile(t *testing.T, enableGzip bool) {
dir, err := ioutil.TempDir("", "deploy")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
httpRequest, _ := http.NewRequest("GET", "/file", nil)
request := &gitRequest{
Request: httpRequest,
relativeURIPath: "/file",
}
if enableGzip {
httpRequest.Header.Set("Accept-Encoding", "gzip, deflate")
}
fileContent := "STATIC"
var fileGzipContent bytes.Buffer
fileGzip := gzip.NewWriter(&fileGzipContent)
fileGzip.Write([]byte(fileContent))
fileGzip.Close()
ioutil.WriteFile(filepath.Join(dir, "file.gz"), fileGzipContent.Bytes(), 0600)
ioutil.WriteFile(filepath.Join(dir, "file"), []byte(fileContent), 0600)
w := httptest.NewRecorder()
handleServeFile(&dir, CacheDisabled, nil)(w, request)
assertResponseCode(t, w, 200)
if enableGzip {
assertResponseHeader(t, w, "Content-Encoding", "gzip")
if bytes.Compare(w.Body.Bytes(), fileGzipContent.Bytes()) != 0 {
t.Error("We should serve the pregzipped file")
}
} else {
assertResponseCode(t, w, 200)
assertResponseHeader(t, w, "Content-Encoding", "")
if w.Body.String() != fileContent {
t.Error("We should serve the file: ", w.Body.String())
}
}
}
func TestServingThePregzippedFile(t *testing.T) {
testServingThePregzippedFile(t, true)
}
func TestServingThePregzippedFileWithoutEncoding(t *testing.T) {
testServingThePregzippedFile(t, false)
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment