Compare commits

...

7 Commits

8 changed files with 94 additions and 28 deletions

View File

@@ -22,6 +22,8 @@ services:
- ${SOCKET_PROXY_LISTEN_ADDR:-127.0.0.1:2375}:2375
frontend:
image: ghcr.io/yusing/godoxy-frontend:${TAG:-latest}
# lite variant
# image: ghcr.io/yusing/godoxy-frontend:${TAG:-latest}-lite
container_name: godoxy-frontend
restart: unless-stopped
env_file: .env
@@ -74,10 +76,9 @@ services:
- ./error_pages:/app/error_pages:ro
- ./data:/app/data
# To use autocert, certs will be stored in "./certs".
# You can also use a docker volume to store it
# This path stores certs obtained from autocert and agent TLS client certs
- ./certs:/app/certs
# remove "./certs:/app/certs" and uncomment below to use existing certificate
# mount existing certificate
# - /path/to/certs/cert.crt:/app/certs/cert.crt
# - /path/to/certs/priv.key:/app/certs/priv.key

View File

@@ -4,6 +4,8 @@
# autocert:
# provider: local
# cert_path: /path/to/cert.crt # default: /app/certs/cert.crt
# key_path: /path/to/priv.key # default: /app/certs/priv.key
# 2. cloudflare
# autocert:

View File

