Compare commits

..

6 Commits

5 changed files with 29 additions and 38 deletions

View File

@@ -63,9 +63,6 @@ GODOXY_METRICS_DISABLE_DISK=false
GODOXY_METRICS_DISABLE_NETWORK=false
GODOXY_METRICS_DISABLE_SENSORS=false
# Frontend listening port
GODOXY_FRONTEND_PORT=3000
# Frontend aliases (subdomains / FQDNs, e.g. godoxy, godoxy.domain.com)
GODOXY_FRONTEND_ALIASES=godoxy

View File

@@ -24,24 +24,23 @@ services:
image: ghcr.io/yusing/godoxy-frontend:${TAG:-latest}
container_name: godoxy-frontend
restart: unless-stopped
network_mode: host # do not change this
env_file: .env
user: ${GODOXY_UID:-1000}:${GODOXY_GID:-1000}
read_only: true
tmpfs:
- /app/.next/cache # next image caching
# for lite variant, do not change uid/gid
# - /var/cache/nginx:uid=101,gid=101
# - /run:uid=101,gid=101
security_opt:
- no-new-privileges:true
cap_drop:
- all
depends_on:
- app
environment:
HOSTNAME: 127.0.0.1
PORT: ${GODOXY_FRONTEND_PORT:-3000}
labels:
proxy.aliases: ${GODOXY_FRONTEND_ALIASES:-godoxy}
proxy.#1.port: ${GODOXY_FRONTEND_PORT:-3000}
# proxy.#1.middlewares.cidr_whitelist: |
# status: 403
# message: IP not allowed

View File

@@ -2,10 +2,10 @@ package api
import (
"net/http"
"strconv"
"time"
"reflect"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/codec/json"
"github.com/gorilla/websocket"
"github.com/rs/zerolog/log"
apiV1 "github.com/yusing/godoxy/internal/api/v1"
@@ -45,6 +45,9 @@ func NewHandler() *gin.Engine {
r := gin.New()
r.Use(ErrorHandler())
r.Use(ErrorLoggingMiddleware())
r.Use(NoCache())
log.Debug().Msg("gin codec json.API: " + reflect.TypeOf(json.API).Name())
r.GET("/api/v1/version", apiV1.Version)
@@ -69,7 +72,7 @@ func NewHandler() *gin.Engine {
}
{
// enable cache for favicon
v1.GET("/favicon", apiV1.FavIcon).Use(Cache(time.Hour * 24))
v1.GET("/favicon", apiV1.FavIcon)
v1.GET("/health", apiV1.Health)
v1.GET("/icons", apiV1.Icons)
v1.POST("/reload", apiV1.Reload)
@@ -140,15 +143,13 @@ func NewHandler() *gin.Engine {
}
}
// disable cache by default
r.Use(NoCache())
return r
}
func NoCache() gin.HandlerFunc {
return func(c *gin.Context) {
// skip cache if Cache-Control header is set or if caching is explicitly enabled
if !c.GetBool("cache_enabled") && c.Writer.Header().Get("Cache-Control") == "" {
// skip cache if Cache-Control header is set
if c.Writer.Header().Get("Cache-Control") == "" {
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
c.Header("Pragma", "no-cache")
c.Header("Expires", "0")
@@ -157,20 +158,6 @@ func NoCache() gin.HandlerFunc {
}
}
func Cache(duration time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
// Signal to NoCache middleware that caching is intended
c.Set("cache_enabled", true)
// skip cache if Cache-Control header is set
if c.Writer.Header().Get("Cache-Control") == "" {
c.Header("Cache-Control", "public, max-age="+strconv.FormatFloat(duration.Seconds(), 'f', 0, 64)+", immutable")
c.Header("Pragma", "public")
c.Header("Expires", time.Now().Add(duration).Format(time.RFC1123))
}
c.Next()
}
}
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
err := auth.GetDefaultAuth().CheckToken(c.Request)

View File

@@ -52,13 +52,13 @@ func (m *modifyHTML) modifyResponse(resp *http.Response) error {
// NOTE: do not put it in the defer, it will be used as resp.Body
content, release, err := httputils.ReadAllBody(resp)
resp.Body.Close()
if err != nil {
log.Err(err).Str("url", fullURL(resp.Request)).Msg("failed to read response body")
resp.Body.Close()
release(content)
resp.Body = eofReader{}
return err
}
resp.Body.Close()
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(content))
if err != nil {
@@ -83,7 +83,11 @@ func (m *modifyHTML) modifyResponse(resp *http.Response) error {
ele.First().AppendHtml(m.HTML)
}
buf := bytes.NewBuffer(content[:0])
// should not use content (from sized pool) directly for bytes.Buffer
buf := m.bytesPool.GetBuffer()
buf.Write(content)
release(content)
err = buildHTML(doc, buf)
if err != nil {
log.Err(err).Str("url", fullURL(resp.Request)).Msg("failed to build html")
@@ -95,8 +99,7 @@ func (m *modifyHTML) modifyResponse(resp *http.Response) error {
resp.Header.Set("Content-Length", strconv.Itoa(buf.Len()))
resp.Header.Set("Content-Type", "text/html; charset=utf-8")
resp.Body = readerWithRelease(buf.Bytes(), func(_ []byte) {
// release content, not buf.Bytes()
release(content)
m.bytesPool.PutBuffer(buf)
})
return nil
}

View File

@@ -188,15 +188,20 @@ var commands = map[string]struct {
if !httputils.IsStatusCodeValid(code) {
return nil, ErrInvalidArguments.Subject(codeStr)
}
return &Tuple[int, string]{code, text}, nil
textTmpl, err := validateTemplate(text, true)
if err != nil {
return nil, ErrInvalidArguments.With(err)
}
return &Tuple[int, templateString]{code, textTmpl}, nil
},
build: func(args any) CommandHandler {
code, text := args.(*Tuple[int, string]).Unpack()
code, textTmpl := args.(*Tuple[int, templateString]).Unpack()
return TerminatingCommand(func(w http.ResponseWriter, r *http.Request) error {
// error command should overwrite the response body
GetInitResponseModifier(w).ResetBody()
http.Error(w, text, code)
return nil
w.WriteHeader(code)
err := textTmpl.ExpandVars(w, r, w)
return err
})
},
},