summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMatt Holt <[email protected]>2015-10-29 10:31:38 -0600
committerMatt Holt <[email protected]>2015-10-29 10:31:38 -0600
commita3f0fff734d6ea89505bb47c0a36b23b56b6f967 (patch)
tree545d14d3f0e752fab343918980a30b59ab5c4fbc
parent234783548f7f089ff355f6d316ef6427382bfaed (diff)
parent5a29107f3bb90fb9d1e1c41c9cb122e5c6cf0492 (diff)
downloadcaddy-a3f0fff734d6ea89505bb47c0a36b23b56b6f967.tar.gz
caddy-a3f0fff734d6ea89505bb47c0a36b23b56b6f967.zip
Merge pull request #296 from Makpoc/last-modified
markdown, templates: Add Last-Modified header
-rw-r--r--middleware/header.go33
-rw-r--r--middleware/header_test.go70
-rw-r--r--middleware/markdown/markdown.go2
-rw-r--r--middleware/templates/templates.go9
4 files changed, 113 insertions, 1 deletions
diff --git a/middleware/header.go b/middleware/header.go
new file mode 100644
index 000000000..56749b554
--- /dev/null
+++ b/middleware/header.go
@@ -0,0 +1,33 @@
+package middleware
+
+import (
+ "net/http"
+ "time"
+)
+
+// currentTime returns time.Now() everytime it's called. It's used for mocking in tests.
+var currentTime = func() time.Time {
+ return time.Now()
+}
+
+// SetLastModifiedHeader checks if the provided modTime is valid and if it is sets it
+// as a Last-Modified header to the ResponseWriter. If the modTime is in the future
+// the current time is used instead.
+func SetLastModifiedHeader(w http.ResponseWriter, modTime time.Time) {
+ if modTime.IsZero() || modTime.Equal(time.Unix(0, 0)) {
+ // the time does not appear to be valid. Don't put it in the response
+ return
+ }
+
+ // RFC 2616 - Section 14.29 - Last-Modified:
+ // An origin server MUST NOT send a Last-Modified date which is later than the
+ // server's time of message origination. In such cases, where the resource's last
+ // modification would indicate some time in the future, the server MUST replace
+ // that date with the message origination date.
+ now := currentTime()
+ if modTime.After(now) {
+ modTime = now
+ }
+
+ w.Header().Set("Last-Modified", modTime.UTC().Format(http.TimeFormat))
+}
diff --git a/middleware/header_test.go b/middleware/header_test.go
new file mode 100644
index 000000000..c76d8e631
--- /dev/null
+++ b/middleware/header_test.go
@@ -0,0 +1,70 @@
+package middleware
+
+import (
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+)
+
+func TestSetLastModified(t *testing.T) {
+ nowTime := time.Now()
+
+ // ovewrite the function to return reliable time
+ originalGetCurrentTimeFunc := currentTime
+ currentTime = func() time.Time {
+ return nowTime
+ }
+ defer func() {
+ currentTime = originalGetCurrentTimeFunc
+ }()
+
+ pastTime := nowTime.Truncate(1 * time.Hour)
+ futureTime := nowTime.Add(1 * time.Hour)
+
+ tests := []struct {
+ inputModTime time.Time
+ expectedIsHeaderSet bool
+ expectedLastModified string
+ }{
+ {
+ inputModTime: pastTime,
+ expectedIsHeaderSet: true,
+ expectedLastModified: pastTime.UTC().Format(http.TimeFormat),
+ },
+ {
+ inputModTime: nowTime,
+ expectedIsHeaderSet: true,
+ expectedLastModified: nowTime.UTC().Format(http.TimeFormat),
+ },
+ {
+ inputModTime: futureTime,
+ expectedIsHeaderSet: true,
+ expectedLastModified: nowTime.UTC().Format(http.TimeFormat),
+ },
+ {
+ inputModTime: time.Time{},
+ expectedIsHeaderSet: false,
+ },
+ }
+
+ for i, test := range tests {
+ responseRecorder := httptest.NewRecorder()
+ errorPrefix := fmt.Sprintf("Test [%d]: ", i)
+ SetLastModifiedHeader(responseRecorder, test.inputModTime)
+ actualLastModifiedHeader := responseRecorder.Header().Get("Last-Modified")
+
+ if test.expectedIsHeaderSet && actualLastModifiedHeader == "" {
+ t.Fatalf(errorPrefix + "Expected to find Last-Modified header, but found nothing")
+ }
+
+ if !test.expectedIsHeaderSet && actualLastModifiedHeader != "" {
+ t.Fatalf(errorPrefix+"Did not expect to find Last-Modified header, but found one [%s].", actualLastModifiedHeader)
+ }
+
+ if test.expectedLastModified != actualLastModifiedHeader {
+ t.Errorf(errorPrefix+"Expected Last-Modified content [%s], found [%s}", test.expectedLastModified, actualLastModifiedHeader)
+ }
+ }
+}
diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go
index bdf142cf2..3b3bc96e0 100644
--- a/middleware/markdown/markdown.go
+++ b/middleware/markdown/markdown.go
@@ -136,6 +136,7 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
// generation, serve the static page
if fs.ModTime().Before(fs1.ModTime()) {
if html, err := ioutil.ReadFile(filepath); err == nil {
+ middleware.SetLastModifiedHeader(w, fs1.ModTime())
w.Write(html)
return http.StatusOK, nil
}
@@ -162,6 +163,7 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
return http.StatusInternalServerError, err
}
+ middleware.SetLastModifiedHeader(w, fs.ModTime())
w.Write(html)
return http.StatusOK, nil
}
diff --git a/middleware/templates/templates.go b/middleware/templates/templates.go
index a699d0026..76447479d 100644
--- a/middleware/templates/templates.go
+++ b/middleware/templates/templates.go
@@ -34,7 +34,8 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
ctx := middleware.Context{Root: t.FileSys, Req: r, URL: r.URL}
// Build the template
- tpl, err := template.ParseFiles(filepath.Join(t.Root, fpath))
+ templatePath := filepath.Join(t.Root, fpath)
+ tpl, err := template.ParseFiles(templatePath)
if err != nil {
if os.IsNotExist(err) {
return http.StatusNotFound, nil
@@ -50,6 +51,12 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
if err != nil {
return http.StatusInternalServerError, err
}
+
+ templateInfo, err := os.Stat(templatePath)
+ if err == nil {
+ // add the Last-Modified header if we were able to optain the information
+ middleware.SetLastModifiedHeader(w, templateInfo.ModTime())
+ }
buf.WriteTo(w)
return http.StatusOK, nil