From a53b27c62e792e09380876db916289c77f2ae2e0 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Wed, 9 Oct 2019 19:22:46 -0600 Subject: http: Add work-in-progress cache handler module This migrates a feature that was previously reserved for enterprise users, according to https://github.com/caddyserver/caddy/issues/2786. The cache HTTP handler will be a high-performing, distributed cache layer for HTTP requests. Right now, the implementation is a very basic proof-of-concept, and further development is required. --- modules/caddyhttp/httpcache/httpcache.go | 205 +++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 modules/caddyhttp/httpcache/httpcache.go (limited to 'modules/caddyhttp') diff --git a/modules/caddyhttp/httpcache/httpcache.go b/modules/caddyhttp/httpcache/httpcache.go new file mode 100644 index 0000000..1b2cfd2 --- /dev/null +++ b/modules/caddyhttp/httpcache/httpcache.go @@ -0,0 +1,205 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package httpcache + +import ( + "bytes" + "encoding/gob" + "fmt" + "io" + "log" + "net/http" + "sync" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/golang/groupcache" +) + +func init() { + caddy.RegisterModule(Cache{}) +} + +// Cache implements a simple distributed cache. +type Cache struct { + group *groupcache.Group +} + +// CaddyModule returns the Caddy module information. +func (Cache) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + Name: "http.handlers.cache", + New: func() caddy.Module { return new(Cache) }, + } +} + +// Provision provisions c. +func (c *Cache) Provision(ctx caddy.Context) error { + // TODO: proper pool configuration + me := "http://localhost:5555" + // TODO: Make max size configurable + maxSize := int64(512 << 20) + poolMu.Lock() + if pool == nil { + pool = groupcache.NewHTTPPool(me) + c.group = groupcache.NewGroup(groupName, maxSize, groupcache.GetterFunc(c.getter)) + } else { + c.group = groupcache.GetGroup(groupName) + } + pool.Set(me) + poolMu.Unlock() + + return nil +} + +// Validate validates c. +func (c *Cache) Validate() error { + // TODO: implement + return nil +} + +func (c *Cache) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + // TODO: proper RFC implementation of cache control headers... + if r.Header.Get("Cache-Control") == "no-cache" || r.Method != "GET" { + return next.ServeHTTP(w, r) + } + + ctx := getterContext{w, r, next} + + // TODO: rigorous performance testing + + // TODO: pretty much everything else to handle the nuances of HTTP caching... + + // TODO: groupcache has no explicit cache eviction, so we need to embed + // all information related to expiring cache entries into the key; right + // now we just use the request URI as a proof-of-concept + key := r.RequestURI + + var cachedBytes []byte + err := c.group.Get(ctx, key, groupcache.AllocatingByteSliceSink(&cachedBytes)) + if err == errUncacheable { + return nil + } + if err != nil { + return err + } + + // the cached bytes consists of two parts: first a + // gob encoding of the status and header, immediately + // followed by the raw bytes of the response body + rdr := bytes.NewReader(cachedBytes) + + // read the header and status first + var hs headerAndStatus + err = gob.NewDecoder(rdr).Decode(&hs) + if err != nil { + return err + } + + // set and write the cached headers + for k, v := range hs.Header { + w.Header()[k] = v + } + w.WriteHeader(hs.Status) + + // write the cached response body + io.Copy(w, rdr) + + return nil +} + +func (c *Cache) getter(ctx groupcache.Context, key string, dest groupcache.Sink) error { + combo := ctx.(getterContext) + + // the buffer will store the gob-encoded header, then the body + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + defer bufPool.Put(buf) + + // we need to record the response if we are to cache it; only cache if + // request is successful (TODO: there's probably much more nuance needed here) + var rr caddyhttp.ResponseRecorder + rr = caddyhttp.NewResponseRecorder(combo.rw, buf, func(status int) bool { + shouldBuf := status < 300 + + if shouldBuf { + // store the header before the body, so we can efficiently + // and conveniently use a single buffer for both; gob + // decoder will only read up to end of gob message, and + // the rest will be the body, which will be written + // implicitly for us by the recorder + err := gob.NewEncoder(buf).Encode(headerAndStatus{ + Header: rr.Header(), + Status: status, + }) + if err != nil { + log.Printf("[ERROR] Encoding headers for cache entry: %v; not caching this request", err) + return false + } + } + + return shouldBuf + }) + + // execute next handlers in chain + err := combo.next.ServeHTTP(rr, combo.req) + if err != nil { + return err + } + + // if response body was not buffered, response was + // already written and we are unable to cache + if !rr.Buffered() { + return errUncacheable + } + + // add to cache + dest.SetBytes(buf.Bytes()) + + return nil +} + +type headerAndStatus struct { + Header http.Header + Status int +} + +type getterContext struct { + rw http.ResponseWriter + req *http.Request + next caddyhttp.Handler +} + +var bufPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +var ( + pool *groupcache.HTTPPool + poolMu sync.Mutex +) + +var errUncacheable = fmt.Errorf("uncacheable") + +const groupName = "http_requests" + +// Interface guards +var ( + _ caddy.Provisioner = (*Cache)(nil) + _ caddy.Validator = (*Cache)(nil) + _ caddyhttp.MiddlewareHandler = (*Cache)(nil) +) -- cgit v1.2.3 From bcbe1c220de99146a13bcc786ec7f5017681de73 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Wed, 9 Oct 2019 19:28:07 -0600 Subject: reverse_proxy: Add local circuit breaker This migrates a feature that was previously reserved for enterprise users, according to https://github.com/caddyserver/caddy/issues/2786. The local circuit breaker is a simple metrics counter that can cause the reverse proxy to consider a backend unhealthy before it actually goes offline, by measuring recent latencies over a sliding window. Credit to Danny Navarro --- modules/caddyhttp/reverseproxy/circuitbreaker.go | 152 +++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 modules/caddyhttp/reverseproxy/circuitbreaker.go (limited to 'modules/caddyhttp') diff --git a/modules/caddyhttp/reverseproxy/circuitbreaker.go b/modules/caddyhttp/reverseproxy/circuitbreaker.go new file mode 100644 index 0000000..de2a6f9 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/circuitbreaker.go @@ -0,0 +1,152 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package reverseproxy + +import ( + "fmt" + "sync/atomic" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/vulcand/oxy/memmetrics" +) + +func init() { + caddy.RegisterModule(localCircuitBreaker{}) +} + +// localCircuitBreaker implements circuit breaking functionality +// for requests within this process over a sliding time window. +type localCircuitBreaker struct { + tripped int32 + cbType int32 + threshold float64 + metrics *memmetrics.RTMetrics + tripTime time.Duration + Config +} + +// CaddyModule returns the Caddy module information. +func (localCircuitBreaker) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + Name: "http.handlers.reverse_proxy.circuit_breakers.local", + New: func() caddy.Module { return new(localCircuitBreaker) }, + } +} + +// Provision sets up a configured circuit breaker. +func (c *localCircuitBreaker) Provision(ctx caddy.Context) error { + t, ok := typeCB[c.Type] + if !ok { + return fmt.Errorf("type is not defined") + } + + if c.TripTime == "" { + c.TripTime = defaultTripTime + } + + tw, err := time.ParseDuration(c.TripTime) + if err != nil { + return fmt.Errorf("cannot parse trip_time duration, %v", err.Error()) + } + + mt, err := memmetrics.NewRTMetrics() + if err != nil { + return fmt.Errorf("cannot create new metrics: %v", err.Error()) + } + + c.cbType = t + c.tripTime = tw + c.threshold = c.Threshold + c.metrics = mt + c.tripped = 0 + + return nil +} + +// Ok returns whether the circuit breaker is tripped or not. +func (c *localCircuitBreaker) Ok() bool { + tripped := atomic.LoadInt32(&c.tripped) + return tripped == 0 +} + +// RecordMetric records a response status code and execution time of a request. This function should be run in a separate goroutine. +func (c *localCircuitBreaker) RecordMetric(statusCode int, latency time.Duration) { + c.metrics.Record(statusCode, latency) + c.checkAndSet() +} + +// Ok checks our metrics to see if we should trip our circuit breaker, or if the fallback duration has completed. +func (c *localCircuitBreaker) checkAndSet() { + var isTripped bool + + switch c.cbType { + case typeErrorRatio: + // check if amount of network errors exceed threshold over sliding window, threshold for comparison should be < 1.0 i.e. .5 = 50th percentile + if c.metrics.NetworkErrorRatio() > c.threshold { + isTripped = true + } + case typeLatency: + // check if threshold in milliseconds is reached and trip + hist, err := c.metrics.LatencyHistogram() + if err != nil { + return + } + + l := hist.LatencyAtQuantile(c.threshold) + if l.Nanoseconds()/int64(time.Millisecond) > int64(c.threshold) { + isTripped = true + } + case typeStatusCodeRatio: + // check ratio of error status codes of sliding window, threshold for comparison should be < 1.0 i.e. .5 = 50th percentile + if c.metrics.ResponseCodeRatio(500, 600, 0, 600) > c.threshold { + isTripped = true + } + } + + if isTripped { + c.metrics.Reset() + atomic.AddInt32(&c.tripped, 1) + + // wait tripTime amount before allowing operations to resume. + t := time.NewTimer(c.tripTime) + <-t.C + + atomic.AddInt32(&c.tripped, -1) + } +} + +// Config represents the configuration of a circuit breaker. +type Config struct { + Threshold float64 `json:"threshold"` + Type string `json:"type"` + TripTime string `json:"trip_time"` +} + +const ( + typeLatency = iota + 1 + typeErrorRatio + typeStatusCodeRatio + defaultTripTime = "5s" +) + +var ( + // typeCB handles converting a Config Type value to the internal circuit breaker types. + typeCB = map[string]int32{ + "latency": typeLatency, + "error_ratio": typeErrorRatio, + "status_ratio": typeStatusCodeRatio, + } +) -- cgit v1.2.3 From 26cc8837084f9cea6057e9908f0b5bde0eb15d3e Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Thu, 10 Oct 2019 11:02:16 -0600 Subject: http: Add Starlark handler This migrates a feature that was previously reserved for enterprise users, according to #2786. The Starlark integration needs to be updated since this was made before some significant changes in the v2 code base. When functional, it makes it possible to have very dynamic HTTP handlers. This will be a long-term ongoing project. Credit to Danny Navarro --- modules/caddyhttp/starlarkmw/example/caddy.json | 19 +++ .../caddyhttp/starlarkmw/internal/lib/module.go | 165 ++++++++++++++++++++ modules/caddyhttp/starlarkmw/starlarkmw.go | 172 +++++++++++++++++++++ .../caddyhttp/starlarkmw/tools/gen/example.star | 40 +++++ 4 files changed, 396 insertions(+) create mode 100644 modules/caddyhttp/starlarkmw/example/caddy.json create mode 100644 modules/caddyhttp/starlarkmw/internal/lib/module.go create mode 100644 modules/caddyhttp/starlarkmw/starlarkmw.go create mode 100644 modules/caddyhttp/starlarkmw/tools/gen/example.star (limited to 'modules/caddyhttp') diff --git a/modules/caddyhttp/starlarkmw/example/caddy.json b/modules/caddyhttp/starlarkmw/example/caddy.json new file mode 100644 index 0000000..66f9f2c --- /dev/null +++ b/modules/caddyhttp/starlarkmw/example/caddy.json @@ -0,0 +1,19 @@ +{ + "apps": { + "http": { + "servers": { + "MY_SERVER": { + "listen": [":3001"], + "routes": [ + { + "handle": { + "handler": "starlark", + "script": "def setup(r):\n\t# create some middlewares specific to this request\n\ttemplates = loadModule('http.handlers.templates', {'include_root': './includes'})\n\tmidChain = execute([templates])\n\ndef serveHTTP (rw, r):\n\trw.Write('Hello world, from Starlark!')\n" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/modules/caddyhttp/starlarkmw/internal/lib/module.go b/modules/caddyhttp/starlarkmw/internal/lib/module.go new file mode 100644 index 0000000..a7164cd --- /dev/null +++ b/modules/caddyhttp/starlarkmw/internal/lib/module.go @@ -0,0 +1,165 @@ +package lib + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + + "github.com/caddyserver/caddy/v2" + "go.starlark.net/starlark" +) + +// ResponderModule represents a module that satisfies the caddyhttp handler. +type ResponderModule struct { + Name string + Cfg json.RawMessage + Instance caddyhttp.Handler +} + +func (r ResponderModule) Freeze() {} +func (r ResponderModule) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: responder module") } +func (r ResponderModule) String() string { return "responder module" } +func (r ResponderModule) Type() string { return "responder module" } +func (r ResponderModule) Truth() starlark.Bool { return true } + +// Middleware represents a module that satisfies the starlark Value interface. +type Middleware struct { + Name string + Cfg json.RawMessage + Instance caddyhttp.MiddlewareHandler +} + +func (r Middleware) Freeze() {} +func (r Middleware) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: middleware") } +func (r Middleware) String() string { return "middleware" } +func (r Middleware) Type() string { return "middleware" } +func (r Middleware) Truth() starlark.Bool { return true } + +// LoadMiddleware represents the method exposed to starlark to load a Caddy module. +type LoadMiddleware struct { + Middleware Middleware + Ctx caddy.Context +} + +func (r LoadMiddleware) Freeze() {} +func (r LoadMiddleware) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: loadMiddleware") } +func (r LoadMiddleware) String() string { return "loadMiddleware" } +func (r LoadMiddleware) Type() string { return "function: loadMiddleware" } +func (r LoadMiddleware) Truth() starlark.Bool { return true } + +// Run is the method bound to the starlark loadMiddleware function. +func (r *LoadMiddleware) Run(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var name string + var cfg *starlark.Dict + err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 2, &name, &cfg) + if err != nil { + return starlark.None, fmt.Errorf("unpacking arguments: %v", err.Error()) + } + + js := json.RawMessage(cfg.String()) + + if strings.Index(name, "http.handlers.") == -1 { + name = fmt.Sprintf("http.handlers.%s", name) + } + + inst, err := r.Ctx.LoadModule(name, js) + if err != nil { + return starlark.None, err + } + + mid, ok := inst.(caddyhttp.MiddlewareHandler) + if !ok { + return starlark.None, fmt.Errorf("could not assert as middleware handler") + } + + m := Middleware{ + Name: name, + Cfg: js, + Instance: mid, + } + + r.Middleware = m + + return m, nil +} + +// LoadResponder represents the method exposed to starlark to load a Caddy middleware responder. +type LoadResponder struct { + Module ResponderModule + Ctx caddy.Context +} + +func (r LoadResponder) Freeze() {} +func (r LoadResponder) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: loadModule") } +func (r LoadResponder) String() string { return "loadModule" } +func (r LoadResponder) Type() string { return "function: loadModule" } +func (r LoadResponder) Truth() starlark.Bool { return true } + +// Run is the method bound to the starlark loadResponder function. +func (r *LoadResponder) Run(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var name string + var cfg *starlark.Dict + err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 2, &name, &cfg) + if err != nil { + return starlark.None, fmt.Errorf("unpacking arguments: %v", err.Error()) + } + + js := json.RawMessage(cfg.String()) + + if strings.Index(name, "http.handlers.") == -1 { + name = fmt.Sprintf("http.handlers.%s", name) + } + + inst, err := r.Ctx.LoadModule(name, js) + if err != nil { + return starlark.None, err + } + + res, ok := inst.(caddyhttp.Handler) + if !ok { + return starlark.None, fmt.Errorf("could not assert as responder") + } + + m := ResponderModule{ + Name: name, + Cfg: js, + Instance: res, + } + + r.Module = m + + return m, nil +} + +// Execute represents the method exposed to starlark to build a middleware chain. +type Execute struct { + Modules []Middleware +} + +func (r Execute) Freeze() {} +func (r Execute) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: execute") } +func (r Execute) String() string { return "execute" } +func (r Execute) Type() string { return "function: execute" } +func (r Execute) Truth() starlark.Bool { return true } + +// Run is the method bound to the starlark execute function. +func (r *Execute) Run(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var mids *starlark.List + err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &mids) + if err != nil { + return starlark.None, fmt.Errorf("unpacking arguments: %v", err.Error()) + } + + for i := 0; i < mids.Len(); i++ { + val, ok := mids.Index(i).(Middleware) + if !ok { + return starlark.None, fmt.Errorf("cannot get module from execute") + } + + r.Modules = append(r.Modules, val) + } + + return starlark.None, nil +} diff --git a/modules/caddyhttp/starlarkmw/starlarkmw.go b/modules/caddyhttp/starlarkmw/starlarkmw.go new file mode 100644 index 0000000..007ddb4 --- /dev/null +++ b/modules/caddyhttp/starlarkmw/starlarkmw.go @@ -0,0 +1,172 @@ +package starlarkmw + +import ( + "context" + "fmt" + "net/http" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + caddyscript "github.com/caddyserver/caddy/v2/pkg/caddyscript/lib" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/starlarkmw/internal/lib" + "github.com/starlight-go/starlight/convert" + "go.starlark.net/starlark" +) + +func init() { + caddy.RegisterModule(StarlarkMW{}) +} + +// StarlarkMW represents a middleware responder written in starlark +type StarlarkMW struct { + Script string `json:"script"` + serveHTTP *starlark.Function + setup *starlark.Function + thread *starlark.Thread + loadMiddleware *lib.LoadMiddleware + execute *lib.Execute + globals *starlark.StringDict + gctx caddy.Context + rctx caddy.Context + rcancel context.CancelFunc +} + +// CaddyModule returns the Caddy module information. +func (StarlarkMW) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + Name: "http.handlers.starlark", + New: func() caddy.Module { return new(StarlarkMW) }, + } +} + +// ServeHTTP responds to an http request with starlark. +func (s *StarlarkMW) ServeHTTP(w http.ResponseWriter, r *http.Request) error { + var mwcancel context.CancelFunc + var mwctx caddy.Context + + // call setup() to prepare the middleware chain if it is defined + if s.setup != nil { + mwctx, mwcancel = caddy.NewContext(s.gctx) + defer mwcancel() + + s.loadMiddleware.Ctx = mwctx + args := starlark.Tuple{caddyscript.HTTPRequest{Req: r}} + + _, err := starlark.Call(new(starlark.Thread), s.setup, args, nil) + if err != nil { + return fmt.Errorf("starlark setup(), %v", err) + } + } + + // dynamically build middleware chain for each request + stack := caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + wr, err := convert.ToValue(w) + if err != nil { + return fmt.Errorf("cannot convert response writer to starlark value") + } + + args := starlark.Tuple{wr, caddyscript.HTTPRequest{Req: r}} + v, err := starlark.Call(new(starlark.Thread), s.serveHTTP, args, nil) + if err != nil { + return fmt.Errorf("starlark serveHTTP(), %v", err) + } + + // if a responder type was returned from starlark we should run it otherwise it + // is expected to handle the request + if resp, ok := v.(lib.ResponderModule); ok { + return resp.Instance.ServeHTTP(w, r) + } + + return nil + }) + + // TODO :- make middlewareResponseWriter exported and wrap w with that + var mid []caddyhttp.Middleware + for _, m := range s.execute.Modules { + mid = append(mid, func(next caddyhttp.HandlerFunc) caddyhttp.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) error { + return m.Instance.ServeHTTP(w, r, next) + } + }) + } + + for i := len(mid) - 1; i >= 0; i-- { + stack = mid[i](stack) + } + + s.execute.Modules = nil + + return stack(w, r) +} + +// Cleanup cleans up any modules loaded during the creation of a starlark route. +func (s *StarlarkMW) Cleanup() error { + s.rcancel() + return nil +} + +// Provision sets up the starlark env. +func (s *StarlarkMW) Provision(ctx caddy.Context) error { + // store global context + s.gctx = ctx + + // setup context for cleaning up any modules loaded during starlark script parsing phase + rctx, cancel := caddy.NewContext(ctx) + s.rcancel = cancel + + // setup starlark global env + env := make(starlark.StringDict) + loadMiddleware := lib.LoadMiddleware{} + loadResponder := lib.LoadResponder{ + Ctx: rctx, + } + execute := lib.Execute{} + + lr := starlark.NewBuiltin("loadResponder", loadResponder.Run) + lr = lr.BindReceiver(&loadResponder) + env["loadResponder"] = lr + + lm := starlark.NewBuiltin("loadMiddleware", loadMiddleware.Run) + lm = lm.BindReceiver(&loadMiddleware) + env["loadMiddleware"] = lm + + ex := starlark.NewBuiltin("execute", execute.Run) + ex = ex.BindReceiver(&execute) + env["execute"] = ex + + // import caddyscript lib + env["time"] = caddyscript.Time{} + env["regexp"] = caddyscript.Regexp{} + + // configure starlark + thread := new(starlark.Thread) + s.thread = thread + + // run starlark script + globals, err := starlark.ExecFile(thread, "", s.Script, env) + if err != nil { + return fmt.Errorf("starlark exec file: %v", err.Error()) + } + + // extract defined methods to setup middleware chain and responder, setup is not required + var setup *starlark.Function + if _, ok := globals["setup"]; ok { + setup, ok = globals["setup"].(*starlark.Function) + if !ok { + return fmt.Errorf("setup function not defined in starlark script") + } + } + + serveHTTP, ok := globals["serveHTTP"].(*starlark.Function) + if !ok { + return fmt.Errorf("serveHTTP function not defined in starlark script") + } + + s.setup = setup + s.serveHTTP = serveHTTP + s.loadMiddleware = &loadMiddleware + s.execute = &execute + s.globals = &globals + + return nil +} diff --git a/modules/caddyhttp/starlarkmw/tools/gen/example.star b/modules/caddyhttp/starlarkmw/tools/gen/example.star new file mode 100644 index 0000000..6ccab32 --- /dev/null +++ b/modules/caddyhttp/starlarkmw/tools/gen/example.star @@ -0,0 +1,40 @@ +# any module that provisions resources +proxyConfig = { + 'load_balance_type': 'round_robin', + 'upstreams': [ + { + 'host': 'http://localhost:8080', + 'circuit_breaker': { + 'type': 'status_ratio', + 'threshold': 0.5 + } + }, + { + 'host': 'http://localhost:8081' + } + ] +} + +sfConfig = { + 'root': '/Users/dev/Desktop', + 'browse': {}, +} + +proxy = loadResponder('reverse_proxy', proxyConfig) +static_files = loadResponder('file_server', sfConfig) + +def setup(r): + # create some middlewares specific to this request + mid = [] + + if r.query.get('log') == 'true': + logMid = loadMiddleware('log', {'file': 'access.log'}) + mid.append(logMid) + + execute(mid) + +def serveHTTP(w, r): + if r.url.find('static') > 0: + return static_files + + return proxy -- cgit v1.2.3