@@ -51,6 +51,10 @@ func ProceedNext(w http.ResponseWriter, r *http.Request) {
}
func AuthCheckHandler(w http.ResponseWriter, r *http.Request) {
if defaultAuth == nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
err := defaultAuth.CheckToken(r)
if err != nil {
defaultAuth.LoginHandler(w, r)
@@ -60,6 +64,10 @@ func AuthCheckHandler(w http.ResponseWriter, r *http.Request) {
}
func AuthOrProceed(w http.ResponseWriter, r *http.Request) (proceed bool) {
if defaultAuth == nil {
w.WriteHeader(http.StatusServiceUnavailable)
return false
}
err := defaultAuth.CheckToken(r)
if err != nil {
defaultAuth.LoginHandler(w, r)

View File

@@ -47,9 +47,10 @@ func (m *modifyHTML) modifyResponse(resp *http.Response) error {
// Skip modification for streaming/chunked responses to avoid blocking reads
// Unknown content length or any transfer encoding indicates streaming.
if resp.ContentLength < 0 || len(resp.TransferEncoding) > 0 {
return nil
}
// if resp.ContentLength < 0 || len(resp.TransferEncoding) > 0 {
// log.Debug().Str("url", fullURL(resp.Request)).Strs("transfer-encoding", resp.TransferEncoding).Msg("skipping modification for streaming/chunked response")
// return nil
// }
// NOTE: do not put it in the defer, it will be used as resp.Body
content, release, err := httputils.ReadAllBody(resp)

View File

@@ -86,6 +86,10 @@ func (s *FileServer) Start(parent task.Parent) gperr.Error {
}
}
if len(s.Rules) > 0 {
s.handler = s.Rules.BuildHandler(s.handler.ServeHTTP)
}
if s.UseHealthCheck() {
s.HealthMon = monitor.NewFileServerHealthMonitor(s.HealthCheck, s.Root)
if err := s.HealthMon.Start(s.task); err != nil {

View File

@@ -82,19 +82,39 @@ type (
impl types.Route
task *task.Task
isValidated bool
lastError gperr.Error
provider types.RouteProvider
// ensure err is read after validation or start
valErr lockedError
startErr lockedError
provider types.RouteProvider
agent *agent.AgentConfig
started chan struct{}
once sync.Once
started chan struct{}
onceStart sync.Once
onceValidate sync.Once
}
Routes map[string]*Route
Port = route.Port
)
type lockedError struct {
err gperr.Error
lock sync.Mutex
}
func (le *lockedError) Get() gperr.Error {
le.lock.Lock()
defer le.lock.Unlock()
return le.err
}
func (le *lockedError) Set(err gperr.Error) {
le.lock.Lock()
defer le.lock.Unlock()
le.err = err
}
const DefaultHost = "localhost"
func (r Routes) Contains(alias string) bool {
@@ -103,11 +123,13 @@ func (r Routes) Contains(alias string) bool {
}
func (r *Route) Validate() gperr.Error {
if r.isValidated {
return r.lastError
}
r.isValidated = true
r.onceValidate.Do(func() {
r.valErr.Set(r.validate())
})
return r.valErr.Get()
}
func (r *Route) validate() gperr.Error {
if r.Agent != "" {
if r.Container != nil {
return gperr.Errorf("specifying agent is not allowed for docker container routes")
@@ -250,7 +272,6 @@ func (r *Route) Validate() gperr.Error {
}
if errs.HasError() {
r.lastError = errs.Error()
return errs.Error()
}
@@ -266,7 +287,6 @@ func (r *Route) Validate() gperr.Error {
}
if err != nil {
r.lastError = err
return err
}
@@ -320,13 +340,10 @@ func (r *Route) Task() *task.Task {
}
func (r *Route) Start(parent task.Parent) gperr.Error {
if r.lastError != nil {
return r.lastError
}
r.once.Do(func() {
r.lastError = r.start(parent)
r.onceStart.Do(func() {
r.startErr.Set(r.start(parent))
})
return r.lastError
return r.startErr.Get()
}
func (r *Route) start(parent task.Parent) gperr.Error {
@@ -496,7 +513,7 @@ func (r *Route) IsZeroPort() bool {
}
func (r *Route) ShouldExclude() bool {
if r.lastError != nil {
if r.valErr.Get() != nil {
return true
}
if r.Excluded {
@@ -565,7 +582,7 @@ func (re ExcludedReason) MarshalJSON() ([]byte, error) {
// no need to unmarshal json because we don't store this
func (r *Route) findExcludedReason() ExcludedReason {
if r.lastError != nil {
if r.valErr.Get() != nil {
return ExcludedReasonError
}
if r.ExcludedReason != ExcludedReasonNone {

View File

@@ -6,7 +6,11 @@ import (
"net/http"
"github.com/bytedance/sonic"
"github.com/quic-go/quic-go/http3"
"github.com/rs/zerolog/log"
"golang.org/x/net/http2"
_ "unsafe"
)
type (
@@ -263,6 +267,30 @@ func (rule *Rule) Handle(w http.ResponseWriter, r *http.Request) error {
return rule.Do.exec.Handle(w, r)
}
//go:linkname errStreamClosed golang.org/x/net/http2.errStreamClosed
var errStreamClosed error
func logError(err error, r *http.Request) {
if errors.Is(err, errStreamClosed) {
return
}
var h2Err http2.StreamError
if errors.As(err, &h2Err) {
// ignore these errors
switch h2Err.Code {
case http2.ErrCodeStreamClosed:
return
}
}
var h3Err *http3.Error
if errors.As(err, &h3Err) {
// ignore these errors
switch h3Err.ErrorCode {
case
http3.ErrCodeNoError,
http3.ErrCodeRequestCanceled:
return
}
}
log.Err(err).Str("method", r.Method).Str("url", r.Host+r.URL.Path).Msg("error executing rules")
}

View File

@@ -41,12 +41,17 @@ func ValidateWithCustomValidator(v reflect.Value) gperr.Error {
} else {
vt := v.Type()
if vt.PkgPath() != "" { // not a builtin type
// prioritize pointer method
if v.CanAddr() {
vAddr := v.Addr()
if vAddr.Type().Implements(validatorType) {
return vAddr.Interface().(CustomValidator).Validate()
}
}
// fallback to value method
if vt.Implements(validatorType) {
return v.Interface().(CustomValidator).Validate()
}
if v.CanAddr() {
return validateWithValidator(v.Addr())
}
}
}
return nil