mirror of
https://github.com/kataras/iris.git
synced 2025-12-18 02:17:05 +00:00
Publish the new version ✈️ | Look description please!
# FAQ ### Looking for free support? http://support.iris-go.com https://kataras.rocket.chat/channel/iris ### Looking for previous versions? https://github.com/kataras/iris#version ### Should I upgrade my Iris? Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready. > Iris uses the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature, so you get truly reproducible builds, as this method guards against upstream renames and deletes. **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`. For further installation support, please click [here](http://support.iris-go.com/d/16-how-to-install-iris-web-framework). ### About our new home page http://iris-go.com Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.com has been upgraded and it's really awesome! [Santosh](https://github.com/santoshanand) is a freelancer, he has a great knowledge of nodejs and express js, Android, iOS, React Native, Vue.js etc, if you need a developer to find or create a solution for your problem or task, please contact with him. The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please! Read more at https://github.com/kataras/iris/blob/master/HISTORY.md Former-commit-id: eec2d71bbe011d6b48d2526eb25919e36e5ad94e
This commit is contained in:
27
cache/LICENSE
vendored
Normal file
27
cache/LICENSE
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Gerasimos Maropoulos nor the name of his
|
||||
username, kataras, may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
70
cache/cache.go
vendored
Normal file
70
cache/cache.go
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/* Package cache provides cache capabilities with rich support of options and rules.
|
||||
|
||||
Example code:
|
||||
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/cache"
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
func main(){
|
||||
app := iris.Default()
|
||||
cachedHandler := cache.CacheHandler(h, 2 *time.Minute)
|
||||
app.Get("/hello", cachedHandler)
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
func h(ctx context.Context) {
|
||||
ctx.HTML("<h1> Hello, this should be cached. Every 2 minutes it will be refreshed, check your browser's inspector</h1>")
|
||||
}
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/cache/client"
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
// Cache accepts two parameters
|
||||
// first is the context.Handler which you want to cache its result
|
||||
// the second is, optional, the cache Entry's expiration duration
|
||||
// if the expiration <=2 seconds then expiration is taken by the "cache-control's maxage" header
|
||||
// returns context.Handler, which you can use as your default router or per-route handler
|
||||
//
|
||||
// All type of responses are cached, templates, json, text, anything.
|
||||
//
|
||||
// You can add validators with this function
|
||||
func Cache(bodyHandler context.Handler, expiration time.Duration) *client.Handler {
|
||||
return client.NewHandler(bodyHandler, expiration)
|
||||
}
|
||||
|
||||
// CacheHandler accepts two parameters
|
||||
// first is the context.Handler which you want to cache its result
|
||||
// the second is, optional, the cache Entry's expiration duration
|
||||
// if the expiration <=2 seconds then expiration is taken by the "cache-control's maxage" header
|
||||
// returns context.Handler, which you can use as your default router or per-route handler
|
||||
//
|
||||
// All type of responses are cached, templates, json, text, anything.
|
||||
//
|
||||
// it returns the context.Handler, for more options use the .Cache .
|
||||
func CacheHandler(bodyHandler context.Handler, expiration time.Duration) context.Handler {
|
||||
return Cache(bodyHandler, expiration).ServeHTTP
|
||||
}
|
||||
|
||||
var (
|
||||
// NoCache called when a particular handler is not valid for cache.
|
||||
// If this function called inside a handler then the handler is not cached
|
||||
// even if it's surrounded with the Cache/CacheFunc/CacheRemote wrappers.
|
||||
NoCache = client.NoCache
|
||||
)
|
||||
29
cache/cfg/cfg.go
vendored
Normal file
29
cache/cfg/cfg.go
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cfg
|
||||
|
||||
import "time"
|
||||
|
||||
// The constants be used by both client and server
|
||||
var (
|
||||
FailStatus = 400
|
||||
SuccessStatus = 200
|
||||
ContentHTML = "text/html; charset=utf-8"
|
||||
ContentTypeHeader = "Content-Type"
|
||||
StatusCodeHeader = "Status"
|
||||
QueryCacheKey = "cache_key"
|
||||
QueryCacheDuration = "cache_duration"
|
||||
QueryCacheStatusCode = "cache_status_code"
|
||||
QueryCacheContentType = "cache_content_type"
|
||||
RequestCacheTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// NoCacheHeader is the static header key which is setted to the response when NoCache is called,
|
||||
// used inside nethttp and fhttp Skippers.
|
||||
var NoCacheHeader = "X-No-Cache"
|
||||
|
||||
// MinimumCacheDuration is the minimum duration from time.Now
|
||||
// which is allowed between cache save and cache clear
|
||||
var MinimumCacheDuration = 2 * time.Second
|
||||
173
cache/client/client.go
vendored
Normal file
173
cache/client/client.go
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/cache/cfg"
|
||||
"github.com/kataras/iris/cache/client/rule"
|
||||
"github.com/kataras/iris/cache/uri"
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
// ClientHandler is the client-side handler
|
||||
// for each of the cached route paths's response
|
||||
// register one client handler per route.
|
||||
//
|
||||
// it's just calls a remote cache service server/handler,
|
||||
// which lives on other, external machine.
|
||||
//
|
||||
type ClientHandler struct {
|
||||
// bodyHandler the original route's handler
|
||||
bodyHandler context.Handler
|
||||
|
||||
// Rule optional validators for pre cache and post cache actions
|
||||
//
|
||||
// See more at ruleset.go
|
||||
rule rule.Rule
|
||||
|
||||
life time.Duration
|
||||
|
||||
remoteHandlerURL string
|
||||
}
|
||||
|
||||
// NewClientHandler returns a new remote client handler
|
||||
// which asks the remote handler the cached entry's response
|
||||
// with a GET request, or add a response with POST request
|
||||
// these all are done automatically, users can use this
|
||||
// handler as they use the local.go/NewHandler
|
||||
//
|
||||
// the ClientHandler is useful when user
|
||||
// wants to apply horizontal scaling to the app and
|
||||
// has a central http server which handles
|
||||
func NewClientHandler(bodyHandler context.Handler, life time.Duration, remote string) *ClientHandler {
|
||||
return &ClientHandler{
|
||||
bodyHandler: bodyHandler,
|
||||
rule: DefaultRuleSet,
|
||||
life: life,
|
||||
remoteHandlerURL: remote,
|
||||
}
|
||||
}
|
||||
|
||||
// Rule sets the ruleset for this handler,
|
||||
// see internal/net/http/ruleset.go for more information.
|
||||
//
|
||||
// returns itself.
|
||||
func (h *ClientHandler) Rule(r rule.Rule) *ClientHandler {
|
||||
if r == nil {
|
||||
// if nothing passed then use the allow-everything rule
|
||||
r = rule.Satisfied()
|
||||
}
|
||||
h.rule = r
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// AddRule adds a rule in the chain, the default rules are executed first.
|
||||
//
|
||||
// returns itself.
|
||||
func (h *ClientHandler) AddRule(r rule.Rule) *ClientHandler {
|
||||
if r == nil {
|
||||
return h
|
||||
}
|
||||
|
||||
h.rule = rule.Chained(h.rule, r)
|
||||
return h
|
||||
}
|
||||
|
||||
// Client is used inside the global Request function
|
||||
// this client is an exported to give you a freedom of change its Transport, Timeout and so on(in case of ssl)
|
||||
var Client = &http.Client{Timeout: cfg.RequestCacheTimeout}
|
||||
|
||||
const (
|
||||
methodGet = "GET"
|
||||
methodPost = "POST"
|
||||
)
|
||||
|
||||
// ServeHTTP , or remote cache client whatever you like, it's the client-side function of the ServeHTTP
|
||||
// sends a request to the server-side remote cache Service and sends the cached response to the frontend client
|
||||
// it is used only when you achieved something like horizontal scaling (separate machines)
|
||||
// look ../remote/remote.ServeHTTP for more
|
||||
//
|
||||
// if cache din't find then it sends a POST request and save the bodyHandler's body to the remote cache.
|
||||
//
|
||||
// It takes 3 parameters
|
||||
// the first is the remote address (it's the address you started your http server which handled by the Service.ServeHTTP)
|
||||
// the second is the handler (or the mux) you want to cache
|
||||
// and the third is the, optionally, cache expiration,
|
||||
// which is used to set cache duration of this specific cache entry to the remote cache service
|
||||
// if <=minimumAllowedCacheDuration then the server will try to parse from "cache-control" header
|
||||
//
|
||||
// client-side function
|
||||
func (h *ClientHandler) ServeHTTP(ctx context.Context) {
|
||||
// check for deniers, if at least one of them return true
|
||||
// for this specific request, then skip the whole cache
|
||||
if !h.rule.Claim(ctx) {
|
||||
h.bodyHandler(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
uri := &uri.URIBuilder{}
|
||||
uri.ServerAddr(h.remoteHandlerURL).ClientURI(ctx.Request().URL.RequestURI()).ClientMethod(ctx.Request().Method)
|
||||
|
||||
// set the full url here because below we have other issues, probably net/http bugs
|
||||
request, err := http.NewRequest(methodGet, uri.String(), nil)
|
||||
if err != nil {
|
||||
//// println("error when requesting to the remote service: " + err.Error())
|
||||
// somehing very bad happens, just execute the user's handler and return
|
||||
h.bodyHandler(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// println("GET Do to the remote cache service with the url: " + request.URL.String())
|
||||
response, err := Client.Do(request)
|
||||
|
||||
if err != nil || response.StatusCode == cfg.FailStatus {
|
||||
|
||||
// if not found on cache, then execute the handler and save the cache to the remote server
|
||||
recorder := ctx.Recorder()
|
||||
h.bodyHandler(ctx)
|
||||
|
||||
// check if it's a valid response, if it's not then just return.
|
||||
if !h.rule.Valid(ctx) {
|
||||
return
|
||||
}
|
||||
// save to the remote cache
|
||||
// we re-create the request for any case
|
||||
body := recorder.Body()[0:]
|
||||
if len(body) == 0 {
|
||||
//// println("Request: len body is zero, do nothing")
|
||||
return
|
||||
}
|
||||
uri.StatusCode(recorder.StatusCode())
|
||||
uri.Lifetime(h.life)
|
||||
uri.ContentType(recorder.Header().Get(cfg.ContentTypeHeader))
|
||||
|
||||
request, err = http.NewRequest(methodPost, uri.String(), bytes.NewBuffer(body)) // yes new buffer every time
|
||||
|
||||
// println("POST Do to the remote cache service with the url: " + request.URL.String())
|
||||
if err != nil {
|
||||
//// println("Request: error on method Post of request to the remote: " + err.Error())
|
||||
return
|
||||
}
|
||||
// go Client.Do(request)
|
||||
Client.Do(request)
|
||||
} else {
|
||||
// get the status code , content type and the write the response body
|
||||
ctx.ContentType(response.Header.Get(cfg.ContentTypeHeader))
|
||||
ctx.StatusCode(response.StatusCode)
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
response.Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ctx.Write(responseBody)
|
||||
|
||||
}
|
||||
}
|
||||
116
cache/client/handler.go
vendored
Normal file
116
cache/client/handler.go
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/cache/cfg"
|
||||
"github.com/kataras/iris/cache/client/rule"
|
||||
"github.com/kataras/iris/cache/entry"
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
// Handler the local cache service handler contains
|
||||
// the original bodyHandler, the memory cache entry and
|
||||
// the validator for each of the incoming requests and post responses
|
||||
type Handler struct {
|
||||
|
||||
// bodyHandler the original route's handler
|
||||
bodyHandler context.Handler
|
||||
|
||||
// Rule optional validators for pre cache and post cache actions
|
||||
//
|
||||
// See more at ruleset.go
|
||||
rule rule.Rule
|
||||
|
||||
// entry is the memory cache entry
|
||||
entry *entry.Entry
|
||||
}
|
||||
|
||||
// NewHandler returns a new cached handler
|
||||
func NewHandler(bodyHandler context.Handler,
|
||||
expireDuration time.Duration) *Handler {
|
||||
|
||||
e := entry.NewEntry(expireDuration)
|
||||
|
||||
return &Handler{
|
||||
bodyHandler: bodyHandler,
|
||||
rule: DefaultRuleSet,
|
||||
entry: e,
|
||||
}
|
||||
}
|
||||
|
||||
// Rule sets the ruleset for this handler.
|
||||
//
|
||||
// returns itself.
|
||||
func (h *Handler) Rule(r rule.Rule) *Handler {
|
||||
if r == nil {
|
||||
// if nothing passed then use the allow-everything rule
|
||||
r = rule.Satisfied()
|
||||
}
|
||||
h.rule = r
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// AddRule adds a rule in the chain, the default rules are executed first.
|
||||
//
|
||||
// returns itself.
|
||||
func (h *Handler) AddRule(r rule.Rule) *Handler {
|
||||
if r == nil {
|
||||
return h
|
||||
}
|
||||
|
||||
h.rule = rule.Chained(h.rule, r)
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *Handler) ServeHTTP(ctx context.Context) {
|
||||
// check for pre-cache validators, if at least one of them return false
|
||||
// for this specific request, then skip the whole cache
|
||||
if !h.rule.Claim(ctx) {
|
||||
h.bodyHandler(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// check if we have a stored response( it is not expired)
|
||||
res, exists := h.entry.Response()
|
||||
if !exists {
|
||||
|
||||
// if it's not exists, then execute the original handler
|
||||
// with our custom response recorder response writer
|
||||
// because the net/http doesn't give us
|
||||
// a built'n way to get the status code & body
|
||||
recorder := ctx.Recorder()
|
||||
h.bodyHandler(ctx)
|
||||
|
||||
// now that we have recordered the response,
|
||||
// we are ready to check if that specific response is valid to be stored.
|
||||
|
||||
// check if it's a valid response, if it's not then just return.
|
||||
if !h.rule.Valid(ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
// no need to copy the body, its already done inside
|
||||
body := recorder.Body()
|
||||
if len(body) == 0 {
|
||||
// if no body then just exit
|
||||
return
|
||||
}
|
||||
|
||||
// check for an expiration time if the
|
||||
// given expiration was not valid then check for GetMaxAge &
|
||||
// update the response & release the recorder
|
||||
h.entry.Reset(recorder.StatusCode(), recorder.Header().Get(cfg.ContentTypeHeader), body, GetMaxAge(ctx.Request()))
|
||||
return
|
||||
}
|
||||
|
||||
// if it's valid then just write the cached results
|
||||
ctx.ContentType(res.ContentType())
|
||||
ctx.StatusCode(res.StatusCode())
|
||||
ctx.Write(res.Body())
|
||||
}
|
||||
113
cache/client/response_recorder.go
vendored
Normal file
113
cache/client/response_recorder.go
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var rpool = sync.Pool{}
|
||||
|
||||
// AcquireResponseRecorder returns a ResponseRecorder
|
||||
func AcquireResponseRecorder(underline http.ResponseWriter) *ResponseRecorder {
|
||||
v := rpool.Get()
|
||||
var res *ResponseRecorder
|
||||
if v != nil {
|
||||
res = v.(*ResponseRecorder)
|
||||
} else {
|
||||
res = &ResponseRecorder{}
|
||||
}
|
||||
res.underline = underline
|
||||
return res
|
||||
}
|
||||
|
||||
// ReleaseResponseRecorder releases a ResponseRecorder which has been previously received by AcquireResponseRecorder
|
||||
func ReleaseResponseRecorder(res *ResponseRecorder) {
|
||||
res.underline = nil
|
||||
res.statusCode = 0
|
||||
res.chunks = res.chunks[0:0]
|
||||
rpool.Put(res)
|
||||
}
|
||||
|
||||
// ResponseRecorder is used by httpcache to be able to get the Body and the StatusCode of a request handler
|
||||
type ResponseRecorder struct {
|
||||
underline http.ResponseWriter
|
||||
chunks [][]byte // 2d because .Write can be called more than one time in the same handler and we want to cache all of them
|
||||
statusCode int // the saved status code which will be used from the cache service
|
||||
}
|
||||
|
||||
// Body joins the chunks to one []byte slice, this is the full body
|
||||
func (res *ResponseRecorder) Body() []byte {
|
||||
var body []byte
|
||||
for i := range res.chunks {
|
||||
body = append(body, res.chunks[i]...)
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
// ContentType returns the header's value of "Content-Type"
|
||||
func (res *ResponseRecorder) ContentType() string {
|
||||
return res.Header().Get("Content-Type")
|
||||
}
|
||||
|
||||
// StatusCode returns the status code, if not given then returns 200
|
||||
// but doesn't changes the existing behavior
|
||||
func (res *ResponseRecorder) StatusCode() int {
|
||||
if res.statusCode == 0 {
|
||||
return 200
|
||||
}
|
||||
return res.statusCode
|
||||
}
|
||||
|
||||
// Header returns the header map that will be sent by
|
||||
// WriteHeader. Changing the header after a call to
|
||||
// WriteHeader (or Write) has no effect unless the modified
|
||||
// headers were declared as trailers by setting the
|
||||
// "Trailer" header before the call to WriteHeader (see example).
|
||||
// To suppress implicit response headers, set their value to nil.
|
||||
func (res *ResponseRecorder) Header() http.Header {
|
||||
return res.underline.Header()
|
||||
}
|
||||
|
||||
// Write writes the data to the connection as part of an HTTP reply.
|
||||
//
|
||||
// If WriteHeader has not yet been called, Write calls
|
||||
// WriteHeader(http.StatusOK) before writing the data. If the Header
|
||||
// does not contain a Content-Type line, Write adds a Content-Type set
|
||||
// to the result of passing the initial 512 bytes of written data to
|
||||
// DetectContentType.
|
||||
//
|
||||
// Depending on the HTTP protocol version and the client, calling
|
||||
// Write or WriteHeader may prevent future reads on the
|
||||
// Request.Body. For HTTP/1.x requests, handlers should read any
|
||||
// needed request body data before writing the response. Once the
|
||||
// headers have been flushed (due to either an explicit Flusher.Flush
|
||||
// call or writing enough data to trigger a flush), the request body
|
||||
// may be unavailable. For HTTP/2 requests, the Go HTTP server permits
|
||||
// handlers to continue to read the request body while concurrently
|
||||
// writing the response. However, such behavior may not be supported
|
||||
// by all HTTP/2 clients. Handlers should read before writing if
|
||||
// possible to maximize compatibility.
|
||||
func (res *ResponseRecorder) Write(contents []byte) (int, error) {
|
||||
if res.statusCode == 0 { // if not setted set it here
|
||||
res.WriteHeader(http.StatusOK)
|
||||
}
|
||||
res.chunks = append(res.chunks, contents)
|
||||
return res.underline.Write(contents)
|
||||
}
|
||||
|
||||
// WriteHeader sends an HTTP response header with status code.
|
||||
// If WriteHeader is not called explicitly, the first call to Write
|
||||
// will trigger an implicit WriteHeader(http.StatusOK).
|
||||
// Thus explicit calls to WriteHeader are mainly used to
|
||||
// send error codes.
|
||||
func (res *ResponseRecorder) WriteHeader(statusCode int) {
|
||||
if res.statusCode == 0 { // set it only if not setted already, we don't want logs about multiple sends
|
||||
res.statusCode = statusCode
|
||||
res.underline.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
}
|
||||
60
cache/client/rule/chained.go
vendored
Normal file
60
cache/client/rule/chained.go
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rule
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
// chainedRule is a Rule with next Rule
|
||||
type chainedRule struct {
|
||||
Rule
|
||||
next Rule
|
||||
}
|
||||
|
||||
var _ Rule = &chainedRule{}
|
||||
|
||||
// chainedSingle returns a new rule witch has a next rule too
|
||||
func chainedSingle(rule Rule, next Rule) Rule {
|
||||
if next == nil {
|
||||
next = Satisfied()
|
||||
}
|
||||
|
||||
return &chainedRule{
|
||||
Rule: rule,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
// Chained returns a new rule which has more than one coming next ruleset
|
||||
func Chained(rule Rule, next ...Rule) Rule {
|
||||
if len(next) == 0 {
|
||||
return chainedSingle(rule, nil)
|
||||
}
|
||||
c := chainedSingle(rule, next[0])
|
||||
|
||||
for i := 1; i < len(next); i++ {
|
||||
c = chainedSingle(c, next[i])
|
||||
}
|
||||
|
||||
return c
|
||||
|
||||
}
|
||||
|
||||
// Claim validator
|
||||
func (c *chainedRule) Claim(ctx context.Context) bool {
|
||||
if !c.Rule.Claim(ctx) {
|
||||
return false
|
||||
}
|
||||
return c.next.Claim(ctx)
|
||||
}
|
||||
|
||||
// Valid validator
|
||||
func (c *chainedRule) Valid(ctx context.Context) bool {
|
||||
if !c.Rule.Valid(ctx) {
|
||||
return false
|
||||
}
|
||||
return c.next.Valid(ctx)
|
||||
}
|
||||
53
cache/client/rule/conditional.go
vendored
Normal file
53
cache/client/rule/conditional.go
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rule
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
// Conditional is a Rule witch adds a predicate in order to its methods to execute
|
||||
type conditionalRule struct {
|
||||
claimPredicate func() bool
|
||||
validPredicate func() bool
|
||||
}
|
||||
|
||||
var emptyConditionalPredicate = func() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var _ Rule = &conditionalRule{}
|
||||
|
||||
// Conditional returns a new rule witch has conditionals
|
||||
func Conditional(claimPredicate func() bool, validPredicate func() bool) Rule {
|
||||
if claimPredicate == nil {
|
||||
claimPredicate = emptyConditionalPredicate
|
||||
}
|
||||
|
||||
if validPredicate == nil {
|
||||
validPredicate = emptyConditionalPredicate
|
||||
}
|
||||
|
||||
return &conditionalRule{
|
||||
claimPredicate: claimPredicate,
|
||||
validPredicate: validPredicate,
|
||||
}
|
||||
}
|
||||
|
||||
// Claim validator
|
||||
func (c *conditionalRule) Claim(ctx context.Context) bool {
|
||||
if !c.claimPredicate() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Valid validator
|
||||
func (c *conditionalRule) Valid(ctx context.Context) bool {
|
||||
if !c.validPredicate() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
59
cache/client/rule/header.go
vendored
Normal file
59
cache/client/rule/header.go
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rule
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
|
||||
"github.com/kataras/iris/cache/ruleset"
|
||||
)
|
||||
|
||||
// The HeaderPredicate should be alived on each of $package/rule BUT GOLANG DOESN'T SUPPORT type alias and I don't want to have so many copies around
|
||||
// read more at ../../ruleset.go
|
||||
|
||||
// headerRule is a Rule witch receives and checks for a header predicates
|
||||
// request headers on Claim and response headers on Valid.
|
||||
type headerRule struct {
|
||||
claim ruleset.HeaderPredicate
|
||||
valid ruleset.HeaderPredicate
|
||||
}
|
||||
|
||||
var _ Rule = &headerRule{}
|
||||
|
||||
// Header returns a new rule witch claims and execute the post validations trough headers
|
||||
func Header(claim ruleset.HeaderPredicate, valid ruleset.HeaderPredicate) Rule {
|
||||
if claim == nil {
|
||||
claim = ruleset.EmptyHeaderPredicate
|
||||
}
|
||||
|
||||
if valid == nil {
|
||||
valid = ruleset.EmptyHeaderPredicate
|
||||
}
|
||||
|
||||
return &headerRule{
|
||||
claim: claim,
|
||||
valid: valid,
|
||||
}
|
||||
}
|
||||
|
||||
// HeaderClaim returns a header rule which cares only about claiming (pre-validation)
|
||||
func HeaderClaim(claim ruleset.HeaderPredicate) Rule {
|
||||
return Header(claim, nil)
|
||||
}
|
||||
|
||||
// HeaderValid returns a header rule which cares only about valid (post-validation)
|
||||
func HeaderValid(valid ruleset.HeaderPredicate) Rule {
|
||||
return Header(nil, valid)
|
||||
}
|
||||
|
||||
// Claim validator
|
||||
func (h *headerRule) Claim(ctx context.Context) bool {
|
||||
return h.claim(ctx.Request().Header.Get)
|
||||
}
|
||||
|
||||
// Valid validator
|
||||
func (h *headerRule) Valid(ctx context.Context) bool {
|
||||
return h.valid(ctx.ResponseWriter().Header().Get)
|
||||
}
|
||||
26
cache/client/rule/not_satisfied.go
vendored
Normal file
26
cache/client/rule/not_satisfied.go
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rule
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
type notSatisfiedRule struct{}
|
||||
|
||||
var _ Rule = ¬SatisfiedRule{}
|
||||
|
||||
// NotSatisfied returns a rule which allows nothing
|
||||
func NotSatisfied() Rule {
|
||||
return ¬SatisfiedRule{}
|
||||
}
|
||||
|
||||
func (n *notSatisfiedRule) Claim(context.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *notSatisfiedRule) Valid(context.Context) bool {
|
||||
return false
|
||||
}
|
||||
15
cache/client/rule/rule.go
vendored
Normal file
15
cache/client/rule/rule.go
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rule
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
// Rule a superset of validators
|
||||
type Rule interface {
|
||||
Claim(ctx context.Context) bool
|
||||
Valid(ctx context.Context) bool
|
||||
}
|
||||
28
cache/client/rule/satisfied.go
vendored
Normal file
28
cache/client/rule/satisfied.go
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rule
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
type satisfiedRule struct{}
|
||||
|
||||
var _ Rule = &satisfiedRule{}
|
||||
|
||||
// Satisfied returns a rule which allows anything,
|
||||
// it's usually the last rule on chained rules if no next rule is given,
|
||||
// but it can be used outside of a chain too as a default allow-all rule.
|
||||
func Satisfied() Rule {
|
||||
return &satisfiedRule{}
|
||||
}
|
||||
|
||||
func (n *satisfiedRule) Claim(context.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (n *satisfiedRule) Valid(context.Context) bool {
|
||||
return true
|
||||
}
|
||||
97
cache/client/rule/validator.go
vendored
Normal file
97
cache/client/rule/validator.go
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rule
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
// Validators are introduced to implement the RFC about cache (https://tools.ietf.org/html/rfc7234#section-1.1).
|
||||
|
||||
// PreValidator like middleware, executes before the cache action begins, if a callback returns false
|
||||
// then this specific cache action, with specific request, is ignored and the real (original)
|
||||
// handler is executed instead.
|
||||
//
|
||||
// I'll not add all specifications here I'll give the opportunity (public API in the httpcache package-level)
|
||||
// to the end-user to specify her/his ignore rules too (ignore-only for now).
|
||||
//
|
||||
// Each package, nethttp and fhttp should implement their own encapsulations because of different request object.
|
||||
//
|
||||
// One function, accepts the request and returns false if should be denied/ignore, otherwise true.
|
||||
// if at least one return false then the original handler will execute as it's
|
||||
// and the whole cache action(set & get) should be ignored, it will be never go to the step of post-cache validations.
|
||||
type PreValidator func(context.Context) bool
|
||||
|
||||
// PostValidator type is is introduced to implement the second part of the RFC about cache.
|
||||
//
|
||||
// Q: What's the difference between this and a PreValidator?
|
||||
// A: PreValidator runs BEFORE trying to get the cache, it cares only for the request
|
||||
// and if at least one PreValidator returns false then it just runs the original handler and stop there, at the other hand
|
||||
// a PostValidator runs if all PreValidators returns true and original handler is executed but with a response recorder,
|
||||
// also the PostValidator should return true to store the cached response.
|
||||
// Last, a PostValidator accepts a context
|
||||
// in order to be able to catch the original handler's response,
|
||||
// the PreValidator checks only for request.
|
||||
//
|
||||
// If a function of type of PostValidator returns true then the (shared-always) cache is allowed to be stored.
|
||||
type PostValidator func(context.Context) bool
|
||||
|
||||
// validatorRule is a rule witch receives PreValidators and PostValidators
|
||||
// it's a 'complete set of rules', you can call it as a Responsible Validator,
|
||||
// it's used when you the user wants to check for special things inside a request and a response.
|
||||
type validatorRule struct {
|
||||
// preValidators a list of PreValidator functions, execute before real cache begins
|
||||
// if at least one of them returns false then the original handler will execute as it's
|
||||
// and the whole cache action(set & get) will be skipped for this specific client's request.
|
||||
//
|
||||
// Read-only 'runtime'
|
||||
preValidators []PreValidator
|
||||
|
||||
// postValidators a list of PostValidator functions, execute after the original handler is executed with the response recorder
|
||||
// and exactly before this cached response is saved,
|
||||
// if at least one of them returns false then the response will be not saved for this specific client's request.
|
||||
//
|
||||
// Read-only 'runtime'
|
||||
postValidators []PostValidator
|
||||
}
|
||||
|
||||
var _ Rule = &validatorRule{}
|
||||
|
||||
// DefaultValidator returns a new validator which contains the default pre and post cache validators
|
||||
func DefaultValidator() Rule { return Validator(nil, nil) }
|
||||
|
||||
// Validator receives the preValidators and postValidators and returns a new Validator rule
|
||||
func Validator(preValidators []PreValidator, postValidators []PostValidator) Rule {
|
||||
return &validatorRule{
|
||||
preValidators: preValidators,
|
||||
postValidators: postValidators,
|
||||
}
|
||||
}
|
||||
|
||||
// Claim returns true if incoming request can claim for a cached handler
|
||||
// the original handler should run as it is and exit
|
||||
func (v *validatorRule) Claim(ctx context.Context) bool {
|
||||
// check for pre-cache validators, if at least one of them return false
|
||||
// for this specific request, then skip the whole cache
|
||||
for _, shouldProcess := range v.preValidators {
|
||||
if !shouldProcess(ctx) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Valid returns true if incoming request and post-response from the original handler
|
||||
// is valid to be store to the cache, if not(false) then the consumer should just exit
|
||||
// otherwise(true) the consumer should store the cached response
|
||||
func (v *validatorRule) Valid(ctx context.Context) bool {
|
||||
// check if it's a valid response, if it's not then just return.
|
||||
for _, valid := range v.postValidators {
|
||||
if !valid(ctx) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
38
cache/client/ruleset.go
vendored
Normal file
38
cache/client/ruleset.go
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/cache/cfg"
|
||||
"github.com/kataras/iris/cache/client/rule"
|
||||
"github.com/kataras/iris/cache/ruleset"
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
// DefaultRuleSet is a list of the default pre-cache validators
|
||||
// which exists in ALL handlers, local and remote.
|
||||
var DefaultRuleSet = rule.Chained(
|
||||
// #1 A shared cache MUST NOT use a cached response to a request with an
|
||||
// Authorization header field
|
||||
rule.HeaderClaim(ruleset.AuthorizationRule),
|
||||
// #2 "must-revalidate" and/or
|
||||
// "s-maxage" response directives are not allowed to be served stale
|
||||
// (Section 4.2.4) by shared caches. In particular, a response with
|
||||
// either "max-age=0, must-revalidate" or "s-maxage=0" cannot be used to
|
||||
// satisfy a subsequent request without revalidating it on the origin
|
||||
// server.
|
||||
rule.HeaderClaim(ruleset.MustRevalidateRule),
|
||||
rule.HeaderClaim(ruleset.ZeroMaxAgeRule),
|
||||
// #3 custom No-Cache header used inside this library
|
||||
// for BOTH request and response (after get-cache action)
|
||||
rule.Header(ruleset.NoCacheRule, ruleset.NoCacheRule),
|
||||
)
|
||||
|
||||
// NoCache called when a particular handler is not valid for cache.
|
||||
// If this function called inside a handler then the handler is not cached
|
||||
// even if it's surrounded with the Cache/CacheFunc wrappers.
|
||||
func NoCache(ctx context.Context) {
|
||||
ctx.Header(cfg.NoCacheHeader, "true")
|
||||
}
|
||||
24
cache/client/utils.go
vendored
Normal file
24
cache/client/utils.go
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/cache/entry"
|
||||
)
|
||||
|
||||
// GetMaxAge parses the "Cache-Control" header
|
||||
// and returns a LifeChanger which can be passed
|
||||
// to the response's Reset
|
||||
func GetMaxAge(r *http.Request) entry.LifeChanger {
|
||||
return func() time.Duration {
|
||||
cacheControlHeader := r.Header.Get("Cache-Control")
|
||||
// headerCacheDur returns the seconds
|
||||
headerCacheDur := entry.ParseMaxAge(cacheControlHeader)
|
||||
return time.Duration(headerCacheDur) * time.Second
|
||||
}
|
||||
}
|
||||
109
cache/entry/entry.go
vendored
Normal file
109
cache/entry/entry.go
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package entry
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/cache/cfg"
|
||||
)
|
||||
|
||||
// Entry is the cache entry
|
||||
// contains the expiration datetime and the response
|
||||
type Entry struct {
|
||||
life time.Duration
|
||||
// ExpiresAt is the time which this cache will not be available
|
||||
expiresAt time.Time
|
||||
|
||||
// Response the response should be served to the client
|
||||
response *Response
|
||||
// but we need the key to invalidate manually...xmm
|
||||
// let's see for that later, maybe we make a slice instead
|
||||
// of store map
|
||||
}
|
||||
|
||||
// NewEntry returns a new cache entry
|
||||
// it doesn't sets the expiresAt & the response
|
||||
// because these are setting each time on Reset
|
||||
func NewEntry(duration time.Duration) *Entry {
|
||||
// if given duration is not <=0 (which means finds from the headers)
|
||||
// then we should check for the MinimumCacheDuration here
|
||||
if duration >= 0 && duration < cfg.MinimumCacheDuration {
|
||||
duration = cfg.MinimumCacheDuration
|
||||
}
|
||||
|
||||
return &Entry{
|
||||
life: duration,
|
||||
response: &Response{},
|
||||
}
|
||||
}
|
||||
|
||||
// Response gets the cache response contents
|
||||
// if it's valid returns them with a true value
|
||||
// otherwise returns nil, false
|
||||
func (e *Entry) Response() (*Response, bool) {
|
||||
if !e.valid() {
|
||||
// it has been expired
|
||||
return nil, false
|
||||
}
|
||||
return e.response, true
|
||||
}
|
||||
|
||||
// valid returns true if this entry's response is still valid
|
||||
// or false if the expiration time passed
|
||||
func (e *Entry) valid() bool {
|
||||
return !time.Now().After(e.expiresAt)
|
||||
}
|
||||
|
||||
// LifeChanger is the function which returns
|
||||
// a duration which will be compared with the current
|
||||
// entry's (cache life) duration
|
||||
// and execute the LifeChanger func
|
||||
// to set the new life time
|
||||
type LifeChanger func() time.Duration
|
||||
|
||||
// ChangeLifetime modifies the life field
|
||||
// which is the life duration of the cached response
|
||||
// of this cache entry
|
||||
//
|
||||
// useful when we find a max-age header from the handler
|
||||
func (e *Entry) ChangeLifetime(fdur LifeChanger) {
|
||||
if e.life < cfg.MinimumCacheDuration {
|
||||
newLifetime := fdur()
|
||||
if newLifetime > e.life {
|
||||
e.life = newLifetime
|
||||
} else {
|
||||
// if even the new lifetime is less than MinimumCacheDuration
|
||||
// then change set it explicitly here
|
||||
e.life = cfg.MinimumCacheDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset called each time the entry is expired
|
||||
// and the handler calls this after the original handler executed
|
||||
// to re-set the response with the new handler's content result
|
||||
func (e *Entry) Reset(statusCode int, contentType string,
|
||||
body []byte, lifeChanger LifeChanger) {
|
||||
|
||||
if e.response == nil {
|
||||
e.response = &Response{}
|
||||
}
|
||||
if statusCode > 0 {
|
||||
e.response.statusCode = statusCode
|
||||
}
|
||||
|
||||
if contentType != "" {
|
||||
e.response.contentType = contentType
|
||||
}
|
||||
|
||||
e.response.body = body
|
||||
// check if a given life changer provided
|
||||
// and if it does then execute the change life time
|
||||
if lifeChanger != nil {
|
||||
e.ChangeLifetime(lifeChanger)
|
||||
}
|
||||
e.expiresAt = time.Now().Add(e.life)
|
||||
}
|
||||
39
cache/entry/response.go
vendored
Normal file
39
cache/entry/response.go
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package entry
|
||||
|
||||
// Response is the cached response will be send to the clients
|
||||
// its fields setted at runtime on each of the non-cached executions
|
||||
// non-cached executions = first execution, and each time after
|
||||
// cache expiration datetime passed
|
||||
type Response struct {
|
||||
// statusCode for the response cache handler
|
||||
statusCode int
|
||||
// contentType for the response cache handler
|
||||
contentType string
|
||||
// body is the contents will be served by the cache handler
|
||||
body []byte
|
||||
}
|
||||
|
||||
// StatusCode returns a valid status code
|
||||
func (r *Response) StatusCode() int {
|
||||
if r.statusCode <= 0 {
|
||||
r.statusCode = 200
|
||||
}
|
||||
return r.statusCode
|
||||
}
|
||||
|
||||
// ContentType returns a valid content type
|
||||
func (r *Response) ContentType() string {
|
||||
if r.contentType == "" {
|
||||
r.contentType = "text/html; charset=utf-8"
|
||||
}
|
||||
return r.contentType
|
||||
}
|
||||
|
||||
// Body returns contents will be served by the cache handler
|
||||
func (r *Response) Body() []byte {
|
||||
return r.body
|
||||
}
|
||||
28
cache/entry/util.go
vendored
Normal file
28
cache/entry/util.go
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package entry
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var maxAgeExp = regexp.MustCompile(`maxage=(\d+)`)
|
||||
|
||||
// ParseMaxAge parses the max age from the receiver parameter, "cache-control" header
|
||||
// returns seconds as int64
|
||||
// if header not found or parse failed then it returns -1
|
||||
func ParseMaxAge(header string) int64 {
|
||||
if header == "" {
|
||||
return -1
|
||||
}
|
||||
m := maxAgeExp.FindStringSubmatch(header)
|
||||
if len(m) == 2 {
|
||||
if v, err := strconv.Atoi(m[1]); err == nil {
|
||||
return int64(v)
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
47
cache/ruleset/ruleset.go
vendored
Normal file
47
cache/ruleset/ruleset.go
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ruleset provides the basics rules which are being extended by rules.
|
||||
package ruleset
|
||||
|
||||
// The shared header-mostly rules for both nethttp and fasthttp
|
||||
var (
|
||||
AuthorizationRule = func(header GetHeader) bool {
|
||||
return header("Authorization") == "" &&
|
||||
header("Proxy-Authenticate") == ""
|
||||
}
|
||||
|
||||
MustRevalidateRule = func(header GetHeader) bool {
|
||||
return header("Must-Revalidate") == ""
|
||||
}
|
||||
|
||||
ZeroMaxAgeRule = func(header GetHeader) bool {
|
||||
return header("S-Maxage") != "0" &&
|
||||
header("Max-Age") != "0"
|
||||
}
|
||||
|
||||
NoCacheRule = func(header GetHeader) bool {
|
||||
return header("No-Cache") != "true"
|
||||
}
|
||||
)
|
||||
|
||||
// THESE ARE HERE BECAUSE THE GOLANG DOESN'T SUPPORTS THE F.... INTERFACE ALIAS, THIS SHOULD EXISTS ONLY ON /$package/rule
|
||||
// or somehow move interface generic rules (such as conditional, header) here, because the code sharing is exactly THE SAME
|
||||
// except the -end interface, this on other language can be designing very very nice but here no OOP so we stuck on this,
|
||||
// it's better than before but not as I wanted to be.
|
||||
// They will make me to forget my software design skills,
|
||||
// they (the language's designers) rollback the feature of type alias, BLOG POSTING that is an UNUSEFUL feature.....
|
||||
|
||||
// GetHeader is a type of func which receives a func of string which returns a string
|
||||
// used to get headers values, both request's and response's.
|
||||
type GetHeader func(string) string
|
||||
|
||||
// HeaderPredicate is a type of func which receives a func of string which returns a string and a boolean,
|
||||
// used to get headers values, both request's and response's.
|
||||
type HeaderPredicate func(GetHeader) bool
|
||||
|
||||
// EmptyHeaderPredicate returns always true
|
||||
var EmptyHeaderPredicate = func(GetHeader) bool {
|
||||
return true
|
||||
}
|
||||
118
cache/uri/uribuilder.go
vendored
Normal file
118
cache/uri/uribuilder.go
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uri
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/cache/cfg"
|
||||
)
|
||||
|
||||
// URIBuilder is the requested url builder
|
||||
// which keeps all the information the server
|
||||
// should know to save or retrieve a cached entry's response
|
||||
// used on client-side only
|
||||
// both from net/http and valyala/fasthttp
|
||||
type URIBuilder struct {
|
||||
serverAddr,
|
||||
clientMethod,
|
||||
clientURI string
|
||||
|
||||
cacheLifetime time.Duration
|
||||
cacheStatuscode int
|
||||
cacheContentType string
|
||||
}
|
||||
|
||||
// ServerAddr sets the server address for the final request url
|
||||
func (r *URIBuilder) ServerAddr(s string) *URIBuilder {
|
||||
r.serverAddr = s
|
||||
return r
|
||||
}
|
||||
|
||||
// ClientMethod sets the method which the client should call the remote server's handler
|
||||
// used to build the final request url too
|
||||
func (r *URIBuilder) ClientMethod(s string) *URIBuilder {
|
||||
r.clientMethod = s
|
||||
return r
|
||||
}
|
||||
|
||||
// ClientURI sets the client path for the final request url
|
||||
func (r *URIBuilder) ClientURI(s string) *URIBuilder {
|
||||
r.clientURI = s
|
||||
return r
|
||||
}
|
||||
|
||||
// Lifetime sets the cache lifetime for the final request url
|
||||
func (r *URIBuilder) Lifetime(d time.Duration) *URIBuilder {
|
||||
r.cacheLifetime = d
|
||||
return r
|
||||
}
|
||||
|
||||
// StatusCode sets the cache status code for the final request url
|
||||
func (r *URIBuilder) StatusCode(code int) *URIBuilder {
|
||||
r.cacheStatuscode = code
|
||||
return r
|
||||
}
|
||||
|
||||
// ContentType sets the cache content type for the final request url
|
||||
func (r *URIBuilder) ContentType(s string) *URIBuilder {
|
||||
r.cacheContentType = s
|
||||
return r
|
||||
}
|
||||
|
||||
// String returns the full url which should be passed to get a cache entry response back
|
||||
// (it could be setted by server too but we need some client-freedom on the requested key)
|
||||
// in order to be sure that the registered cache entries are unique among different clients with the same key
|
||||
// note1: we do it manually*,
|
||||
// note2: on fasthttp that is not required because the query args added as expected but we will use it there too to be align with net/http
|
||||
func (r URIBuilder) String() string {
|
||||
return r.build()
|
||||
}
|
||||
|
||||
func (r URIBuilder) build() string {
|
||||
|
||||
remoteURL := r.serverAddr
|
||||
|
||||
// fasthttp appends the "/" in the last uri (with query args also, that's probably a fasthttp bug which I'll fix later)
|
||||
// for now lets make that check:
|
||||
|
||||
if !strings.HasSuffix(remoteURL, "/") {
|
||||
remoteURL += "/"
|
||||
}
|
||||
scheme := "http://"
|
||||
// validate the remoteURL, should contains a scheme, if not then check if the client has given a scheme and if so
|
||||
// use that for the server too
|
||||
if !strings.Contains(remoteURL, "://") {
|
||||
if strings.Contains(remoteURL, ":443") || strings.Contains(remoteURL, ":https") {
|
||||
remoteURL = "https://" + remoteURL
|
||||
} else {
|
||||
remoteURL = scheme + "://" + remoteURL
|
||||
}
|
||||
}
|
||||
var cacheDurationStr, statusCodeStr string
|
||||
|
||||
if r.cacheLifetime.Seconds() > 0 {
|
||||
cacheDurationStr = strconv.Itoa(int(r.cacheLifetime.Seconds()))
|
||||
}
|
||||
|
||||
if r.cacheStatuscode > 0 {
|
||||
statusCodeStr = strconv.Itoa(r.cacheStatuscode)
|
||||
}
|
||||
|
||||
s := remoteURL + "?" + cfg.QueryCacheKey + "=" + url.QueryEscape(r.clientMethod+scheme+r.clientURI)
|
||||
if cacheDurationStr != "" {
|
||||
s += "&" + cfg.QueryCacheDuration + "=" + url.QueryEscape(cacheDurationStr)
|
||||
}
|
||||
if statusCodeStr != "" {
|
||||
s += "&" + cfg.QueryCacheStatusCode + "=" + url.QueryEscape(statusCodeStr)
|
||||
}
|
||||
if r.cacheContentType != "" {
|
||||
s += "&" + cfg.QueryCacheContentType + "=" + url.QueryEscape(r.cacheContentType)
|
||||
}
|
||||
return s
|
||||
}
|
||||
Reference in New Issue
Block a user