README ¶
#+HTML: <a href="https://github.com/androidacy/cdp-cache/actions?query=workflow%3ACI"><img src="https://github.com/androidacy/cdp-cache/workflows/CI/badge.svg?branch=master" /></a> #+HTML: </div> #+HTML: <a href="https://www.codacy.com/manual/androidacy/cdp-cache?utm_source=github.com&utm_medium=referral&utm_content=androidacy/cdp-cache&utm_campaign=Badge_Grade"><img src="https://app.codacy.com/project/badge/Grade/43d801ba437a42419e479492eca72ee2" /></a> #+HTML: </div> #+HTML: <a href="https://goreportcard.com/report/github.com/androidacy/cdp-cache"><img src="https://goreportcard.com/badge/github.com/androidacy/cdp-cache" /></a> #+HTML: </div> #+HTML: <a href="https://codeclimate.com/github/androidacy/cdp-cache/test_coverage"><img src="https://api.codeclimate.com/v1/badges/a99b4ae948836cdedd12/test_coverage" /></a> * Caddy Http Cache This is a http cache plugin for caddy 2. In fact, I am not so familiar with the part of cdn cache's mechanism so I reference the code from https://github.com/nicolasazrak/caddy-cache. which is the caddy v1's cache module. Then, I migrate the codebase to be consist with the caddy2 architecture and develop more features based on that. Currently, this doesn't support =distributed cache= yet. It's still in plan. * Features ** Multi storage backends support Now the following backends are supported. - file - inmemory - redis In the latter part, I will show the example Caddyfile to serve different type of proxy cache server. ** Conditional rule to cache the upstream response - uri path matcher - http header matcher ** Set default cache's max age A default age for matched responses that do not have an explicit expiration. ** Purge cache There are exposed endpoints to purge cache. I implement it with admin apis endpoints. The following will show you how to purge cache *** first you can list the current caches Given that you specify the port 7777 to serve caddy admin, you can get the list of cache by the api below. #+begin_src restclient GET http://example.com:7777/caches #+end_src *** purge the cache with http DELETE request It supports the regular expression. #+begin_src restclient DELETE http://example.com:7777/caches/purge Content-Type: application/json { "method": "GET", "host": "localhost", "uri": ".*\\.txt" } #+end_src ** Support cluster with consul NOTE: still under development and only the =memory= backend supports. I've provided a simple example to mimic a cluster environment. #+begin_src sh PROJECT_PATH=/app docker-compose --project-directory=./ -f example/distributed_cache/docker-compose.yaml up #+end_src * How to build In development, go to the cmd folder and type the following commands. #+begin_src sh go build -ldflags="-w -s" -o caddy #+end_src To build linux binary by this #+begin_src GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o caddy #+end_src Or you can use the [[https://github.com/caddyserver/xcaddy][xcaddy]] to build the executable file. Ensure you've install it by =go get -u github.com/caddyserver/xcaddy/cmd/xcaddy= #+begin_src sh xcaddy build v2.0.0 --with github.com/androidacy/cdp-cache #+end_src Xcaddy also provide a way to develop plugin locally. #+begin_src sh xcaddy run --config cmd/Caddyfile #+end_src To remove unused dependencies #+begin_src sh go mod tidy #+end_src * How to run You can directly run with the binary. #+begin_src sh caddy run --config [caddy file] --adapter caddyfile #+end_src Or if you are preferred to use the docker #+begin_src sh docker run -it -p80:80 -p443:443 -v [the path of caddyfile]:/app/Caddyfile docker.pkg.github.com/androidacy/cdp-cache/caddy:latest #+end_src * Configuration The following will list current support configs in the caddyfile. *** status_header The header to set cache status. default value: =X-Cache-Status= *** match_path Only the request's path match the condition will be cached. Ex. =/= means all request need to be cached because all request's path must start with =/= *** default_max_age The cache's expiration time. *** match_header only the req's header match the condtions ex. #+begin_quote match_header Content-Type image/jpg image/png "text/plain; charset=utf-8" #+end_quote *** path The position where to save the file. Only applied when the =cache_type= is =file=. *** cache_key The key of cache entry. The default value is ={http.request.method} {http.request.host}{http.request.uri.path}?{http.request.uri.query}= *** cache_bucket_num The bucket number of the mod of cache_key's checksum. The default value is 256. *** cache_type Indicate to use which kind of cache's storage backend. Currently, there are two choices. One is =file= and the other is =in_memory= *** cache_max_memory_size The max memory usage for in_memory backend. *** distributed Working in process. Currently, only support =consul= to establish the cluster of cache server node. To see a example config, please refer [[file:example/distributed_cache/Caddyfile::health_check ":7777/health"][this]] **** service_name specify your service to be registered in the consul agent. **** addr the address of the consul agent. **** health_check indicate the health_check endpoint which consul agent will use this endpoint to check the cache server is healthy ** Example configs You can go to the directory [[file:example/][example]]. It shows you each type of cache's configuration. * Benchmarks Now, I just simply compares the performance between in-memory and disk. ** Env Caddy run with the config file under directory =benchmark= and tests were run on the mac book pro (1.4 GHz Intel Core i5, 16 GB 2133 MHz LPDDR3) ** Test Result The following benchmark is analysized by =wrk -c 50 -d 30s --latency -t 4 http://localhost:9991/pg31674.txt= without log open. Before running this, ensure you provision the tests data by =bash benchmark/provision.sh= | | req/s | latency (50% 90% 99%) | | proxy + file cache | 13853 | 3.29ms / 4.09ms / 5.26ms | | proxy + in memory cache | 20622 | 2.20ms / 3.03ms / 4.68ms | * Todo list - [ ] distributed cache (in progress) - [ ] more optimization..
Documentation ¶
Index ¶
- Constants
- type CacheType
- type Config
- type Entry
- type HTTPCache
- type Handler
- func (Handler) CaddyModule() caddy.ModuleInfo
- func (h *Handler) Cleanup() error
- func (h *Handler) Provision(ctx caddy.Context) error
- func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error
- func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error
- func (h *Handler) Validate() error
- type HeaderRuleMatcher
- type PathRuleMatcher
- type PurgePayload
- type Response
- func (r *Response) Clean() error
- func (r *Response) Close() error
- func (r *Response) Flush()
- func (r *Response) GetReader() (io.ReadCloser, error)
- func (r *Response) Header() http.Header
- func (r *Response) SetBody(body backends.Backend)
- func (r *Response) WaitClose()
- func (r *Response) WaitHeaders()
- func (r *Response) Write(buf []byte) (int, error)
- func (r *Response) WriteHeader(code int)
- type RuleMatcher
- type RuleMatcherRawWithType
- type RuleMatcherType
- type URLLock
Constants ¶
const ( BYTE = 1 << (iota * 10) KB MB GB )
BYTE represents the num of byte 1MB = 2^10 BYTE 1GB = 2^10 MB
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CacheType ¶
type CacheType string
CacheType is the type of cache which means the backend for storing cache content.
type Config ¶
type Config struct { Type CacheType `json:"type,omitempty"` StatusHeader string `json:"status_header,omitempty"` DefaultMaxAge time.Duration `json:"default_max_age,omitempty"` LockTimeout time.Duration `json:"lock_timeout,omitempty"` RuleMatchersRaws []RuleMatcherRawWithType `json:"rule_matcher_raws,omitempty"` RuleMatchers []RuleMatcher `json:"-"` CacheBucketsNum int `json:"cache_buckets_num,omitempty"` CacheMaxMemorySize int `json:"cache_max_memory_size,omitempty"` Path string `json:"path,omitempty"` CacheKeyTemplate string `json:"cache_key_template,omitempty"` RedisConnectionSetting string `json:"redis_connection_setting,omitempty"` }
Config is the configuration for cache process
type Entry ¶
type Entry struct { Request *http.Request Response *Response // contains filtered or unexported fields }
Entry consists of a cache key and one or more response corresponding to the prior requests. https://httpwg.org/specs/rfc7234.html#caching.overview
func NewEntry ¶
NewEntry creates a new Entry for the given request and response and it also calculates whether it is public or not
func (*Entry) WriteBodyTo ¶
func (e *Entry) WriteBodyTo(w http.ResponseWriter) error
WriteBodyTo sends the body to the http.ResponseWritter
type HTTPCache ¶
type HTTPCache struct {
// contains filtered or unexported fields
}
HTTPCache is a http cache for http request which is focus on static files
func NewHTTPCache ¶
NewHTTPCache new a HTTPCache to handle cache entries
type Handler ¶
type Handler struct { Config *Config `json:"config,omitempty"` Cache *HTTPCache `json:"-"` URLLocks *URLLock `json:"-"` DistributedRaw json.RawMessage `json:"distributed,omitempty" caddy:"namespace=distributed inline_key=distributed"` Distributed *distributed.ConsulService `json:"-"` // contains filtered or unexported fields }
Handler is a http handler as a middleware to cache the response
func (Handler) CaddyModule ¶
func (Handler) CaddyModule() caddy.ModuleInfo
CaddyModule returns the Caddy module information
func (*Handler) UnmarshalCaddyfile ¶
UnmarshalCaddyfile sets up the handler from Caddyfile
:4000 { reverse_proxy yourserver:5000 http_cache { match_path /assets match_header Content-Type image/jpg image/png status_header X-Cache-Status default_max_age 15m path /tmp/caddy-cache distributed consul { service_name addr } } }
type HeaderRuleMatcher ¶
HeaderRuleMatcher determines whether the request's header is matched.
type PathRuleMatcher ¶
type PathRuleMatcher struct {
Path string `json:"path"`
}
PathRuleMatcher determines whether the request's path is matched.
type PurgePayload ¶
type PurgePayload struct { Method string `json:"method"` Host string `json:"host"` URI string `json:"uri"` // contains filtered or unexported fields }
PurgePayload holds the field which will be unmarshalled from the request's body NOTE: the format of URI can contains the query param. ex. when the client send a delete request with the body
{ "method": "GET", "hots": "example.com", "uri": "/static?ext=txt", }
type Response ¶
type Response struct { Code int HeaderMap http.Header IsFirstByteWritten bool // contains filtered or unexported fields }
Response encapsulates the entry
func (*Response) Close ¶
Close indicate the data is completely written to the body so that we can close it.
func (*Response) Flush ¶
func (r *Response) Flush()
Flush flushes the backend's storage (currently, only file storage need to call this)
func (*Response) GetReader ¶
func (r *Response) GetReader() (io.ReadCloser, error)
GetReader gets the reader from the setted backend
func (*Response) WaitClose ¶
func (r *Response) WaitClose()
WaitClose waits the response to be closed.
func (*Response) WaitHeaders ¶
func (r *Response) WaitHeaders()
WaitHeaders waits the header to be written
func (*Response) Write ¶
Write writes the upstream's content in the backend's storage this will wait the body(backend) is set
func (*Response) WriteHeader ¶
WriteHeader keeps the upstream response header
type RuleMatcher ¶
type RuleMatcher interface {
// contains filtered or unexported methods
}
RuleMatcher determines whether the request should be cached or not.
type RuleMatcherRawWithType ¶
type RuleMatcherRawWithType struct { Type RuleMatcherType Data json.RawMessage }
RuleMatcherRawWithType stores the marshal content for unmarshalling in provision stage
type RuleMatcherType ¶
type RuleMatcherType string
RuleMatcherType specifies the type of matching rule to cache.
const ( MatcherTypePath RuleMatcherType = "path" MatcherTypeHeader RuleMatcherType = "header" )
the following list the different way to decide the request whether is matched or not to be cached it's response.