forked from go-ozzo/ozzo-routing
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
329 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package caching | ||
|
||
import ( | ||
"fmt" | ||
routing "github.com/go-ozzo/ozzo-routing/v2" | ||
"strings" | ||
"time" | ||
) | ||
|
||
type AccessType string | ||
|
||
const ( | ||
AccessPublic = AccessType("public") | ||
AccessPrivate = AccessType("private") | ||
) | ||
|
||
// Options defines Cache Control directive values as defined in | ||
// RFC 7234 Section 5.2.1. | ||
// | ||
// https://www.rfc-editor.org/rfc/rfc7234#section-5.2.1 | ||
// | ||
// Additional Reference: | ||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#directives | ||
type Options struct { | ||
Access AccessType | ||
MaxAge time.Duration | ||
SMaxAge time.Duration | ||
NoCache bool | ||
NoStore bool | ||
MustRevalidate bool | ||
ProxyRevalidate bool | ||
MustUnderstand bool | ||
NoTransform bool | ||
Immutable bool | ||
} | ||
|
||
func (t Options) BuildHeaderValues() string { | ||
var sb strings.Builder | ||
|
||
if t.Access != "" { | ||
fmt.Fprintf(&sb, "%s,", t.Access) | ||
} | ||
|
||
if t.MaxAge != 0 { | ||
fmt.Fprintf(&sb, "max-age=%d,", int64(t.MaxAge.Round(time.Second).Seconds())) | ||
} | ||
|
||
if t.SMaxAge != 0 { | ||
fmt.Fprintf(&sb, "s-max-age=%d,", int64(t.SMaxAge.Round(time.Second).Seconds())) | ||
} | ||
|
||
if t.NoCache { | ||
fmt.Fprint(&sb, "no-cache,") | ||
} | ||
|
||
if t.NoStore { | ||
fmt.Fprint(&sb, "no-store,") | ||
} | ||
|
||
if t.MustRevalidate { | ||
fmt.Fprint(&sb, "must-revalidate,") | ||
} | ||
|
||
if t.ProxyRevalidate { | ||
fmt.Fprint(&sb, "proxy-revalidate,") | ||
} | ||
|
||
if t.MustUnderstand { | ||
fmt.Fprint(&sb, "must-understand,") | ||
} | ||
|
||
if t.NoTransform { | ||
fmt.Fprint(&sb, "no-transform,") | ||
} | ||
|
||
if t.Immutable { | ||
fmt.Fprint(&sb, "immutable,") | ||
} | ||
|
||
v := sb.String() | ||
if len(v) == 0 { | ||
return "" | ||
} | ||
|
||
return v[:len(v)-1] | ||
} | ||
|
||
// Handler returns a routing.Handler which sets the "Access-Control" header | ||
// value as defined in the given options to the response. | ||
func Handler(options Options) routing.Handler { | ||
headerValue := options.BuildHeaderValues() | ||
return func(c *routing.Context) error { | ||
c.Response.Header().Set("Access-Control", headerValue) | ||
return nil | ||
} | ||
} | ||
|
||
// Public returns a routing.Handler which sets the "Access-Control" header | ||
// value to "public,max-age=<maxAge>". | ||
func Public(maxAge time.Duration) routing.Handler { | ||
return Handler(Options{ | ||
Access: AccessPublic, | ||
MaxAge: maxAge, | ||
}) | ||
} | ||
|
||
// Private returns a routing.Handler which sets the "Access-Control" header | ||
// value to "private,max-age=<maxAge>". | ||
func Private(maxAge time.Duration) routing.Handler { | ||
return Handler(Options{ | ||
Access: AccessPrivate, | ||
MaxAge: maxAge, | ||
}) | ||
} | ||
|
||
// NoCache returns a routing.Handler which sets the "Access-Control" header | ||
// value to "no-cache". | ||
func NoCache() routing.Handler { | ||
return Handler(Options{NoCache: true}) | ||
} | ||
|
||
// NoStore returns a routing.Handler which sets the "Access-Control" header | ||
// value to "no-store". | ||
func NoStore() routing.Handler { | ||
return Handler(Options{NoStore: true}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package caching | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestOptions_BuildHeaderValues(t1 *testing.T) { | ||
tests := []struct { | ||
name string | ||
fields Options | ||
want string | ||
}{ | ||
{ | ||
name: "all", | ||
fields: Options{ | ||
Access: AccessPublic, | ||
MaxAge: 5 * time.Minute, | ||
SMaxAge: 10 * time.Minute, | ||
NoCache: true, | ||
NoStore: true, | ||
MustRevalidate: true, | ||
ProxyRevalidate: true, | ||
MustUnderstand: true, | ||
NoTransform: true, | ||
Immutable: true, | ||
}, | ||
want: "public,max-age=300,s-max-age=600,no-cache,no-store,must-revalidate,proxy-revalidate,must-understand,no-transform,immutable", | ||
}, | ||
{ | ||
name: "partial", | ||
fields: Options{ | ||
Access: AccessPublic, | ||
MaxAge: 30 * time.Second, | ||
MustRevalidate: true, | ||
}, | ||
want: "public,max-age=30,must-revalidate", | ||
}, | ||
{ | ||
name: "empty", | ||
fields: Options{}, | ||
want: "", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t1.Run(tt.name, func(t1 *testing.T) { | ||
t := Options{ | ||
Access: tt.fields.Access, | ||
MaxAge: tt.fields.MaxAge, | ||
SMaxAge: tt.fields.SMaxAge, | ||
NoCache: tt.fields.NoCache, | ||
NoStore: tt.fields.NoStore, | ||
MustRevalidate: tt.fields.MustRevalidate, | ||
ProxyRevalidate: tt.fields.ProxyRevalidate, | ||
MustUnderstand: tt.fields.MustUnderstand, | ||
NoTransform: tt.fields.NoTransform, | ||
Immutable: tt.fields.Immutable, | ||
} | ||
if got := t.BuildHeaderValues(); got != tt.want { | ||
t1.Errorf("BuildHeaderValues() failed\n"+ | ||
"\twas: %v\n"+ | ||
"\twant: %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package file | ||
|
||
import ( | ||
"path/filepath" | ||
|
||
routing "github.com/go-ozzo/ozzo-routing/v2" | ||
) | ||
|
||
// ServerOptions defines the possible options for the Server handler. | ||
type ServerOptions struct { | ||
// The path that all files to be served should be located within. The path map passed to the Server method | ||
// are all relative to this path. This property can be specified as an absolute file path or a path relative | ||
// to the current working path. If not set, this property defaults to the current working path. | ||
RootPath string | ||
// The file (e.g. index.html) to be served when the current request corresponds to a directory. | ||
// If not set, the handler will return a 404 HTTP error when the request corresponds to a directory. | ||
// This should only be a file name without the directory part. | ||
IndexFile string | ||
// The file to be served when no file or directory matches the current request. | ||
// If not set, the handler will return a 404 HTTP error when no file/directory matches the request. | ||
// The path of this file is relative to RootPath | ||
CatchAllFile string | ||
// A function that checks if the requested file path is allowed. If allowed, the function | ||
// may do additional work such as setting Expires HTTP header. | ||
// The function should return a boolean indicating whether the file should be served or not. | ||
// If false, a 404 HTTP error will be returned by the handler. | ||
Allow func(*routing.Context, string) bool | ||
// Define available compression encodings for serving files. Encodings are negotiated against the | ||
// unser agent. The first encoding which matches the accepted encodings from the user agent as well | ||
// as is available as file is served to the client. | ||
Compression []Encoding | ||
} | ||
|
||
// Merge takes another instance of ServerOptions and merges it with the current instance. | ||
// Thereby other overwrites values in t if existent. The merged instance is returned as new | ||
// ServerOptions instance. | ||
func (t ServerOptions) Merge(other ServerOptions) (new ServerOptions) { | ||
new = t | ||
|
||
if other.Allow != nil { | ||
new.Allow = other.Allow | ||
} | ||
if other.CatchAllFile != "" { | ||
new.CatchAllFile = other.CatchAllFile | ||
} | ||
if other.Compression != nil { | ||
new.Compression = other.Compression | ||
} | ||
if other.IndexFile != "" { | ||
new.IndexFile = other.IndexFile | ||
} | ||
if other.RootPath != "" { | ||
new.RootPath = other.RootPath | ||
} | ||
|
||
return new | ||
} | ||
|
||
func getServerOptions(opts []ServerOptions) ServerOptions { | ||
var options ServerOptions | ||
|
||
for _, opt := range opts { | ||
options = options.Merge(opt) | ||
} | ||
|
||
if !filepath.IsAbs(options.RootPath) { | ||
options.RootPath = filepath.Join(RootPath, options.RootPath) | ||
} | ||
|
||
return options | ||
} |
Oops, something went wrong.