diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3483518 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ + +.PHONY: default test test-cover dev + + +# for test +test: + go test -race -cover ./... + +test-cover: + go test -race -coverprofile=test.out ./... && go tool cover --html=test.out + +bench: + go test -bench=. ./ \ No newline at end of file diff --git a/README.md b/README.md index f9df397..82d1cfc 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,10 @@ HTTP response freshness testing,it is copied from [fresh](https://github.com/j - `ResponseHeader` ```go -reqHeader = &RequestHeader{ - IfNoneMatch: []byte("\"foo\""), - IfModifiedSince: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), -} -resHeader = &ResponseHeader{ - ETag: []byte("\"foo\""), - LastModified: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), -} +req := httptest.NewRequest("GET", "/users/me", nil) +resp := httptest.NewRecorder() // true -Fresh(reqHeader, resHeader) +Fresh(req.Header, resp.Header) ``` ### Check diff --git a/fresh.go b/fresh.go index 035d35b..88c76eb 100644 --- a/fresh.go +++ b/fresh.go @@ -2,22 +2,23 @@ package fresh import ( "bytes" + "net/http" "regexp" "time" ) -// RequestHeader 请求头 -type RequestHeader struct { - IfModifiedSince []byte - IfNoneMatch []byte - CacheControl []byte -} - -// ResponseHeader 响应头 -type ResponseHeader struct { - ETag []byte - LastModified []byte -} +const ( + // HeaderIfModifiedSince if modified since + HeaderIfModifiedSince = "If-Modified-Since" + // HeaderIfNoneMatch if none match + HeaderIfNoneMatch = "If-None-Match" + // HeaderCacheControl Cache-Control + HeaderCacheControl = "Cache-Control" + // HeaderETag ETag + HeaderETag = "ETag" + // HeaderLastModified last modified + HeaderLastModified = "Last-Modified" +) var noCacheReg = regexp.MustCompile(`(?:^|,)\s*?no-cache\s*?(?:,|$)`) @@ -108,6 +109,13 @@ func Check(modifiedSince, noneMatch, cacheControl, lastModified, etag []byte) bo } // Fresh 判断该请求是否 fresh -func Fresh(reqHeader *RequestHeader, resHeader *ResponseHeader) bool { - return Check(reqHeader.IfModifiedSince, reqHeader.IfNoneMatch, reqHeader.CacheControl, resHeader.LastModified, resHeader.ETag) +func Fresh(reqHeader http.Header, resHeader http.Header) bool { + modifiedSince := []byte(reqHeader.Get(HeaderIfModifiedSince)) + noneMatch := []byte(reqHeader.Get(HeaderIfNoneMatch)) + cacheControl := []byte(reqHeader.Get(HeaderCacheControl)) + + lastModified := []byte(resHeader.Get(HeaderLastModified)) + etag := []byte(resHeader.Get(HeaderETag)) + + return Check(modifiedSince, noneMatch, cacheControl, lastModified, etag) } diff --git a/fresh_test.go b/fresh_test.go index 966e650..5ad8902 100644 --- a/fresh_test.go +++ b/fresh_test.go @@ -2,179 +2,158 @@ package fresh import ( "log" + "net/http" + "net/http/httptest" "testing" ) +func createRequestHeader(modifiedSince, noneMatch, cacheControl string) http.Header { + req := httptest.NewRequest("GET", "/users/me", nil) + header := req.Header + if modifiedSince != "" { + header.Set(HeaderIfModifiedSince, modifiedSince) + } + + if noneMatch != "" { + header.Set(HeaderIfNoneMatch, noneMatch) + } + + if cacheControl != "" { + header.Set(HeaderCacheControl, cacheControl) + } + return header +} + +func createResponseHeader(lastModified, etag string) http.Header { + resp := httptest.NewRecorder() + header := resp.Header() + if lastModified != "" { + header.Set(HeaderLastModified, lastModified) + } + + if etag != "" { + header.Set(HeaderETag, etag) + } + return header +} func TestFresh(t *testing.T) { // when a non-conditional GET is performed - reqHeader := &RequestHeader{} - resHeader := &ResponseHeader{} + reqHeader := createRequestHeader("", "", "") + resHeader := createResponseHeader("", "") if Fresh(reqHeader, resHeader) != false { t.Fatalf("should be stale") } // when ETags match - reqHeader = &RequestHeader{ - IfNoneMatch: []byte("\"foo\""), - } - resHeader = &ResponseHeader{ - ETag: []byte("\"foo\""), - } + reqHeader = createRequestHeader("", "\"foo\"", "") + + resHeader = createResponseHeader("", "\"foo\"") if Fresh(reqHeader, resHeader) != true { t.Fatalf("should be fresh'") } - reqHeader = &RequestHeader{ - IfNoneMatch: []byte("W/\"foo\""), - } - resHeader = &ResponseHeader{ - ETag: []byte("\"foo\""), - } + reqHeader = createRequestHeader("", "W/\"foo\"", "") + resHeader = createResponseHeader("", "\"foo\"") if Fresh(reqHeader, resHeader) != true { t.Fatalf("should be fresh'") } - reqHeader = &RequestHeader{ - IfNoneMatch: []byte("\"foo\""), - } - resHeader = &ResponseHeader{ - ETag: []byte("W/\"foo\""), - } + reqHeader = createRequestHeader("", "\"foo\"", "") + resHeader = createResponseHeader("", "W/\"foo\"") if Fresh(reqHeader, resHeader) != true { t.Fatalf("should be fresh'") } // when ETags mismatch - reqHeader = &RequestHeader{ - IfNoneMatch: []byte("\"foo\""), - } - resHeader = &ResponseHeader{ - ETag: []byte("\"bar\""), - } + reqHeader = createRequestHeader("", "\"foo\"", "") + resHeader = createResponseHeader("", "\"bar\"") if Fresh(reqHeader, resHeader) != false { t.Fatalf("should be stale'") } // when at least one matches - reqHeader = &RequestHeader{ - IfNoneMatch: []byte(" \"bar\" , \"foo\""), - } - resHeader = &ResponseHeader{ - ETag: []byte("\"foo\""), - } + reqHeader = createRequestHeader("", " \"bar\" , \"foo\"", "") + resHeader = createResponseHeader("", "\"foo\"") if Fresh(reqHeader, resHeader) != true { t.Fatalf("should be fresh") } // when etag is missing - reqHeader = &RequestHeader{ - IfNoneMatch: []byte("\"foo\""), - } - resHeader = &ResponseHeader{} + reqHeader = createRequestHeader("", "\"foo\"", "") + resHeader = createResponseHeader("", "") if Fresh(reqHeader, resHeader) != false { t.Fatalf("should be stale'") } // when ETag is weak - reqHeader = &RequestHeader{ - IfNoneMatch: []byte("W/\"foo\""), - } - resHeader = &ResponseHeader{ - ETag: []byte("W/\"foo\""), - } + reqHeader = createRequestHeader("", "W/\"foo\"", "") + resHeader = createResponseHeader("", "W/\"foo\"") + if Fresh(reqHeader, resHeader) != true { t.Fatalf("should be fresh on exact match") } - resHeader = &ResponseHeader{ - ETag: []byte("\"foo\""), - } + resHeader = createResponseHeader("", "\"foo\"") if Fresh(reqHeader, resHeader) != true { t.Fatalf("should be fresh on strong match") } // when ETag is strong - reqHeader = &RequestHeader{ - IfNoneMatch: []byte("\"foo\""), - } - resHeader = &ResponseHeader{ - ETag: []byte("\"foo\""), - } + reqHeader = createRequestHeader("", "\"foo\"", "") + resHeader = createResponseHeader("", "\"foo\"") + if Fresh(reqHeader, resHeader) != true { t.Fatalf("should be fresh on exact match") } - resHeader = &ResponseHeader{ - ETag: []byte("W/\"foo\""), - } + resHeader = createResponseHeader("", "W/\"foo\"") if Fresh(reqHeader, resHeader) != true { t.Fatalf("sshould be fresh on weak match") } // when * is given - reqHeader = &RequestHeader{ - IfNoneMatch: []byte("*"), - } - resHeader = &ResponseHeader{ - ETag: []byte("\"foo\""), - } + reqHeader = createRequestHeader("", "*", "") + resHeader = createResponseHeader("", "\"foo\"") if Fresh(reqHeader, resHeader) != true { t.Fatalf("should be fresh") } - reqHeader = &RequestHeader{ - IfNoneMatch: []byte("*, \"bar\""), - } + + reqHeader = createRequestHeader("", "*, \"bar\"", "") if Fresh(reqHeader, resHeader) != false { t.Fatalf("should get ignored if not only value") } // when modified since the date - reqHeader = &RequestHeader{ - IfModifiedSince: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), - } - resHeader = &ResponseHeader{ - LastModified: []byte("Sat, 01 Jan 2000 01:00:00 GMT"), - } + reqHeader = createRequestHeader("Sat, 01 Jan 2000 00:00:00 GMT", "", "") + resHeader = createResponseHeader("Sat, 01 Jan 2000 01:00:00 GMT", "") if Fresh(reqHeader, resHeader) != false { t.Fatalf("should be stale") } // when unmodified since the date - reqHeader = &RequestHeader{ - IfModifiedSince: []byte("Sat, 01 Jan 2000 01:00:00 GMT"), - } - resHeader = &ResponseHeader{ - LastModified: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), - } + reqHeader = createRequestHeader("Sat, 01 Jan 2000 01:00:00 GMT", "", "") + resHeader = createResponseHeader("Sat, 01 Jan 2000 00:00:00 GMT", "") + if Fresh(reqHeader, resHeader) != true { t.Fatalf("should be fresh") } // when Last-Modified is missing - reqHeader = &RequestHeader{ - IfModifiedSince: []byte("Sat, 01 Jan 2000 01:00:00 GMT"), - } - resHeader = &ResponseHeader{} + reqHeader = createRequestHeader("Sat, 01 Jan 2000 01:00:00 GMT", "", "") + resHeader = createResponseHeader("", "") + if Fresh(reqHeader, resHeader) != false { t.Fatalf("should be stale") } // with invalid If-Modified-Since date - reqHeader = &RequestHeader{ - IfModifiedSince: []byte("foo"), - } - resHeader = &ResponseHeader{ - LastModified: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), - } + reqHeader = createRequestHeader("foo", "", "") + resHeader = createResponseHeader("Sat, 01 Jan 2000 00:00:00 GMT", "") if Fresh(reqHeader, resHeader) != false { t.Fatalf("should be stale") } // with invalid Last-Modified date - reqHeader = &RequestHeader{ - IfModifiedSince: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), - } - resHeader = &ResponseHeader{ - LastModified: []byte("foo"), - } + reqHeader = createRequestHeader("Sat, 01 Jan 2000 00:00:00 GMT", "", "") + resHeader = createResponseHeader("foo", "") if Fresh(reqHeader, resHeader) != false { t.Fatalf("should be stale") } @@ -183,83 +162,47 @@ func TestFresh(t *testing.T) { // both match log.Println("both match") - reqHeader = &RequestHeader{ - IfNoneMatch: []byte("\"foo\""), - IfModifiedSince: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), - } - resHeader = &ResponseHeader{ - ETag: []byte("\"foo\""), - LastModified: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), - } + reqHeader = createRequestHeader("Sat, 01 Jan 2000 00:00:00 GMT", "\"foo\"", "") + resHeader = createResponseHeader("Sat, 01 Jan 2000 00:00:00 GMT", "\"foo\"") if Fresh(reqHeader, resHeader) != true { t.Fatalf("should be fresh") } // when only ETag matches - reqHeader = &RequestHeader{ - IfNoneMatch: []byte("\"foo\""), - IfModifiedSince: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), - } - resHeader = &ResponseHeader{ - ETag: []byte("\"foo\""), - LastModified: []byte("Sat, 01 Jan 2000 01:00:00 GMT'"), - } + reqHeader = createRequestHeader("Sat, 01 Jan 2000 00:00:00 GMT", "\"foo\"", "") + resHeader = createResponseHeader("Sat, 01 Jan 2000 01:00:00 GMT", "\"foo\"") if Fresh(reqHeader, resHeader) != false { t.Fatalf("should be stale") } // when only Last-Modified matches - reqHeader = &RequestHeader{ - IfNoneMatch: []byte("\"foo\""), - IfModifiedSince: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), - } - resHeader = &ResponseHeader{ - ETag: []byte("\"bar\""), - LastModified: []byte("Sat, 01 Jan 2000 00:00:00 GMT'"), - } + reqHeader = createRequestHeader("Sat, 01 Jan 2000 00:00:00 GMT", "\"foo\"", "") + resHeader = createResponseHeader("Sat, 01 Jan 2000 00:00:00 GMT", "\"bar\"") if Fresh(reqHeader, resHeader) != false { t.Fatalf("should be stale") } // when none match - reqHeader = &RequestHeader{ - IfNoneMatch: []byte("\"foo\""), - IfModifiedSince: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), - } - resHeader = &ResponseHeader{ - ETag: []byte("\"bar\""), - LastModified: []byte("Sat, 01 Jan 2000 01:00:00 GMT"), - } + reqHeader = createRequestHeader("Sat, 01 Jan 2000 00:00:00 GMT", "\"foo\"", "") + resHeader = createResponseHeader("Sat, 01 Jan 2000 01:00:00 GMT", "\"bar\"") if Fresh(reqHeader, resHeader) != false { t.Fatalf("should be stale") } // when requested with Cache-Control: no-cache - reqHeader = &RequestHeader{ - CacheControl: []byte("no-cache"), - } - resHeader = &ResponseHeader{} + reqHeader = createRequestHeader("", "", "no-cache") + resHeader = createResponseHeader("", "") if Fresh(reqHeader, resHeader) != false { t.Fatalf("should be stale") } // when ETags match - reqHeader = &RequestHeader{ - CacheControl: []byte("no-cache"), - IfNoneMatch: []byte("\"foo\""), - } - resHeader = &ResponseHeader{ - ETag: []byte("\"foo\""), - } + reqHeader = createRequestHeader("", "\"foo\"", "no-cache") + resHeader = createResponseHeader("", "\"foo\"") if Fresh(reqHeader, resHeader) != false { t.Fatalf("should be stale") } + // when unmodified since the date - reqHeader = &RequestHeader{ - CacheControl: []byte("no-cache"), - IfModifiedSince: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), - } - resHeader = &ResponseHeader{ - ETag: []byte("\"foo\""), - LastModified: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), - } + reqHeader = createRequestHeader("Sat, 01 Jan 2000 00:00:00 GMT", "", "no-cache") + resHeader = createResponseHeader("Sat, 01 Jan 2000 00:00:00 GMT", "\"foo\"") if Fresh(reqHeader, resHeader) != false { t.Fatalf("should be stale") } @@ -267,15 +210,9 @@ func TestFresh(t *testing.T) { func BenchmarkFresh(b *testing.B) { b.ResetTimer() + reqHeader := createRequestHeader("Sat, 01 Jan 2000 00:00:00 GMT", "\"foo\"", "") + resHeader := createResponseHeader("Sat, 01 Jan 2000 00:00:00 GMT", "\"foo\"") for i := 0; i < b.N; i++ { - reqHeader := &RequestHeader{ - IfNoneMatch: []byte("\"foo\""), - IfModifiedSince: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), - } - resHeader := &ResponseHeader{ - ETag: []byte("\"foo\""), - LastModified: []byte("Sat, 01 Jan 2000 00:00:00 GMT"), - } Fresh(reqHeader, resHeader) } }