Skip to content

Commit 959cc90

Browse files
author
Gregor Noczinski
committed
* Added ability to format now and last_modified in responses
1 parent 96a30f4 commit 959cc90

File tree

3 files changed

+103
-0
lines changed

3 files changed

+103
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ filter max_buffer_size <maximum buffer size in bytes>
5252
* ``request_proto``: Used proto
5353
* ``request_remoteAddress``: Remote address of the calling client
5454
* ``response_header_<header name>``: Contains a header value of the response, if provided or empty.
55+
* ``now[:<pattern>]``: Current timestamp. If pattern not provided, `RFC` or `RFC3339` [RFC3339](https://tools.ietf.org/html/rfc3339) is used. Other values: [`unix`](https://en.wikipedia.org/wiki/Unix_time), [`timestamp`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date/now) or free format following [Golang time formatting rules](https://golang.org/pkg/time/#pkg-constants).
56+
* ``response_header_last_modified[:<pattern>]``: Same like `now` for last modification time of current resource - see above. If not send by server current time will be used.
5557
* Replacements in files: If the replacement is prefixed with a ``@`` character it will be tried
5658
to find a file with this name and load the replacement from there. This will help you to also
5759
add replacements with larger payloads which will be ugly direct within the Caddyfile.

ruleReplaceAction.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"regexp"
66
"strconv"
77
"strings"
8+
"time"
9+
"fmt"
10+
"log"
811
)
912

1013
var paramReplacementPattern = regexp.MustCompile("\\{[a-zA-Z0-9_\\-.]+}")
@@ -57,6 +60,12 @@ func (instance *ruleReplaceAction) contextValueBy(name string) (string, bool) {
5760
if strings.HasPrefix(name, "response_") {
5861
return instance.contextResponseValueBy(name[9:])
5962
}
63+
if name == "now" {
64+
return instance.contextNowValueBy("")
65+
}
66+
if strings.HasPrefix(name, "now:") {
67+
return instance.contextNowValueBy(name[4:])
68+
}
6069
return "", false
6170
}
6271

@@ -83,8 +92,48 @@ func (instance *ruleReplaceAction) contextRequestValueBy(name string) (string, b
8392
}
8493

8594
func (instance *ruleReplaceAction) contextResponseValueBy(name string) (string, bool) {
95+
if name == "header_last_modified" || name == "header_last-modified" {
96+
return instance.contextLastModifiedValueBy("")
97+
}
98+
if strings.HasPrefix(name, "header_last_modified:") || strings.HasPrefix(name, "header_last-modified:") {
99+
return instance.contextLastModifiedValueBy(name[21:])
100+
}
86101
if strings.HasPrefix(name, "header_") {
87102
return (*instance.responseHeader).Get(name[7:]), true
88103
}
89104
return "", false
90105
}
106+
107+
func (instance *ruleReplaceAction) contextNowValueBy(pattern string) (string, bool) {
108+
return instance.formatTimeBy(time.Now(), pattern), true
109+
}
110+
111+
func (instance *ruleReplaceAction) contextLastModifiedValueBy(pattern string) (string, bool) {
112+
plain := instance.responseHeader.Get("last-Modified")
113+
if plain == "" {
114+
// Fallback to now
115+
return instance.contextNowValueBy(pattern)
116+
}
117+
t, err := time.Parse(time.RFC1123, plain)
118+
if err != nil {
119+
log.Printf("[WARN] Serving illegal 'Last-Modified' header value '%v' for '%v': Got: %v", plain, instance.request.URL, err)
120+
// Fallback to now
121+
return instance.contextNowValueBy(pattern)
122+
}
123+
return instance.formatTimeBy(t, pattern), true
124+
}
125+
126+
func (instance *ruleReplaceAction) formatTimeBy(t time.Time, pattern string) string {
127+
if pattern == "" || pattern == "RFC" || pattern == "RFC3339" {
128+
return t.Format(time.RFC3339)
129+
}
130+
if pattern == "unix" {
131+
return fmt.Sprintf("%d", t.Unix())
132+
}
133+
if pattern == "timestamp" {
134+
stamp := t.Unix() * 1000
135+
stamp += int64(t.Nanosecond()) / int64(1000000)
136+
return fmt.Sprintf("%d", stamp)
137+
}
138+
return t.Format(pattern)
139+
}

ruleReplaceAction_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"net/http"
66
"net/url"
77
"regexp"
8+
"time"
9+
"fmt"
810
)
911

