mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-20 12:42:34 +02:00
refactor and organize code
This commit is contained in:
parent
1af6dd9cf8
commit
18d258aaa2
169 changed files with 1020 additions and 755 deletions
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/yusing/go-proxy/agent/pkg/agent"
|
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||||
"github.com/yusing/go-proxy/agent/pkg/env"
|
"github.com/yusing/go-proxy/agent/pkg/env"
|
||||||
"github.com/yusing/go-proxy/agent/pkg/server"
|
"github.com/yusing/go-proxy/agent/pkg/server"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
"github.com/yusing/go-proxy/pkg"
|
"github.com/yusing/go-proxy/pkg"
|
||||||
|
@ -16,7 +16,7 @@ func main() {
|
||||||
args := os.Args
|
args := os.Args
|
||||||
if len(args) > 1 && args[1] == "migrate" {
|
if len(args) > 1 && args[1] == "migrate" {
|
||||||
if err := agent.MigrateFromOld(); err != nil {
|
if err := agent.MigrateFromOld(); err != nil {
|
||||||
E.LogFatal("failed to migrate from old docker compose", err)
|
gperr.LogFatal("failed to migrate from old docker compose", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -24,21 +24,21 @@ func main() {
|
||||||
ca := &agent.PEMPair{}
|
ca := &agent.PEMPair{}
|
||||||
err := ca.Load(env.AgentCACert)
|
err := ca.Load(env.AgentCACert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
E.LogFatal("init CA error", err)
|
gperr.LogFatal("init CA error", err)
|
||||||
}
|
}
|
||||||
caCert, err := ca.ToTLSCert()
|
caCert, err := ca.ToTLSCert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
E.LogFatal("init CA error", err)
|
gperr.LogFatal("init CA error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := &agent.PEMPair{}
|
srv := &agent.PEMPair{}
|
||||||
srv.Load(env.AgentSSLCert)
|
srv.Load(env.AgentSSLCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
E.LogFatal("init SSL error", err)
|
gperr.LogFatal("init SSL error", err)
|
||||||
}
|
}
|
||||||
srvCert, err := srv.ToTLSCert()
|
srvCert, err := srv.ToTLSCert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
E.LogFatal("init SSL error", err)
|
gperr.LogFatal("init SSL error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logging.Info().Msgf("GoDoxy Agent version %s", pkg.GetVersion())
|
logging.Info().Msgf("GoDoxy Agent version %s", pkg.GetVersion())
|
||||||
|
|
|
@ -12,9 +12,9 @@ import (
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/yusing/go-proxy/agent/pkg/certs"
|
"github.com/yusing/go-proxy/agent/pkg/certs"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
"github.com/yusing/go-proxy/internal/net/types"
|
"github.com/yusing/go-proxy/internal/net/types"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
"github.com/yusing/go-proxy/pkg"
|
"github.com/yusing/go-proxy/pkg"
|
||||||
|
@ -80,17 +80,17 @@ func checkVersion(a, b string) bool {
|
||||||
return withoutBuildTime(a) == withoutBuildTime(b)
|
return withoutBuildTime(a) == withoutBuildTime(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *AgentConfig) StartWithCerts(parent task.Parent, ca, crt, key []byte) E.Error {
|
func (cfg *AgentConfig) StartWithCerts(parent task.Parent, ca, crt, key []byte) error {
|
||||||
clientCert, err := tls.X509KeyPair(crt, key)
|
clientCert, err := tls.X509KeyPair(crt, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Wrap(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// create tls config
|
// create tls config
|
||||||
caCertPool := x509.NewCertPool()
|
caCertPool := x509.NewCertPool()
|
||||||
ok := caCertPool.AppendCertsFromPEM(ca)
|
ok := caCertPool.AppendCertsFromPEM(ca)
|
||||||
if !ok {
|
if !ok {
|
||||||
return E.New("invalid CA certificate")
|
return gperr.New("invalid CA certificate")
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.tlsConfig = &tls.Config{
|
cfg.tlsConfig = &tls.Config{
|
||||||
|
@ -108,17 +108,17 @@ func (cfg *AgentConfig) StartWithCerts(parent task.Parent, ca, crt, key []byte)
|
||||||
// check agent version
|
// check agent version
|
||||||
version, _, err := cfg.Fetch(ctx, EndpointVersion)
|
version, _, err := cfg.Fetch(ctx, EndpointVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Wrap(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !checkVersion(string(version), pkg.GetVersion()) {
|
if !checkVersion(string(version), pkg.GetVersion()) {
|
||||||
return E.Errorf("agent version mismatch: server: %s, agent: %s", pkg.GetVersion(), string(version))
|
return gperr.Errorf("agent version mismatch: server: %s, agent: %s", pkg.GetVersion(), string(version))
|
||||||
}
|
}
|
||||||
|
|
||||||
// get agent name
|
// get agent name
|
||||||
name, _, err := cfg.Fetch(ctx, EndpointName)
|
name, _, err := cfg.Fetch(ctx, EndpointName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Wrap(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.name = string(name)
|
cfg.name = string(name)
|
||||||
|
@ -128,18 +128,18 @@ func (cfg *AgentConfig) StartWithCerts(parent task.Parent, ca, crt, key []byte)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *AgentConfig) Start(parent task.Parent) E.Error {
|
func (cfg *AgentConfig) Start(parent task.Parent) error {
|
||||||
certData, err := os.ReadFile(certs.AgentCertsFilename(cfg.Addr))
|
certData, err := os.ReadFile(certs.AgentCertsFilename(cfg.Addr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return E.Errorf("agents certs not found, did you run `godoxy new-agent %s ...`?", cfg.Addr)
|
return gperr.Errorf("agents certs not found, did you run `godoxy new-agent %s ...`?", cfg.Addr)
|
||||||
}
|
}
|
||||||
return E.Wrap(err)
|
return gperr.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ca, crt, key, err := certs.ExtractCert(certData)
|
ca, crt, key, err := certs.ExtractCert(certData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Wrap(err)
|
return gperr.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg.StartWithCerts(parent, ca, crt, key)
|
return cfg.StartWithCerts(parent, ca, crt, key)
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/utils"
|
"github.com/yusing/go-proxy/internal/utils"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
@ -110,11 +110,11 @@ func MigrateFromOld() error {
|
||||||
composeConfig.SSLCert = agentCert.String()
|
composeConfig.SSLCert = agentCert.String()
|
||||||
composeTemplate, err := composeConfig.Generate()
|
composeTemplate, err := composeConfig.Generate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Wrap(err, "failed to generate new docker compose")
|
return gperr.Wrap(err, "failed to generate new docker compose")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile("/app/compose.yml", []byte(composeTemplate), 0600); err != nil {
|
if err := os.WriteFile("/app/compose.yml", []byte(composeTemplate), 0600); err != nil {
|
||||||
return E.Wrap(err, "failed to write new docker compose")
|
return gperr.Wrap(err, "failed to write new docker compose")
|
||||||
}
|
}
|
||||||
|
|
||||||
logging.Info().Msg("Migrated from old docker compose:")
|
logging.Info().Msg("Migrated from old docker compose:")
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
apiUtils "github.com/yusing/go-proxy/internal/api/v1/utils"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
"github.com/yusing/go-proxy/internal/net/types"
|
"github.com/yusing/go-proxy/internal/net/types"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
||||||
|
@ -72,5 +72,5 @@ func CheckHealth(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiUtils.RespondJSON(w, r, result)
|
gphttp.RespondJSON(w, r, result)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/internal/docker"
|
"github.com/yusing/go-proxy/internal/docker"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/reverseproxy"
|
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
|
||||||
"github.com/yusing/go-proxy/internal/net/types"
|
"github.com/yusing/go-proxy/internal/net/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/agent/pkg/agent"
|
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||||
agentproxy "github.com/yusing/go-proxy/agent/pkg/agentproxy"
|
"github.com/yusing/go-proxy/agent/pkg/agentproxy"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/reverseproxy"
|
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
|
||||||
"github.com/yusing/go-proxy/internal/net/types"
|
"github.com/yusing/go-proxy/internal/net/types"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
"github.com/yusing/go-proxy/agent/pkg/env"
|
"github.com/yusing/go-proxy/agent/pkg/env"
|
||||||
"github.com/yusing/go-proxy/agent/pkg/handler"
|
"github.com/yusing/go-proxy/agent/pkg/handler"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/server"
|
"github.com/yusing/go-proxy/internal/net/gphttp/server"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
10
cmd/main.go
10
cmd/main.go
|
@ -11,10 +11,10 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/api/v1/query"
|
"github.com/yusing/go-proxy/internal/api/v1/query"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/internal/config"
|
"github.com/yusing/go-proxy/internal/config"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/homepage"
|
"github.com/yusing/go-proxy/internal/homepage"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
"github.com/yusing/go-proxy/internal/net/gphttp/middleware"
|
||||||
"github.com/yusing/go-proxy/internal/route/routes/routequery"
|
"github.com/yusing/go-proxy/internal/route/routes/routequery"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
"github.com/yusing/go-proxy/pkg"
|
"github.com/yusing/go-proxy/pkg"
|
||||||
|
@ -32,7 +32,7 @@ func main() {
|
||||||
return
|
return
|
||||||
case common.CommandReload:
|
case common.CommandReload:
|
||||||
if err := query.ReloadServer(); err != nil {
|
if err := query.ReloadServer(); err != nil {
|
||||||
E.LogFatal("server reload error", err)
|
gperr.LogFatal("server reload error", err)
|
||||||
}
|
}
|
||||||
rawLogger.Println("ok")
|
rawLogger.Println("ok")
|
||||||
return
|
return
|
||||||
|
@ -88,9 +88,9 @@ func main() {
|
||||||
middleware.LoadComposeFiles()
|
middleware.LoadComposeFiles()
|
||||||
|
|
||||||
var cfg *config.Config
|
var cfg *config.Config
|
||||||
var err E.Error
|
var err gperr.Error
|
||||||
if cfg, err = config.Load(); err != nil {
|
if cfg, err = config.Load(); err != nil {
|
||||||
E.LogWarn("errors in config", err)
|
gperr.LogWarn("errors in config", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch args.Command {
|
switch args.Command {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/logging/memlogger"
|
"github.com/yusing/go-proxy/internal/logging/memlogger"
|
||||||
"github.com/yusing/go-proxy/internal/metrics/uptime"
|
"github.com/yusing/go-proxy/internal/metrics/uptime"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/httpheaders"
|
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,18 +6,19 @@ import (
|
||||||
|
|
||||||
"github.com/coder/websocket"
|
"github.com/coder/websocket"
|
||||||
"github.com/coder/websocket/wsjson"
|
"github.com/coder/websocket/wsjson"
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
config "github.com/yusing/go-proxy/internal/config/types"
|
config "github.com/yusing/go-proxy/internal/config/types"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/httpheaders"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/gpwebsocket"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ListAgents(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
func ListAgents(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
||||||
if httpheaders.IsWebsocket(r.Header) {
|
if httpheaders.IsWebsocket(r.Header) {
|
||||||
U.PeriodicWS(w, r, 10*time.Second, func(conn *websocket.Conn) error {
|
gpwebsocket.Periodic(w, r, 10*time.Second, func(conn *websocket.Conn) error {
|
||||||
wsjson.Write(r.Context(), conn, cfg.ListAgents())
|
wsjson.Write(r.Context(), conn, cfg.ListAgents())
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
U.RespondJSON(w, r, cfg.ListAgents())
|
gphttp.RespondJSON(w, r, cfg.ListAgents())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@ package auth
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultAuth Provider
|
var defaultAuth Provider
|
||||||
|
@ -44,7 +44,7 @@ func RequireAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||||
if IsEnabled() {
|
if IsEnabled() {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := defaultAuth.CheckToken(r); err != nil {
|
if err := defaultAuth.CheckToken(r); err != nil {
|
||||||
U.RespondError(w, err, http.StatusUnauthorized)
|
gphttp.ClientError(w, err, http.StatusUnauthorized)
|
||||||
} else {
|
} else {
|
||||||
next(w, r)
|
next(w, r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
CE "github.com/yusing/go-proxy/internal/utils"
|
CE "github.com/yusing/go-proxy/internal/utils"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
@ -154,7 +153,7 @@ func generateState() (string, error) {
|
||||||
func (auth *OIDCProvider) RedirectLoginPage(w http.ResponseWriter, r *http.Request) {
|
func (auth *OIDCProvider) RedirectLoginPage(w http.ResponseWriter, r *http.Request) {
|
||||||
state, err := generateState()
|
state, err := generateState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err, http.StatusInternalServerError)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
@ -171,7 +170,7 @@ func (auth *OIDCProvider) RedirectLoginPage(w http.ResponseWriter, r *http.Reque
|
||||||
if auth.isMiddleware {
|
if auth.isMiddleware {
|
||||||
u, err := r.URL.Parse(redirURL)
|
u, err := r.URL.Parse(redirURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err, http.StatusInternalServerError)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
|
@ -201,31 +200,31 @@ func (auth *OIDCProvider) LoginCallbackHandler(w http.ResponseWriter, r *http.Re
|
||||||
|
|
||||||
state, err := r.Cookie(CookieOauthState)
|
state, err := r.Cookie(CookieOauthState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, E.New("missing state cookie"), http.StatusBadRequest)
|
gphttp.BadRequest(w, "missing state cookie")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
query := r.URL.Query()
|
query := r.URL.Query()
|
||||||
if query.Get("state") != state.Value {
|
if query.Get("state") != state.Value {
|
||||||
U.HandleErr(w, r, E.New("invalid oauth state"), http.StatusBadRequest)
|
gphttp.BadRequest(w, "invalid oauth state")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oauth2Token, err := auth.exchange(r)
|
oauth2Token, err := auth.exchange(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, fmt.Errorf("failed to exchange token: %w", err), http.StatusInternalServerError)
|
gphttp.ServerError(w, r, fmt.Errorf("failed to exchange token: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
U.HandleErr(w, r, E.New("missing id_token"), http.StatusInternalServerError)
|
gphttp.BadRequest(w, "missing id_token")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
idToken, err := auth.oidcVerifier.Verify(r.Context(), rawIDToken)
|
idToken, err := auth.oidcVerifier.Verify(r.Context(), rawIDToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, fmt.Errorf("failed to verify ID token: %w", err), http.StatusInternalServerError)
|
gphttp.ServerError(w, r, fmt.Errorf("failed to verify ID token: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,7 +242,7 @@ func (auth *OIDCProvider) LogoutCallbackHandler(w http.ResponseWriter, r *http.R
|
||||||
|
|
||||||
token, err := r.Cookie(auth.TokenCookieName())
|
token, err := r.Cookie(auth.TokenCookieName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, E.New("missing token cookie"), http.StatusBadRequest)
|
gphttp.BadRequest(w, "missing token cookie")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clearTokenCookie(w, r, auth.TokenCookieName())
|
clearTokenCookie(w, r, auth.TokenCookieName())
|
||||||
|
@ -258,12 +257,12 @@ func (auth *OIDCProvider) LogoutCallbackHandler(w http.ResponseWriter, r *http.R
|
||||||
func (auth *OIDCProvider) handleTestCallback(w http.ResponseWriter, r *http.Request) {
|
func (auth *OIDCProvider) handleTestCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
state, err := r.Cookie(CookieOauthState)
|
state, err := r.Cookie(CookieOauthState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, E.New("missing state cookie"), http.StatusBadRequest)
|
gphttp.BadRequest(w, "missing state cookie")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.URL.Query().Get("state") != state.Value {
|
if r.URL.Query().Get("state") != state.Value {
|
||||||
U.HandleErr(w, r, E.New("invalid oauth state"), http.StatusBadRequest)
|
gphttp.BadRequest(w, "invalid oauth state")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,16 +7,16 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidUsername = E.New("invalid username")
|
ErrInvalidUsername = gperr.New("invalid username")
|
||||||
ErrInvalidPassword = E.New("invalid password")
|
ErrInvalidPassword = gperr.New("invalid password")
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -94,7 +94,7 @@ func (auth *UserPassAuth) CheckToken(r *http.Request) error {
|
||||||
case claims.Username != auth.username:
|
case claims.Username != auth.username:
|
||||||
return ErrUserNotAllowed.Subject(claims.Username)
|
return ErrUserNotAllowed.Subject(claims.Username)
|
||||||
case claims.ExpiresAt.Before(time.Now()):
|
case claims.ExpiresAt.Before(time.Now()):
|
||||||
return E.Errorf("token expired on %s", strutils.FormatTime(claims.ExpiresAt.Time))
|
return gperr.Errorf("token expired on %s", strutils.FormatTime(claims.ExpiresAt.Time))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -111,17 +111,16 @@ func (auth *UserPassAuth) LoginCallbackHandler(w http.ResponseWriter, r *http.Re
|
||||||
}
|
}
|
||||||
err := json.NewDecoder(r.Body).Decode(&creds)
|
err := json.NewDecoder(r.Body).Decode(&creds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err, http.StatusBadRequest)
|
gphttp.Unauthorized(w, "invalid credentials")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := auth.validatePassword(creds.User, creds.Pass); err != nil {
|
if err := auth.validatePassword(creds.User, creds.Pass); err != nil {
|
||||||
U.LogError(r).Err(err).Msg("auth: invalid credentials")
|
gphttp.Unauthorized(w, "invalid credentials")
|
||||||
U.RespondError(w, E.New("invalid credentials"), http.StatusUnauthorized)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
token, err := auth.NewToken()
|
token, err := auth.NewToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err, http.StatusInternalServerError)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setTokenCookie(w, r, auth.TokenCookieName(), token, auth.tokenTTL)
|
setTokenCookie(w, r, auth.TokenCookieName(), token, auth.tokenTTL)
|
||||||
|
|
|
@ -5,14 +5,14 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrMissingToken = E.New("missing token")
|
ErrMissingToken = gperr.New("missing token")
|
||||||
ErrInvalidToken = E.New("invalid token")
|
ErrInvalidToken = gperr.New("invalid token")
|
||||||
ErrUserNotAllowed = E.New("user not allowed")
|
ErrUserNotAllowed = gperr.New("user not allowed")
|
||||||
)
|
)
|
||||||
|
|
||||||
// cookieFQDN returns the fully qualified domain name of the request host
|
// cookieFQDN returns the fully qualified domain name of the request host
|
||||||
|
|
|
@ -13,10 +13,10 @@ import (
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
"github.com/vincent-petithory/dataurl"
|
"github.com/vincent-petithory/dataurl"
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/homepage"
|
"github.com/yusing/go-proxy/internal/homepage"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
"github.com/yusing/go-proxy/internal/route/routes"
|
"github.com/yusing/go-proxy/internal/route/routes"
|
||||||
route "github.com/yusing/go-proxy/internal/route/types"
|
route "github.com/yusing/go-proxy/internal/route/types"
|
||||||
)
|
)
|
||||||
|
@ -53,11 +53,11 @@ func (res *fetchResult) ContentType() string {
|
||||||
func GetFavIcon(w http.ResponseWriter, req *http.Request) {
|
func GetFavIcon(w http.ResponseWriter, req *http.Request) {
|
||||||
url, alias := req.FormValue("url"), req.FormValue("alias")
|
url, alias := req.FormValue("url"), req.FormValue("alias")
|
||||||
if url == "" && alias == "" {
|
if url == "" && alias == "" {
|
||||||
U.RespondError(w, U.ErrMissingKey("url or alias"), http.StatusBadRequest)
|
gphttp.ClientError(w, gphttp.ErrMissingKey("url or alias"), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if url != "" && alias != "" {
|
if url != "" && alias != "" {
|
||||||
U.RespondError(w, U.ErrInvalidKey("url and alias are mutually exclusive"), http.StatusBadRequest)
|
gphttp.ClientError(w, gperr.New("url and alias are mutually exclusive"), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ func GetFavIcon(w http.ResponseWriter, req *http.Request) {
|
||||||
if url != "" {
|
if url != "" {
|
||||||
var iconURL homepage.IconURL
|
var iconURL homepage.IconURL
|
||||||
if err := iconURL.Parse(url); err != nil {
|
if err := iconURL.Parse(url); err != nil {
|
||||||
U.RespondError(w, err, http.StatusBadRequest)
|
gphttp.ClientError(w, err, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fetchResult := getFavIconFromURL(&iconURL)
|
fetchResult := getFavIconFromURL(&iconURL)
|
||||||
|
@ -74,14 +74,14 @@ func GetFavIcon(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", fetchResult.ContentType())
|
w.Header().Set("Content-Type", fetchResult.ContentType())
|
||||||
U.WriteBody(w, fetchResult.icon)
|
gphttp.WriteBody(w, fetchResult.icon)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// try with route.Homepage.Icon
|
// try with route.Homepage.Icon
|
||||||
r, ok := routes.GetHTTPRoute(alias)
|
r, ok := routes.GetHTTPRoute(alias)
|
||||||
if !ok {
|
if !ok {
|
||||||
U.RespondError(w, errors.New("no such route"), http.StatusNotFound)
|
gphttp.ClientError(w, errors.New("no such route"), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ func GetFavIcon(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", result.ContentType())
|
w.Header().Set("Content-Type", result.ContentType())
|
||||||
U.WriteBody(w, result.icon)
|
gphttp.WriteBody(w, result.icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFavIconFromURL(iconURL *homepage.IconURL) *fetchResult {
|
func getFavIconFromURL(iconURL *homepage.IconURL) *fetchResult {
|
||||||
|
@ -125,7 +125,7 @@ func fetchIconAbsolute(url string) *fetchResult {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := U.Get(url)
|
resp, err := gphttp.Get(url)
|
||||||
if err != nil || resp.StatusCode != http.StatusOK {
|
if err != nil || resp.StatusCode != http.StatusOK {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = errors.New(resp.Status)
|
err = errors.New(resp.Status)
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
config "github.com/yusing/go-proxy/internal/config/types"
|
config "github.com/yusing/go-proxy/internal/config/types"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/middleware"
|
||||||
"github.com/yusing/go-proxy/internal/route/provider"
|
"github.com/yusing/go-proxy/internal/route/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -51,12 +51,12 @@ func (t FileType) GetPath(filename string) string {
|
||||||
func getArgs(r *http.Request) (fileType FileType, filename string, err error) {
|
func getArgs(r *http.Request) (fileType FileType, filename string, err error) {
|
||||||
fileType = FileType(r.PathValue("type"))
|
fileType = FileType(r.PathValue("type"))
|
||||||
if !fileType.IsValid() {
|
if !fileType.IsValid() {
|
||||||
err = U.ErrInvalidKey("type")
|
err = gphttp.ErrInvalidKey("type")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
filename = r.PathValue("filename")
|
filename = r.PathValue("filename")
|
||||||
if filename == "" {
|
if filename == "" {
|
||||||
err = U.ErrMissingKey("filename")
|
err = gphttp.ErrMissingKey("filename")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -64,23 +64,23 @@ func getArgs(r *http.Request) (fileType FileType, filename string, err error) {
|
||||||
func GetFileContent(w http.ResponseWriter, r *http.Request) {
|
func GetFileContent(w http.ResponseWriter, r *http.Request) {
|
||||||
fileType, filename, err := getArgs(r)
|
fileType, filename, err := getArgs(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.RespondError(w, err, http.StatusBadRequest)
|
gphttp.BadRequest(w, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content, err := os.ReadFile(fileType.GetPath(filename))
|
content, err := os.ReadFile(fileType.GetPath(filename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
U.WriteBody(w, content)
|
gphttp.WriteBody(w, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateFile(fileType FileType, content []byte) error {
|
func validateFile(fileType FileType, content []byte) gperr.Error {
|
||||||
switch fileType {
|
switch fileType {
|
||||||
case FileTypeConfig:
|
case FileTypeConfig:
|
||||||
return config.Validate(content)
|
return config.Validate(content)
|
||||||
case FileTypeMiddleware:
|
case FileTypeMiddleware:
|
||||||
errs := E.NewBuilder("middleware errors")
|
errs := gperr.NewBuilder("middleware errors")
|
||||||
middleware.BuildMiddlewaresFromYAML("", content, errs)
|
middleware.BuildMiddlewaresFromYAML("", content, errs)
|
||||||
return errs.Error()
|
return errs.Error()
|
||||||
}
|
}
|
||||||
|
@ -90,18 +90,17 @@ func validateFile(fileType FileType, content []byte) error {
|
||||||
func ValidateFile(w http.ResponseWriter, r *http.Request) {
|
func ValidateFile(w http.ResponseWriter, r *http.Request) {
|
||||||
fileType := FileType(r.PathValue("type"))
|
fileType := FileType(r.PathValue("type"))
|
||||||
if !fileType.IsValid() {
|
if !fileType.IsValid() {
|
||||||
U.RespondError(w, U.ErrInvalidKey("type"), http.StatusBadRequest)
|
gphttp.BadRequest(w, "invalid file type")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content, err := io.ReadAll(r.Body)
|
content, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.Body.Close()
|
r.Body.Close()
|
||||||
err = validateFile(fileType, content)
|
if valErr := validateFile(fileType, content); valErr != nil {
|
||||||
if err != nil {
|
gphttp.JSONError(w, valErr, http.StatusBadRequest)
|
||||||
U.RespondError(w, err, http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
@ -110,23 +109,23 @@ func ValidateFile(w http.ResponseWriter, r *http.Request) {
|
||||||
func SetFileContent(w http.ResponseWriter, r *http.Request) {
|
func SetFileContent(w http.ResponseWriter, r *http.Request) {
|
||||||
fileType, filename, err := getArgs(r)
|
fileType, filename, err := getArgs(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.RespondError(w, err, http.StatusBadRequest)
|
gphttp.BadRequest(w, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content, err := io.ReadAll(r.Body)
|
content, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if valErr := validateFile(fileType, content); valErr != nil {
|
if valErr := validateFile(fileType, content); valErr != nil {
|
||||||
U.RespondError(w, valErr, http.StatusBadRequest)
|
gphttp.JSONError(w, valErr, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(fileType.GetPath(filename), content, 0o644)
|
err = os.WriteFile(fileType.GetPath(filename), content, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
|
@ -6,17 +6,18 @@ import (
|
||||||
|
|
||||||
"github.com/coder/websocket"
|
"github.com/coder/websocket"
|
||||||
"github.com/coder/websocket/wsjson"
|
"github.com/coder/websocket/wsjson"
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/httpheaders"
|
"github.com/yusing/go-proxy/internal/net/gphttp/gpwebsocket"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
|
||||||
"github.com/yusing/go-proxy/internal/route/routes/routequery"
|
"github.com/yusing/go-proxy/internal/route/routes/routequery"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Health(w http.ResponseWriter, r *http.Request) {
|
func Health(w http.ResponseWriter, r *http.Request) {
|
||||||
if httpheaders.IsWebsocket(r.Header) {
|
if httpheaders.IsWebsocket(r.Header) {
|
||||||
U.PeriodicWS(w, r, 1*time.Second, func(conn *websocket.Conn) error {
|
gpwebsocket.Periodic(w, r, 1*time.Second, func(conn *websocket.Conn) error {
|
||||||
return wsjson.Write(r.Context(), conn, routequery.HealthMap())
|
return wsjson.Write(r.Context(), conn, routequery.HealthMap())
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
U.RespondJSON(w, r, routequery.HealthMap())
|
gphttp.RespondJSON(w, r, routequery.HealthMap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
"github.com/yusing/go-proxy/internal/homepage"
|
"github.com/yusing/go-proxy/internal/homepage"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -37,13 +37,13 @@ type (
|
||||||
func SetHomePageOverrides(w http.ResponseWriter, r *http.Request) {
|
func SetHomePageOverrides(w http.ResponseWriter, r *http.Request) {
|
||||||
what := r.FormValue("what")
|
what := r.FormValue("what")
|
||||||
if what == "" {
|
if what == "" {
|
||||||
http.Error(w, "missing what or which", http.StatusBadRequest)
|
gphttp.BadRequest(w, "missing what or which")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(r.Body)
|
data, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondError(w, err, http.StatusBadRequest)
|
gphttp.ClientError(w, err, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.Body.Close()
|
r.Body.Close()
|
||||||
|
@ -53,21 +53,21 @@ func SetHomePageOverrides(w http.ResponseWriter, r *http.Request) {
|
||||||
case HomepageOverrideItem:
|
case HomepageOverrideItem:
|
||||||
var params HomepageOverrideItemParams
|
var params HomepageOverrideItemParams
|
||||||
if err := json.Unmarshal(data, ¶ms); err != nil {
|
if err := json.Unmarshal(data, ¶ms); err != nil {
|
||||||
utils.RespondError(w, err, http.StatusBadRequest)
|
gphttp.ClientError(w, err, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
overrides.OverrideItem(params.Which, ¶ms.Value)
|
overrides.OverrideItem(params.Which, ¶ms.Value)
|
||||||
case HomepageOverrideItemsBatch:
|
case HomepageOverrideItemsBatch:
|
||||||
var params HomepageOverrideItemsBatchParams
|
var params HomepageOverrideItemsBatchParams
|
||||||
if err := json.Unmarshal(data, ¶ms); err != nil {
|
if err := json.Unmarshal(data, ¶ms); err != nil {
|
||||||
utils.RespondError(w, err, http.StatusBadRequest)
|
gphttp.ClientError(w, err, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
overrides.OverrideItems(params.Value)
|
overrides.OverrideItems(params.Value)
|
||||||
case HomepageOverrideItemVisible: // POST /v1/item_visible [a,b,c], false => hide a, b, c
|
case HomepageOverrideItemVisible: // POST /v1/item_visible [a,b,c], false => hide a, b, c
|
||||||
var params HomepageOverrideItemVisibleParams
|
var params HomepageOverrideItemVisibleParams
|
||||||
if err := json.Unmarshal(data, ¶ms); err != nil {
|
if err := json.Unmarshal(data, ¶ms); err != nil {
|
||||||
utils.RespondError(w, err, http.StatusBadRequest)
|
gphttp.ClientError(w, err, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if params.Value {
|
if params.Value {
|
||||||
|
@ -78,7 +78,7 @@ func SetHomePageOverrides(w http.ResponseWriter, r *http.Request) {
|
||||||
case HomepageOverrideCategoryOrder:
|
case HomepageOverrideCategoryOrder:
|
||||||
var params HomepageOverrideCategoryOrderParams
|
var params HomepageOverrideCategoryOrderParams
|
||||||
if err := json.Unmarshal(data, ¶ms); err != nil {
|
if err := json.Unmarshal(data, ¶ms); err != nil {
|
||||||
utils.RespondError(w, err, http.StatusBadRequest)
|
gphttp.ClientError(w, err, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
overrides.SetCategoryOrder(params.Which, params.Value)
|
overrides.SetCategoryOrder(params.Which, params.Value)
|
||||||
|
|
|
@ -3,9 +3,9 @@ package v1
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
. "github.com/yusing/go-proxy/internal/api/v1/utils"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Index(w http.ResponseWriter, r *http.Request) {
|
func Index(w http.ResponseWriter, r *http.Request) {
|
||||||
WriteBody(w, []byte("API ready"))
|
gphttp.WriteBody(w, []byte("API ready"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal"
|
"github.com/yusing/go-proxy/internal"
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
config "github.com/yusing/go-proxy/internal/config/types"
|
config "github.com/yusing/go-proxy/internal/config/types"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/middleware"
|
||||||
"github.com/yusing/go-proxy/internal/route/routes/routequery"
|
"github.com/yusing/go-proxy/internal/route/routes/routequery"
|
||||||
route "github.com/yusing/go-proxy/internal/route/types"
|
route "github.com/yusing/go-proxy/internal/route/types"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
|
@ -41,26 +42,25 @@ func List(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
||||||
case ListRoute:
|
case ListRoute:
|
||||||
if route := listRoute(which); route == nil {
|
if route := listRoute(which); route == nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
|
||||||
} else {
|
} else {
|
||||||
U.RespondJSON(w, r, route)
|
gphttp.RespondJSON(w, r, route)
|
||||||
}
|
}
|
||||||
case ListRoutes:
|
case ListRoutes:
|
||||||
U.RespondJSON(w, r, routequery.RoutesByAlias(route.RouteType(r.FormValue("type"))))
|
gphttp.RespondJSON(w, r, routequery.RoutesByAlias(route.RouteType(r.FormValue("type"))))
|
||||||
case ListFiles:
|
case ListFiles:
|
||||||
listFiles(w, r)
|
listFiles(w, r)
|
||||||
case ListMiddlewares:
|
case ListMiddlewares:
|
||||||
U.RespondJSON(w, r, middleware.All())
|
gphttp.RespondJSON(w, r, middleware.All())
|
||||||
case ListMiddlewareTraces:
|
case ListMiddlewareTraces:
|
||||||
U.RespondJSON(w, r, middleware.GetAllTrace())
|
gphttp.RespondJSON(w, r, middleware.GetAllTrace())
|
||||||
case ListMatchDomains:
|
case ListMatchDomains:
|
||||||
U.RespondJSON(w, r, cfg.Value().MatchDomains)
|
gphttp.RespondJSON(w, r, cfg.Value().MatchDomains)
|
||||||
case ListHomepageConfig:
|
case ListHomepageConfig:
|
||||||
U.RespondJSON(w, r, routequery.HomepageConfig(cfg.Value().Homepage.UseDefaultCategories, r.FormValue("category"), r.FormValue("provider")))
|
gphttp.RespondJSON(w, r, routequery.HomepageConfig(cfg.Value().Homepage.UseDefaultCategories, r.FormValue("category"), r.FormValue("provider")))
|
||||||
case ListRouteProviders:
|
case ListRouteProviders:
|
||||||
U.RespondJSON(w, r, cfg.RouteProviderList())
|
gphttp.RespondJSON(w, r, cfg.RouteProviderList())
|
||||||
case ListHomepageCategories:
|
case ListHomepageCategories:
|
||||||
U.RespondJSON(w, r, routequery.HomepageCategories())
|
gphttp.RespondJSON(w, r, routequery.HomepageCategories())
|
||||||
case ListIcons:
|
case ListIcons:
|
||||||
limit, err := strconv.Atoi(r.FormValue("limit"))
|
limit, err := strconv.Atoi(r.FormValue("limit"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -68,17 +68,17 @@ func List(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
icons, err := internal.SearchIcons(r.FormValue("keyword"), limit)
|
icons, err := internal.SearchIcons(r.FormValue("keyword"), limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.RespondError(w, err)
|
gphttp.ClientError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if icons == nil {
|
if icons == nil {
|
||||||
icons = []string{}
|
icons = []string{}
|
||||||
}
|
}
|
||||||
U.RespondJSON(w, r, icons)
|
gphttp.RespondJSON(w, r, icons)
|
||||||
case ListTasks:
|
case ListTasks:
|
||||||
U.RespondJSON(w, r, task.DebugTaskList())
|
gphttp.RespondJSON(w, r, task.DebugTaskList())
|
||||||
default:
|
default:
|
||||||
U.HandleErr(w, r, U.ErrInvalidKey("what"), http.StatusBadRequest)
|
gphttp.BadRequest(w, fmt.Sprintf("invalid what: %s", what))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ func listRoute(which string) any {
|
||||||
func listFiles(w http.ResponseWriter, r *http.Request) {
|
func listFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
files, err := utils.ListFiles(common.ConfigBasePath, 0, true)
|
files, err := utils.ListFiles(common.ConfigBasePath, 0, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp := map[FileType][]string{
|
resp := map[FileType][]string{
|
||||||
|
@ -116,12 +116,12 @@ func listFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
mids, err := utils.ListFiles(common.MiddlewareComposeBasePath, 0, true)
|
mids, err := utils.ListFiles(common.MiddlewareComposeBasePath, 0, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, mid := range mids {
|
for _, mid := range mids {
|
||||||
mid = strings.TrimPrefix(mid, common.MiddlewareComposeBasePath+"/")
|
mid = strings.TrimPrefix(mid, common.MiddlewareComposeBasePath+"/")
|
||||||
resp[FileTypeMiddleware] = append(resp[FileTypeMiddleware], mid)
|
resp[FileTypeMiddleware] = append(resp[FileTypeMiddleware], mid)
|
||||||
}
|
}
|
||||||
U.RespondJSON(w, r, resp)
|
gphttp.RespondJSON(w, r, resp)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,9 @@ import (
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/agent/pkg/agent"
|
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||||
"github.com/yusing/go-proxy/agent/pkg/certs"
|
"github.com/yusing/go-proxy/agent/pkg/certs"
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
config "github.com/yusing/go-proxy/internal/config/types"
|
config "github.com/yusing/go-proxy/internal/config/types"
|
||||||
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,27 +22,27 @@ func NewAgent(w http.ResponseWriter, r *http.Request) {
|
||||||
q := r.URL.Query()
|
q := r.URL.Query()
|
||||||
name := q.Get("name")
|
name := q.Get("name")
|
||||||
if name == "" {
|
if name == "" {
|
||||||
U.RespondError(w, U.ErrMissingKey("name"))
|
gphttp.ClientError(w, gphttp.ErrMissingKey("name"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
host := q.Get("host")
|
host := q.Get("host")
|
||||||
if host == "" {
|
if host == "" {
|
||||||
U.RespondError(w, U.ErrMissingKey("host"))
|
gphttp.ClientError(w, gphttp.ErrMissingKey("host"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
portStr := q.Get("port")
|
portStr := q.Get("port")
|
||||||
if portStr == "" {
|
if portStr == "" {
|
||||||
U.RespondError(w, U.ErrMissingKey("port"))
|
gphttp.ClientError(w, gphttp.ErrMissingKey("port"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
port, err := strconv.Atoi(portStr)
|
port, err := strconv.Atoi(portStr)
|
||||||
if err != nil || port < 1 || port > 65535 {
|
if err != nil || port < 1 || port > 65535 {
|
||||||
U.RespondError(w, U.ErrInvalidKey("port"))
|
gphttp.ClientError(w, gphttp.ErrInvalidKey("port"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hostport := fmt.Sprintf("%s:%d", host, port)
|
hostport := fmt.Sprintf("%s:%d", host, port)
|
||||||
if _, ok := config.GetInstance().GetAgent(hostport); ok {
|
if _, ok := config.GetInstance().GetAgent(hostport); ok {
|
||||||
U.RespondError(w, U.ErrAlreadyExists("agent", hostport), http.StatusConflict)
|
gphttp.ClientError(w, gphttp.ErrAlreadyExists("agent", hostport), http.StatusConflict)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t := q.Get("type")
|
t := q.Get("type")
|
||||||
|
@ -49,13 +50,13 @@ func NewAgent(w http.ResponseWriter, r *http.Request) {
|
||||||
case "docker":
|
case "docker":
|
||||||
break
|
break
|
||||||
case "system":
|
case "system":
|
||||||
U.RespondError(w, U.Errorf("system agent is not supported yet"), http.StatusNotImplemented)
|
gphttp.ClientError(w, gperr.Errorf("system agent is not supported yet"), http.StatusNotImplemented)
|
||||||
return
|
return
|
||||||
case "":
|
case "":
|
||||||
U.RespondError(w, U.ErrMissingKey("type"))
|
gphttp.ClientError(w, gphttp.ErrMissingKey("type"))
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
U.RespondError(w, U.ErrInvalidKey("type"))
|
gphttp.ClientError(w, gphttp.ErrInvalidKey("type"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +70,7 @@ func NewAgent(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
ca, srv, client, err := agent.NewAgent()
|
ca, srv, client, err := agent.NewAgent()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,11 +84,11 @@ func NewAgent(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
template, err := cfg.Generate()
|
template, err := cfg.Generate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
U.RespondJSON(w, r, map[string]any{
|
gphttp.RespondJSON(w, r, map[string]any{
|
||||||
"compose": template,
|
"compose": template,
|
||||||
"ca": ca,
|
"ca": ca,
|
||||||
"client": client,
|
"client": client,
|
||||||
|
@ -98,7 +99,7 @@ func AddAgent(w http.ResponseWriter, r *http.Request) {
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
clientPEMData, err := io.ReadAll(r.Body)
|
clientPEMData, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,24 +110,24 @@ func AddAgent(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(clientPEMData, &data); err != nil {
|
if err := json.Unmarshal(clientPEMData, &data); err != nil {
|
||||||
U.RespondError(w, err, http.StatusBadRequest)
|
gphttp.ClientError(w, err, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nRoutesAdded, err := config.GetInstance().AddAgent(data.Host, data.CA, data.Client)
|
nRoutesAdded, err := config.GetInstance().AddAgent(data.Host, data.CA, data.Client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.RespondError(w, err)
|
gphttp.ClientError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
zip, err := certs.ZipCert(data.CA.Cert, data.Client.Cert, data.Client.Key)
|
zip, err := certs.ZipCert(data.CA.Cert, data.Client.Cert, data.Client.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(certs.AgentCertsFilename(data.Host), zip, 0600); err != nil {
|
if err := os.WriteFile(certs.AgentCertsFilename(data.Host), zip, 0600); err != nil {
|
||||||
U.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,20 +7,20 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
v1 "github.com/yusing/go-proxy/internal/api/v1"
|
v1 "github.com/yusing/go-proxy/internal/api/v1"
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ReloadServer() E.Error {
|
func ReloadServer() gperr.Error {
|
||||||
resp, err := U.Post(common.APIHTTPURL+"/v1/reload", "", nil)
|
resp, err := gphttp.Post(common.APIHTTPURL+"/v1/reload", "", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.From(err)
|
return gperr.Wrap(err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
failure := E.Errorf("server reload status %v", resp.StatusCode)
|
failure := gperr.Errorf("server reload status %v", resp.StatusCode)
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return failure.With(err)
|
return failure.With(err)
|
||||||
|
@ -31,34 +31,34 @@ func ReloadServer() E.Error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func List[T any](what string) (_ T, outErr E.Error) {
|
func List[T any](what string) (_ T, outErr gperr.Error) {
|
||||||
resp, err := U.Get(fmt.Sprintf("%s/v1/list/%s", common.APIHTTPURL, what))
|
resp, err := gphttp.Get(fmt.Sprintf("%s/v1/list/%s", common.APIHTTPURL, what))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outErr = E.From(err)
|
outErr = gperr.Wrap(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
outErr = E.Errorf("list %s: failed, status %v", what, resp.StatusCode)
|
outErr = gperr.Errorf("list %s: failed, status %v", what, resp.StatusCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var res T
|
var res T
|
||||||
err = json.NewDecoder(resp.Body).Decode(&res)
|
err = json.NewDecoder(resp.Body).Decode(&res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outErr = E.From(err)
|
outErr = gperr.Wrap(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListRoutes() (map[string]map[string]any, E.Error) {
|
func ListRoutes() (map[string]map[string]any, gperr.Error) {
|
||||||
return List[map[string]map[string]any](v1.ListRoutes)
|
return List[map[string]map[string]any](v1.ListRoutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListMiddlewareTraces() (middleware.Traces, E.Error) {
|
func ListMiddlewareTraces() (middleware.Traces, gperr.Error) {
|
||||||
return List[middleware.Traces](v1.ListMiddlewareTraces)
|
return List[middleware.Traces](v1.ListMiddlewareTraces)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DebugListTasks() (map[string]any, E.Error) {
|
func DebugListTasks() (map[string]any, gperr.Error) {
|
||||||
return List[map[string]any](v1.ListTasks)
|
return List[map[string]any](v1.ListTasks)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,14 @@ package v1
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
config "github.com/yusing/go-proxy/internal/config/types"
|
config "github.com/yusing/go-proxy/internal/config/types"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Reload(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
func Reload(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
||||||
if err := cfg.Reload(); err != nil {
|
if err := cfg.Reload(); err != nil {
|
||||||
U.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
U.WriteBody(w, []byte("OK"))
|
gphttp.WriteBody(w, []byte("OK"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,19 +6,20 @@ import (
|
||||||
|
|
||||||
"github.com/coder/websocket"
|
"github.com/coder/websocket"
|
||||||
"github.com/coder/websocket/wsjson"
|
"github.com/coder/websocket/wsjson"
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
config "github.com/yusing/go-proxy/internal/config/types"
|
config "github.com/yusing/go-proxy/internal/config/types"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/httpheaders"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/gpwebsocket"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Stats(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
func Stats(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
||||||
if httpheaders.IsWebsocket(r.Header) {
|
if httpheaders.IsWebsocket(r.Header) {
|
||||||
U.PeriodicWS(w, r, 1*time.Second, func(conn *websocket.Conn) error {
|
gpwebsocket.Periodic(w, r, 1*time.Second, func(conn *websocket.Conn) error {
|
||||||
return wsjson.Write(r.Context(), conn, getStats(cfg))
|
return wsjson.Write(r.Context(), conn, getStats(cfg))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
U.RespondJSON(w, r, getStats(cfg))
|
gphttp.RespondJSON(w, r, getStats(cfg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
agentPkg "github.com/yusing/go-proxy/agent/pkg/agent"
|
agentPkg "github.com/yusing/go-proxy/agent/pkg/agent"
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
config "github.com/yusing/go-proxy/internal/config/types"
|
config "github.com/yusing/go-proxy/internal/config/types"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/metrics/systeminfo"
|
"github.com/yusing/go-proxy/internal/metrics/systeminfo"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/httpheaders"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/reverseproxy"
|
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SystemInfo(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
func SystemInfo(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -23,7 +23,7 @@ func SystemInfo(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Reques
|
||||||
|
|
||||||
agent, ok := cfg.GetAgent(agentAddr)
|
agent, ok := cfg.GetAgent(agentAddr)
|
||||||
if !ok {
|
if !ok {
|
||||||
U.HandleErr(w, r, U.ErrInvalidKey("agent_addr"), http.StatusNotFound)
|
gphttp.NotFound(w, "agent_addr")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,20 +31,20 @@ func SystemInfo(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Reques
|
||||||
if !isWS {
|
if !isWS {
|
||||||
respData, status, err := agent.Forward(r, agentPkg.EndpointSystemInfo)
|
respData, status, err := agent.Forward(r, agentPkg.EndpointSystemInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, E.Wrap(err, "failed to forward request to agent"))
|
gphttp.ServerError(w, r, gperr.Wrap(err, "failed to forward request to agent"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if status != http.StatusOK {
|
if status != http.StatusOK {
|
||||||
http.Error(w, string(respData), status)
|
http.Error(w, string(respData), status)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
U.WriteBody(w, respData)
|
gphttp.WriteBody(w, respData)
|
||||||
} else {
|
} else {
|
||||||
rp := reverseproxy.NewReverseProxy("agent", agentPkg.AgentURL, agent.Transport())
|
rp := reverseproxy.NewReverseProxy("agent", agentPkg.AgentURL, agent.Transport())
|
||||||
header := r.Header.Clone()
|
header := r.Header.Clone()
|
||||||
r, err := http.NewRequestWithContext(r.Context(), r.Method, agentPkg.EndpointSystemInfo+"?"+query.Encode(), nil)
|
r, err := http.NewRequestWithContext(r.Context(), r.Method, agentPkg.EndpointSystemInfo+"?"+query.Encode(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
U.HandleErr(w, r, E.Wrap(err, "failed to create request"))
|
gphttp.ServerError(w, r, gperr.Wrap(err, "failed to create request"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.Header = header
|
r.Header = header
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
|
||||||
"github.com/yusing/go-proxy/internal/net/http/httpheaders"
|
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils/ansi"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HandleErr logs the error and returns an error code to the client.
|
|
||||||
// If code is specified, it will be used as the HTTP status code; otherwise,
|
|
||||||
// http.StatusInternalServerError is used.
|
|
||||||
//
|
|
||||||
// The error is only logged but not returned to the client.
|
|
||||||
func HandleErr(w http.ResponseWriter, r *http.Request, err error, code ...int) {
|
|
||||||
switch {
|
|
||||||
case err == nil,
|
|
||||||
errors.Is(err, context.Canceled),
|
|
||||||
errors.Is(err, syscall.EPIPE),
|
|
||||||
errors.Is(err, syscall.ECONNRESET):
|
|
||||||
return
|
|
||||||
}
|
|
||||||
LogError(r).Msg(err.Error())
|
|
||||||
if httpheaders.IsWebsocket(r.Header) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(code) == 0 {
|
|
||||||
code = []int{http.StatusInternalServerError}
|
|
||||||
}
|
|
||||||
http.Error(w, http.StatusText(code[0]), code[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// RespondError returns error details to the client.
|
|
||||||
// If code is specified, it will be used as the HTTP status code; otherwise,
|
|
||||||
// http.StatusBadRequest is used.
|
|
||||||
func RespondError(w http.ResponseWriter, err error, code ...int) {
|
|
||||||
if len(code) == 0 {
|
|
||||||
code = []int{http.StatusBadRequest}
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
http.Error(w, ansi.StripANSI(err.Error()), code[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func Errorf(format string, args ...any) error {
|
|
||||||
return E.Errorf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ErrMissingKey(k string) error {
|
|
||||||
return E.New(k + " is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
func ErrInvalidKey(k string) error {
|
|
||||||
return E.New(k + " is invalid")
|
|
||||||
}
|
|
||||||
|
|
||||||
func ErrAlreadyExists(k, v string) error {
|
|
||||||
return E.Errorf("%s %q already exists", k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ErrNotFound(k, v string) error {
|
|
||||||
return E.Errorf("%s %q not found", k, v)
|
|
||||||
}
|
|
|
@ -3,10 +3,10 @@ package v1
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
. "github.com/yusing/go-proxy/internal/api/v1/utils"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
"github.com/yusing/go-proxy/pkg"
|
"github.com/yusing/go-proxy/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetVersion(w http.ResponseWriter, r *http.Request) {
|
func GetVersion(w http.ResponseWriter, r *http.Request) {
|
||||||
WriteBody(w, []byte(pkg.GetVersion()))
|
gphttp.WriteBody(w, []byte(pkg.GetVersion()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/utils"
|
"github.com/yusing/go-proxy/internal/utils"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
|
@ -30,17 +30,17 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrMissingDomain = E.New("missing field 'domains'")
|
ErrMissingDomain = gperr.New("missing field 'domains'")
|
||||||
ErrMissingEmail = E.New("missing field 'email'")
|
ErrMissingEmail = gperr.New("missing field 'email'")
|
||||||
ErrMissingProvider = E.New("missing field 'provider'")
|
ErrMissingProvider = gperr.New("missing field 'provider'")
|
||||||
ErrInvalidDomain = E.New("invalid domain")
|
ErrInvalidDomain = gperr.New("invalid domain")
|
||||||
ErrUnknownProvider = E.New("unknown provider")
|
ErrUnknownProvider = gperr.New("unknown provider")
|
||||||
)
|
)
|
||||||
|
|
||||||
var domainOrWildcardRE = regexp.MustCompile(`^\*?([^.]+\.)+[^.]+$`)
|
var domainOrWildcardRE = regexp.MustCompile(`^\*?([^.]+\.)+[^.]+$`)
|
||||||
|
|
||||||
// Validate implements the utils.CustomValidator interface.
|
// Validate implements the utils.CustomValidator interface.
|
||||||
func (cfg *AutocertConfig) Validate() E.Error {
|
func (cfg *AutocertConfig) Validate() gperr.Error {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ func (cfg *AutocertConfig) Validate() E.Error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
b := E.NewBuilder("autocert errors")
|
b := gperr.NewBuilder("autocert errors")
|
||||||
if cfg.Provider != ProviderLocal {
|
if cfg.Provider != ProviderLocal {
|
||||||
if len(cfg.Domains) == 0 {
|
if len(cfg.Domains) == 0 {
|
||||||
b.Add(ErrMissingDomain)
|
b.Add(ErrMissingDomain)
|
||||||
|
@ -79,7 +79,7 @@ func (cfg *AutocertConfig) Validate() E.Error {
|
||||||
return b.Error()
|
return b.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *AutocertConfig) GetProvider() (*Provider, E.Error) {
|
func (cfg *AutocertConfig) GetProvider() (*Provider, gperr.Error) {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
cfg = new(AutocertConfig)
|
cfg = new(AutocertConfig)
|
||||||
}
|
}
|
||||||
|
@ -107,10 +107,10 @@ func (cfg *AutocertConfig) GetProvider() (*Provider, E.Error) {
|
||||||
logging.Info().Msg("generate new ACME private key")
|
logging.Info().Msg("generate new ACME private key")
|
||||||
privKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
privKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.New("generate ACME private key").With(err)
|
return nil, gperr.New("generate ACME private key").With(err)
|
||||||
}
|
}
|
||||||
if err = cfg.saveACMEKey(privKey); err != nil {
|
if err = cfg.saveACMEKey(privKey); err != nil {
|
||||||
return nil, E.New("save ACME private key").With(err)
|
return nil, gperr.New("save ACME private key").With(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -14,7 +15,7 @@ import (
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
|
@ -32,7 +33,7 @@ type (
|
||||||
tlsCert *tls.Certificate
|
tlsCert *tls.Certificate
|
||||||
certExpiries CertExpiries
|
certExpiries CertExpiries
|
||||||
}
|
}
|
||||||
ProviderGenerator func(ProviderOpt) (challenge.Provider, E.Error)
|
ProviderGenerator func(ProviderOpt) (challenge.Provider, gperr.Error)
|
||||||
|
|
||||||
CertExpiries map[string]time.Time
|
CertExpiries map[string]time.Time
|
||||||
)
|
)
|
||||||
|
@ -62,7 +63,7 @@ func (p *Provider) GetExpiries() CertExpiries {
|
||||||
return p.certExpiries
|
return p.certExpiries
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) ObtainCert() E.Error {
|
func (p *Provider) ObtainCert() error {
|
||||||
if p.cfg.Provider == ProviderLocal {
|
if p.cfg.Provider == ProviderLocal {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -75,7 +76,7 @@ func (p *Provider) ObtainCert() E.Error {
|
||||||
|
|
||||||
if p.user.Registration == nil {
|
if p.user.Registration == nil {
|
||||||
if err := p.registerACME(); err != nil {
|
if err := p.registerACME(); err != nil {
|
||||||
return E.From(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,22 +101,22 @@ func (p *Provider) ObtainCert() E.Error {
|
||||||
Bundle: true,
|
Bundle: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.From(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = p.saveCert(cert); err != nil {
|
if err = p.saveCert(cert); err != nil {
|
||||||
return E.From(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsCert, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
|
tlsCert, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.From(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
expiries, err := getCertExpiries(&tlsCert)
|
expiries, err := getCertExpiries(&tlsCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.From(err)
|
return err
|
||||||
}
|
}
|
||||||
p.tlsCert = &tlsCert
|
p.tlsCert = &tlsCert
|
||||||
p.certExpiries = expiries
|
p.certExpiries = expiries
|
||||||
|
@ -123,14 +124,14 @@ func (p *Provider) ObtainCert() E.Error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) LoadCert() E.Error {
|
func (p *Provider) LoadCert() error {
|
||||||
cert, err := tls.LoadX509KeyPair(p.cfg.CertPath, p.cfg.KeyPath)
|
cert, err := tls.LoadX509KeyPair(p.cfg.CertPath, p.cfg.KeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Errorf("load SSL certificate: %w", err)
|
return fmt.Errorf("load SSL certificate: %w", err)
|
||||||
}
|
}
|
||||||
expiries, err := getCertExpiries(&cert)
|
expiries, err := getCertExpiries(&cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Errorf("parse SSL certificate: %w", err)
|
return fmt.Errorf("parse SSL certificate: %w", err)
|
||||||
}
|
}
|
||||||
p.tlsCert = &cert
|
p.tlsCert = &cert
|
||||||
p.certExpiries = expiries
|
p.certExpiries = expiries
|
||||||
|
@ -171,7 +172,7 @@ func (p *Provider) ScheduleRenewal(parent task.Parent) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := p.renewIfNeeded(); err != nil {
|
if err := p.renewIfNeeded(); err != nil {
|
||||||
E.LogWarn("cert renew failed", err)
|
gperr.LogWarn("cert renew failed", err)
|
||||||
lastErrOn = time.Now()
|
lastErrOn = time.Now()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -184,10 +185,10 @@ func (p *Provider) ScheduleRenewal(parent task.Parent) {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) initClient() E.Error {
|
func (p *Provider) initClient() error {
|
||||||
legoClient, err := lego.NewClient(p.legoCfg)
|
legoClient, err := lego.NewClient(p.legoCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.From(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
generator := providersGenMap[p.cfg.Provider]
|
generator := providersGenMap[p.cfg.Provider]
|
||||||
|
@ -198,7 +199,7 @@ func (p *Provider) initClient() E.Error {
|
||||||
|
|
||||||
err = legoClient.Challenge.SetDNS01Provider(legoProvider)
|
err = legoClient.Challenge.SetDNS01Provider(legoProvider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.From(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
p.client = legoClient
|
p.client = legoClient
|
||||||
|
@ -273,7 +274,7 @@ func (p *Provider) certState() CertState {
|
||||||
return CertStateValid
|
return CertStateValid
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) renewIfNeeded() E.Error {
|
func (p *Provider) renewIfNeeded() error {
|
||||||
if p.cfg.Provider == ProviderLocal {
|
if p.cfg.Provider == ProviderLocal {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -312,13 +313,13 @@ func providerGenerator[CT any, PT challenge.Provider](
|
||||||
defaultCfg func() *CT,
|
defaultCfg func() *CT,
|
||||||
newProvider func(*CT) (PT, error),
|
newProvider func(*CT) (PT, error),
|
||||||
) ProviderGenerator {
|
) ProviderGenerator {
|
||||||
return func(opt ProviderOpt) (challenge.Provider, E.Error) {
|
return func(opt ProviderOpt) (challenge.Provider, gperr.Error) {
|
||||||
cfg := defaultCfg()
|
cfg := defaultCfg()
|
||||||
err := U.Deserialize(opt, &cfg)
|
err := U.Deserialize(opt, &cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
p, pErr := newProvider(cfg)
|
p, pErr := newProvider(cfg)
|
||||||
return p, E.From(pErr)
|
return p, gperr.Wrap(pErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
package autocert
|
package autocert
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *Provider) Setup() (err E.Error) {
|
func (p *Provider) Setup() (err error) {
|
||||||
if err = p.LoadCert(); err != nil {
|
if err = p.LoadCert(); err != nil {
|
||||||
if !err.Is(os.ErrNotExist) { // ignore if cert doesn't exist
|
if !errors.Is(err, os.ErrNotExist) { // ignore if cert doesn't exist
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logging.Debug().Msg("obtaining cert due to error loading cert")
|
logging.Debug().Msg("obtaining cert due to error loading cert")
|
||||||
|
|
|
@ -4,12 +4,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
ConnectionTimeout = 5 * time.Second
|
|
||||||
DialTimeout = 3 * time.Second
|
|
||||||
KeepAlive = 60 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
// file, folder structure
|
// file, folder structure
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -50,5 +44,3 @@ const (
|
||||||
StopTimeoutDefault = "30s"
|
StopTimeoutDefault = "30s"
|
||||||
StopMethodDefault = "stop"
|
StopMethodDefault = "stop"
|
||||||
)
|
)
|
||||||
|
|
||||||
const HeaderCheckRedirect = "X-Goproxy-Check-Redirect"
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/yusing/go-proxy/agent/pkg/agent"
|
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/route/provider"
|
"github.com/yusing/go-proxy/internal/route/provider"
|
||||||
"github.com/yusing/go-proxy/internal/utils/functional"
|
"github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
)
|
)
|
||||||
|
@ -29,12 +29,12 @@ func (cfg *Config) GetAgent(agentAddrOrDockerHost string) (*agent.AgentConfig, b
|
||||||
return GetAgent(agent.GetAgentAddrFromDockerHost(agentAddrOrDockerHost))
|
return GetAgent(agent.GetAgentAddrFromDockerHost(agentAddrOrDockerHost))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) AddAgent(host string, ca agent.PEMPair, client agent.PEMPair) (int, E.Error) {
|
func (cfg *Config) AddAgent(host string, ca agent.PEMPair, client agent.PEMPair) (int, gperr.Error) {
|
||||||
var agentCfg agent.AgentConfig
|
var agentCfg agent.AgentConfig
|
||||||
agentCfg.Addr = host
|
agentCfg.Addr = host
|
||||||
err := agentCfg.StartWithCerts(cfg.Task(), ca.Cert, client.Cert, client.Key)
|
err := agentCfg.StartWithCerts(cfg.Task(), ca.Cert, client.Cert, client.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, gperr.Wrap(err, "failed to start agent")
|
||||||
}
|
}
|
||||||
|
|
||||||
provider := provider.NewAgentProvider(&agentCfg)
|
provider := provider.NewAgentProvider(&agentCfg)
|
||||||
|
|
|
@ -14,9 +14,9 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
config "github.com/yusing/go-proxy/internal/config/types"
|
config "github.com/yusing/go-proxy/internal/config/types"
|
||||||
"github.com/yusing/go-proxy/internal/entrypoint"
|
"github.com/yusing/go-proxy/internal/entrypoint"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/server"
|
"github.com/yusing/go-proxy/internal/net/gphttp/server"
|
||||||
"github.com/yusing/go-proxy/internal/notif"
|
"github.com/yusing/go-proxy/internal/notif"
|
||||||
proxy "github.com/yusing/go-proxy/internal/route/provider"
|
proxy "github.com/yusing/go-proxy/internal/route/provider"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
|
@ -64,7 +64,7 @@ func newConfig() *Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load() (*Config, E.Error) {
|
func Load() (*Config, gperr.Error) {
|
||||||
if config.HasInstance() {
|
if config.HasInstance() {
|
||||||
panic(errors.New("config already loaded"))
|
panic(errors.New("config already loaded"))
|
||||||
}
|
}
|
||||||
|
@ -84,8 +84,8 @@ func WatchChanges() {
|
||||||
t,
|
t,
|
||||||
configEventFlushInterval,
|
configEventFlushInterval,
|
||||||
OnConfigChange,
|
OnConfigChange,
|
||||||
func(err E.Error) {
|
func(err gperr.Error) {
|
||||||
E.LogError("config reload error", err)
|
gperr.LogError("config reload error", err)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
eventQueue.Start(cfgWatcher.Events(t.Context()))
|
eventQueue.Start(cfgWatcher.Events(t.Context()))
|
||||||
|
@ -109,7 +109,7 @@ func OnConfigChange(ev []events.Event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Reload() E.Error {
|
func Reload() gperr.Error {
|
||||||
// avoid race between config change and API reload request
|
// avoid race between config change and API reload request
|
||||||
reloadMu.Lock()
|
reloadMu.Lock()
|
||||||
defer reloadMu.Unlock()
|
defer reloadMu.Unlock()
|
||||||
|
@ -118,7 +118,7 @@ func Reload() E.Error {
|
||||||
err := newCfg.load()
|
err := newCfg.load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
newCfg.task.Finish(err)
|
newCfg.task.Finish(err)
|
||||||
return E.New("using last config").With(err)
|
return gperr.New("using last config").With(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// cancel all current subtasks -> wait
|
// cancel all current subtasks -> wait
|
||||||
|
@ -133,10 +133,13 @@ func (cfg *Config) Value() *config.Config {
|
||||||
return cfg.value
|
return cfg.value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) Reload() E.Error {
|
func (cfg *Config) Reload() gperr.Error {
|
||||||
return Reload()
|
return Reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AutoCertProvider returns the autocert provider.
|
||||||
|
//
|
||||||
|
// If the autocert provider is not configured, it returns nil.
|
||||||
func (cfg *Config) AutoCertProvider() *autocert.Provider {
|
func (cfg *Config) AutoCertProvider() *autocert.Provider {
|
||||||
return cfg.autocertProvider
|
return cfg.autocertProvider
|
||||||
}
|
}
|
||||||
|
@ -163,7 +166,7 @@ func (cfg *Config) StartAutoCert() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := autocert.Setup(); err != nil {
|
if err := autocert.Setup(); err != nil {
|
||||||
E.LogFatal("autocert setup error", err)
|
gperr.LogFatal("autocert setup error", err)
|
||||||
} else {
|
} else {
|
||||||
autocert.ScheduleRenewal(cfg.task)
|
autocert.ScheduleRenewal(cfg.task)
|
||||||
}
|
}
|
||||||
|
@ -175,8 +178,8 @@ func (cfg *Config) StartProxyProviders() {
|
||||||
return p.Start(cfg.task)
|
return p.Start(cfg.task)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := E.Join(errs...); err != nil {
|
if err := gperr.Join(errs...); err != nil {
|
||||||
E.LogError("route provider errors", err)
|
gperr.LogError("route provider errors", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,21 +213,21 @@ func (cfg *Config) StartServers(opts ...*StartServersOptions) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) load() E.Error {
|
func (cfg *Config) load() gperr.Error {
|
||||||
const errMsg = "config load error"
|
const errMsg = "config load error"
|
||||||
|
|
||||||
data, err := os.ReadFile(common.ConfigPath)
|
data, err := os.ReadFile(common.ConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
E.LogFatal(errMsg, err)
|
gperr.LogFatal(errMsg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
model := config.DefaultConfig()
|
model := config.DefaultConfig()
|
||||||
if err := utils.DeserializeYAML(data, model); err != nil {
|
if err := utils.DeserializeYAML(data, model); err != nil {
|
||||||
E.LogFatal(errMsg, err)
|
gperr.LogFatal(errMsg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// errors are non fatal below
|
// errors are non fatal below
|
||||||
errs := E.NewBuilder(errMsg)
|
errs := gperr.NewBuilder(errMsg)
|
||||||
errs.Add(cfg.entrypoint.SetMiddlewares(model.Entrypoint.Middlewares))
|
errs.Add(cfg.entrypoint.SetMiddlewares(model.Entrypoint.Middlewares))
|
||||||
errs.Add(cfg.entrypoint.SetAccessLogger(cfg.task, model.Entrypoint.AccessLog))
|
errs.Add(cfg.entrypoint.SetAccessLogger(cfg.task, model.Entrypoint.AccessLog))
|
||||||
cfg.initNotification(model.Providers.Notification)
|
cfg.initNotification(model.Providers.Notification)
|
||||||
|
@ -252,7 +255,7 @@ func (cfg *Config) initNotification(notifCfg []notif.NotificationConfig) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) initAutoCert(autocertCfg *autocert.AutocertConfig) (err E.Error) {
|
func (cfg *Config) initAutoCert(autocertCfg *autocert.AutocertConfig) (err gperr.Error) {
|
||||||
if cfg.autocertProvider != nil {
|
if cfg.autocertProvider != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -261,9 +264,9 @@ func (cfg *Config) initAutoCert(autocertCfg *autocert.AutocertConfig) (err E.Err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) errIfExists(p *proxy.Provider) E.Error {
|
func (cfg *Config) errIfExists(p *proxy.Provider) gperr.Error {
|
||||||
if _, ok := cfg.providers.Load(p.String()); ok {
|
if _, ok := cfg.providers.Load(p.String()); ok {
|
||||||
return E.Errorf("provider %s already exists", p.String())
|
return gperr.Errorf("provider %s already exists", p.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -272,9 +275,9 @@ func (cfg *Config) storeProvider(p *proxy.Provider) {
|
||||||
cfg.providers.Store(p.String(), p)
|
cfg.providers.Store(p.String(), p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) loadRouteProviders(providers *config.Providers) E.Error {
|
func (cfg *Config) loadRouteProviders(providers *config.Providers) gperr.Error {
|
||||||
errs := E.NewBuilder("route provider errors")
|
errs := gperr.NewBuilder("route provider errors")
|
||||||
results := E.NewBuilder("loaded route providers")
|
results := gperr.NewBuilder("loaded route providers")
|
||||||
|
|
||||||
removeAllAgents()
|
removeAllAgents()
|
||||||
|
|
||||||
|
@ -297,7 +300,7 @@ func (cfg *Config) loadRouteProviders(providers *config.Providers) E.Error {
|
||||||
err = cfg.errIfExists(p)
|
err = cfg.errIfExists(p)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs.Add(E.PrependSubject(filename, err))
|
errs.Add(gperr.PrependSubject(filename, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cfg.storeProvider(p)
|
cfg.storeProvider(p)
|
||||||
|
|
|
@ -8,11 +8,10 @@ import (
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/yusing/go-proxy/agent/pkg/agent"
|
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||||
"github.com/yusing/go-proxy/internal/autocert"
|
"github.com/yusing/go-proxy/internal/autocert"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/accesslog"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
|
||||||
"github.com/yusing/go-proxy/internal/notif"
|
"github.com/yusing/go-proxy/internal/notif"
|
||||||
"github.com/yusing/go-proxy/internal/utils"
|
"github.com/yusing/go-proxy/internal/utils"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -37,12 +36,12 @@ type (
|
||||||
|
|
||||||
ConfigInstance interface {
|
ConfigInstance interface {
|
||||||
Value() *Config
|
Value() *Config
|
||||||
Reload() E.Error
|
Reload() gperr.Error
|
||||||
Statistics() map[string]any
|
Statistics() map[string]any
|
||||||
RouteProviderList() []string
|
RouteProviderList() []string
|
||||||
Context() context.Context
|
Context() context.Context
|
||||||
GetAgent(agentAddrOrDockerHost string) (*agent.AgentConfig, bool)
|
GetAgent(agentAddrOrDockerHost string) (*agent.AgentConfig, bool)
|
||||||
AddAgent(host string, ca agent.PEMPair, client agent.PEMPair) (int, E.Error)
|
AddAgent(host string, ca agent.PEMPair, client agent.PEMPair) (int, gperr.Error)
|
||||||
ListAgents() []*agent.AgentConfig
|
ListAgents() []*agent.AgentConfig
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -79,7 +78,7 @@ func HasInstance() bool {
|
||||||
return instance != nil
|
return instance != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Validate(data []byte) E.Error {
|
func Validate(data []byte) gperr.Error {
|
||||||
var model Config
|
var model Config
|
||||||
return utils.DeserializeYAML(data, &model)
|
return utils.DeserializeYAML(data, &model)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
|
||||||
)
|
)
|
||||||
|
|
||||||
type templateData struct {
|
type templateData struct {
|
||||||
|
@ -23,11 +23,11 @@ func (w *Watcher) makeLoadingPageBody() []byte {
|
||||||
msg := w.ContainerName + " is starting..."
|
msg := w.ContainerName + " is starting..."
|
||||||
|
|
||||||
data := new(templateData)
|
data := new(templateData)
|
||||||
data.CheckRedirectHeader = common.HeaderCheckRedirect
|
data.CheckRedirectHeader = httpheaders.HeaderGoDoxyCheckRedirect
|
||||||
data.Title = w.ContainerName
|
data.Title = w.ContainerName
|
||||||
data.Message = strings.ReplaceAll(msg, " ", " ")
|
data.Message = strings.ReplaceAll(msg, " ", " ")
|
||||||
|
|
||||||
buf := bytes.NewBuffer(make([]byte, len(loadingPage)+len(data.Title)+len(data.Message)+len(common.HeaderCheckRedirect)))
|
buf := bytes.NewBuffer(make([]byte, len(loadingPage)+len(data.Title)+len(data.Message)+len(httpheaders.HeaderGoDoxyCheckRedirect)))
|
||||||
err := loadingPageTmpl.Execute(buf, data)
|
err := loadingPageTmpl.Execute(buf, data)
|
||||||
if err != nil { // should never happen in production
|
if err != nil { // should never happen in production
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/docker"
|
"github.com/yusing/go-proxy/internal/docker"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -40,7 +40,7 @@ var validSignals = map[string]struct{}{
|
||||||
"INT": {}, "TERM": {}, "HUP": {}, "QUIT": {},
|
"INT": {}, "TERM": {}, "HUP": {}, "QUIT": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateConfig(cont *docker.Container) (*Config, E.Error) {
|
func ValidateConfig(cont *docker.Container) (*Config, gperr.Error) {
|
||||||
if cont == nil {
|
if cont == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -54,14 +54,14 @@ func ValidateConfig(cont *docker.Container) (*Config, E.Error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
errs := E.NewBuilder("invalid idlewatcher config")
|
errs := gperr.NewBuilder("invalid idlewatcher config")
|
||||||
|
|
||||||
idleTimeout := E.Collect(errs, validateDurationPostitive, cont.IdleTimeout)
|
idleTimeout := gperr.Collect(errs, validateDurationPostitive, cont.IdleTimeout)
|
||||||
wakeTimeout := E.Collect(errs, validateDurationPostitive, cont.WakeTimeout)
|
wakeTimeout := gperr.Collect(errs, validateDurationPostitive, cont.WakeTimeout)
|
||||||
stopTimeout := E.Collect(errs, validateDurationPostitive, cont.StopTimeout)
|
stopTimeout := gperr.Collect(errs, validateDurationPostitive, cont.StopTimeout)
|
||||||
stopMethod := E.Collect(errs, validateStopMethod, cont.StopMethod)
|
stopMethod := gperr.Collect(errs, validateStopMethod, cont.StopMethod)
|
||||||
signal := E.Collect(errs, validateSignal, cont.StopSignal)
|
signal := gperr.Collect(errs, validateSignal, cont.StopSignal)
|
||||||
startEndpoint := E.Collect(errs, validateStartEndpoint, cont.StartEndpoint)
|
startEndpoint := gperr.Collect(errs, validateStartEndpoint, cont.StartEndpoint)
|
||||||
|
|
||||||
if errs.HasError() {
|
if errs.HasError() {
|
||||||
return nil, errs.Error()
|
return nil, errs.Error()
|
||||||
|
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
"github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/metrics"
|
"github.com/yusing/go-proxy/internal/metrics"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/reverseproxy"
|
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
|
||||||
net "github.com/yusing/go-proxy/internal/net/types"
|
net "github.com/yusing/go-proxy/internal/net/types"
|
||||||
route "github.com/yusing/go-proxy/internal/route/types"
|
route "github.com/yusing/go-proxy/internal/route/types"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
|
@ -37,7 +37,7 @@ const (
|
||||||
|
|
||||||
// TODO: support stream
|
// TODO: support stream
|
||||||
|
|
||||||
func newWaker(parent task.Parent, route route.Route, rp *reverseproxy.ReverseProxy, stream net.Stream) (Waker, E.Error) {
|
func newWaker(parent task.Parent, route route.Route, rp *reverseproxy.ReverseProxy, stream net.Stream) (Waker, gperr.Error) {
|
||||||
hcCfg := route.HealthCheckConfig()
|
hcCfg := route.HealthCheckConfig()
|
||||||
hcCfg.Timeout = idleWakerCheckTimeout
|
hcCfg.Timeout = idleWakerCheckTimeout
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ func newWaker(parent task.Parent, route route.Route, rp *reverseproxy.ReversePro
|
||||||
task := parent.Subtask("idlewatcher." + route.TargetName())
|
task := parent.Subtask("idlewatcher." + route.TargetName())
|
||||||
watcher, err := registerWatcher(task, route, waker)
|
watcher, err := registerWatcher(task, route, waker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Errorf("register watcher: %w", err)
|
return nil, gperr.Errorf("register watcher: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
@ -66,16 +66,16 @@ func newWaker(parent task.Parent, route route.Route, rp *reverseproxy.ReversePro
|
||||||
}
|
}
|
||||||
|
|
||||||
// lifetime should follow route provider.
|
// lifetime should follow route provider.
|
||||||
func NewHTTPWaker(parent task.Parent, route route.Route, rp *reverseproxy.ReverseProxy) (Waker, E.Error) {
|
func NewHTTPWaker(parent task.Parent, route route.Route, rp *reverseproxy.ReverseProxy) (Waker, gperr.Error) {
|
||||||
return newWaker(parent, route, rp, nil)
|
return newWaker(parent, route, rp, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStreamWaker(parent task.Parent, route route.Route, stream net.Stream) (Waker, E.Error) {
|
func NewStreamWaker(parent task.Parent, route route.Route, stream net.Stream) (Waker, gperr.Error) {
|
||||||
return newWaker(parent, route, nil, stream)
|
return newWaker(parent, route, nil, stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start implements health.HealthMonitor.
|
// Start implements health.HealthMonitor.
|
||||||
func (w *Watcher) Start(parent task.Parent) E.Error {
|
func (w *Watcher) Start(parent task.Parent) gperr.Error {
|
||||||
w.task.OnCancel("route_cleanup", func() {
|
w.task.OnCancel("route_cleanup", func() {
|
||||||
parent.Finish(w.task.FinishCause())
|
parent.Finish(w.task.FinishCause())
|
||||||
if w.metric != nil {
|
if w.metric != nil {
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ func (w *Watcher) wakeFromHTTP(rw http.ResponseWriter, r *http.Request) (shouldN
|
||||||
accept := gphttp.GetAccept(r.Header)
|
accept := gphttp.GetAccept(r.Header)
|
||||||
acceptHTML := (r.Method == http.MethodGet && accept.AcceptHTML() || r.RequestURI == "/" && accept.IsEmpty())
|
acceptHTML := (r.Method == http.MethodGet && accept.AcceptHTML() || r.RequestURI == "/" && accept.IsEmpty())
|
||||||
|
|
||||||
isCheckRedirect := r.Header.Get(common.HeaderCheckRedirect) != ""
|
isCheckRedirect := r.Header.Get(httpheaders.HeaderGoDoxyCheckRedirect) != ""
|
||||||
if !isCheckRedirect && acceptHTML {
|
if !isCheckRedirect && acceptHTML {
|
||||||
// Send a loading response to the client
|
// Send a loading response to the client
|
||||||
body := w.makeLoadingPageBody()
|
body := w.makeLoadingPageBody()
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
D "github.com/yusing/go-proxy/internal/docker"
|
D "github.com/yusing/go-proxy/internal/docker"
|
||||||
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
route "github.com/yusing/go-proxy/internal/route/types"
|
route "github.com/yusing/go-proxy/internal/route/types"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
|
@ -180,7 +180,7 @@ func (w *Watcher) wakeIfStopped() error {
|
||||||
case "running":
|
case "running":
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return E.Errorf("unexpected container status: %s", status)
|
return gperr.Errorf("unexpected container status: %s", status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ func (w *Watcher) expires() time.Time {
|
||||||
return w.lastReset.Add(w.IdleTimeout)
|
return w.lastReset.Add(w.IdleTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Watcher) getEventCh(dockerWatcher watcher.DockerWatcher) (eventCh <-chan events.Event, errCh <-chan E.Error) {
|
func (w *Watcher) getEventCh(dockerWatcher watcher.DockerWatcher) (eventCh <-chan events.Event, errCh <-chan gperr.Error) {
|
||||||
eventCh, errCh = dockerWatcher.EventsWithOptions(w.Task().Context(), watcher.DockerListOptions{
|
eventCh, errCh = dockerWatcher.EventsWithOptions(w.Task().Context(), watcher.DockerListOptions{
|
||||||
Filters: watcher.NewDockerFilter(
|
Filters: watcher.NewDockerFilter(
|
||||||
watcher.DockerFilterContainer,
|
watcher.DockerFilterContainer,
|
||||||
|
@ -251,7 +251,7 @@ func (w *Watcher) watchUntilDestroy() (returnCause error) {
|
||||||
return w.task.FinishCause()
|
return w.task.FinishCause()
|
||||||
case err := <-dockerEventErrCh:
|
case err := <-dockerEventErrCh:
|
||||||
if !err.Is(context.Canceled) {
|
if !err.Is(context.Canceled) {
|
||||||
E.LogError("idlewatcher error", err, &w.Logger)
|
gperr.LogError("idlewatcher error", err, &w.Logger)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
case e := <-dockerEventCh:
|
case e := <-dockerEventCh:
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LabelMap = map[string]any
|
type LabelMap = map[string]any
|
||||||
|
|
||||||
var ErrInvalidLabel = E.New("invalid label")
|
var ErrInvalidLabel = gperr.New("invalid label")
|
||||||
|
|
||||||
func ParseLabels(labels map[string]string) (LabelMap, E.Error) {
|
func ParseLabels(labels map[string]string) (LabelMap, gperr.Error) {
|
||||||
nestedMap := make(LabelMap)
|
nestedMap := make(LabelMap)
|
||||||
errs := E.NewBuilder("labels error")
|
errs := gperr.NewBuilder("labels error")
|
||||||
|
|
||||||
for lbl, value := range labels {
|
for lbl, value := range labels {
|
||||||
parts := strutils.SplitRune(lbl, '.')
|
parts := strutils.SplitRune(lbl, '.')
|
||||||
|
@ -37,7 +37,7 @@ func ParseLabels(labels map[string]string) (LabelMap, E.Error) {
|
||||||
// Move deeper into the nested map
|
// Move deeper into the nested map
|
||||||
m, ok := currentMap[k].(LabelMap)
|
m, ok := currentMap[k].(LabelMap)
|
||||||
if !ok && currentMap[k] != "" {
|
if !ok && currentMap[k] != "" {
|
||||||
errs.Add(E.Errorf("expect mapping, got %T", currentMap[k]).Subject(lbl))
|
errs.Add(gperr.Errorf("expect mapping, got %T", currentMap[k]).Subject(lbl))
|
||||||
continue
|
continue
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
m = make(LabelMap)
|
m = make(LabelMap)
|
||||||
|
|
|
@ -7,10 +7,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/accesslog"
|
"github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
"github.com/yusing/go-proxy/internal/net/gphttp/middleware"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware/errorpage"
|
"github.com/yusing/go-proxy/internal/net/gphttp/middleware/errorpage"
|
||||||
"github.com/yusing/go-proxy/internal/route/routes"
|
"github.com/yusing/go-proxy/internal/route/routes"
|
||||||
route "github.com/yusing/go-proxy/internal/route/types"
|
route "github.com/yusing/go-proxy/internal/route/types"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
|
|
106
internal/gperr/README.md
Normal file
106
internal/gperr/README.md
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
# gperr
|
||||||
|
|
||||||
|
gperr is an error interface that supports nested structure and subject highlighting.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### gperr.Error
|
||||||
|
|
||||||
|
The error interface.
|
||||||
|
|
||||||
|
### gperr.New
|
||||||
|
|
||||||
|
Like `errors.New`, but returns a `gperr.Error`.
|
||||||
|
|
||||||
|
### gperr.Wrap
|
||||||
|
|
||||||
|
Like `fmt.Errorf("%s: %w", message, err)`, but returns a `gperr.Error`.
|
||||||
|
|
||||||
|
### gperr.Error.Subject
|
||||||
|
|
||||||
|
Returns a new error with the subject prepended to the error message. The main subject is highlighted.
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := gperr.New("error message")
|
||||||
|
err = err.Subject("bar")
|
||||||
|
err = err.Subject("foo")
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
<code>foo > <span style="color: red;">bar</span>: error message</code>
|
||||||
|
|
||||||
|
### gperr.Error.Subjectf
|
||||||
|
|
||||||
|
Like `gperr.Error.Subject`, but formats the subject with `fmt.Sprintf`.
|
||||||
|
|
||||||
|
### gperr.PrependSubject
|
||||||
|
|
||||||
|
Prepends the subject to the error message like `gperr.Error.Subject`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := gperr.New("error message")
|
||||||
|
err = gperr.PrependSubject(err, "foo")
|
||||||
|
err = gperr.PrependSubject(err, "bar")
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
<code>bar > <span style="color: red;">foo</span>: error message</code>
|
||||||
|
|
||||||
|
### gperr.Error.With
|
||||||
|
|
||||||
|
Adds a new error to the error chain.
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := gperr.New("error message")
|
||||||
|
err = err.With(gperr.New("inner error"))
|
||||||
|
err = err.With(gperr.New("inner error 2").With(gperr.New("inner inner error")))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
error message:
|
||||||
|
• inner error
|
||||||
|
• inner error 2
|
||||||
|
• inner inner error
|
||||||
|
```
|
||||||
|
|
||||||
|
### gperr.Error.Withf
|
||||||
|
|
||||||
|
Like `gperr.Error.With`, but formats the error with `fmt.Errorf`.
|
||||||
|
|
||||||
|
### gperr.Error.Is
|
||||||
|
|
||||||
|
Returns true if the error is equal to the given error.
|
||||||
|
|
||||||
|
### gperr.Builder
|
||||||
|
|
||||||
|
A builder for `gperr.Error`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
builder := gperr.NewBuilder("foo")
|
||||||
|
builder.Add(gperr.New("error message"))
|
||||||
|
builder.Addf("error message: %s", "foo")
|
||||||
|
builder.AddRange(gperr.New("error message 1"), gperr.New("error message 2"))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo:
|
||||||
|
• error message
|
||||||
|
• error message: foo
|
||||||
|
• error message 1
|
||||||
|
• error message 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### gperr.Builder.Build
|
||||||
|
|
||||||
|
Builds a `gperr.Error` from the builder.
|
||||||
|
|
||||||
|
## When to return gperr.Error
|
||||||
|
|
||||||
|
- When you want to return multiple errors
|
||||||
|
- When the error has a subject
|
|
@ -1,4 +1,4 @@
|
||||||
package err
|
package gperr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
|
@ -1,4 +1,4 @@
|
||||||
package err
|
package gperr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -36,7 +36,7 @@ func (b *Builder) error() Error {
|
||||||
|
|
||||||
func (b *Builder) Error() Error {
|
func (b *Builder) Error() Error {
|
||||||
if len(b.errs) == 1 {
|
if len(b.errs) == 1 {
|
||||||
return From(b.errs[0])
|
return wrap(b.errs[0])
|
||||||
}
|
}
|
||||||
return b.error()
|
return b.error()
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ func (b *Builder) Add(err error) *Builder {
|
||||||
b.Lock()
|
b.Lock()
|
||||||
defer b.Unlock()
|
defer b.Unlock()
|
||||||
|
|
||||||
switch err := From(err).(type) {
|
switch err := wrap(err).(type) {
|
||||||
case *baseError:
|
case *baseError:
|
||||||
b.errs = append(b.errs, err.Err)
|
b.errs = append(b.errs, err.Err)
|
||||||
case *nestedError:
|
case *nestedError:
|
|
@ -1,4 +1,4 @@
|
||||||
package err_test
|
package gperr_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -6,7 +6,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/yusing/go-proxy/internal/error"
|
. "github.com/yusing/go-proxy/internal/gperr"
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package err
|
package gperr
|
||||||
|
|
||||||
type Error interface {
|
type Error interface {
|
||||||
error
|
error
|
|
@ -1,4 +1,4 @@
|
||||||
package err
|
package gperr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -44,7 +44,7 @@ func TestBaseWithExtra(t *testing.T) {
|
||||||
|
|
||||||
func TestBaseUnwrap(t *testing.T) {
|
func TestBaseUnwrap(t *testing.T) {
|
||||||
err := errors.New("err")
|
err := errors.New("err")
|
||||||
wrapped := From(err)
|
wrapped := Wrap(err)
|
||||||
|
|
||||||
ExpectError(t, err, errors.Unwrap(wrapped))
|
ExpectError(t, err, errors.Unwrap(wrapped))
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ func TestBaseUnwrap(t *testing.T) {
|
||||||
func TestNestedUnwrap(t *testing.T) {
|
func TestNestedUnwrap(t *testing.T) {
|
||||||
err := errors.New("err")
|
err := errors.New("err")
|
||||||
err2 := New("err2")
|
err2 := New("err2")
|
||||||
wrapped := From(err).Subject("foo").With(err2.Subject("bar"))
|
wrapped := Wrap(err).Subject("foo").With(err2.Subject("bar"))
|
||||||
|
|
||||||
unwrapper, ok := wrapped.(interface{ Unwrap() []error })
|
unwrapper, ok := wrapped.(interface{ Unwrap() []error })
|
||||||
ExpectTrue(t, ok)
|
ExpectTrue(t, ok)
|
||||||
|
@ -64,7 +64,7 @@ func TestNestedUnwrap(t *testing.T) {
|
||||||
|
|
||||||
func TestErrorIs(t *testing.T) {
|
func TestErrorIs(t *testing.T) {
|
||||||
from := errors.New("error")
|
from := errors.New("error")
|
||||||
err := From(from)
|
err := Wrap(from)
|
||||||
ExpectError(t, from, err)
|
ExpectError(t, from, err)
|
||||||
|
|
||||||
ExpectTrue(t, err.Is(from))
|
ExpectTrue(t, err.Is(from))
|
|
@ -1,4 +1,4 @@
|
||||||
package err
|
package gperr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
@ -6,42 +6,35 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getLogger(logger ...*zerolog.Logger) *zerolog.Logger {
|
func log(msg string, err error, level zerolog.Level, logger ...*zerolog.Logger) {
|
||||||
|
var l *zerolog.Logger
|
||||||
if len(logger) > 0 {
|
if len(logger) > 0 {
|
||||||
return logger[0]
|
l = logger[0]
|
||||||
|
} else {
|
||||||
|
l = logging.GetLogger()
|
||||||
}
|
}
|
||||||
return logging.GetLogger()
|
l.WithLevel(level).Msg(msg + ": " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:inline
|
|
||||||
func LogFatal(msg string, err error, logger ...*zerolog.Logger) {
|
func LogFatal(msg string, err error, logger ...*zerolog.Logger) {
|
||||||
if common.IsDebug {
|
if common.IsDebug {
|
||||||
LogPanic(msg, err, logger...)
|
LogPanic(msg, err, logger...)
|
||||||
}
|
}
|
||||||
getLogger(logger...).Fatal().Msg(err.Error())
|
log(msg, err, zerolog.FatalLevel, logger...)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:inline
|
|
||||||
func LogError(msg string, err error, logger ...*zerolog.Logger) {
|
func LogError(msg string, err error, logger ...*zerolog.Logger) {
|
||||||
getLogger(logger...).Error().Msg(err.Error())
|
log(msg, err, zerolog.ErrorLevel, logger...)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:inline
|
|
||||||
func LogWarn(msg string, err error, logger ...*zerolog.Logger) {
|
func LogWarn(msg string, err error, logger ...*zerolog.Logger) {
|
||||||
getLogger(logger...).Warn().Msg(err.Error())
|
log(msg, err, zerolog.WarnLevel, logger...)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:inline
|
|
||||||
func LogPanic(msg string, err error, logger ...*zerolog.Logger) {
|
func LogPanic(msg string, err error, logger ...*zerolog.Logger) {
|
||||||
getLogger(logger...).Panic().Msg(err.Error())
|
log(msg, err, zerolog.PanicLevel, logger...)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:inline
|
|
||||||
func LogInfo(msg string, err error, logger ...*zerolog.Logger) {
|
|
||||||
getLogger(logger...).Info().Msg(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:inline
|
|
||||||
func LogDebug(msg string, err error, logger ...*zerolog.Logger) {
|
func LogDebug(msg string, err error, logger ...*zerolog.Logger) {
|
||||||
getLogger(logger...).Debug().Msg(err.Error())
|
log(msg, err, zerolog.DebugLevel, logger...)
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package err
|
package gperr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -99,7 +99,7 @@ func makeLines(errs []error, level int) []string {
|
||||||
}
|
}
|
||||||
lines := make([]string, 0, len(errs))
|
lines := make([]string, 0, len(errs))
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
switch err := From(err).(type) {
|
switch err := wrap(err).(type) {
|
||||||
case *nestedError:
|
case *nestedError:
|
||||||
if err.Err != nil {
|
if err.Err != nil {
|
||||||
lines = append(lines, makeLine(err.Err.Error(), level))
|
lines = append(lines, makeLine(err.Err.Error(), level))
|
|
@ -1,4 +1,4 @@
|
||||||
package err
|
package gperr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
|
@ -1,6 +1,8 @@
|
||||||
package err
|
package gperr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,27 +21,50 @@ func Errorf(format string, args ...any) Error {
|
||||||
return &baseError{fmt.Errorf(format, args...)}
|
return &baseError{fmt.Errorf(format, args...)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrap wraps message in front of the error message.
|
||||||
func Wrap(err error, message ...string) Error {
|
func Wrap(err error, message ...string) Error {
|
||||||
if len(message) == 0 || message[0] == "" {
|
if err == nil {
|
||||||
return From(err)
|
return nil
|
||||||
}
|
}
|
||||||
return Errorf("%s: %w", message[0], err)
|
if len(message) == 0 || message[0] == "" {
|
||||||
|
return wrap(err)
|
||||||
|
}
|
||||||
|
//nolint:errorlint
|
||||||
|
switch err := err.(type) {
|
||||||
|
case *baseError:
|
||||||
|
err.Err = fmt.Errorf("%s: %w", message[0], err.Err)
|
||||||
|
return err
|
||||||
|
case *nestedError:
|
||||||
|
err.Err = fmt.Errorf("%s: %w", message[0], err.Err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return &baseError{fmt.Errorf("%s: %w", message[0], err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func From(err error) Error {
|
func wrap(err error) Error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
//nolint:errorlint
|
//nolint:errorlint
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case *baseError:
|
case Error:
|
||||||
return err
|
|
||||||
case *nestedError:
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return &baseError{err}
|
return &baseError{err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsJSONMarshallable(err error) bool {
|
||||||
|
switch err := err.(type) {
|
||||||
|
case *nestedError, *withSubject:
|
||||||
|
return true
|
||||||
|
case *baseError:
|
||||||
|
return IsJSONMarshallable(err.Err)
|
||||||
|
default:
|
||||||
|
var v json.Marshaler
|
||||||
|
return errors.As(err, &v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Join(errors ...error) Error {
|
func Join(errors ...error) Error {
|
||||||
n := 0
|
n := 0
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
58
internal/gperr/utils_test.go
Normal file
58
internal/gperr/utils_test.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package gperr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testErr struct{}
|
||||||
|
|
||||||
|
func (e *testErr) Error() string {
|
||||||
|
return "test error"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *testErr) MarshalJSON() ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsJSONMarshallable(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "testErr",
|
||||||
|
err: &testErr{},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "baseError",
|
||||||
|
err: &baseError{},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nestedError",
|
||||||
|
err: &nestedError{},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "withSubject",
|
||||||
|
err: &withSubject{},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "standard error",
|
||||||
|
err: errors.New("test error"),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
if got := IsJSONMarshallable(test.err); got != test.want {
|
||||||
|
t.Errorf("IsJSONMarshallable(%v) = %v, want %v", test.err, got, test.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal"
|
"github.com/yusing/go-proxy/internal"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -31,7 +31,7 @@ const (
|
||||||
IconSourceSelfhSt
|
IconSourceSelfhSt
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrInvalidIconURL = E.New("invalid icon url")
|
var ErrInvalidIconURL = gperr.New("invalid icon url")
|
||||||
|
|
||||||
func NewSelfhStIconURL(reference, format string) *IconURL {
|
func NewSelfhStIconURL(reference, format string) *IconURL {
|
||||||
return &IconURL{
|
return &IconURL{
|
||||||
|
|
|
@ -11,9 +11,10 @@ import (
|
||||||
|
|
||||||
"github.com/coder/websocket"
|
"github.com/coder/websocket"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/gpwebsocket"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
)
|
)
|
||||||
|
@ -27,8 +28,9 @@ type memLogger struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
notifyLock sync.RWMutex
|
notifyLock sync.RWMutex
|
||||||
connChans F.Map[chan *logEntryRange, struct{}]
|
connChans F.Map[chan *logEntryRange, struct{}]
|
||||||
|
listeners F.Map[chan []byte, struct{}]
|
||||||
|
|
||||||
bufPool sync.Pool // used in hook mode
|
bufPool sync.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
type MemLogger io.Writer
|
type MemLogger io.Writer
|
||||||
|
@ -41,15 +43,16 @@ const (
|
||||||
maxMemLogSize = 16 * 1024
|
maxMemLogSize = 16 * 1024
|
||||||
truncateSize = maxMemLogSize / 2
|
truncateSize = maxMemLogSize / 2
|
||||||
initialWriteChunkSize = 4 * 1024
|
initialWriteChunkSize = 4 * 1024
|
||||||
hookModeBufSize = 256
|
bufPoolSize = 256
|
||||||
)
|
)
|
||||||
|
|
||||||
var memLoggerInstance = &memLogger{
|
var memLoggerInstance = &memLogger{
|
||||||
connChans: F.NewMapOf[chan *logEntryRange, struct{}](),
|
connChans: F.NewMapOf[chan *logEntryRange, struct{}](),
|
||||||
|
listeners: F.NewMapOf[chan []byte, struct{}](),
|
||||||
bufPool: sync.Pool{
|
bufPool: sync.Pool{
|
||||||
New: func() any {
|
New: func() any {
|
||||||
return &buffer{
|
return &buffer{
|
||||||
data: make([]byte, 0, hookModeBufSize),
|
data: make([]byte, 0, bufPoolSize),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -92,6 +95,10 @@ func HandlerFunc() http.HandlerFunc {
|
||||||
return memLoggerInstance.ServeHTTP
|
return memLoggerInstance.ServeHTTP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Events() (<-chan []byte, func()) {
|
||||||
|
return memLoggerInstance.events()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *memLogger) truncateIfNeeded(n int) {
|
func (m *memLogger) truncateIfNeeded(n int) {
|
||||||
m.RLock()
|
m.RLock()
|
||||||
needTruncate := m.Len()+n > maxMemLogSize
|
needTruncate := m.Len()+n > maxMemLogSize
|
||||||
|
@ -111,7 +118,7 @@ func (m *memLogger) truncateIfNeeded(n int) {
|
||||||
|
|
||||||
func (m *memLogger) notifyWS(pos, n int) {
|
func (m *memLogger) notifyWS(pos, n int) {
|
||||||
if m.connChans.Size() > 0 {
|
if m.connChans.Size() > 0 {
|
||||||
timeout := time.NewTimer(1 * time.Second)
|
timeout := time.NewTimer(2 * time.Second)
|
||||||
defer timeout.Stop()
|
defer timeout.Stop()
|
||||||
|
|
||||||
m.notifyLock.RLock()
|
m.notifyLock.RLock()
|
||||||
|
@ -125,6 +132,19 @@ func (m *memLogger) notifyWS(pos, n int) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if m.listeners.Size() > 0 {
|
||||||
|
msg := make([]byte, n)
|
||||||
|
copy(msg, m.Buffer.Bytes()[pos:pos+n])
|
||||||
|
m.listeners.Range(func(ch chan []byte, _ struct{}) bool {
|
||||||
|
select {
|
||||||
|
case <-timeout.C:
|
||||||
|
logging.Warn().Msg("mem logger: timeout logging to channel")
|
||||||
|
return false
|
||||||
|
case ch <- msg:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,9 +173,9 @@ func (m *memLogger) Write(p []byte) (n int, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memLogger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (m *memLogger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
conn, err := utils.InitiateWS(w, r)
|
conn, err := gpwebsocket.Initiate(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,13 +192,27 @@ func (m *memLogger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := m.wsInitial(r.Context(), conn); err != nil {
|
if err := m.wsInitial(r.Context(), conn); err != nil {
|
||||||
utils.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m.wsStreamLog(r.Context(), conn, logCh)
|
m.wsStreamLog(r.Context(), conn, logCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *memLogger) events() (<-chan []byte, func()) {
|
||||||
|
ch := make(chan []byte, 10)
|
||||||
|
m.notifyLock.Lock()
|
||||||
|
defer m.notifyLock.Unlock()
|
||||||
|
m.listeners.Store(ch, struct{}{})
|
||||||
|
|
||||||
|
return ch, func() {
|
||||||
|
m.notifyLock.Lock()
|
||||||
|
defer m.notifyLock.Unlock()
|
||||||
|
m.listeners.Delete(ch)
|
||||||
|
close(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *memLogger) writeBytes(ctx context.Context, conn *websocket.Conn, b []byte) error {
|
func (m *memLogger) writeBytes(ctx context.Context, conn *websocket.Conn, b []byte) error {
|
||||||
return conn.Write(ctx, websocket.MessageText, b)
|
return conn.Write(ctx, websocket.MessageText, b)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,10 @@ import (
|
||||||
|
|
||||||
"github.com/coder/websocket"
|
"github.com/coder/websocket"
|
||||||
"github.com/coder/websocket/wsjson"
|
"github.com/coder/websocket/wsjson"
|
||||||
"github.com/yusing/go-proxy/internal/api/v1/utils"
|
|
||||||
metricsutils "github.com/yusing/go-proxy/internal/metrics/utils"
|
metricsutils "github.com/yusing/go-proxy/internal/metrics/utils"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/httpheaders"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/gpwebsocket"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServeHTTP serves the data for the given period.
|
// ServeHTTP serves the data for the given period.
|
||||||
|
@ -36,7 +37,7 @@ func (p *Poller[T, AggregateT]) ServeHTTP(w http.ResponseWriter, r *http.Request
|
||||||
if interval < minInterval {
|
if interval < minInterval {
|
||||||
interval = minInterval
|
interval = minInterval
|
||||||
}
|
}
|
||||||
utils.PeriodicWS(w, r, interval, func(conn *websocket.Conn) error {
|
gpwebsocket.Periodic(w, r, interval, func(conn *websocket.Conn) error {
|
||||||
data, err := p.getRespData(r)
|
data, err := p.getRespData(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -49,14 +50,14 @@ func (p *Poller[T, AggregateT]) ServeHTTP(w http.ResponseWriter, r *http.Request
|
||||||
} else {
|
} else {
|
||||||
data, err := p.getRespData(r)
|
data, err := p.getRespData(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if data == nil {
|
if data == nil {
|
||||||
http.Error(w, "no data", http.StatusNoContent)
|
http.Error(w, "no data", http.StatusNoContent)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
utils.RespondJSON(w, r, data)
|
gphttp.RespondJSON(w, r, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
)
|
)
|
||||||
|
@ -84,7 +84,7 @@ func (p *Poller[T, AggregateT]) gatherErrs() (string, bool) {
|
||||||
if len(p.errs) == 0 {
|
if len(p.errs) == 0 {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
errs := E.NewBuilder(fmt.Sprintf("poller %s has encountered %d errors in the last %s:", p.name, len(p.errs), gatherErrsInterval))
|
errs := gperr.NewBuilder(fmt.Sprintf("poller %s has encountered %d errors in the last %s:", p.name, len(p.errs), gatherErrsInterval))
|
||||||
for _, e := range p.errs {
|
for _, e := range p.errs {
|
||||||
errs.Addf("%w: %d times", e.err, e.count)
|
errs.Addf("%w: %d times", e.err, e.count)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"github.com/shirou/gopsutil/v4/net"
|
"github.com/shirou/gopsutil/v4/net"
|
||||||
"github.com/shirou/gopsutil/v4/sensors"
|
"github.com/shirou/gopsutil/v4/sensors"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/metrics/period"
|
"github.com/yusing/go-proxy/internal/metrics/period"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
|
@ -40,7 +40,7 @@ func _() { // check if this behavior is not changed
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSystemInfo(ctx context.Context, lastResult *SystemInfo) (*SystemInfo, error) {
|
func getSystemInfo(ctx context.Context, lastResult *SystemInfo) (*SystemInfo, error) {
|
||||||
errs := E.NewBuilder("failed to get system info")
|
errs := gperr.NewBuilder("failed to get system info")
|
||||||
var systemInfo SystemInfo
|
var systemInfo SystemInfo
|
||||||
|
|
||||||
if !common.MetricsDisableCPU {
|
if !common.MetricsDisableCPU {
|
||||||
|
@ -95,8 +95,8 @@ func getSystemInfo(ctx context.Context, lastResult *SystemInfo) (*SystemInfo, er
|
||||||
}
|
}
|
||||||
|
|
||||||
if errs.HasError() {
|
if errs.HasError() {
|
||||||
allWarnings := E.NewBuilder("")
|
allWarnings := gperr.NewBuilder("")
|
||||||
allErrors := E.NewBuilder("failed to get system info")
|
allErrors := gperr.NewBuilder("failed to get system info")
|
||||||
errs.ForEach(func(err error) {
|
errs.ForEach(func(err error) {
|
||||||
// disk.Warnings has the same type
|
// disk.Warnings has the same type
|
||||||
// all Warnings are alias of common.Warnings from "github.com/shirou/gopsutil/v4/internal/common"
|
// all Warnings are alias of common.Warnings from "github.com/shirou/gopsutil/v4/internal/common"
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
)
|
)
|
||||||
|
@ -131,7 +131,7 @@ func (l *AccessLogger) Flush(force bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *AccessLogger) handleErr(err error) {
|
func (l *AccessLogger) handleErr(err error) {
|
||||||
E.LogError("failed to write access log", err)
|
gperr.LogError("failed to write access log", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *AccessLogger) start() {
|
func (l *AccessLogger) start() {
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/yusing/go-proxy/internal/net/http/accesslog"
|
. "github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/docker"
|
"github.com/yusing/go-proxy/internal/docker"
|
||||||
. "github.com/yusing/go-proxy/internal/net/http/accesslog"
|
. "github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
|
||||||
"github.com/yusing/go-proxy/internal/utils"
|
"github.com/yusing/go-proxy/internal/utils"
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
|
@ -3,7 +3,7 @@ package accesslog_test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/yusing/go-proxy/internal/net/http/accesslog"
|
. "github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/net/types"
|
"github.com/yusing/go-proxy/internal/net/types"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
)
|
)
|
||||||
|
@ -27,7 +27,7 @@ type (
|
||||||
CIDR struct{ types.CIDR }
|
CIDR struct{ types.CIDR }
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrInvalidHTTPHeaderFilter = E.New("invalid http header filter")
|
var ErrInvalidHTTPHeaderFilter = gperr.New("invalid http header filter")
|
||||||
|
|
||||||
func (f *LogFilter[T]) CheckKeep(req *http.Request, res *http.Response) bool {
|
func (f *LogFilter[T]) CheckKeep(req *http.Request, res *http.Response) bool {
|
||||||
if len(f.Values) == 0 {
|
if len(f.Values) == 0 {
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/yusing/go-proxy/internal/net/http/accesslog"
|
. "github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,8 +19,8 @@ type Retention struct {
|
||||||
const chunkSizeMax int64 = 128 * 1024 // 128KB
|
const chunkSizeMax int64 = 128 * 1024 // 128KB
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidSyntax = E.New("invalid syntax")
|
ErrInvalidSyntax = gperr.New("invalid syntax")
|
||||||
ErrZeroValue = E.New("zero value")
|
ErrZeroValue = gperr.New("zero value")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Syntax:
|
// Syntax:
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/yusing/go-proxy/internal/net/http/accesslog"
|
. "github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
|
@ -3,7 +3,7 @@ package accesslog
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ type StatusCodeRange struct {
|
||||||
End int
|
End int
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrInvalidStatusCodeRange = E.New("invalid status code range")
|
var ErrInvalidStatusCodeRange = gperr.New("invalid status code range")
|
||||||
|
|
||||||
func (r *StatusCodeRange) Includes(code int) bool {
|
func (r *StatusCodeRange) Includes(code int) bool {
|
||||||
return r.Start <= code && code <= r.End
|
return r.Start <= code && code <= r.End
|
||||||
|
@ -25,7 +25,7 @@ func (r *StatusCodeRange) Parse(v string) error {
|
||||||
case 1:
|
case 1:
|
||||||
start, err := strconv.Atoi(split[0])
|
start, err := strconv.Atoi(split[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.From(err)
|
return gperr.Wrap(err)
|
||||||
}
|
}
|
||||||
r.Start = start
|
r.Start = start
|
||||||
r.End = start
|
r.End = start
|
||||||
|
@ -33,7 +33,7 @@ func (r *StatusCodeRange) Parse(v string) error {
|
||||||
case 2:
|
case 2:
|
||||||
start, errStart := strconv.Atoi(split[0])
|
start, errStart := strconv.Atoi(split[0])
|
||||||
end, errEnd := strconv.Atoi(split[1])
|
end, errEnd := strconv.Atoi(split[1])
|
||||||
if err := E.Join(errStart, errEnd); err != nil {
|
if err := gperr.Join(errStart, errEnd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.Start = start
|
r.Start = start
|
|
@ -1,7 +1,9 @@
|
||||||
package utils
|
package gphttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
@ -10,7 +12,13 @@ import (
|
||||||
|
|
||||||
func WriteBody(w http.ResponseWriter, body []byte) {
|
func WriteBody(w http.ResponseWriter, body []byte) {
|
||||||
if _, err := w.Write(body); err != nil {
|
if _, err := w.Write(body); err != nil {
|
||||||
logging.Err(err).Msg("failed to write body")
|
switch {
|
||||||
|
case errors.Is(err, http.ErrHandlerTimeout),
|
||||||
|
errors.Is(err, context.DeadlineExceeded):
|
||||||
|
logging.Err(err).Msg("timeout writing body")
|
||||||
|
default:
|
||||||
|
logging.Err(err).Msg("failed to write body")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,13 +33,13 @@ func RespondJSON(w http.ResponseWriter, r *http.Request, data any, code ...int)
|
||||||
case string:
|
case string:
|
||||||
_, err = w.Write([]byte(fmt.Sprintf("%q", data)))
|
_, err = w.Write([]byte(fmt.Sprintf("%q", data)))
|
||||||
case []byte:
|
case []byte:
|
||||||
_, err = w.Write(data)
|
panic("use WriteBody instead")
|
||||||
default:
|
default:
|
||||||
err = json.NewEncoder(w).Encode(data)
|
err = json.NewEncoder(w).Encode(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
HandleErr(w, r, err)
|
LogError(r).Err(err).Msg("failed to encode json")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
|
@ -1,4 +1,4 @@
|
||||||
package http
|
package gphttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mime"
|
"mime"
|
|
@ -1,4 +1,4 @@
|
||||||
package http
|
package gphttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
|
@ -1,22 +1,21 @@
|
||||||
package utils
|
package gphttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
httpClient = &http.Client{
|
httpClient = &http.Client{
|
||||||
Timeout: common.ConnectionTimeout,
|
Timeout: 5 * time.Second,
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DisableKeepAlives: true,
|
DisableKeepAlives: true,
|
||||||
ForceAttemptHTTP2: false,
|
ForceAttemptHTTP2: false,
|
||||||
DialContext: (&net.Dialer{
|
DialContext: (&net.Dialer{
|
||||||
Timeout: common.DialTimeout,
|
Timeout: 3 * time.Second,
|
||||||
KeepAlive: common.KeepAlive, // this is different from DisableKeepAlives
|
KeepAlive: 60 * time.Second, // this is different from DisableKeepAlives
|
||||||
}).DialContext,
|
}).DialContext,
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
},
|
},
|
95
internal/net/gphttp/error.go
Normal file
95
internal/net/gphttp/error.go
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package gphttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServerError is for handling server errors.
|
||||||
|
//
|
||||||
|
// It logs the error and returns http.StatusInternalServerError to the client.
|
||||||
|
// Status code can be specified as an argument.
|
||||||
|
func ServerError(w http.ResponseWriter, r *http.Request, err error, code ...int) {
|
||||||
|
switch {
|
||||||
|
case err == nil,
|
||||||
|
errors.Is(err, context.Canceled),
|
||||||
|
errors.Is(err, syscall.EPIPE),
|
||||||
|
errors.Is(err, syscall.ECONNRESET):
|
||||||
|
return
|
||||||
|
}
|
||||||
|
LogError(r).Msg(err.Error())
|
||||||
|
if httpheaders.IsWebsocket(r.Header) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(code) == 0 {
|
||||||
|
code = []int{http.StatusInternalServerError}
|
||||||
|
}
|
||||||
|
http.Error(w, http.StatusText(code[0]), code[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientError is for responding to client errors.
|
||||||
|
//
|
||||||
|
// It returns http.StatusBadRequest with reason to the client.
|
||||||
|
// Status code can be specified as an argument.
|
||||||
|
//
|
||||||
|
// For JSON marshallable errors (e.g. gperr.Error), it returns the error details as JSON.
|
||||||
|
// Otherwise, it returns the error details as plain text.
|
||||||
|
func ClientError(w http.ResponseWriter, err error, code ...int) {
|
||||||
|
if len(code) == 0 {
|
||||||
|
code = []int{http.StatusBadRequest}
|
||||||
|
}
|
||||||
|
if gperr.IsJSONMarshallable(err) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(err)
|
||||||
|
} else {
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
}
|
||||||
|
http.Error(w, err.Error(), code[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONError returns a JSON response of gperr.Error with the given status code.
|
||||||
|
func JSONError(w http.ResponseWriter, err gperr.Error, code int) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(err)
|
||||||
|
http.Error(w, err.Error(), code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BadRequest returns a Bad Request response with the given error message.
|
||||||
|
func BadRequest(w http.ResponseWriter, err string, code ...int) {
|
||||||
|
if len(code) == 0 {
|
||||||
|
code = []int{http.StatusBadRequest}
|
||||||
|
}
|
||||||
|
http.Error(w, err, code[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unauthorized returns an Unauthorized response with the given error message.
|
||||||
|
func Unauthorized(w http.ResponseWriter, err string) {
|
||||||
|
BadRequest(w, err, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotFound returns a Not Found response with the given error message.
|
||||||
|
func NotFound(w http.ResponseWriter, err string) {
|
||||||
|
BadRequest(w, err, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrMissingKey(k string) error {
|
||||||
|
return gperr.New(k + " is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrInvalidKey(k string) error {
|
||||||
|
return gperr.New(k + " is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrAlreadyExists(k, v string) error {
|
||||||
|
return gperr.Errorf("%s %q already exists", k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrNotFound(k, v string) error {
|
||||||
|
return gperr.Errorf("%s %q not found", k, v)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package utils
|
package gpwebsocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -7,8 +7,10 @@ import (
|
||||||
|
|
||||||
"github.com/coder/websocket"
|
"github.com/coder/websocket"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/httpheaders"
|
"github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
|
||||||
)
|
)
|
||||||
|
|
||||||
func warnNoMatchDomains() {
|
func warnNoMatchDomains() {
|
||||||
|
@ -17,7 +19,7 @@ func warnNoMatchDomains() {
|
||||||
|
|
||||||
var warnNoMatchDomainOnce sync.Once
|
var warnNoMatchDomainOnce sync.Once
|
||||||
|
|
||||||
func InitiateWS(w http.ResponseWriter, r *http.Request) (*websocket.Conn, error) {
|
func Initiate(w http.ResponseWriter, r *http.Request) (*websocket.Conn, error) {
|
||||||
var originPats []string
|
var originPats []string
|
||||||
|
|
||||||
localAddresses := []string{"127.0.0.1", "10.0.*.*", "172.16.*.*", "192.168.*.*"}
|
localAddresses := []string{"127.0.0.1", "10.0.*.*", "172.16.*.*", "192.168.*.*"}
|
||||||
|
@ -42,17 +44,17 @@ func InitiateWS(w http.ResponseWriter, r *http.Request) (*websocket.Conn, error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func PeriodicWS(w http.ResponseWriter, r *http.Request, interval time.Duration, do func(conn *websocket.Conn) error) {
|
func Periodic(w http.ResponseWriter, r *http.Request, interval time.Duration, do func(conn *websocket.Conn) error) {
|
||||||
conn, err := InitiateWS(w, r)
|
conn, err := Initiate(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//nolint:errcheck
|
//nolint:errcheck
|
||||||
defer conn.CloseNow()
|
defer conn.CloseNow()
|
||||||
|
|
||||||
if err := do(conn); err != nil {
|
if err := do(conn); err != nil {
|
||||||
HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,9 +67,20 @@ func PeriodicWS(w http.ResponseWriter, r *http.Request, interval time.Duration,
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
if err := do(conn); err != nil {
|
if err := do(conn); err != nil {
|
||||||
HandleErr(w, r, err)
|
gphttp.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteText writes a text message to the websocket connection.
|
||||||
|
// It returns true if the message was written successfully, false otherwise.
|
||||||
|
// It logs an error if the message is not written successfully.
|
||||||
|
func WriteText(r *http.Request, conn *websocket.Conn, msg string) bool {
|
||||||
|
if err := conn.Write(r.Context(), websocket.MessageText, []byte(msg)); err != nil {
|
||||||
|
gperr.LogError("failed to write text message", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -17,13 +17,15 @@ const (
|
||||||
HeaderXForwardedURI = "X-Forwarded-Uri"
|
HeaderXForwardedURI = "X-Forwarded-Uri"
|
||||||
HeaderXRealIP = "X-Real-IP"
|
HeaderXRealIP = "X-Real-IP"
|
||||||
|
|
||||||
HeaderUpstreamName = "X-GoDoxy-Upstream-Name"
|
|
||||||
HeaderUpstreamScheme = "X-GoDoxy-Upstream-Scheme"
|
|
||||||
HeaderUpstreamHost = "X-GoDoxy-Upstream-Host"
|
|
||||||
HeaderUpstreamPort = "X-GoDoxy-Upstream-Port"
|
|
||||||
|
|
||||||
HeaderContentType = "Content-Type"
|
HeaderContentType = "Content-Type"
|
||||||
HeaderContentLength = "Content-Length"
|
HeaderContentLength = "Content-Length"
|
||||||
|
|
||||||
|
HeaderUpstreamName = "X-Godoxy-Upstream-Name"
|
||||||
|
HeaderUpstreamScheme = "X-Godoxy-Upstream-Scheme"
|
||||||
|
HeaderUpstreamHost = "X-Godoxy-Upstream-Host"
|
||||||
|
HeaderUpstreamPort = "X-Godoxy-Upstream-Port"
|
||||||
|
|
||||||
|
HeaderGoDoxyCheckRedirect = "X-Godoxy-Check-Redirect"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hop-by-hop headers. These are removed when sent to the backend.
|
// Hop-by-hop headers. These are removed when sent to the backend.
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
"github.com/yusing/go-proxy/internal/net/gphttp/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ipHash struct {
|
type ipHash struct {
|
||||||
|
@ -23,10 +23,10 @@ func (lb *LoadBalancer) newIPHash() impl {
|
||||||
if len(lb.Options) == 0 {
|
if len(lb.Options) == 0 {
|
||||||
return impl
|
return impl
|
||||||
}
|
}
|
||||||
var err E.Error
|
var err gperr.Error
|
||||||
impl.realIP, err = middleware.RealIP.New(lb.Options)
|
impl.realIP, err = middleware.RealIP.New(lb.Options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
E.LogError("invalid real_ip options, ignoring", err, &impl.l)
|
gperr.LogError("invalid real_ip options, ignoring", err, &impl.l)
|
||||||
}
|
}
|
||||||
return impl
|
return impl
|
||||||
}
|
}
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
|
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
|
||||||
"github.com/yusing/go-proxy/internal/route/routes"
|
"github.com/yusing/go-proxy/internal/route/routes"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||||
|
@ -54,7 +54,7 @@ func New(cfg *Config) *LoadBalancer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start implements task.TaskStarter.
|
// Start implements task.TaskStarter.
|
||||||
func (lb *LoadBalancer) Start(parent task.Parent) E.Error {
|
func (lb *LoadBalancer) Start(parent task.Parent) gperr.Error {
|
||||||
lb.startTime = time.Now()
|
lb.startTime = time.Now()
|
||||||
lb.task = parent.Subtask("loadbalancer."+lb.Link, false)
|
lb.task = parent.Subtask("loadbalancer."+lb.Link, false)
|
||||||
parent.OnCancel("lb_remove_route", func() {
|
parent.OnCancel("lb_remove_route", func() {
|
||||||
|
@ -227,7 +227,7 @@ func (lb *LoadBalancer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(rw, "Service unavailable", http.StatusServiceUnavailable)
|
http.Error(rw, "Service unavailable", http.StatusServiceUnavailable)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if r.Header.Get(common.HeaderCheckRedirect) != "" {
|
if r.Header.Get(httpheaders.HeaderGoDoxyCheckRedirect) != "" {
|
||||||
// wake all servers
|
// wake all servers
|
||||||
for _, srv := range srvs {
|
for _, srv := range srvs {
|
||||||
if err := srv.TryWake(); err != nil {
|
if err := srv.TryWake(); err != nil {
|
|
@ -3,7 +3,7 @@ package loadbalancer
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
|
"github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package loadbalancer
|
package loadbalancer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
|
"github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
|
@ -1,4 +1,4 @@
|
||||||
package utils
|
package gphttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
|
@ -1,4 +1,4 @@
|
||||||
package http
|
package gphttp
|
||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
"github.com/yusing/go-proxy/internal/net/types"
|
"github.com/yusing/go-proxy/internal/net/types"
|
||||||
"github.com/yusing/go-proxy/internal/utils"
|
"github.com/yusing/go-proxy/internal/utils"
|
||||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/utils"
|
"github.com/yusing/go-proxy/internal/utils"
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
@ -61,7 +61,7 @@ func TestCIDRWhitelistValidation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCIDRWhitelist(t *testing.T) {
|
func TestCIDRWhitelist(t *testing.T) {
|
||||||
errs := E.NewBuilder("")
|
errs := gperr.NewBuilder("")
|
||||||
mids := BuildMiddlewaresFromYAML("", testCIDRWhitelistCompose, errs)
|
mids := BuildMiddlewaresFromYAML("", testCIDRWhitelistCompose, errs)
|
||||||
ExpectNoError(t, errs.Error())
|
ExpectNoError(t, errs.Error())
|
||||||
deny = mids["deny@file"]
|
deny = mids["deny@file"]
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/httpheaders"
|
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware/errorpage"
|
"github.com/yusing/go-proxy/internal/net/gphttp/middleware/errorpage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type customErrorPage struct{}
|
type customErrorPage struct{}
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
|
@ -90,7 +90,7 @@ func watchDir() {
|
||||||
loadContent()
|
loadContent()
|
||||||
}
|
}
|
||||||
case err := <-errCh:
|
case err := <-errCh:
|
||||||
E.LogError("error watching error page directory", err)
|
gperr.LogError("error watching error page directory", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue