refactor and organize code

This commit is contained in:
yusing 2025-02-15 05:44:47 +08:00
parent 1af6dd9cf8
commit 18d258aaa2
169 changed files with 1020 additions and 755 deletions

View file

@ -6,7 +6,7 @@ import (
"github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/agent/pkg/env"
"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/task"
"github.com/yusing/go-proxy/pkg"
@ -16,7 +16,7 @@ func main() {
args := os.Args
if len(args) > 1 && args[1] == "migrate" {
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
}
@ -24,21 +24,21 @@ func main() {
ca := &agent.PEMPair{}
err := ca.Load(env.AgentCACert)
if err != nil {
E.LogFatal("init CA error", err)
gperr.LogFatal("init CA error", err)
}
caCert, err := ca.ToTLSCert()
if err != nil {
E.LogFatal("init CA error", err)
gperr.LogFatal("init CA error", err)
}
srv := &agent.PEMPair{}
srv.Load(env.AgentSSLCert)
if err != nil {
E.LogFatal("init SSL error", err)
gperr.LogFatal("init SSL error", err)
}
srvCert, err := srv.ToTLSCert()
if err != nil {
E.LogFatal("init SSL error", err)
gperr.LogFatal("init SSL error", err)
}
logging.Info().Msgf("GoDoxy Agent version %s", pkg.GetVersion())

View file

@ -12,9 +12,9 @@ import (
"github.com/rs/zerolog"
"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"
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/task"
"github.com/yusing/go-proxy/pkg"
@ -80,17 +80,17 @@ func checkVersion(a, b string) bool {
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)
if err != nil {
return E.Wrap(err)
return err
}
// create tls config
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(ca)
if !ok {
return E.New("invalid CA certificate")
return gperr.New("invalid CA certificate")
}
cfg.tlsConfig = &tls.Config{
@ -108,17 +108,17 @@ func (cfg *AgentConfig) StartWithCerts(parent task.Parent, ca, crt, key []byte)
// check agent version
version, _, err := cfg.Fetch(ctx, EndpointVersion)
if err != nil {
return E.Wrap(err)
return err
}
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
name, _, err := cfg.Fetch(ctx, EndpointName)
if err != nil {
return E.Wrap(err)
return err
}
cfg.name = string(name)
@ -128,18 +128,18 @@ func (cfg *AgentConfig) StartWithCerts(parent task.Parent, ca, crt, key []byte)
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))
if err != nil {
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)
if err != nil {
return E.Wrap(err)
return gperr.Wrap(err)
}
return cfg.StartWithCerts(parent, ca, crt, key)

View file

@ -9,7 +9,7 @@ import (
_ "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/utils"
"gopkg.in/yaml.v3"
@ -110,11 +110,11 @@ func MigrateFromOld() error {
composeConfig.SSLCert = agentCert.String()
composeTemplate, err := composeConfig.Generate()
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 {
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:")

View file

@ -7,7 +7,7 @@ import (
"os"
"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/watcher/health"
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
@ -72,5 +72,5 @@ func CheckHealth(w http.ResponseWriter, r *http.Request) {
return
}
apiUtils.RespondJSON(w, r, result)
gphttp.RespondJSON(w, r, result)
}

View file

@ -8,7 +8,7 @@ import (
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/docker"
"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"
)

View file

@ -8,10 +8,10 @@ import (
"time"
"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"
gphttp "github.com/yusing/go-proxy/internal/net/http"
"github.com/yusing/go-proxy/internal/net/http/reverseproxy"
"github.com/yusing/go-proxy/internal/net/gphttp"
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/utils/strutils"
)

View file

@ -14,7 +14,7 @@ import (
"github.com/yusing/go-proxy/agent/pkg/env"
"github.com/yusing/go-proxy/agent/pkg/handler"
"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"
)

View file

@ -11,10 +11,10 @@ import (
"github.com/yusing/go-proxy/internal/api/v1/query"
"github.com/yusing/go-proxy/internal/common"
"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/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/task"
"github.com/yusing/go-proxy/pkg"
@ -32,7 +32,7 @@ func main() {
return
case common.CommandReload:
if err := query.ReloadServer(); err != nil {
E.LogFatal("server reload error", err)
gperr.LogFatal("server reload error", err)
}
rawLogger.Println("ok")
return
@ -88,9 +88,9 @@ func main() {
middleware.LoadComposeFiles()
var cfg *config.Config
var err E.Error
var err gperr.Error
if cfg, err = config.Load(); err != nil {
E.LogWarn("errors in config", err)
gperr.LogWarn("errors in config", err)
}
switch args.Command {

View file

@ -13,7 +13,7 @@ import (
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/logging/memlogger"
"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"
)

View file

@ -6,18 +6,19 @@ import (
"github.com/coder/websocket"
"github.com/coder/websocket/wsjson"
U "github.com/yusing/go-proxy/internal/api/v1/utils"
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) {
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())
return nil
})
} else {
U.RespondJSON(w, r, cfg.ListAgents())
gphttp.RespondJSON(w, r, cfg.ListAgents())
}
}

View file

@ -3,9 +3,9 @@ package auth
import (
"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/logging"
"github.com/yusing/go-proxy/internal/net/gphttp"
)
var defaultAuth Provider
@ -44,7 +44,7 @@ func RequireAuth(next http.HandlerFunc) http.HandlerFunc {
if IsEnabled() {
return func(w http.ResponseWriter, r *http.Request) {
if err := defaultAuth.CheckToken(r); err != nil {
U.RespondError(w, err, http.StatusUnauthorized)
gphttp.ClientError(w, err, http.StatusUnauthorized)
} else {
next(w, r)
}

View file

@ -12,9 +12,8 @@ import (
"time"
"github.com/coreos/go-oidc/v3/oidc"
U "github.com/yusing/go-proxy/internal/api/v1/utils"
"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"
"github.com/yusing/go-proxy/internal/utils/strutils"
"golang.org/x/oauth2"
@ -154,7 +153,7 @@ func generateState() (string, error) {
func (auth *OIDCProvider) RedirectLoginPage(w http.ResponseWriter, r *http.Request) {
state, err := generateState()
if err != nil {
U.HandleErr(w, r, err, http.StatusInternalServerError)
gphttp.ServerError(w, r, err)
return
}
http.SetCookie(w, &http.Cookie{
@ -171,7 +170,7 @@ func (auth *OIDCProvider) RedirectLoginPage(w http.ResponseWriter, r *http.Reque
if auth.isMiddleware {
u, err := r.URL.Parse(redirURL)
if err != nil {
U.HandleErr(w, r, err, http.StatusInternalServerError)
gphttp.ServerError(w, r, err)
return
}
q := u.Query()
@ -201,31 +200,31 @@ func (auth *OIDCProvider) LoginCallbackHandler(w http.ResponseWriter, r *http.Re
state, err := r.Cookie(CookieOauthState)
if err != nil {
U.HandleErr(w, r, E.New("missing state cookie"), http.StatusBadRequest)
gphttp.BadRequest(w, "missing state cookie")
return
}
query := r.URL.Query()
if query.Get("state") != state.Value {
U.HandleErr(w, r, E.New("invalid oauth state"), http.StatusBadRequest)
gphttp.BadRequest(w, "invalid oauth state")
return
}
oauth2Token, err := auth.exchange(r)
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
}
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
U.HandleErr(w, r, E.New("missing id_token"), http.StatusInternalServerError)
gphttp.BadRequest(w, "missing id_token")
return
}
idToken, err := auth.oidcVerifier.Verify(r.Context(), rawIDToken)
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
}
@ -243,7 +242,7 @@ func (auth *OIDCProvider) LogoutCallbackHandler(w http.ResponseWriter, r *http.R
token, err := r.Cookie(auth.TokenCookieName())
if err != nil {
U.HandleErr(w, r, E.New("missing token cookie"), http.StatusBadRequest)
gphttp.BadRequest(w, "missing token cookie")
return
}
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) {
state, err := r.Cookie(CookieOauthState)
if err != nil {
U.HandleErr(w, r, E.New("missing state cookie"), http.StatusBadRequest)
gphttp.BadRequest(w, "missing state cookie")
return
}
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
}

View file

@ -7,16 +7,16 @@ import (
"time"
"github.com/golang-jwt/jwt/v5"
U "github.com/yusing/go-proxy/internal/api/v1/utils"
"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"
"golang.org/x/crypto/bcrypt"
)
var (
ErrInvalidUsername = E.New("invalid username")
ErrInvalidPassword = E.New("invalid password")
ErrInvalidUsername = gperr.New("invalid username")
ErrInvalidPassword = gperr.New("invalid password")
)
type (
@ -94,7 +94,7 @@ func (auth *UserPassAuth) CheckToken(r *http.Request) error {
case claims.Username != auth.username:
return ErrUserNotAllowed.Subject(claims.Username)
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
@ -111,17 +111,16 @@ func (auth *UserPassAuth) LoginCallbackHandler(w http.ResponseWriter, r *http.Re
}
err := json.NewDecoder(r.Body).Decode(&creds)
if err != nil {
U.HandleErr(w, r, err, http.StatusBadRequest)
gphttp.Unauthorized(w, "invalid credentials")
return
}
if err := auth.validatePassword(creds.User, creds.Pass); err != nil {
U.LogError(r).Err(err).Msg("auth: invalid credentials")
U.RespondError(w, E.New("invalid credentials"), http.StatusUnauthorized)
gphttp.Unauthorized(w, "invalid credentials")
return
}
token, err := auth.NewToken()
if err != nil {
U.HandleErr(w, r, err, http.StatusInternalServerError)
gphttp.ServerError(w, r, err)
return
}
setTokenCookie(w, r, auth.TokenCookieName(), token, auth.tokenTTL)

View file

@ -5,14 +5,14 @@ import (
"net/http"
"time"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
var (
ErrMissingToken = E.New("missing token")
ErrInvalidToken = E.New("invalid token")
ErrUserNotAllowed = E.New("user not allowed")
ErrMissingToken = gperr.New("missing token")
ErrInvalidToken = gperr.New("invalid token")
ErrUserNotAllowed = gperr.New("user not allowed")
)
// cookieFQDN returns the fully qualified domain name of the request host

View file

@ -13,10 +13,10 @@ import (
"github.com/PuerkitoBio/goquery"
"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/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"
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) {
url, alias := req.FormValue("url"), req.FormValue("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
}
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
}
@ -65,7 +65,7 @@ func GetFavIcon(w http.ResponseWriter, req *http.Request) {
if url != "" {
var iconURL homepage.IconURL
if err := iconURL.Parse(url); err != nil {
U.RespondError(w, err, http.StatusBadRequest)
gphttp.ClientError(w, err, http.StatusBadRequest)
return
}
fetchResult := getFavIconFromURL(&iconURL)
@ -74,14 +74,14 @@ func GetFavIcon(w http.ResponseWriter, req *http.Request) {
return
}
w.Header().Set("Content-Type", fetchResult.ContentType())
U.WriteBody(w, fetchResult.icon)
gphttp.WriteBody(w, fetchResult.icon)
return
}
// try with route.Homepage.Icon
r, ok := routes.GetHTTPRoute(alias)
if !ok {
U.RespondError(w, errors.New("no such route"), http.StatusNotFound)
gphttp.ClientError(w, errors.New("no such route"), http.StatusNotFound)
return
}
@ -105,7 +105,7 @@ func GetFavIcon(w http.ResponseWriter, req *http.Request) {
return
}
w.Header().Set("Content-Type", result.ContentType())
U.WriteBody(w, result.icon)
gphttp.WriteBody(w, result.icon)
}
func getFavIconFromURL(iconURL *homepage.IconURL) *fetchResult {
@ -125,7 +125,7 @@ func fetchIconAbsolute(url string) *fetchResult {
return result
}
resp, err := U.Get(url)
resp, err := gphttp.Get(url)
if err != nil || resp.StatusCode != http.StatusOK {
if err == nil {
err = errors.New(resp.Status)

View file

@ -7,11 +7,11 @@ import (
"path"
"strings"
U "github.com/yusing/go-proxy/internal/api/v1/utils"
"github.com/yusing/go-proxy/internal/common"
config "github.com/yusing/go-proxy/internal/config/types"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/net/http/middleware"
"github.com/yusing/go-proxy/internal/gperr"
"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"
)
@ -51,12 +51,12 @@ func (t FileType) GetPath(filename string) string {
func getArgs(r *http.Request) (fileType FileType, filename string, err error) {
fileType = FileType(r.PathValue("type"))
if !fileType.IsValid() {
err = U.ErrInvalidKey("type")
err = gphttp.ErrInvalidKey("type")
return
}
filename = r.PathValue("filename")
if filename == "" {
err = U.ErrMissingKey("filename")
err = gphttp.ErrMissingKey("filename")
}
return
}
@ -64,23 +64,23 @@ func getArgs(r *http.Request) (fileType FileType, filename string, err error) {
func GetFileContent(w http.ResponseWriter, r *http.Request) {
fileType, filename, err := getArgs(r)
if err != nil {
U.RespondError(w, err, http.StatusBadRequest)
gphttp.BadRequest(w, err.Error())
return
}
content, err := os.ReadFile(fileType.GetPath(filename))
if err != nil {
U.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
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 {
case FileTypeConfig:
return config.Validate(content)
case FileTypeMiddleware:
errs := E.NewBuilder("middleware errors")
errs := gperr.NewBuilder("middleware errors")
middleware.BuildMiddlewaresFromYAML("", content, errs)
return errs.Error()
}
@ -90,18 +90,17 @@ func validateFile(fileType FileType, content []byte) error {
func ValidateFile(w http.ResponseWriter, r *http.Request) {
fileType := FileType(r.PathValue("type"))
if !fileType.IsValid() {
U.RespondError(w, U.ErrInvalidKey("type"), http.StatusBadRequest)
gphttp.BadRequest(w, "invalid file type")
return
}
content, err := io.ReadAll(r.Body)
if err != nil {
U.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
r.Body.Close()
err = validateFile(fileType, content)
if err != nil {
U.RespondError(w, err, http.StatusBadRequest)
if valErr := validateFile(fileType, content); valErr != nil {
gphttp.JSONError(w, valErr, http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
@ -110,23 +109,23 @@ func ValidateFile(w http.ResponseWriter, r *http.Request) {
func SetFileContent(w http.ResponseWriter, r *http.Request) {
fileType, filename, err := getArgs(r)
if err != nil {
U.RespondError(w, err, http.StatusBadRequest)
gphttp.BadRequest(w, err.Error())
return
}
content, err := io.ReadAll(r.Body)
if err != nil {
U.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
if valErr := validateFile(fileType, content); valErr != nil {
U.RespondError(w, valErr, http.StatusBadRequest)
gphttp.JSONError(w, valErr, http.StatusBadRequest)
return
}
err = os.WriteFile(fileType.GetPath(filename), content, 0o644)
if err != nil {
U.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
w.WriteHeader(http.StatusOK)

View file

@ -6,17 +6,18 @@ import (
"github.com/coder/websocket"
"github.com/coder/websocket/wsjson"
U "github.com/yusing/go-proxy/internal/api/v1/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"
"github.com/yusing/go-proxy/internal/route/routes/routequery"
)
func Health(w http.ResponseWriter, r *http.Request) {
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())
})
} else {
U.RespondJSON(w, r, routequery.HealthMap())
gphttp.RespondJSON(w, r, routequery.HealthMap())
}
}

View file

@ -5,8 +5,8 @@ import (
"io"
"net/http"
"github.com/yusing/go-proxy/internal/api/v1/utils"
"github.com/yusing/go-proxy/internal/homepage"
"github.com/yusing/go-proxy/internal/net/gphttp"
)
const (
@ -37,13 +37,13 @@ type (
func SetHomePageOverrides(w http.ResponseWriter, r *http.Request) {
what := r.FormValue("what")
if what == "" {
http.Error(w, "missing what or which", http.StatusBadRequest)
gphttp.BadRequest(w, "missing what or which")
return
}
data, err := io.ReadAll(r.Body)
if err != nil {
utils.RespondError(w, err, http.StatusBadRequest)
gphttp.ClientError(w, err, http.StatusBadRequest)
return
}
r.Body.Close()
@ -53,21 +53,21 @@ func SetHomePageOverrides(w http.ResponseWriter, r *http.Request) {
case HomepageOverrideItem:
var params HomepageOverrideItemParams
if err := json.Unmarshal(data, &params); err != nil {
utils.RespondError(w, err, http.StatusBadRequest)
gphttp.ClientError(w, err, http.StatusBadRequest)
return
}
overrides.OverrideItem(params.Which, &params.Value)
case HomepageOverrideItemsBatch:
var params HomepageOverrideItemsBatchParams
if err := json.Unmarshal(data, &params); err != nil {
utils.RespondError(w, err, http.StatusBadRequest)
gphttp.ClientError(w, err, http.StatusBadRequest)
return
}
overrides.OverrideItems(params.Value)
case HomepageOverrideItemVisible: // POST /v1/item_visible [a,b,c], false => hide a, b, c
var params HomepageOverrideItemVisibleParams
if err := json.Unmarshal(data, &params); err != nil {
utils.RespondError(w, err, http.StatusBadRequest)
gphttp.ClientError(w, err, http.StatusBadRequest)
return
}
if params.Value {
@ -78,7 +78,7 @@ func SetHomePageOverrides(w http.ResponseWriter, r *http.Request) {
case HomepageOverrideCategoryOrder:
var params HomepageOverrideCategoryOrderParams
if err := json.Unmarshal(data, &params); err != nil {
utils.RespondError(w, err, http.StatusBadRequest)
gphttp.ClientError(w, err, http.StatusBadRequest)
return
}
overrides.SetCategoryOrder(params.Which, params.Value)

View file

@ -3,9 +3,9 @@ package v1
import (
"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) {
WriteBody(w, []byte("API ready"))
gphttp.WriteBody(w, []byte("API ready"))
}

View file

@ -1,15 +1,16 @@
package v1
import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/yusing/go-proxy/internal"
U "github.com/yusing/go-proxy/internal/api/v1/utils"
"github.com/yusing/go-proxy/internal/common"
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"
route "github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/task"
@ -41,26 +42,25 @@ func List(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
case ListRoute:
if route := listRoute(which); route == nil {
http.NotFound(w, r)
return
} else {
U.RespondJSON(w, r, route)
gphttp.RespondJSON(w, r, route)
}
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:
listFiles(w, r)
case ListMiddlewares:
U.RespondJSON(w, r, middleware.All())
gphttp.RespondJSON(w, r, middleware.All())
case ListMiddlewareTraces:
U.RespondJSON(w, r, middleware.GetAllTrace())
gphttp.RespondJSON(w, r, middleware.GetAllTrace())
case ListMatchDomains:
U.RespondJSON(w, r, cfg.Value().MatchDomains)
gphttp.RespondJSON(w, r, cfg.Value().MatchDomains)
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:
U.RespondJSON(w, r, cfg.RouteProviderList())
gphttp.RespondJSON(w, r, cfg.RouteProviderList())
case ListHomepageCategories:
U.RespondJSON(w, r, routequery.HomepageCategories())
gphttp.RespondJSON(w, r, routequery.HomepageCategories())
case ListIcons:
limit, err := strconv.Atoi(r.FormValue("limit"))
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)
if err != nil {
U.RespondError(w, err)
gphttp.ClientError(w, err)
return
}
if icons == nil {
icons = []string{}
}
U.RespondJSON(w, r, icons)
gphttp.RespondJSON(w, r, icons)
case ListTasks:
U.RespondJSON(w, r, task.DebugTaskList())
gphttp.RespondJSON(w, r, task.DebugTaskList())
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) {
files, err := utils.ListFiles(common.ConfigBasePath, 0, true)
if err != nil {
U.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
resp := map[FileType][]string{
@ -116,12 +116,12 @@ func listFiles(w http.ResponseWriter, r *http.Request) {
mids, err := utils.ListFiles(common.MiddlewareComposeBasePath, 0, true)
if err != nil {
U.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
for _, mid := range mids {
mid = strings.TrimPrefix(mid, common.MiddlewareComposeBasePath+"/")
resp[FileTypeMiddleware] = append(resp[FileTypeMiddleware], mid)
}
U.RespondJSON(w, r, resp)
gphttp.RespondJSON(w, r, resp)
}

View file

@ -12,8 +12,9 @@ import (
"github.com/yusing/go-proxy/agent/pkg/agent"
"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"
"github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/net/gphttp"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
@ -21,27 +22,27 @@ func NewAgent(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
name := q.Get("name")
if name == "" {
U.RespondError(w, U.ErrMissingKey("name"))
gphttp.ClientError(w, gphttp.ErrMissingKey("name"))
return
}
host := q.Get("host")
if host == "" {
U.RespondError(w, U.ErrMissingKey("host"))
gphttp.ClientError(w, gphttp.ErrMissingKey("host"))
return
}
portStr := q.Get("port")
if portStr == "" {
U.RespondError(w, U.ErrMissingKey("port"))
gphttp.ClientError(w, gphttp.ErrMissingKey("port"))
return
}
port, err := strconv.Atoi(portStr)
if err != nil || port < 1 || port > 65535 {
U.RespondError(w, U.ErrInvalidKey("port"))
gphttp.ClientError(w, gphttp.ErrInvalidKey("port"))
return
}
hostport := fmt.Sprintf("%s:%d", host, port)
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
}
t := q.Get("type")
@ -49,13 +50,13 @@ func NewAgent(w http.ResponseWriter, r *http.Request) {
case "docker":
break
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
case "":
U.RespondError(w, U.ErrMissingKey("type"))
gphttp.ClientError(w, gphttp.ErrMissingKey("type"))
return
default:
U.RespondError(w, U.ErrInvalidKey("type"))
gphttp.ClientError(w, gphttp.ErrInvalidKey("type"))
return
}
@ -69,7 +70,7 @@ func NewAgent(w http.ResponseWriter, r *http.Request) {
ca, srv, client, err := agent.NewAgent()
if err != nil {
U.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
@ -83,11 +84,11 @@ func NewAgent(w http.ResponseWriter, r *http.Request) {
template, err := cfg.Generate()
if err != nil {
U.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
U.RespondJSON(w, r, map[string]any{
gphttp.RespondJSON(w, r, map[string]any{
"compose": template,
"ca": ca,
"client": client,
@ -98,7 +99,7 @@ func AddAgent(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
clientPEMData, err := io.ReadAll(r.Body)
if err != nil {
U.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
@ -109,24 +110,24 @@ func AddAgent(w http.ResponseWriter, r *http.Request) {
}
if err := json.Unmarshal(clientPEMData, &data); err != nil {
U.RespondError(w, err, http.StatusBadRequest)
gphttp.ClientError(w, err, http.StatusBadRequest)
return
}
nRoutesAdded, err := config.GetInstance().AddAgent(data.Host, data.CA, data.Client)
if err != nil {
U.RespondError(w, err)
gphttp.ClientError(w, err)
return
}
zip, err := certs.ZipCert(data.CA.Cert, data.Client.Cert, data.Client.Key)
if err != nil {
U.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
if err := os.WriteFile(certs.AgentCertsFilename(data.Host), zip, 0600); err != nil {
U.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}

View file

@ -7,20 +7,20 @@ import (
"net/http"
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"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/net/http/middleware"
"github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/net/gphttp"
"github.com/yusing/go-proxy/internal/net/gphttp/middleware"
)
func ReloadServer() E.Error {
resp, err := U.Post(common.APIHTTPURL+"/v1/reload", "", nil)
func ReloadServer() gperr.Error {
resp, err := gphttp.Post(common.APIHTTPURL+"/v1/reload", "", nil)
if err != nil {
return E.From(err)
return gperr.Wrap(err)
}
defer resp.Body.Close()
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)
if err != nil {
return failure.With(err)
@ -31,34 +31,34 @@ func ReloadServer() E.Error {
return nil
}
func List[T any](what string) (_ T, outErr E.Error) {
resp, err := U.Get(fmt.Sprintf("%s/v1/list/%s", common.APIHTTPURL, what))
func List[T any](what string) (_ T, outErr gperr.Error) {
resp, err := gphttp.Get(fmt.Sprintf("%s/v1/list/%s", common.APIHTTPURL, what))
if err != nil {
outErr = E.From(err)
outErr = gperr.Wrap(err)
return
}
defer resp.Body.Close()
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
}
var res T
err = json.NewDecoder(resp.Body).Decode(&res)
if err != nil {
outErr = E.From(err)
outErr = gperr.Wrap(err)
return
}
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)
}
func ListMiddlewareTraces() (middleware.Traces, E.Error) {
func ListMiddlewareTraces() (middleware.Traces, gperr.Error) {
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)
}

View file

@ -3,14 +3,14 @@ package v1
import (
"net/http"
U "github.com/yusing/go-proxy/internal/api/v1/utils"
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) {
if err := cfg.Reload(); err != nil {
U.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
U.WriteBody(w, []byte("OK"))
gphttp.WriteBody(w, []byte("OK"))
}

View file

@ -6,19 +6,20 @@ import (
"github.com/coder/websocket"
"github.com/coder/websocket/wsjson"
U "github.com/yusing/go-proxy/internal/api/v1/utils"
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"
)
func Stats(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
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))
})
} else {
U.RespondJSON(w, r, getStats(cfg))
gphttp.RespondJSON(w, r, getStats(cfg))
}
}

View file

@ -4,12 +4,12 @@ import (
"net/http"
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"
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/net/http/httpheaders"
"github.com/yusing/go-proxy/internal/net/http/reverseproxy"
"github.com/yusing/go-proxy/internal/net/gphttp"
"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) {
@ -23,7 +23,7 @@ func SystemInfo(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Reques
agent, ok := cfg.GetAgent(agentAddr)
if !ok {
U.HandleErr(w, r, U.ErrInvalidKey("agent_addr"), http.StatusNotFound)
gphttp.NotFound(w, "agent_addr")
return
}
@ -31,20 +31,20 @@ func SystemInfo(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Reques
if !isWS {
respData, status, err := agent.Forward(r, agentPkg.EndpointSystemInfo)
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
}
if status != http.StatusOK {
http.Error(w, string(respData), status)
return
}
U.WriteBody(w, respData)
gphttp.WriteBody(w, respData)
} else {
rp := reverseproxy.NewReverseProxy("agent", agentPkg.AgentURL, agent.Transport())
header := r.Header.Clone()
r, err := http.NewRequestWithContext(r.Context(), r.Method, agentPkg.EndpointSystemInfo+"?"+query.Encode(), 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
}
r.Header = header

View file

@ -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)
}

View file

@ -3,10 +3,10 @@ package v1
import (
"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"
)
func GetVersion(w http.ResponseWriter, r *http.Request) {
WriteBody(w, []byte(pkg.GetVersion()))
gphttp.WriteBody(w, []byte(pkg.GetVersion()))
}

View file

@ -10,7 +10,7 @@ import (
"github.com/go-acme/lego/v4/certcrypto"
"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/utils"
"github.com/yusing/go-proxy/internal/utils/strutils"
@ -30,17 +30,17 @@ type (
)
var (
ErrMissingDomain = E.New("missing field 'domains'")
ErrMissingEmail = E.New("missing field 'email'")
ErrMissingProvider = E.New("missing field 'provider'")
ErrInvalidDomain = E.New("invalid domain")
ErrUnknownProvider = E.New("unknown provider")
ErrMissingDomain = gperr.New("missing field 'domains'")
ErrMissingEmail = gperr.New("missing field 'email'")
ErrMissingProvider = gperr.New("missing field 'provider'")
ErrInvalidDomain = gperr.New("invalid domain")
ErrUnknownProvider = gperr.New("unknown provider")
)
var domainOrWildcardRE = regexp.MustCompile(`^\*?([^.]+\.)+[^.]+$`)
// Validate implements the utils.CustomValidator interface.
func (cfg *AutocertConfig) Validate() E.Error {
func (cfg *AutocertConfig) Validate() gperr.Error {
if cfg == nil {
return nil
}
@ -50,7 +50,7 @@ func (cfg *AutocertConfig) Validate() E.Error {
return nil
}
b := E.NewBuilder("autocert errors")
b := gperr.NewBuilder("autocert errors")
if cfg.Provider != ProviderLocal {
if len(cfg.Domains) == 0 {
b.Add(ErrMissingDomain)
@ -79,7 +79,7 @@ func (cfg *AutocertConfig) Validate() E.Error {
return b.Error()
}
func (cfg *AutocertConfig) GetProvider() (*Provider, E.Error) {
func (cfg *AutocertConfig) GetProvider() (*Provider, gperr.Error) {
if cfg == nil {
cfg = new(AutocertConfig)
}
@ -107,10 +107,10 @@ func (cfg *AutocertConfig) GetProvider() (*Provider, E.Error) {
logging.Info().Msg("generate new ACME private key")
privKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
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 {
return nil, E.New("save ACME private key").With(err)
return nil, gperr.New("save ACME private key").With(err)
}
}
}

View file

@ -4,6 +4,7 @@ import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"os"
"path"
"reflect"
@ -14,7 +15,7 @@ import (
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/lego"
"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/task"
U "github.com/yusing/go-proxy/internal/utils"
@ -32,7 +33,7 @@ type (
tlsCert *tls.Certificate
certExpiries CertExpiries
}
ProviderGenerator func(ProviderOpt) (challenge.Provider, E.Error)
ProviderGenerator func(ProviderOpt) (challenge.Provider, gperr.Error)
CertExpiries map[string]time.Time
)
@ -62,7 +63,7 @@ func (p *Provider) GetExpiries() CertExpiries {
return p.certExpiries
}
func (p *Provider) ObtainCert() E.Error {
func (p *Provider) ObtainCert() error {
if p.cfg.Provider == ProviderLocal {
return nil
}
@ -75,7 +76,7 @@ func (p *Provider) ObtainCert() E.Error {
if p.user.Registration == 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,
})
if err != nil {
return E.From(err)
return err
}
}
if err = p.saveCert(cert); err != nil {
return E.From(err)
return err
}
tlsCert, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
if err != nil {
return E.From(err)
return err
}
expiries, err := getCertExpiries(&tlsCert)
if err != nil {
return E.From(err)
return err
}
p.tlsCert = &tlsCert
p.certExpiries = expiries
@ -123,14 +124,14 @@ func (p *Provider) ObtainCert() E.Error {
return nil
}
func (p *Provider) LoadCert() E.Error {
func (p *Provider) LoadCert() error {
cert, err := tls.LoadX509KeyPair(p.cfg.CertPath, p.cfg.KeyPath)
if err != nil {
return E.Errorf("load SSL certificate: %w", err)
return fmt.Errorf("load SSL certificate: %w", err)
}
expiries, err := getCertExpiries(&cert)
if err != nil {
return E.Errorf("parse SSL certificate: %w", err)
return fmt.Errorf("parse SSL certificate: %w", err)
}
p.tlsCert = &cert
p.certExpiries = expiries
@ -171,7 +172,7 @@ func (p *Provider) ScheduleRenewal(parent task.Parent) {
continue
}
if err := p.renewIfNeeded(); err != nil {
E.LogWarn("cert renew failed", err)
gperr.LogWarn("cert renew failed", err)
lastErrOn = time.Now()
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)
if err != nil {
return E.From(err)
return err
}
generator := providersGenMap[p.cfg.Provider]
@ -198,7 +199,7 @@ func (p *Provider) initClient() E.Error {
err = legoClient.Challenge.SetDNS01Provider(legoProvider)
if err != nil {
return E.From(err)
return err
}
p.client = legoClient
@ -273,7 +274,7 @@ func (p *Provider) certState() CertState {
return CertStateValid
}
func (p *Provider) renewIfNeeded() E.Error {
func (p *Provider) renewIfNeeded() error {
if p.cfg.Provider == ProviderLocal {
return nil
}
@ -312,13 +313,13 @@ func providerGenerator[CT any, PT challenge.Provider](
defaultCfg func() *CT,
newProvider func(*CT) (PT, error),
) ProviderGenerator {
return func(opt ProviderOpt) (challenge.Provider, E.Error) {
return func(opt ProviderOpt) (challenge.Provider, gperr.Error) {
cfg := defaultCfg()
err := U.Deserialize(opt, &cfg)
if err != nil {
return nil, err
}
p, pErr := newProvider(cfg)
return p, E.From(pErr)
return p, gperr.Wrap(pErr)
}
}

View file

@ -1,16 +1,16 @@
package autocert
import (
"errors"
"os"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/logging"
"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.Is(os.ErrNotExist) { // ignore if cert doesn't exist
if !errors.Is(err, os.ErrNotExist) { // ignore if cert doesn't exist
return err
}
logging.Debug().Msg("obtaining cert due to error loading cert")

View file

@ -4,12 +4,6 @@ import (
"time"
)
const (
ConnectionTimeout = 5 * time.Second
DialTimeout = 3 * time.Second
KeepAlive = 60 * time.Second
)
// file, folder structure
const (
@ -50,5 +44,3 @@ const (
StopTimeoutDefault = "30s"
StopMethodDefault = "stop"
)
const HeaderCheckRedirect = "X-Goproxy-Check-Redirect"

View file

@ -2,7 +2,7 @@ package config
import (
"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/utils/functional"
)
@ -29,12 +29,12 @@ func (cfg *Config) GetAgent(agentAddrOrDockerHost string) (*agent.AgentConfig, b
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
agentCfg.Addr = host
err := agentCfg.StartWithCerts(cfg.Task(), ca.Cert, client.Cert, client.Key)
if err != nil {
return 0, err
return 0, gperr.Wrap(err, "failed to start agent")
}
provider := provider.NewAgentProvider(&agentCfg)

View file

@ -14,9 +14,9 @@ import (
"github.com/yusing/go-proxy/internal/common"
config "github.com/yusing/go-proxy/internal/config/types"
"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/net/http/server"
"github.com/yusing/go-proxy/internal/net/gphttp/server"
"github.com/yusing/go-proxy/internal/notif"
proxy "github.com/yusing/go-proxy/internal/route/provider"
"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() {
panic(errors.New("config already loaded"))
}
@ -84,8 +84,8 @@ func WatchChanges() {
t,
configEventFlushInterval,
OnConfigChange,
func(err E.Error) {
E.LogError("config reload error", err)
func(err gperr.Error) {
gperr.LogError("config reload error", err)
},
)
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
reloadMu.Lock()
defer reloadMu.Unlock()
@ -118,7 +118,7 @@ func Reload() E.Error {
err := newCfg.load()
if err != nil {
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
@ -133,10 +133,13 @@ func (cfg *Config) Value() *config.Config {
return cfg.value
}
func (cfg *Config) Reload() E.Error {
func (cfg *Config) Reload() gperr.Error {
return Reload()
}
// AutoCertProvider returns the autocert provider.
//
// If the autocert provider is not configured, it returns nil.
func (cfg *Config) AutoCertProvider() *autocert.Provider {
return cfg.autocertProvider
}
@ -163,7 +166,7 @@ func (cfg *Config) StartAutoCert() {
}
if err := autocert.Setup(); err != nil {
E.LogFatal("autocert setup error", err)
gperr.LogFatal("autocert setup error", err)
} else {
autocert.ScheduleRenewal(cfg.task)
}
@ -175,8 +178,8 @@ func (cfg *Config) StartProxyProviders() {
return p.Start(cfg.task)
})
if err := E.Join(errs...); err != nil {
E.LogError("route provider errors", err)
if err := gperr.Join(errs...); err != nil {
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"
data, err := os.ReadFile(common.ConfigPath)
if err != nil {
E.LogFatal(errMsg, err)
gperr.LogFatal(errMsg, err)
}
model := config.DefaultConfig()
if err := utils.DeserializeYAML(data, model); err != nil {
E.LogFatal(errMsg, err)
gperr.LogFatal(errMsg, err)
}
// 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.SetAccessLogger(cfg.task, model.Entrypoint.AccessLog))
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 {
return
}
@ -261,9 +264,9 @@ func (cfg *Config) initAutoCert(autocertCfg *autocert.AutocertConfig) (err E.Err
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 {
return E.Errorf("provider %s already exists", p.String())
return gperr.Errorf("provider %s already exists", p.String())
}
return nil
}
@ -272,9 +275,9 @@ func (cfg *Config) storeProvider(p *proxy.Provider) {
cfg.providers.Store(p.String(), p)
}
func (cfg *Config) loadRouteProviders(providers *config.Providers) E.Error {
errs := E.NewBuilder("route provider errors")
results := E.NewBuilder("loaded route providers")
func (cfg *Config) loadRouteProviders(providers *config.Providers) gperr.Error {
errs := gperr.NewBuilder("route provider errors")
results := gperr.NewBuilder("loaded route providers")
removeAllAgents()
@ -297,7 +300,7 @@ func (cfg *Config) loadRouteProviders(providers *config.Providers) E.Error {
err = cfg.errIfExists(p)
}
if err != nil {
errs.Add(E.PrependSubject(filename, err))
errs.Add(gperr.PrependSubject(filename, err))
continue
}
cfg.storeProvider(p)

View file

@ -8,11 +8,10 @@ import (
"github.com/go-playground/validator/v10"
"github.com/yusing/go-proxy/agent/pkg/agent"
"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/utils"
E "github.com/yusing/go-proxy/internal/error"
)
type (
@ -37,12 +36,12 @@ type (
ConfigInstance interface {
Value() *Config
Reload() E.Error
Reload() gperr.Error
Statistics() map[string]any
RouteProviderList() []string
Context() context.Context
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
}
)
@ -79,7 +78,7 @@ func HasInstance() bool {
return instance != nil
}
func Validate(data []byte) E.Error {
func Validate(data []byte) gperr.Error {
var model Config
return utils.DeserializeYAML(data, &model)
}

View file

@ -6,7 +6,7 @@ import (
"strings"
"text/template"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
)
type templateData struct {
@ -23,11 +23,11 @@ func (w *Watcher) makeLoadingPageBody() []byte {
msg := w.ContainerName + " is starting..."
data := new(templateData)
data.CheckRedirectHeader = common.HeaderCheckRedirect
data.CheckRedirectHeader = httpheaders.HeaderGoDoxyCheckRedirect
data.Title = w.ContainerName
data.Message = strings.ReplaceAll(msg, " ", "&ensp;")
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)
if err != nil { // should never happen in production
panic(err)

View file

@ -7,7 +7,7 @@ import (
"time"
"github.com/yusing/go-proxy/internal/docker"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/gperr"
)
type (
@ -40,7 +40,7 @@ var validSignals = map[string]struct{}{
"INT": {}, "TERM": {}, "HUP": {}, "QUIT": {},
}
func ValidateConfig(cont *docker.Container) (*Config, E.Error) {
func ValidateConfig(cont *docker.Container) (*Config, gperr.Error) {
if cont == nil {
return nil, nil
}
@ -54,14 +54,14 @@ func ValidateConfig(cont *docker.Container) (*Config, E.Error) {
}, nil
}
errs := E.NewBuilder("invalid idlewatcher config")
errs := gperr.NewBuilder("invalid idlewatcher config")
idleTimeout := E.Collect(errs, validateDurationPostitive, cont.IdleTimeout)
wakeTimeout := E.Collect(errs, validateDurationPostitive, cont.WakeTimeout)
stopTimeout := E.Collect(errs, validateDurationPostitive, cont.StopTimeout)
stopMethod := E.Collect(errs, validateStopMethod, cont.StopMethod)
signal := E.Collect(errs, validateSignal, cont.StopSignal)
startEndpoint := E.Collect(errs, validateStartEndpoint, cont.StartEndpoint)
idleTimeout := gperr.Collect(errs, validateDurationPostitive, cont.IdleTimeout)
wakeTimeout := gperr.Collect(errs, validateDurationPostitive, cont.WakeTimeout)
stopTimeout := gperr.Collect(errs, validateDurationPostitive, cont.StopTimeout)
stopMethod := gperr.Collect(errs, validateStopMethod, cont.StopMethod)
signal := gperr.Collect(errs, validateSignal, cont.StopSignal)
startEndpoint := gperr.Collect(errs, validateStartEndpoint, cont.StartEndpoint)
if errs.HasError() {
return nil, errs.Error()

View file

@ -5,9 +5,9 @@ import (
"time"
"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/net/http/reverseproxy"
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
net "github.com/yusing/go-proxy/internal/net/types"
route "github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/task"
@ -37,7 +37,7 @@ const (
// 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.Timeout = idleWakerCheckTimeout
@ -48,7 +48,7 @@ func newWaker(parent task.Parent, route route.Route, rp *reverseproxy.ReversePro
task := parent.Subtask("idlewatcher." + route.TargetName())
watcher, err := registerWatcher(task, route, waker)
if err != nil {
return nil, E.Errorf("register watcher: %w", err)
return nil, gperr.Errorf("register watcher: %w", err)
}
switch {
@ -66,16 +66,16 @@ func newWaker(parent task.Parent, route route.Route, rp *reverseproxy.ReversePro
}
// 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)
}
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)
}
// 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() {
parent.Finish(w.task.FinishCause())
if w.metric != nil {

View file

@ -7,8 +7,8 @@ import (
"strconv"
"time"
"github.com/yusing/go-proxy/internal/common"
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/gphttp/httpheaders"
"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)
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 {
// Send a loading response to the client
body := w.makeLoadingPageBody()

View file

@ -10,7 +10,7 @@ import (
"github.com/rs/zerolog"
D "github.com/yusing/go-proxy/internal/docker"
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"
route "github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/task"
@ -180,7 +180,7 @@ func (w *Watcher) wakeIfStopped() error {
case "running":
return nil
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)
}
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{
Filters: watcher.NewDockerFilter(
watcher.DockerFilterContainer,
@ -251,7 +251,7 @@ func (w *Watcher) watchUntilDestroy() (returnCause error) {
return w.task.FinishCause()
case err := <-dockerEventErrCh:
if !err.Is(context.Canceled) {
E.LogError("idlewatcher error", err, &w.Logger)
gperr.LogError("idlewatcher error", err, &w.Logger)
}
return err
case e := <-dockerEventCh:

View file

@ -1,17 +1,17 @@
package docker
import (
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
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)
errs := E.NewBuilder("labels error")
errs := gperr.NewBuilder("labels error")
for lbl, value := range labels {
parts := strutils.SplitRune(lbl, '.')
@ -37,7 +37,7 @@ func ParseLabels(labels map[string]string) (LabelMap, E.Error) {
// Move deeper into the nested map
m, ok := currentMap[k].(LabelMap)
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
} else if !ok {
m = make(LabelMap)

View file

@ -7,10 +7,10 @@ import (
"strings"
"github.com/yusing/go-proxy/internal/logging"
gphttp "github.com/yusing/go-proxy/internal/net/http"
"github.com/yusing/go-proxy/internal/net/http/accesslog"
"github.com/yusing/go-proxy/internal/net/http/middleware"
"github.com/yusing/go-proxy/internal/net/http/middleware/errorpage"
gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
"github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
"github.com/yusing/go-proxy/internal/net/gphttp/middleware"
"github.com/yusing/go-proxy/internal/net/gphttp/middleware/errorpage"
"github.com/yusing/go-proxy/internal/route/routes"
route "github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/task"

106
internal/gperr/README.md Normal file
View 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

View file

@ -1,4 +1,4 @@
package err
package gperr
import (
"encoding/json"

View file

@ -1,4 +1,4 @@
package err
package gperr
import (
"fmt"
@ -36,7 +36,7 @@ func (b *Builder) error() Error {
func (b *Builder) Error() Error {
if len(b.errs) == 1 {
return From(b.errs[0])
return wrap(b.errs[0])
}
return b.error()
}
@ -60,7 +60,7 @@ func (b *Builder) Add(err error) *Builder {
b.Lock()
defer b.Unlock()
switch err := From(err).(type) {
switch err := wrap(err).(type) {
case *baseError:
b.errs = append(b.errs, err.Err)
case *nestedError:

View file

@ -1,4 +1,4 @@
package err_test
package gperr_test
import (
"context"
@ -6,7 +6,7 @@ import (
"io"
"testing"
. "github.com/yusing/go-proxy/internal/error"
. "github.com/yusing/go-proxy/internal/gperr"
. "github.com/yusing/go-proxy/internal/utils/testing"
)

View file

@ -1,4 +1,4 @@
package err
package gperr
type Error interface {
error

View file

@ -1,4 +1,4 @@
package err
package gperr
import (
"errors"
@ -44,7 +44,7 @@ func TestBaseWithExtra(t *testing.T) {
func TestBaseUnwrap(t *testing.T) {
err := errors.New("err")
wrapped := From(err)
wrapped := Wrap(err)
ExpectError(t, err, errors.Unwrap(wrapped))
}
@ -52,7 +52,7 @@ func TestBaseUnwrap(t *testing.T) {
func TestNestedUnwrap(t *testing.T) {
err := errors.New("err")
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 })
ExpectTrue(t, ok)
@ -64,7 +64,7 @@ func TestNestedUnwrap(t *testing.T) {
func TestErrorIs(t *testing.T) {
from := errors.New("error")
err := From(from)
err := Wrap(from)
ExpectError(t, from, err)
ExpectTrue(t, err.Is(from))

View file

@ -1,4 +1,4 @@
package err
package gperr
import (
"github.com/rs/zerolog"
@ -6,42 +6,35 @@ import (
"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 {
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) {
if common.IsDebug {
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) {
getLogger(logger...).Error().Msg(err.Error())
log(msg, err, zerolog.ErrorLevel, logger...)
}
//go:inline
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) {
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) {
getLogger(logger...).Debug().Msg(err.Error())
log(msg, err, zerolog.DebugLevel, logger...)
}

View file

@ -1,4 +1,4 @@
package err
package gperr
import (
"errors"
@ -99,7 +99,7 @@ func makeLines(errs []error, level int) []string {
}
lines := make([]string, 0, len(errs))
for _, err := range errs {
switch err := From(err).(type) {
switch err := wrap(err).(type) {
case *nestedError:
if err.Err != nil {
lines = append(lines, makeLine(err.Err.Error(), level))

View file

@ -1,4 +1,4 @@
package err
package gperr
import (
"encoding/json"

View file

@ -1,6 +1,8 @@
package err
package gperr
import (
"encoding/json"
"errors"
"fmt"
)
@ -19,27 +21,50 @@ func Errorf(format string, args ...any) Error {
return &baseError{fmt.Errorf(format, args...)}
}
// Wrap wraps message in front of the error message.
func Wrap(err error, message ...string) Error {
if len(message) == 0 || message[0] == "" {
return From(err)
if err == nil {
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 {
return nil
}
//nolint:errorlint
switch err := err.(type) {
case *baseError:
return err
case *nestedError:
case Error:
return 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 {
n := 0
for _, err := range errors {

View 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)
}
})
}
}

View file

@ -5,7 +5,7 @@ import (
"strings"
"github.com/yusing/go-proxy/internal"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/gperr"
)
type (
@ -31,7 +31,7 @@ const (
IconSourceSelfhSt
)
var ErrInvalidIconURL = E.New("invalid icon url")
var ErrInvalidIconURL = gperr.New("invalid icon url")
func NewSelfhStIconURL(reference, format string) *IconURL {
return &IconURL{

View file

@ -11,9 +11,10 @@ import (
"github.com/coder/websocket"
"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/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"
F "github.com/yusing/go-proxy/internal/utils/functional"
)
@ -27,8 +28,9 @@ type memLogger struct {
sync.RWMutex
notifyLock sync.RWMutex
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
@ -41,15 +43,16 @@ const (
maxMemLogSize = 16 * 1024
truncateSize = maxMemLogSize / 2
initialWriteChunkSize = 4 * 1024
hookModeBufSize = 256
bufPoolSize = 256
)
var memLoggerInstance = &memLogger{
connChans: F.NewMapOf[chan *logEntryRange, struct{}](),
listeners: F.NewMapOf[chan []byte, struct{}](),
bufPool: sync.Pool{
New: func() any {
return &buffer{
data: make([]byte, 0, hookModeBufSize),
data: make([]byte, 0, bufPoolSize),
}
},
},
@ -92,6 +95,10 @@ func HandlerFunc() http.HandlerFunc {
return memLoggerInstance.ServeHTTP
}
func Events() (<-chan []byte, func()) {
return memLoggerInstance.events()
}
func (m *memLogger) truncateIfNeeded(n int) {
m.RLock()
needTruncate := m.Len()+n > maxMemLogSize
@ -111,7 +118,7 @@ func (m *memLogger) truncateIfNeeded(n int) {
func (m *memLogger) notifyWS(pos, n int) {
if m.connChans.Size() > 0 {
timeout := time.NewTimer(1 * time.Second)
timeout := time.NewTimer(2 * time.Second)
defer timeout.Stop()
m.notifyLock.RLock()
@ -125,6 +132,19 @@ func (m *memLogger) notifyWS(pos, n int) {
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
}
}
@ -153,9 +173,9 @@ func (m *memLogger) Write(p []byte) (n int, err error) {
}
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 {
utils.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
@ -172,13 +192,27 @@ func (m *memLogger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}()
if err := m.wsInitial(r.Context(), conn); err != nil {
utils.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
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 {
return conn.Write(ctx, websocket.MessageText, b)
}

View file

@ -7,9 +7,10 @@ import (
"github.com/coder/websocket"
"github.com/coder/websocket/wsjson"
"github.com/yusing/go-proxy/internal/api/v1/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.
@ -36,7 +37,7 @@ func (p *Poller[T, AggregateT]) ServeHTTP(w http.ResponseWriter, r *http.Request
if 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)
if err != nil {
return err
@ -49,14 +50,14 @@ func (p *Poller[T, AggregateT]) ServeHTTP(w http.ResponseWriter, r *http.Request
} else {
data, err := p.getRespData(r)
if err != nil {
utils.HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
if data == nil {
http.Error(w, "no data", http.StatusNoContent)
return
}
utils.RespondJSON(w, r, data)
gphttp.RespondJSON(w, r, data)
}
}

View file

@ -6,7 +6,7 @@ import (
"net/url"
"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/task"
)
@ -84,7 +84,7 @@ func (p *Poller[T, AggregateT]) gatherErrs() (string, bool) {
if len(p.errs) == 0 {
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 {
errs.Addf("%w: %d times", e.err, e.count)
}

View file

@ -12,7 +12,7 @@ import (
"github.com/shirou/gopsutil/v4/net"
"github.com/shirou/gopsutil/v4/sensors"
"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/metrics/period"
"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) {
errs := E.NewBuilder("failed to get system info")
errs := gperr.NewBuilder("failed to get system info")
var systemInfo SystemInfo
if !common.MetricsDisableCPU {
@ -95,8 +95,8 @@ func getSystemInfo(ctx context.Context, lastResult *SystemInfo) (*SystemInfo, er
}
if errs.HasError() {
allWarnings := E.NewBuilder("")
allErrors := E.NewBuilder("failed to get system info")
allWarnings := gperr.NewBuilder("")
allErrors := gperr.NewBuilder("failed to get system info")
errs.ForEach(func(err error) {
// disk.Warnings has the same type
// all Warnings are alias of common.Warnings from "github.com/shirou/gopsutil/v4/internal/common"

View file

@ -7,7 +7,7 @@ import (
"sync"
"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/task"
)
@ -131,7 +131,7 @@ func (l *AccessLogger) Flush(force bool) {
}
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() {

View file

@ -9,7 +9,7 @@ import (
"testing"
"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/utils/testing"
)

View file

@ -4,7 +4,7 @@ import (
"testing"
"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/testing"
)

View file

@ -3,7 +3,7 @@ package accesslog_test
import (
"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"
)

View file

@ -5,7 +5,7 @@ import (
"net/http"
"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/utils/strutils"
)
@ -27,7 +27,7 @@ type (
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 {
if len(f.Values) == 0 {

View file

@ -4,7 +4,7 @@ import (
"net/http"
"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/testing"
)

View file

@ -7,7 +7,7 @@ import (
"strconv"
"time"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
@ -19,8 +19,8 @@ type Retention struct {
const chunkSizeMax int64 = 128 * 1024 // 128KB
var (
ErrInvalidSyntax = E.New("invalid syntax")
ErrZeroValue = E.New("zero value")
ErrInvalidSyntax = gperr.New("invalid syntax")
ErrZeroValue = gperr.New("zero value")
)
// Syntax:

View file

@ -4,7 +4,7 @@ import (
"testing"
"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/utils/strutils"
. "github.com/yusing/go-proxy/internal/utils/testing"

View file

@ -3,7 +3,7 @@ package accesslog
import (
"strconv"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
@ -12,7 +12,7 @@ type StatusCodeRange struct {
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 {
return r.Start <= code && code <= r.End
@ -25,7 +25,7 @@ func (r *StatusCodeRange) Parse(v string) error {
case 1:
start, err := strconv.Atoi(split[0])
if err != nil {
return E.From(err)
return gperr.Wrap(err)
}
r.Start = start
r.End = start
@ -33,7 +33,7 @@ func (r *StatusCodeRange) Parse(v string) error {
case 2:
start, errStart := strconv.Atoi(split[0])
end, errEnd := strconv.Atoi(split[1])
if err := E.Join(errStart, errEnd); err != nil {
if err := gperr.Join(errStart, errEnd); err != nil {
return err
}
r.Start = start

View file

@ -1,7 +1,9 @@
package utils
package gphttp
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
@ -10,9 +12,15 @@ import (
func WriteBody(w http.ResponseWriter, body []byte) {
if _, err := w.Write(body); err != nil {
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")
}
}
}
func RespondJSON(w http.ResponseWriter, r *http.Request, data any, code ...int) (canProceed bool) {
if len(code) > 0 {
@ -25,13 +33,13 @@ func RespondJSON(w http.ResponseWriter, r *http.Request, data any, code ...int)
case string:
_, err = w.Write([]byte(fmt.Sprintf("%q", data)))
case []byte:
_, err = w.Write(data)
panic("use WriteBody instead")
default:
err = json.NewEncoder(w).Encode(data)
}
if err != nil {
HandleErr(w, r, err)
LogError(r).Err(err).Msg("failed to encode json")
return false
}
return true

View file

@ -1,4 +1,4 @@
package http
package gphttp
import (
"mime"

View file

@ -1,4 +1,4 @@
package http
package gphttp
import (
"net/http"

View file

@ -1,22 +1,21 @@
package utils
package gphttp
import (
"crypto/tls"
"net"
"net/http"
"github.com/yusing/go-proxy/internal/common"
"time"
)
var (
httpClient = &http.Client{
Timeout: common.ConnectionTimeout,
Timeout: 5 * time.Second,
Transport: &http.Transport{
DisableKeepAlives: true,
ForceAttemptHTTP2: false,
DialContext: (&net.Dialer{
Timeout: common.DialTimeout,
KeepAlive: common.KeepAlive, // this is different from DisableKeepAlives
Timeout: 3 * time.Second,
KeepAlive: 60 * time.Second, // this is different from DisableKeepAlives
}).DialContext,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},

View 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)
}

View file

@ -1,4 +1,4 @@
package utils
package gpwebsocket
import (
"net/http"
@ -7,8 +7,10 @@ import (
"github.com/coder/websocket"
"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/net/http/httpheaders"
"github.com/yusing/go-proxy/internal/net/gphttp"
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
)
func warnNoMatchDomains() {
@ -17,7 +19,7 @@ func warnNoMatchDomains() {
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
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) {
conn, err := InitiateWS(w, r)
func Periodic(w http.ResponseWriter, r *http.Request, interval time.Duration, do func(conn *websocket.Conn) error) {
conn, err := Initiate(w, r)
if err != nil {
HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
//nolint:errcheck
defer conn.CloseNow()
if err := do(conn); err != nil {
HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
return
}
@ -65,9 +67,20 @@ func PeriodicWS(w http.ResponseWriter, r *http.Request, interval time.Duration,
return
case <-ticker.C:
if err := do(conn); err != nil {
HandleErr(w, r, err)
gphttp.ServerError(w, r, err)
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
}

View file

@ -17,13 +17,15 @@ const (
HeaderXForwardedURI = "X-Forwarded-Uri"
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"
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.

View file

@ -6,8 +6,8 @@ import (
"net/http"
"sync"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/net/http/middleware"
"github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/net/gphttp/middleware"
)
type ipHash struct {
@ -23,10 +23,10 @@ func (lb *LoadBalancer) newIPHash() impl {
if len(lb.Options) == 0 {
return impl
}
var err E.Error
var err gperr.Error
impl.realIP, err = middleware.RealIP.New(lb.Options)
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
}

View file

@ -6,10 +6,10 @@ import (
"time"
"github.com/rs/zerolog"
"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/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/task"
"github.com/yusing/go-proxy/internal/watcher/health"
@ -54,7 +54,7 @@ func New(cfg *Config) *LoadBalancer {
}
// 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.task = parent.Subtask("loadbalancer."+lb.Link, false)
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)
return
}
if r.Header.Get(common.HeaderCheckRedirect) != "" {
if r.Header.Get(httpheaders.HeaderGoDoxyCheckRedirect) != "" {
// wake all servers
for _, srv := range srvs {
if err := srv.TryWake(); err != nil {

View file

@ -3,7 +3,7 @@ package loadbalancer
import (
"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"
)

View file

@ -1,7 +1,7 @@
package loadbalancer
import (
"github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
"github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
)
type (

View file

@ -1,4 +1,4 @@
package utils
package gphttp
import (
"net/http"

View file

@ -1,4 +1,4 @@
package http
package gphttp
import "net/http"

View file

@ -5,7 +5,7 @@ import (
"net/http"
"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/utils"
F "github.com/yusing/go-proxy/internal/utils/functional"

View file

@ -7,7 +7,7 @@ import (
"strings"
"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/testing"
)
@ -61,7 +61,7 @@ func TestCIDRWhitelistValidation(t *testing.T) {
}
func TestCIDRWhitelist(t *testing.T) {
errs := E.NewBuilder("")
errs := gperr.NewBuilder("")
mids := BuildMiddlewaresFromYAML("", testCIDRWhitelistCompose, errs)
ExpectNoError(t, errs.Error())
deny = mids["deny@file"]

View file

@ -9,9 +9,9 @@ import (
"strings"
"github.com/yusing/go-proxy/internal/logging"
gphttp "github.com/yusing/go-proxy/internal/net/http"
"github.com/yusing/go-proxy/internal/net/http/httpheaders"
"github.com/yusing/go-proxy/internal/net/http/middleware/errorpage"
gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
"github.com/yusing/go-proxy/internal/net/gphttp/middleware/errorpage"
)
type customErrorPage struct{}

View file

@ -7,7 +7,7 @@ import (
"sync"
"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/task"
U "github.com/yusing/go-proxy/internal/utils"
@ -90,7 +90,7 @@ func watchDir() {
loadContent()
}
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