diff --git a/Makefile b/Makefile index 7380ef6..6e061fe 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,13 @@ -.PHONY: lint run-caddy run-roadrunner +.PHONY: lint run-caddy run-server run-traefik lint: ## Run golangci-lint to ensure the code quality docker run --rm -v $(PWD):/app -w /app golangci/golangci-lint golangci-lint run -run-caddy: ## Build caddy binary +run-caddy: ## Build and run caddy binary cd middleware/caddy && $(MAKE) build && $(MAKE) run + +run-server: ## Run server main.go + go run middleware/server/main.go + +run-traefik: ## Run server main.go + cd middleware/traefik && $(MAKE) build && $(MAKE) run diff --git a/README.md b/README.md index 3bf574c..71a5ffe 100644 --- a/README.md +++ b/README.md @@ -51,18 +51,18 @@ func functionToParseESITags(b []byte, r *http.Request) []byte { ```bash xcaddy build --with github.com/darkweak/go-esi/middleware/caddy ``` -Refer to the [sample Caddyfile](https://github.com/darkweak/go-esi/blob/master/middleware/caddy/Caddyfile) to know how to configure that. +Refer to the [sample Caddyfile](https://github.com/darkweak/go-esi/blob/master/middleware/caddy/Caddyfile) to know how to use that. ### Træfik middleware -```bash +```yaml # anywhere/traefik.yml experimental: plugins: souin: moduleName: github.com/darkweak/go-esi - version: v0.0.4 + version: v0.0.5 ``` -```bash +```yaml # anywhere/dynamic-configuration http: routers: @@ -74,12 +74,9 @@ http: middlewares: esi: plugin: - esi: - # We don't care about the configuration but we have ot declare that block - # due to shitty træfik empty configuration handle. - disable: false + esi: {} ``` -Refer to the [sample Caddyfile](https://github.com/darkweak/go-esi/blob/master/middleware/caddy/Caddyfile) to know how to configure that. +Refer to the [sample traefik file](https://github.com/darkweak/go-esi/blob/master/middleware/traefik/esi-configuration.yml) to know how to use that. ## TODO - [x] choose tag diff --git a/esi/choose.go b/esi/choose.go index 38a24f7..572fdb3 100644 --- a/esi/choose.go +++ b/esi/choose.go @@ -30,7 +30,7 @@ type chooseTag struct { // // // ). -func (c *chooseTag) process(b []byte, req *http.Request) ([]byte, int) { +func (c *chooseTag) Process(b []byte, req *http.Request) ([]byte, int) { found := closeChoose.FindIndex(b) if found == nil { return nil, len(b) @@ -56,3 +56,14 @@ func (c *chooseTag) process(b []byte, req *http.Request) ([]byte, int) { return res, c.length } + +func (*chooseTag) HasClose(b []byte) bool { + return closeChoose.FindIndex(b) != nil +} + +func (*chooseTag) GetClosePosition(b []byte) int { + if idx := closeChoose.FindIndex(b); idx != nil { + return idx[1] + } + return 0 +} diff --git a/esi/comment.go b/esi/comment.go index 1e0b596..e9c3137 100644 --- a/esi/comment.go +++ b/esi/comment.go @@ -14,7 +14,7 @@ type commentTag struct { } // Input (e.g. comment text="This is a comment." />). -func (c *commentTag) process(b []byte, req *http.Request) ([]byte, int) { +func (c *commentTag) Process(b []byte, req *http.Request) ([]byte, int) { found := closeComment.FindIndex(b) if found == nil { return nil, len(b) @@ -22,3 +22,14 @@ func (c *commentTag) process(b []byte, req *http.Request) ([]byte, int) { return []byte{}, found[1] } + +func (*commentTag) HasClose(b []byte) bool { + return closeComment.FindIndex(b) != nil +} + +func (*commentTag) GetClosePosition(b []byte) int { + if idx := closeComment.FindIndex(b); idx != nil { + return idx[1] + } + return 0 +} diff --git a/esi/escape.go b/esi/escape.go index 7a8ece2..a3129c1 100644 --- a/esi/escape.go +++ b/esi/escape.go @@ -16,7 +16,7 @@ type escapeTag struct { *baseTag } -func (e *escapeTag) process(b []byte, req *http.Request) ([]byte, int) { +func (e *escapeTag) Process(b []byte, req *http.Request) ([]byte, int) { closeIdx := closeEscape.FindIndex(b) if closeIdx == nil { @@ -28,3 +28,14 @@ func (e *escapeTag) process(b []byte, req *http.Request) ([]byte, int) { return b, e.length } + +func (*escapeTag) HasClose(b []byte) bool { + return closeEscape.FindIndex(b) != nil +} + +func (*escapeTag) GetClosePosition(b []byte) int { + if idx := closeEscape.FindIndex(b); idx != nil { + return idx[1] + } + return 0 +} diff --git a/esi/esi.go b/esi/esi.go index b20d45a..9deea63 100644 --- a/esi/esi.go +++ b/esi/esi.go @@ -4,7 +4,7 @@ import ( "net/http" ) -func findTagName(b []byte) tag { +func findTagName(b []byte) Tag { name := tagname.FindSubmatch(b) if name == nil { return nil @@ -43,6 +43,43 @@ func findTagName(b []byte) tag { return nil } +func HasOpenedTags(b []byte) bool { + return esi.FindIndex(b) != nil || escapeRg.FindIndex(b) != nil +} + +func CanProcess(b []byte) bool { + if tag := findTagName(b); tag != nil { + return tag.HasClose(b) + } + + return false +} + +func ReadToTag(next []byte, pointer int) (startTagPosition, esiPointer int, t Tag) { + tagIdx := esi.FindIndex(next) + var isEscapeTag bool + + if escIdx := escapeRg.FindIndex(next); escIdx != nil && (tagIdx == nil || escIdx[0] < tagIdx[0]) { + tagIdx = escIdx + tagIdx[1] = escIdx[0] + isEscapeTag = true + } + + if tagIdx == nil { + return len(next), 0, nil + } + + esiPointer = tagIdx[1] + startTagPosition = tagIdx[0] + t = findTagName(next[esiPointer:]) + + if isEscapeTag { + esiPointer += 7 + } + + return +} + func Parse(b []byte, req *http.Request) []byte { pointer := 0 @@ -69,7 +106,7 @@ func Parse(b []byte, req *http.Request) []byte { esiPointer += 7 } - res, p := t.process(next[esiPointer:], req) + res, p := t.Process(next[esiPointer:], req) esiPointer += p b = append(b[:pointer], append(next[:tagIdx[0]], append(res, next[esiPointer:]...)...)...) diff --git a/esi/include.go b/esi/include.go index dcdf95b..1ecebe5 100644 --- a/esi/include.go +++ b/esi/include.go @@ -40,7 +40,7 @@ func (i *includeTag) loadAttributes(b []byte) error { // With or without the alt // With or without a space separator before the closing // With or without the quotes around the src/alt value. -func (i *includeTag) process(b []byte, req *http.Request) ([]byte, int) { +func (i *includeTag) Process(b []byte, req *http.Request) ([]byte, int) { closeIdx := closeInclude.FindIndex(b) if closeIdx == nil { @@ -71,3 +71,14 @@ func (i *includeTag) process(b []byte, req *http.Request) ([]byte, int) { return b, i.length } + +func (*includeTag) HasClose(b []byte) bool { + return closeInclude.FindIndex(b) != nil +} + +func (*includeTag) GetClosePosition(b []byte) int { + if idx := closeInclude.FindIndex(b); idx != nil { + return idx[1] + } + return 0 +} diff --git a/esi/remove.go b/esi/remove.go index c73b1f7..3c16157 100644 --- a/esi/remove.go +++ b/esi/remove.go @@ -13,7 +13,7 @@ type removeTag struct { *baseTag } -func (r *removeTag) process(b []byte, req *http.Request) ([]byte, int) { +func (r *removeTag) Process(b []byte, req *http.Request) ([]byte, int) { closeIdx := closeRemove.FindIndex(b) if closeIdx == nil { return []byte{}, len(b) @@ -23,3 +23,14 @@ func (r *removeTag) process(b []byte, req *http.Request) ([]byte, int) { return []byte{}, r.length } + +func (*removeTag) HasClose(b []byte) bool { + return closeRemove.FindIndex(b) != nil +} + +func (*removeTag) GetClosePosition(b []byte) int { + if idx := closeRemove.FindIndex(b); idx != nil { + return idx[1] + } + return 0 +} diff --git a/esi/type.go b/esi/type.go index 232f2f8..8388015 100644 --- a/esi/type.go +++ b/esi/type.go @@ -5,8 +5,10 @@ import ( ) type ( - tag interface { - process([]byte, *http.Request) ([]byte, int) + Tag interface { + Process([]byte, *http.Request) ([]byte, int) + HasClose([]byte) bool + GetClosePosition([]byte) int } baseTag struct { @@ -18,6 +20,6 @@ func newBaseTag() *baseTag { return &baseTag{length: 0} } -func (b *baseTag) process(content []byte, _ *http.Request) ([]byte, int) { +func (b *baseTag) Process(content []byte, _ *http.Request) ([]byte, int) { return []byte{}, len(content) } diff --git a/esi/vars.go b/esi/vars.go index a3bdc26..39bfb5c 100644 --- a/esi/vars.go +++ b/esi/vars.go @@ -75,7 +75,7 @@ type varsTag struct { } // Input (e.g. comment text="This is a comment." />). -func (c *varsTag) process(b []byte, req *http.Request) ([]byte, int) { +func (c *varsTag) Process(b []byte, req *http.Request) ([]byte, int) { found := closeVars.FindIndex(b) if found == nil { return nil, len(b) @@ -87,3 +87,14 @@ func (c *varsTag) process(b []byte, req *http.Request) ([]byte, int) { return []byte(parseVariables(b, req)) }), c.length } + +func (*varsTag) HasClose(b []byte) bool { + return closeVars.FindIndex(b) != nil +} + +func (*varsTag) GetClosePosition(b []byte) int { + if idx := closeVars.FindIndex(b); idx != nil { + return idx[1] + } + return 0 +} diff --git a/middleware/caddy/Caddyfile b/middleware/caddy/Caddyfile index a61b0f4..fe74f7a 100644 --- a/middleware/caddy/Caddyfile +++ b/middleware/caddy/Caddyfile @@ -5,7 +5,7 @@ esi } -:80 { +localhost:443 { route /chained-esi-include-1 { header Content-Type text/html respond `` @@ -28,8 +28,8 @@ route /* { esi - - root * ../../fixtures - file_server + reverse_proxy 127.0.0.1:81 + # root * ../../fixtures + # file_server } } \ No newline at end of file diff --git a/middleware/caddy/esi.go b/middleware/caddy/esi.go index 15967f2..f0831a8 100644 --- a/middleware/caddy/esi.go +++ b/middleware/caddy/esi.go @@ -2,7 +2,6 @@ package caddy_esi import ( "bytes" - "fmt" "net/http" "sync" @@ -10,7 +9,7 @@ import ( "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/modules/caddyhttp" - "github.com/darkweak/go-esi/esi" + "github.com/darkweak/go-esi/writer" ) var bufPool *sync.Pool = &sync.Pool{ @@ -45,14 +44,33 @@ func (e *ESI) ServeHTTP(rw http.ResponseWriter, r *http.Request, next caddyhttp. buf := bufPool.Get().(*bytes.Buffer) buf.Reset() defer bufPool.Put(buf) - cw := newWriter(buf, rw) + cw := writer.NewWriter(buf, rw, r) + go func(w *writer.Writer) { + w.Header().Del("Content-Length") + if w.Rq.ProtoMajor == 1 { + w.Header().Set("Content-Encoding", "chunked") + } + var i = 0 + for { + if len(cw.AsyncBuf) <= i { + continue + } + rs := <-cw.AsyncBuf[i] + if rs == nil { + cw.Done <- true + break + } + _, _ = rw.Write(rs) + i++ + } + }(cw) next.ServeHTTP(cw, r) + cw.AsyncBuf = append(cw.AsyncBuf, make(chan []byte)) + go func(w *writer.Writer, iteration int) { + w.AsyncBuf[iteration] <- nil + }(cw, cw.Iteration) - b := esi.Parse(cw.buf.Bytes(), r) - - rw.Header().Set("Content-Length", fmt.Sprintf("%d", len(b))) - rw.WriteHeader(cw.status) - _, _ = rw.Write(b) + <-cw.Done return nil } diff --git a/middleware/caddy/go.mod b/middleware/caddy/go.mod index d5763e2..07c2b38 100644 --- a/middleware/caddy/go.mod +++ b/middleware/caddy/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/caddyserver/caddy/v2 v2.5.2 - github.com/darkweak/go-esi v0.0.4 + github.com/darkweak/go-esi v0.0.5 ) require ( @@ -111,4 +111,4 @@ require ( howett.net/plist v1.0.0 // indirect ) -replace github.com/darkweak/go-esi v0.0.4 => ../.. +replace github.com/darkweak/go-esi v0.0.5 => ../.. diff --git a/middleware/caddy/writer.go b/middleware/caddy/writer.go deleted file mode 100644 index a7fb39e..0000000 --- a/middleware/caddy/writer.go +++ /dev/null @@ -1,37 +0,0 @@ -package caddy_esi - -import ( - "bytes" - "net/http" -) - -type writer struct { - buf *bytes.Buffer - rw http.ResponseWriter - status int -} - -func newWriter(buf *bytes.Buffer, rw http.ResponseWriter) *writer { - return &writer{ - buf: buf, - rw: rw, - } -} - -// Header implements http.ResponseWriter -func (w *writer) Header() http.Header { - return w.rw.Header() -} - -// WriteHeader implements http.ResponseWriter -func (w *writer) WriteHeader(statusCode int) { - w.status = statusCode -} - -// Write will write the response body -func (w *writer) Write(b []byte) (int, error) { - w.buf.Write(b) - return len(b), nil -} - -var _ http.ResponseWriter = (*writer)(nil) diff --git a/middleware/server/main.go b/middleware/server/main.go index b5a57f5..febcc93 100644 --- a/middleware/server/main.go +++ b/middleware/server/main.go @@ -2,11 +2,44 @@ package main import ( "net/http" + "time" "github.com/darkweak/go-esi/esi" ) +var respond = []byte(` + + <esi:vars>Hello from $(HTTP_HOST)</esi:vars> + + + + + + + + + + +`) + func main() { rq, _ := http.NewRequest(http.MethodGet, "domain.com/", nil) esi.Parse([]byte{}, rq) + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(respond[0:97]) + w.(http.Flusher).Flush() + time.Sleep(3 * time.Second) + _, _ = w.Write(respond[97:194]) + time.Sleep(3 * time.Second) + _, _ = w.Write(respond[194:291]) + time.Sleep(3 * time.Second) + _, _ = w.Write(respond[291:388]) + }) + + _ = http.ListenAndServe(":81", nil) } diff --git a/middleware/traefik/Makefile b/middleware/traefik/Makefile new file mode 100644 index 0000000..2d83d3b --- /dev/null +++ b/middleware/traefik/Makefile @@ -0,0 +1,8 @@ +.PHONY: build run + +build: ## Build caddy binary + go mod tidy + go mod download + +run: ## Run caddy with go-esi + docker-compose up --remove-orphans \ No newline at end of file diff --git a/middleware/traefik/esi-configuration.yml b/middleware/traefik/esi-configuration.yml index 40cd7b2..307e7a6 100644 --- a/middleware/traefik/esi-configuration.yml +++ b/middleware/traefik/esi-configuration.yml @@ -18,7 +18,4 @@ http: middlewares: esi: plugin: - esi: - # We don't care about the configuration but we have ot declare that block - # due to shitty træfik empty configuration handle. - disable: false \ No newline at end of file + esi: {} \ No newline at end of file diff --git a/middleware/traefik/esi.go b/middleware/traefik/esi.go index 7145238..d798bb6 100644 --- a/middleware/traefik/esi.go +++ b/middleware/traefik/esi.go @@ -1,35 +1,71 @@ package traefik import ( + "bytes" "context" "net/http" + "sync" - "github.com/darkweak/go-esi/esi" + "github.com/darkweak/go-esi/writer" ) -// Config the plugin configuration. +var bufPool *sync.Pool = &sync.Pool{ + New: func() any { + return &bytes.Buffer{} + }, +} + +// Config the ESI plugin configuration. type Config struct{} -// CreateConfig creates the default plugin configuration. +// CreateConfig creates the ESI plugin configuration. func CreateConfig() *Config { return &Config{} } -// Demo a Demo plugin. -type Demo struct { +// ESI is a plugin that allow users to process the ESI tags. +type ESI struct { next http.Handler name string } -// New created a new Demo plugin. +// New created a new ESI plugin. func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) { - return &Demo{ + return &ESI{ next: next, name: name, }, nil } -func (a *Demo) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - esi.Parse([]byte{}, req) - a.next.ServeHTTP(rw, req) +func (e *ESI) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + defer bufPool.Put(buf) + cw := writer.NewWriter(buf, rw, req) + go func(w *writer.Writer) { + w.Header().Del("Content-Length") + if w.Rq.ProtoMajor == 1 { + w.Header().Set("Content-Encoding", "chunked") + } + var i = 0 + for { + if len(cw.AsyncBuf) <= i { + continue + } + rs := <-cw.AsyncBuf[i] + if rs == nil { + cw.Done <- true + break + } + _, _ = rw.Write(rs) + i++ + } + }(cw) + e.next.ServeHTTP(cw, req) + cw.AsyncBuf = append(cw.AsyncBuf, make(chan []byte)) + go func(w *writer.Writer, iteration int) { + w.AsyncBuf[iteration] <- nil + }(cw, cw.Iteration) + + <-cw.Done } diff --git a/middleware/traefik/go.mod b/middleware/traefik/go.mod index e6747d7..66a3ec0 100644 --- a/middleware/traefik/go.mod +++ b/middleware/traefik/go.mod @@ -2,6 +2,6 @@ module github.com/darkweak/go-esi/middleware/traefik go 1.18 -require github.com/darkweak/go-esi v0.0.4 +require github.com/darkweak/go-esi v0.0.5 -replace github.com/darkweak/go-esi v0.0.4 => ../.. +replace github.com/darkweak/go-esi v0.0.5 => ../.. diff --git a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/choose.go b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/choose.go index 38a24f7..572fdb3 100644 --- a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/choose.go +++ b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/choose.go @@ -30,7 +30,7 @@ type chooseTag struct { // // // ). -func (c *chooseTag) process(b []byte, req *http.Request) ([]byte, int) { +func (c *chooseTag) Process(b []byte, req *http.Request) ([]byte, int) { found := closeChoose.FindIndex(b) if found == nil { return nil, len(b) @@ -56,3 +56,14 @@ func (c *chooseTag) process(b []byte, req *http.Request) ([]byte, int) { return res, c.length } + +func (*chooseTag) HasClose(b []byte) bool { + return closeChoose.FindIndex(b) != nil +} + +func (*chooseTag) GetClosePosition(b []byte) int { + if idx := closeChoose.FindIndex(b); idx != nil { + return idx[1] + } + return 0 +} diff --git a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/comment.go b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/comment.go index 1e0b596..e9c3137 100644 --- a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/comment.go +++ b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/comment.go @@ -14,7 +14,7 @@ type commentTag struct { } // Input (e.g. comment text="This is a comment." />). -func (c *commentTag) process(b []byte, req *http.Request) ([]byte, int) { +func (c *commentTag) Process(b []byte, req *http.Request) ([]byte, int) { found := closeComment.FindIndex(b) if found == nil { return nil, len(b) @@ -22,3 +22,14 @@ func (c *commentTag) process(b []byte, req *http.Request) ([]byte, int) { return []byte{}, found[1] } + +func (*commentTag) HasClose(b []byte) bool { + return closeComment.FindIndex(b) != nil +} + +func (*commentTag) GetClosePosition(b []byte) int { + if idx := closeComment.FindIndex(b); idx != nil { + return idx[1] + } + return 0 +} diff --git a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/escape.go b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/escape.go index 7a8ece2..a3129c1 100644 --- a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/escape.go +++ b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/escape.go @@ -16,7 +16,7 @@ type escapeTag struct { *baseTag } -func (e *escapeTag) process(b []byte, req *http.Request) ([]byte, int) { +func (e *escapeTag) Process(b []byte, req *http.Request) ([]byte, int) { closeIdx := closeEscape.FindIndex(b) if closeIdx == nil { @@ -28,3 +28,14 @@ func (e *escapeTag) process(b []byte, req *http.Request) ([]byte, int) { return b, e.length } + +func (*escapeTag) HasClose(b []byte) bool { + return closeEscape.FindIndex(b) != nil +} + +func (*escapeTag) GetClosePosition(b []byte) int { + if idx := closeEscape.FindIndex(b); idx != nil { + return idx[1] + } + return 0 +} diff --git a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/esi.go b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/esi.go index b20d45a..9deea63 100644 --- a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/esi.go +++ b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/esi.go @@ -4,7 +4,7 @@ import ( "net/http" ) -func findTagName(b []byte) tag { +func findTagName(b []byte) Tag { name := tagname.FindSubmatch(b) if name == nil { return nil @@ -43,6 +43,43 @@ func findTagName(b []byte) tag { return nil } +func HasOpenedTags(b []byte) bool { + return esi.FindIndex(b) != nil || escapeRg.FindIndex(b) != nil +} + +func CanProcess(b []byte) bool { + if tag := findTagName(b); tag != nil { + return tag.HasClose(b) + } + + return false +} + +func ReadToTag(next []byte, pointer int) (startTagPosition, esiPointer int, t Tag) { + tagIdx := esi.FindIndex(next) + var isEscapeTag bool + + if escIdx := escapeRg.FindIndex(next); escIdx != nil && (tagIdx == nil || escIdx[0] < tagIdx[0]) { + tagIdx = escIdx + tagIdx[1] = escIdx[0] + isEscapeTag = true + } + + if tagIdx == nil { + return len(next), 0, nil + } + + esiPointer = tagIdx[1] + startTagPosition = tagIdx[0] + t = findTagName(next[esiPointer:]) + + if isEscapeTag { + esiPointer += 7 + } + + return +} + func Parse(b []byte, req *http.Request) []byte { pointer := 0 @@ -69,7 +106,7 @@ func Parse(b []byte, req *http.Request) []byte { esiPointer += 7 } - res, p := t.process(next[esiPointer:], req) + res, p := t.Process(next[esiPointer:], req) esiPointer += p b = append(b[:pointer], append(next[:tagIdx[0]], append(res, next[esiPointer:]...)...)...) diff --git a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/include.go b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/include.go index dcdf95b..1ecebe5 100644 --- a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/include.go +++ b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/include.go @@ -40,7 +40,7 @@ func (i *includeTag) loadAttributes(b []byte) error { // With or without the alt // With or without a space separator before the closing // With or without the quotes around the src/alt value. -func (i *includeTag) process(b []byte, req *http.Request) ([]byte, int) { +func (i *includeTag) Process(b []byte, req *http.Request) ([]byte, int) { closeIdx := closeInclude.FindIndex(b) if closeIdx == nil { @@ -71,3 +71,14 @@ func (i *includeTag) process(b []byte, req *http.Request) ([]byte, int) { return b, i.length } + +func (*includeTag) HasClose(b []byte) bool { + return closeInclude.FindIndex(b) != nil +} + +func (*includeTag) GetClosePosition(b []byte) int { + if idx := closeInclude.FindIndex(b); idx != nil { + return idx[1] + } + return 0 +} diff --git a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/remove.go b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/remove.go index c73b1f7..3c16157 100644 --- a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/remove.go +++ b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/remove.go @@ -13,7 +13,7 @@ type removeTag struct { *baseTag } -func (r *removeTag) process(b []byte, req *http.Request) ([]byte, int) { +func (r *removeTag) Process(b []byte, req *http.Request) ([]byte, int) { closeIdx := closeRemove.FindIndex(b) if closeIdx == nil { return []byte{}, len(b) @@ -23,3 +23,14 @@ func (r *removeTag) process(b []byte, req *http.Request) ([]byte, int) { return []byte{}, r.length } + +func (*removeTag) HasClose(b []byte) bool { + return closeRemove.FindIndex(b) != nil +} + +func (*removeTag) GetClosePosition(b []byte) int { + if idx := closeRemove.FindIndex(b); idx != nil { + return idx[1] + } + return 0 +} diff --git a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/type.go b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/type.go index 232f2f8..8388015 100644 --- a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/type.go +++ b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/type.go @@ -5,8 +5,10 @@ import ( ) type ( - tag interface { - process([]byte, *http.Request) ([]byte, int) + Tag interface { + Process([]byte, *http.Request) ([]byte, int) + HasClose([]byte) bool + GetClosePosition([]byte) int } baseTag struct { @@ -18,6 +20,6 @@ func newBaseTag() *baseTag { return &baseTag{length: 0} } -func (b *baseTag) process(content []byte, _ *http.Request) ([]byte, int) { +func (b *baseTag) Process(content []byte, _ *http.Request) ([]byte, int) { return []byte{}, len(content) } diff --git a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/vars.go b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/vars.go index a3bdc26..39bfb5c 100644 --- a/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/vars.go +++ b/middleware/traefik/vendor/github.com/darkweak/go-esi/esi/vars.go @@ -75,7 +75,7 @@ type varsTag struct { } // Input (e.g. comment text="This is a comment." />). -func (c *varsTag) process(b []byte, req *http.Request) ([]byte, int) { +func (c *varsTag) Process(b []byte, req *http.Request) ([]byte, int) { found := closeVars.FindIndex(b) if found == nil { return nil, len(b) @@ -87,3 +87,14 @@ func (c *varsTag) process(b []byte, req *http.Request) ([]byte, int) { return []byte(parseVariables(b, req)) }), c.length } + +func (*varsTag) HasClose(b []byte) bool { + return closeVars.FindIndex(b) != nil +} + +func (*varsTag) GetClosePosition(b []byte) int { + if idx := closeVars.FindIndex(b); idx != nil { + return idx[1] + } + return 0 +} diff --git a/middleware/traefik/vendor/github.com/darkweak/go-esi/writer/writer.go b/middleware/traefik/vendor/github.com/darkweak/go-esi/writer/writer.go new file mode 100644 index 0000000..459a836 --- /dev/null +++ b/middleware/traefik/vendor/github.com/darkweak/go-esi/writer/writer.go @@ -0,0 +1,97 @@ +package writer + +import ( + "bytes" + "net/http" + + "github.com/darkweak/go-esi/esi" +) + +type Writer struct { + buf *bytes.Buffer + rw http.ResponseWriter + Rq *http.Request + AsyncBuf []chan []byte + Done chan bool + flushed bool + Iteration int +} + +func NewWriter(buf *bytes.Buffer, rw http.ResponseWriter, rq *http.Request) *Writer { + return &Writer{ + buf: buf, + Rq: rq, + rw: rw, + AsyncBuf: make([]chan []byte, 0), + Done: make(chan bool), + } +} + +// Header implements http.ResponseWriter +func (w *Writer) Header() http.Header { + return w.rw.Header() +} + +// WriteHeader implements http.ResponseWriter +func (w *Writer) WriteHeader(statusCode int) { + if statusCode == 0 { + w.rw.WriteHeader(http.StatusOK) + } +} + +// Flush implements http.Flusher +func (w *Writer) Flush() { + if !w.flushed { + w.rw.(http.Flusher).Flush() + w.flushed = true + } +} + +// Write will write the response body +func (w *Writer) Write(b []byte) (int, error) { + buf := append(w.buf.Bytes(), b...) + w.buf.Reset() + + if esi.HasOpenedTags(buf) { + position := 0 + for position < len(buf) { + startPos, nextPos, t := esi.ReadToTag(buf[position:], position) + + if startPos != 0 { + w.AsyncBuf = append(w.AsyncBuf, make(chan []byte)) + go func(tmpBuf []byte, i int, cw *Writer) { + cw.AsyncBuf[i] <- tmpBuf + }(buf[position:position+startPos], w.Iteration, w) + w.Iteration++ + } + + if t == nil { + break + } + + closePosition := t.GetClosePosition(buf[position+startPos:]) + if closePosition == 0 { + position += startPos + break + } + + position += nextPos + w.AsyncBuf = append(w.AsyncBuf, make(chan []byte)) + go func(currentTag esi.Tag, tmpBuf []byte, cw *Writer, Iteration int) { + p, _ := currentTag.Process(tmpBuf, cw.Rq) + cw.AsyncBuf[Iteration] <- p + }(t, buf[position:(position-nextPos)+startPos+closePosition], w, w.Iteration) + position += startPos + closePosition - nextPos + w.Iteration++ + } + w.buf.Write(buf[position:]) + return len(b), nil + } + + w.AsyncBuf = append(w.AsyncBuf, make(chan []byte)) + w.AsyncBuf[w.Iteration] <- buf + w.Iteration++ + return len(b), nil +} + +var _ http.ResponseWriter = (*Writer)(nil) diff --git a/middleware/traefik/vendor/modules.txt b/middleware/traefik/vendor/modules.txt index 78e242b..dfd38ea 100644 --- a/middleware/traefik/vendor/modules.txt +++ b/middleware/traefik/vendor/modules.txt @@ -1,3 +1,4 @@ -# github.com/darkweak/go-esi v0.0.4 => ../.. +# github.com/darkweak/go-esi v0.0.5 => ../.. ## explicit; go 1.18 github.com/darkweak/go-esi/esi +github.com/darkweak/go-esi/writer diff --git a/writer/writer.go b/writer/writer.go new file mode 100644 index 0000000..459a836 --- /dev/null +++ b/writer/writer.go @@ -0,0 +1,97 @@ +package writer + +import ( + "bytes" + "net/http" + + "github.com/darkweak/go-esi/esi" +) + +type Writer struct { + buf *bytes.Buffer + rw http.ResponseWriter + Rq *http.Request + AsyncBuf []chan []byte + Done chan bool + flushed bool + Iteration int +} + +func NewWriter(buf *bytes.Buffer, rw http.ResponseWriter, rq *http.Request) *Writer { + return &Writer{ + buf: buf, + Rq: rq, + rw: rw, + AsyncBuf: make([]chan []byte, 0), + Done: make(chan bool), + } +} + +// Header implements http.ResponseWriter +func (w *Writer) Header() http.Header { + return w.rw.Header() +} + +// WriteHeader implements http.ResponseWriter +func (w *Writer) WriteHeader(statusCode int) { + if statusCode == 0 { + w.rw.WriteHeader(http.StatusOK) + } +} + +// Flush implements http.Flusher +func (w *Writer) Flush() { + if !w.flushed { + w.rw.(http.Flusher).Flush() + w.flushed = true + } +} + +// Write will write the response body +func (w *Writer) Write(b []byte) (int, error) { + buf := append(w.buf.Bytes(), b...) + w.buf.Reset() + + if esi.HasOpenedTags(buf) { + position := 0 + for position < len(buf) { + startPos, nextPos, t := esi.ReadToTag(buf[position:], position) + + if startPos != 0 { + w.AsyncBuf = append(w.AsyncBuf, make(chan []byte)) + go func(tmpBuf []byte, i int, cw *Writer) { + cw.AsyncBuf[i] <- tmpBuf + }(buf[position:position+startPos], w.Iteration, w) + w.Iteration++ + } + + if t == nil { + break + } + + closePosition := t.GetClosePosition(buf[position+startPos:]) + if closePosition == 0 { + position += startPos + break + } + + position += nextPos + w.AsyncBuf = append(w.AsyncBuf, make(chan []byte)) + go func(currentTag esi.Tag, tmpBuf []byte, cw *Writer, Iteration int) { + p, _ := currentTag.Process(tmpBuf, cw.Rq) + cw.AsyncBuf[Iteration] <- p + }(t, buf[position:(position-nextPos)+startPos+closePosition], w, w.Iteration) + position += startPos + closePosition - nextPos + w.Iteration++ + } + w.buf.Write(buf[position:]) + return len(b), nil + } + + w.AsyncBuf = append(w.AsyncBuf, make(chan []byte)) + w.AsyncBuf[w.Iteration] <- buf + w.Iteration++ + return len(b), nil +} + +var _ http.ResponseWriter = (*Writer)(nil)