1012
var (
@@ -44,8 +46,11 @@ func (s *ruleReplaceActionTest) Test_paramReplacer(c *C) {
4446
rra := &ruleReplaceAction{
4547
responseHeader: &http.Header{
4648
"A": []string{"c"},
49+
"Last-Modified": []string{"Tue, 01 Aug 2017 15:13:59 GMT"},
4750
},
4851
}
52+
yearString := time.Now().Format("2006-")
53+
4954
c.Assert(rra.paramReplacer([]byte("{0}"), groups), DeepEquals, []byte("a"))
5055
c.Assert(rra.paramReplacer([]byte("{1}"), groups), DeepEquals, []byte("b"))
5156
c.Assert(rra.paramReplacer([]byte("{response_header_A}"), groups), DeepEquals, []byte("c"))
@@ -55,12 +60,17 @@ func (s *ruleReplaceActionTest) Test_paramReplacer(c *C) {
5560
c.Assert(rra.paramReplacer([]byte("{2}"), groups), DeepEquals, []byte("{2}"))
5661
c.Assert(rra.paramReplacer([]byte("{response_headers_A}"), groups), DeepEquals, []byte("{response_headers_A}"))
5762
c.Assert(rra.paramReplacer([]byte("{foo}"), groups), DeepEquals, []byte("{foo}"))
63+
c.Assert(rra.paramReplacer([]byte("{now:2006-}"), groups), DeepEquals, []byte(yearString))
64+
c.Assert(string(rra.paramReplacer([]byte("{response_header_last_modified}"), groups)), DeepEquals, "2017-08-01T15:13:59Z")
65+
c.Assert(string(rra.paramReplacer([]byte("{response_header_last_modified:RFC}"), groups)), DeepEquals, "2017-08-01T15:13:59Z")
66+
c.Assert(string(rra.paramReplacer([]byte("{response_header_last_modified:timestamp}"), groups)), DeepEquals, "1501600439000")
5867
}
5968

6069
func (s *ruleReplaceActionTest) Test_contextValueBy(c *C) {
6170
rra := &ruleReplaceAction{
6271
responseHeader: &http.Header{
6372
"A": []string{"fromResponse"},
73+
"Last-Modified": []string{"Tue, 01 Aug 2017 15:13:59 GMT"},
6474
},
6575
request: &http.Request{
6676
Header: http.Header{
@@ -69,6 +79,8 @@ func (s *ruleReplaceActionTest) Test_contextValueBy(c *C) {
6979
},
7080
}
7181

82+
yearString := time.Now().Format("2006-")
83+
7284
r, ok := rra.contextValueBy("request_header_A")
7385
c.Assert(ok, Equals, true)
7486
c.Assert(r, Equals, "fromRequest")
@@ -77,6 +89,32 @@ func (s *ruleReplaceActionTest) Test_contextValueBy(c *C) {
7789
c.Assert(ok, Equals, true)
7890
c.Assert(r, Equals, "fromResponse")
7991

92+
r, ok = rra.contextValueBy("now")
93+
c.Assert(ok, Equals, true)
94+
c.Assert(len(r), Equals, 25)
95+
c.Assert(r[:5], Equals, yearString)
96+
97+
r, ok = rra.contextValueBy("now:")
98+
c.Assert(ok, Equals, true)
99+
c.Assert(len(r), Equals, 25)
100+
c.Assert(r[:5], Equals, yearString)
101+
102+
r, ok = rra.contextValueBy("now:xxx2006-xxx")
103+
c.Assert(ok, Equals, true)
104+
c.Assert(r, Equals, fmt.Sprintf("xxx%sxxx", yearString))
105+
106+
r, ok = rra.contextValueBy("response_header_last_modified")
107+
c.Assert(ok, Equals, true)
108+
c.Assert(r, Equals, "2017-08-01T15:13:59Z")
109+
110+
r, ok = rra.contextValueBy("response_header_last_modified:RFC")
111+
c.Assert(ok, Equals, true)
112+
c.Assert(r, Equals, "2017-08-01T15:13:59Z")
113+
114+
r, ok = rra.contextValueBy("response_header_last_modified:timestamp")
115+
c.Assert(ok, Equals, true)
116+
c.Assert(r, Equals, "1501600439000")
117+
80118
r, ok = rra.contextValueBy("foo")
81119
c.Assert(ok, Equals, false)
82120
c.Assert(r, Equals, "")
@@ -168,3 +206,17 @@ func (s *ruleReplaceActionTest) Test_contextResponseValueBy(c *C) {
168206
c.Assert(ok, Equals, false)
169207
c.Assert(r, Equals, "")
170208
}
209+
210+
func (s *ruleReplaceActionTest) Test_formatTimeBy(c *C) {
211+
rra := &ruleReplaceAction{}
212+
now, err := time.Parse(time.RFC3339Nano, "2017-08-15T14:00:00.123456789+02:00")
213+
c.Assert(err, IsNil)
214+
215+
c.Assert(rra.formatTimeBy(now, ""), Equals, "2017-08-15T14:00:00+02:00")
216+
c.Assert(rra.formatTimeBy(now, "RFC"), Equals, "2017-08-15T14:00:00+02:00")
217+
c.Assert(rra.formatTimeBy(now, "RFC3339"), Equals, "2017-08-15T14:00:00+02:00")
218+
c.Assert(rra.formatTimeBy(now, "unix"), Equals, "1502798400")
219+
c.Assert(rra.formatTimeBy(now, "timestamp"), Equals, "1502798400123")
220+
c.Assert(rra.formatTimeBy(now, "2006-01-02"), Equals, "2017-08-15")
221+
c.Assert(rra.formatTimeBy(now, "xxx"), Equals, "xxx")
222+
}

0 commit comments

Comments
 (0)