From 3441cdef6487bb1c07d8c7f1359ea91b7f9d8d1b Mon Sep 17 00:00:00 2001 From: Pieter Raubenheimer Date: Fri, 1 Apr 2016 22:24:04 +0100 Subject: [PATCH 1/2] Add Etag header --- middleware/fileserver.go | 5 +++++ middleware/fileserver_test.go | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/middleware/fileserver.go b/middleware/fileserver.go index 8d9dabcc..b86f5005 100644 --- a/middleware/fileserver.go +++ b/middleware/fileserver.go @@ -1,6 +1,7 @@ package middleware import ( + "fmt" "net/http" "os" "path" @@ -128,6 +129,10 @@ func (fh *fileHandler) serveFile(w http.ResponseWriter, r *http.Request, name st return http.StatusNotFound, nil } + // Add ETag header + e := fmt.Sprintf("\"%x-%x\"", d.ModTime().Unix(), d.Size()) + w.Header().Set("Etag", e) + // Note: Errors generated by ServeContent are written immediately // to the response. This usually only happens if seeking fails (rare). http.ServeContent(w, r, d.Name(), d.ModTime(), f) diff --git a/middleware/fileserver_test.go b/middleware/fileserver_test.go index c9011238..d42561b4 100644 --- a/middleware/fileserver_test.go +++ b/middleware/fileserver_test.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" "testing" + "time" ) var testDir = filepath.Join(os.TempDir(), "caddy_testdir") @@ -44,6 +45,7 @@ func TestServeHTTP(t *testing.T) { expectedStatus int expectedBodyContent string + expectedEtag string }{ // Test 0 - access without any path { @@ -60,12 +62,14 @@ func TestServeHTTP(t *testing.T) { url: "https://foo/file1.html", expectedStatus: http.StatusOK, expectedBodyContent: testFiles["file1.html"], + expectedEtag: "\"1e240-13\"", }, // Test 3 - access folder with index file with trailing slash { url: "https://foo/dirwithindex/", expectedStatus: http.StatusOK, expectedBodyContent: testFiles[filepath.Join("dirwithindex", "index.html")], + expectedEtag: "\"1e240-20\"", }, // Test 4 - access folder with index file without trailing slash { @@ -105,6 +109,7 @@ func TestServeHTTP(t *testing.T) { url: "https://foo/dirwithindex/index.html", expectedStatus: http.StatusOK, expectedBodyContent: testFiles[filepath.Join("dirwithindex", "index.html")], + expectedEtag: "\"1e240-20\"", }, // Test 11 - send a request with query params { @@ -143,6 +148,7 @@ func TestServeHTTP(t *testing.T) { responseRecorder := httptest.NewRecorder() request, err := http.NewRequest("GET", test.url, strings.NewReader("")) status, err := fileserver.ServeHTTP(responseRecorder, request) + etag := responseRecorder.Header().Get("Etag") // check if error matches expectations if err != nil { @@ -154,6 +160,11 @@ func TestServeHTTP(t *testing.T) { t.Errorf(getTestPrefix(i)+"Expected status %d, found %d", test.expectedStatus, status) } + // check etag + if test.expectedEtag != etag { + t.Errorf(getTestPrefix(i)+"Expected Etag header %d, found %d", test.expectedEtag, etag) + } + // check body content if !strings.Contains(responseRecorder.Body.String(), test.expectedBodyContent) { t.Errorf(getTestPrefix(i)+"Expected body to contain %q, found %q", test.expectedBodyContent, responseRecorder.Body.String()) @@ -173,6 +184,8 @@ func beforeServeHTTPTest(t *testing.T) { } } + fixedTime := time.Unix(123456, 0) + for relFile, fileContent := range testFiles { absFile := filepath.Join(testDir, relFile) @@ -197,6 +210,12 @@ func beforeServeHTTPTest(t *testing.T) { return } f.Close() + + // and set the last modified time + err = os.Chtimes(absFile, fixedTime, fixedTime) + if err != nil { + t.Fatalf("Failed to set file time to %s. Error was: %v", fixedTime, err) + } } } From 93d982a5a4c2bfc75f8d31bee101f00a0c746518 Mon Sep 17 00:00:00 2001 From: Pieter Raubenheimer Date: Sun, 3 Apr 2016 20:14:10 +0100 Subject: [PATCH 2/2] Improve readability and fully comply with RFC7232 --- middleware/fileserver.go | 4 ++-- middleware/fileserver_test.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/middleware/fileserver.go b/middleware/fileserver.go index b86f5005..cba4da09 100644 --- a/middleware/fileserver.go +++ b/middleware/fileserver.go @@ -130,8 +130,8 @@ func (fh *fileHandler) serveFile(w http.ResponseWriter, r *http.Request, name st } // Add ETag header - e := fmt.Sprintf("\"%x-%x\"", d.ModTime().Unix(), d.Size()) - w.Header().Set("Etag", e) + e := fmt.Sprintf(`W/"%x-%x"`, d.ModTime().Unix(), d.Size()) + w.Header().Set("ETag", e) // Note: Errors generated by ServeContent are written immediately // to the response. This usually only happens if seeking fails (rare). diff --git a/middleware/fileserver_test.go b/middleware/fileserver_test.go index d42561b4..96292bdc 100644 --- a/middleware/fileserver_test.go +++ b/middleware/fileserver_test.go @@ -62,14 +62,14 @@ func TestServeHTTP(t *testing.T) { url: "https://foo/file1.html", expectedStatus: http.StatusOK, expectedBodyContent: testFiles["file1.html"], - expectedEtag: "\"1e240-13\"", + expectedEtag: `W/"1e240-13"`, }, // Test 3 - access folder with index file with trailing slash { url: "https://foo/dirwithindex/", expectedStatus: http.StatusOK, expectedBodyContent: testFiles[filepath.Join("dirwithindex", "index.html")], - expectedEtag: "\"1e240-20\"", + expectedEtag: `W/"1e240-20"`, }, // Test 4 - access folder with index file without trailing slash { @@ -109,7 +109,7 @@ func TestServeHTTP(t *testing.T) { url: "https://foo/dirwithindex/index.html", expectedStatus: http.StatusOK, expectedBodyContent: testFiles[filepath.Join("dirwithindex", "index.html")], - expectedEtag: "\"1e240-20\"", + expectedEtag: `W/"1e240-20"`, }, // Test 11 - send a request with query params {