mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-19 20:32:35 +02:00
fix incorrect reload behaviors, further organize code
This commit is contained in:
parent
8bbb5d2e09
commit
b3c47e759f
26 changed files with 418 additions and 336 deletions
|
@ -23,10 +23,10 @@ lint:
|
||||||
enabled:
|
enabled:
|
||||||
- hadolint@2.12.1-beta
|
- hadolint@2.12.1-beta
|
||||||
- actionlint@1.7.6
|
- actionlint@1.7.6
|
||||||
- checkov@3.2.347
|
- checkov@3.2.350
|
||||||
- git-diff-check
|
- git-diff-check
|
||||||
- gofmt@1.20.4
|
- gofmt@1.20.4
|
||||||
- golangci-lint@1.62.2
|
- golangci-lint@1.63.4
|
||||||
- osv-scanner@1.9.2
|
- osv-scanner@1.9.2
|
||||||
- oxipng@9.1.3
|
- oxipng@9.1.3
|
||||||
- prettier@3.4.2
|
- prettier@3.4.2
|
||||||
|
|
50
cmd/main.go
50
cmd/main.go
|
@ -3,23 +3,19 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal"
|
"github.com/yusing/go-proxy/internal"
|
||||||
"github.com/yusing/go-proxy/internal/api"
|
|
||||||
"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"
|
||||||
"github.com/yusing/go-proxy/internal/entrypoint"
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
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/metrics"
|
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/server"
|
"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/pkg"
|
"github.com/yusing/go-proxy/pkg"
|
||||||
)
|
)
|
||||||
|
@ -97,16 +93,16 @@ func main() {
|
||||||
switch args.Command {
|
switch args.Command {
|
||||||
case common.CommandListRoutes:
|
case common.CommandListRoutes:
|
||||||
cfg.StartProxyProviders()
|
cfg.StartProxyProviders()
|
||||||
printJSON(config.RoutesByAlias())
|
printJSON(routes.RoutesByAlias())
|
||||||
return
|
return
|
||||||
case common.CommandListConfigs:
|
case common.CommandListConfigs:
|
||||||
printJSON(config.Value())
|
printJSON(cfg.Value())
|
||||||
return
|
return
|
||||||
case common.CommandDebugListEntries:
|
case common.CommandDebugListEntries:
|
||||||
printJSON(config.DumpEntries())
|
printJSON(cfg.DumpEntries())
|
||||||
return
|
return
|
||||||
case common.CommandDebugListProviders:
|
case common.CommandDebugListProviders:
|
||||||
printJSON(config.DumpProviders())
|
printJSON(cfg.DumpProviders())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +110,7 @@ func main() {
|
||||||
logging.Warn().Msg("API JWT secret is empty, authentication is disabled")
|
logging.Warn().Msg("API JWT secret is empty, authentication is disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.StartProxyProviders()
|
cfg.Start()
|
||||||
config.WatchChanges()
|
config.WatchChanges()
|
||||||
|
|
||||||
sig := make(chan os.Signal, 1)
|
sig := make(chan os.Signal, 1)
|
||||||
|
@ -122,44 +118,12 @@ func main() {
|
||||||
signal.Notify(sig, syscall.SIGTERM)
|
signal.Notify(sig, syscall.SIGTERM)
|
||||||
signal.Notify(sig, syscall.SIGHUP)
|
signal.Notify(sig, syscall.SIGHUP)
|
||||||
|
|
||||||
autocert := config.GetAutoCertProvider()
|
|
||||||
if autocert != nil {
|
|
||||||
if err := autocert.Setup(); err != nil {
|
|
||||||
E.LogFatal("autocert setup error", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logging.Info().Msg("autocert not configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
server.StartServer(server.Options{
|
|
||||||
Name: "proxy",
|
|
||||||
CertProvider: autocert,
|
|
||||||
HTTPAddr: common.ProxyHTTPAddr,
|
|
||||||
HTTPSAddr: common.ProxyHTTPSAddr,
|
|
||||||
Handler: http.HandlerFunc(entrypoint.Handler),
|
|
||||||
})
|
|
||||||
server.StartServer(server.Options{
|
|
||||||
Name: "api",
|
|
||||||
CertProvider: autocert,
|
|
||||||
HTTPAddr: common.APIHTTPAddr,
|
|
||||||
Handler: api.NewHandler(),
|
|
||||||
})
|
|
||||||
|
|
||||||
if common.PrometheusEnabled {
|
|
||||||
server.StartServer(server.Options{
|
|
||||||
Name: "metrics",
|
|
||||||
CertProvider: autocert,
|
|
||||||
HTTPAddr: common.MetricsHTTPAddr,
|
|
||||||
Handler: metrics.NewHandler(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for signal
|
// wait for signal
|
||||||
<-sig
|
<-sig
|
||||||
|
|
||||||
// grafully shutdown
|
// grafully shutdown
|
||||||
logging.Info().Msg("shutting down")
|
logging.Info().Msg("shutting down")
|
||||||
_ = task.GracefulShutdown(time.Second * time.Duration(config.Value().TimeoutShutdown))
|
_ = task.GracefulShutdown(time.Second * time.Duration(cfg.Value().TimeoutShutdown))
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareDirectory(dir string) {
|
func prepareDirectory(dir string) {
|
||||||
|
|
|
@ -8,38 +8,41 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/api/v1/auth"
|
"github.com/yusing/go-proxy/internal/api/v1/auth"
|
||||||
. "github.com/yusing/go-proxy/internal/api/v1/utils"
|
. "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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServeMux struct{ *http.ServeMux }
|
type ServeMux struct{ *http.ServeMux }
|
||||||
|
|
||||||
func NewServeMux() ServeMux {
|
|
||||||
return ServeMux{http.NewServeMux()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mux ServeMux) HandleFunc(method, endpoint string, handler http.HandlerFunc) {
|
func (mux ServeMux) HandleFunc(method, endpoint string, handler http.HandlerFunc) {
|
||||||
mux.ServeMux.HandleFunc(method+" "+endpoint, checkHost(handler))
|
mux.ServeMux.HandleFunc(method+" "+endpoint, checkHost(handler))
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler() http.Handler {
|
func NewHandler(cfg config.ConfigInstance) http.Handler {
|
||||||
mux := NewServeMux()
|
mux := ServeMux{http.NewServeMux()}
|
||||||
mux.HandleFunc("GET", "/v1", v1.Index)
|
mux.HandleFunc("GET", "/v1", v1.Index)
|
||||||
mux.HandleFunc("GET", "/v1/version", v1.GetVersion)
|
mux.HandleFunc("GET", "/v1/version", v1.GetVersion)
|
||||||
mux.HandleFunc("POST", "/v1/login", auth.LoginHandler)
|
mux.HandleFunc("POST", "/v1/login", auth.LoginHandler)
|
||||||
mux.HandleFunc("GET", "/v1/logout", auth.LogoutHandler)
|
mux.HandleFunc("GET", "/v1/logout", auth.LogoutHandler)
|
||||||
mux.HandleFunc("POST", "/v1/logout", auth.LogoutHandler)
|
mux.HandleFunc("POST", "/v1/logout", auth.LogoutHandler)
|
||||||
mux.HandleFunc("POST", "/v1/reload", v1.Reload)
|
mux.HandleFunc("POST", "/v1/reload", useCfg(cfg, v1.Reload))
|
||||||
mux.HandleFunc("GET", "/v1/list", auth.RequireAuth(v1.List))
|
mux.HandleFunc("GET", "/v1/list", auth.RequireAuth(useCfg(cfg, v1.List)))
|
||||||
mux.HandleFunc("GET", "/v1/list/{what}", auth.RequireAuth(v1.List))
|
mux.HandleFunc("GET", "/v1/list/{what}", auth.RequireAuth(useCfg(cfg, v1.List)))
|
||||||
mux.HandleFunc("GET", "/v1/list/{what}/{which}", auth.RequireAuth(v1.List))
|
mux.HandleFunc("GET", "/v1/list/{what}/{which}", auth.RequireAuth(useCfg(cfg, v1.List)))
|
||||||
mux.HandleFunc("GET", "/v1/file/{type}/{filename}", auth.RequireAuth(v1.GetFileContent))
|
mux.HandleFunc("GET", "/v1/file/{type}/{filename}", auth.RequireAuth(v1.GetFileContent))
|
||||||
mux.HandleFunc("POST", "/v1/file/{type}/{filename}", auth.RequireAuth(v1.SetFileContent))
|
mux.HandleFunc("POST", "/v1/file/{type}/{filename}", auth.RequireAuth(v1.SetFileContent))
|
||||||
mux.HandleFunc("PUT", "/v1/file/{type}/{filename}", auth.RequireAuth(v1.SetFileContent))
|
mux.HandleFunc("PUT", "/v1/file/{type}/{filename}", auth.RequireAuth(v1.SetFileContent))
|
||||||
mux.HandleFunc("GET", "/v1/schema/{filename...}", v1.GetSchemaFile)
|
mux.HandleFunc("GET", "/v1/schema/{filename...}", v1.GetSchemaFile)
|
||||||
mux.HandleFunc("GET", "/v1/stats", v1.Stats)
|
mux.HandleFunc("GET", "/v1/stats", useCfg(cfg, v1.Stats))
|
||||||
mux.HandleFunc("GET", "/v1/stats/ws", v1.StatsWS)
|
mux.HandleFunc("GET", "/v1/stats/ws", useCfg(cfg, v1.StatsWS))
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func useCfg(cfg config.ConfigInstance, handler func(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request)) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
handler(cfg, w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// allow only requests to API server with localhost.
|
// allow only requests to API server with localhost.
|
||||||
func checkHost(f http.HandlerFunc) http.HandlerFunc {
|
func checkHost(f http.HandlerFunc) http.HandlerFunc {
|
||||||
if common.IsDebug {
|
if common.IsDebug {
|
||||||
|
@ -55,4 +58,4 @@ func checkHost(f http.HandlerFunc) http.HandlerFunc {
|
||||||
LogDebug(r).Interface("headers", r.Header).Msg("API request")
|
LogDebug(r).Interface("headers", r.Header).Msg("API request")
|
||||||
f(w, r)
|
f(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
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/config"
|
config "github.com/yusing/go-proxy/internal/config/types"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
||||||
"github.com/yusing/go-proxy/internal/route/provider"
|
"github.com/yusing/go-proxy/internal/route/provider"
|
||||||
|
|
|
@ -6,9 +6,10 @@ import (
|
||||||
|
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
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/config"
|
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/http/middleware"
|
||||||
"github.com/yusing/go-proxy/internal/route"
|
"github.com/yusing/go-proxy/internal/route/routes"
|
||||||
|
route "github.com/yusing/go-proxy/internal/route/types"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
"github.com/yusing/go-proxy/internal/utils"
|
"github.com/yusing/go-proxy/internal/utils"
|
||||||
)
|
)
|
||||||
|
@ -24,7 +25,7 @@ const (
|
||||||
ListTasks = "tasks"
|
ListTasks = "tasks"
|
||||||
)
|
)
|
||||||
|
|
||||||
func List(w http.ResponseWriter, r *http.Request) {
|
func List(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
||||||
what := r.PathValue("what")
|
what := r.PathValue("what")
|
||||||
if what == "" {
|
if what == "" {
|
||||||
what = ListRoutes
|
what = ListRoutes
|
||||||
|
@ -40,7 +41,7 @@ func List(w http.ResponseWriter, r *http.Request) {
|
||||||
U.RespondJSON(w, r, route)
|
U.RespondJSON(w, r, route)
|
||||||
}
|
}
|
||||||
case ListRoutes:
|
case ListRoutes:
|
||||||
U.RespondJSON(w, r, config.RoutesByAlias(route.RouteType(r.FormValue("type"))))
|
U.RespondJSON(w, r, routes.RoutesByAlias(route.RouteType(r.FormValue("type"))))
|
||||||
case ListFiles:
|
case ListFiles:
|
||||||
listFiles(w, r)
|
listFiles(w, r)
|
||||||
case ListMiddlewares:
|
case ListMiddlewares:
|
||||||
|
@ -48,9 +49,9 @@ func List(w http.ResponseWriter, r *http.Request) {
|
||||||
case ListMiddlewareTraces:
|
case ListMiddlewareTraces:
|
||||||
U.RespondJSON(w, r, middleware.GetAllTrace())
|
U.RespondJSON(w, r, middleware.GetAllTrace())
|
||||||
case ListMatchDomains:
|
case ListMatchDomains:
|
||||||
U.RespondJSON(w, r, config.Value().MatchDomains)
|
U.RespondJSON(w, r, cfg.Value().MatchDomains)
|
||||||
case ListHomepageConfig:
|
case ListHomepageConfig:
|
||||||
U.RespondJSON(w, r, config.HomepageConfig())
|
U.RespondJSON(w, r, routes.HomepageConfig(cfg.Value().Homepage.UseDefaultCategories))
|
||||||
case ListTasks:
|
case ListTasks:
|
||||||
U.RespondJSON(w, r, task.DebugTaskList())
|
U.RespondJSON(w, r, task.DebugTaskList())
|
||||||
default:
|
default:
|
||||||
|
@ -60,9 +61,9 @@ func List(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func listRoute(which string) any {
|
func listRoute(which string) any {
|
||||||
if which == "" || which == "all" {
|
if which == "" || which == "all" {
|
||||||
return config.RoutesByAlias()
|
return routes.RoutesByAlias()
|
||||||
}
|
}
|
||||||
routes := config.RoutesByAlias()
|
routes := routes.RoutesByAlias()
|
||||||
route, ok := routes[which]
|
route, ok := routes[which]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -4,11 +4,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
||||||
"github.com/yusing/go-proxy/internal/config"
|
config "github.com/yusing/go-proxy/internal/config/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Reload(w http.ResponseWriter, r *http.Request) {
|
func Reload(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
||||||
if err := config.Reload(); err != nil {
|
if err := cfg.Reload(); err != nil {
|
||||||
U.HandleErr(w, r, err)
|
U.HandleErr(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,25 +9,25 @@ import (
|
||||||
"github.com/coder/websocket/wsjson"
|
"github.com/coder/websocket/wsjson"
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
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/config"
|
config "github.com/yusing/go-proxy/internal/config/types"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Stats(w http.ResponseWriter, r *http.Request) {
|
func Stats(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
||||||
U.RespondJSON(w, r, getStats())
|
U.RespondJSON(w, r, getStats(cfg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func StatsWS(w http.ResponseWriter, r *http.Request) {
|
func StatsWS(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
||||||
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.*.*"}
|
||||||
|
|
||||||
if len(config.Value().MatchDomains) == 0 {
|
if len(cfg.Value().MatchDomains) == 0 {
|
||||||
U.LogWarn(r).Msg("no match domains configured, accepting websocket API request from all origins")
|
U.LogWarn(r).Msg("no match domains configured, accepting websocket API request from all origins")
|
||||||
originPats = []string{"*"}
|
originPats = []string{"*"}
|
||||||
} else {
|
} else {
|
||||||
originPats = make([]string, len(config.Value().MatchDomains))
|
originPats = make([]string, len(cfg.Value().MatchDomains))
|
||||||
for i, domain := range config.Value().MatchDomains {
|
for i, domain := range cfg.Value().MatchDomains {
|
||||||
originPats[i] = "*" + domain
|
originPats[i] = "*" + domain
|
||||||
}
|
}
|
||||||
originPats = append(originPats, localAddresses...)
|
originPats = append(originPats, localAddresses...)
|
||||||
|
@ -52,7 +52,7 @@ func StatsWS(w http.ResponseWriter, r *http.Request) {
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
stats := getStats()
|
stats := getStats(cfg)
|
||||||
if err := wsjson.Write(ctx, conn, stats); err != nil {
|
if err := wsjson.Write(ctx, conn, stats); err != nil {
|
||||||
U.LogError(r).Msg("failed to write JSON")
|
U.LogError(r).Msg("failed to write JSON")
|
||||||
return
|
return
|
||||||
|
@ -62,9 +62,9 @@ func StatsWS(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
var startTime = time.Now()
|
var startTime = time.Now()
|
||||||
|
|
||||||
func getStats() map[string]any {
|
func getStats(cfg config.ConfigInstance) map[string]any {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"proxies": config.Statistics(),
|
"proxies": cfg.Statistics(),
|
||||||
"uptime": strutils.FormatDuration(time.Since(startTime)),
|
"uptime": strutils.FormatDuration(time.Since(startTime)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,6 @@ func (p *Provider) Setup() (err E.Error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.ScheduleRenewal()
|
|
||||||
|
|
||||||
for _, expiry := range p.GetExpiries() {
|
for _, expiry := range p.GetExpiries() {
|
||||||
logger.Info().Msg("certificate expire on " + strutils.FormatTime(expiry))
|
logger.Info().Msg("certificate expire on " + strutils.FormatTime(expiry))
|
||||||
break
|
break
|
||||||
|
|
|
@ -7,12 +7,15 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/yusing/go-proxy/internal/api"
|
||||||
"github.com/yusing/go-proxy/internal/autocert"
|
"github.com/yusing/go-proxy/internal/autocert"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/internal/config/types"
|
"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"
|
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/metrics"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/http/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"
|
||||||
|
@ -26,7 +29,9 @@ type Config struct {
|
||||||
value *types.Config
|
value *types.Config
|
||||||
providers F.Map[string, *proxy.Provider]
|
providers F.Map[string, *proxy.Provider]
|
||||||
autocertProvider *autocert.Provider
|
autocertProvider *autocert.Provider
|
||||||
task *task.Task
|
entrypoint *entrypoint.Entrypoint
|
||||||
|
|
||||||
|
task *task.Task
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -45,15 +50,18 @@ Make sure you rename it back before next time you start.`
|
||||||
You may run "ls-config" to show or dump the current config.`
|
You may run "ls-config" to show or dump the current config.`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var Validate = types.Validate
|
||||||
|
|
||||||
func GetInstance() *Config {
|
func GetInstance() *Config {
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfig() *Config {
|
func newConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
value: types.DefaultConfig(),
|
value: types.DefaultConfig(),
|
||||||
providers: F.NewMapOf[string, *proxy.Provider](),
|
providers: F.NewMapOf[string, *proxy.Provider](),
|
||||||
task: task.RootTask("config", false),
|
entrypoint: entrypoint.NewEntrypoint(),
|
||||||
|
task: task.RootTask("config", false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,11 +74,6 @@ func Load() (*Config, E.Error) {
|
||||||
return instance, instance.load()
|
return instance, instance.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Validate(data []byte) E.Error {
|
|
||||||
var model types.Config
|
|
||||||
return utils.DeserializeYAML(data, &model)
|
|
||||||
}
|
|
||||||
|
|
||||||
func MatchDomains() []string {
|
func MatchDomains() []string {
|
||||||
return instance.value.MatchDomains
|
return instance.value.MatchDomains
|
||||||
}
|
}
|
||||||
|
@ -101,6 +104,7 @@ func OnConfigChange(ev []events.Event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := Reload(); err != nil {
|
if err := Reload(); err != nil {
|
||||||
|
logger.Warn().Msg("using last config")
|
||||||
// recovered in event queue
|
// recovered in event queue
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -122,15 +126,19 @@ func Reload() E.Error {
|
||||||
// -> replace config -> start new subtasks
|
// -> replace config -> start new subtasks
|
||||||
instance.task.Finish("config changed")
|
instance.task.Finish("config changed")
|
||||||
instance = newCfg
|
instance = newCfg
|
||||||
instance.StartProxyProviders()
|
instance.Start()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Value() types.Config {
|
func (cfg *Config) Value() *types.Config {
|
||||||
return *instance.value
|
return instance.value
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAutoCertProvider() *autocert.Provider {
|
func (cfg *Config) Reload() E.Error {
|
||||||
|
return Reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) AutoCertProvider() *autocert.Provider {
|
||||||
return instance.autocertProvider
|
return instance.autocertProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +146,26 @@ func (cfg *Config) Task() *task.Task {
|
||||||
return cfg.task
|
return cfg.task
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) Start() {
|
||||||
|
cfg.StartAutoCert()
|
||||||
|
cfg.StartProxyProviders()
|
||||||
|
cfg.StartServers()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) StartAutoCert() {
|
||||||
|
autocert := cfg.autocertProvider
|
||||||
|
if autocert == nil {
|
||||||
|
logging.Info().Msg("autocert not configured")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := autocert.Setup(); err != nil {
|
||||||
|
E.LogFatal("autocert setup error", err)
|
||||||
|
} else {
|
||||||
|
autocert.ScheduleRenewal(cfg.task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *Config) StartProxyProviders() {
|
func (cfg *Config) StartProxyProviders() {
|
||||||
errs := cfg.providers.CollectErrorsParallel(
|
errs := cfg.providers.CollectErrorsParallel(
|
||||||
func(_ string, p *proxy.Provider) error {
|
func(_ string, p *proxy.Provider) error {
|
||||||
|
@ -149,6 +177,30 @@ func (cfg *Config) StartProxyProviders() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) StartServers() {
|
||||||
|
server.StartServer(cfg.task, server.Options{
|
||||||
|
Name: "proxy",
|
||||||
|
CertProvider: cfg.AutoCertProvider(),
|
||||||
|
HTTPAddr: common.ProxyHTTPAddr,
|
||||||
|
HTTPSAddr: common.ProxyHTTPSAddr,
|
||||||
|
Handler: cfg.entrypoint,
|
||||||
|
})
|
||||||
|
server.StartServer(cfg.task, server.Options{
|
||||||
|
Name: "api",
|
||||||
|
CertProvider: cfg.AutoCertProvider(),
|
||||||
|
HTTPAddr: common.APIHTTPAddr,
|
||||||
|
Handler: api.NewHandler(cfg),
|
||||||
|
})
|
||||||
|
if common.PrometheusEnabled {
|
||||||
|
server.StartServer(cfg.task, server.Options{
|
||||||
|
Name: "metrics",
|
||||||
|
CertProvider: cfg.AutoCertProvider(),
|
||||||
|
HTTPAddr: common.MetricsHTTPAddr,
|
||||||
|
Handler: metrics.NewHandler(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *Config) load() E.Error {
|
func (cfg *Config) load() E.Error {
|
||||||
const errMsg = "config load error"
|
const errMsg = "config load error"
|
||||||
|
|
||||||
|
@ -164,8 +216,8 @@ func (cfg *Config) load() E.Error {
|
||||||
|
|
||||||
// errors are non fatal below
|
// errors are non fatal below
|
||||||
errs := E.NewBuilder(errMsg)
|
errs := E.NewBuilder(errMsg)
|
||||||
errs.Add(entrypoint.SetMiddlewares(model.Entrypoint.Middlewares))
|
errs.Add(cfg.entrypoint.SetMiddlewares(model.Entrypoint.Middlewares))
|
||||||
errs.Add(entrypoint.SetAccessLogger(cfg.task, model.Entrypoint.AccessLog))
|
errs.Add(cfg.entrypoint.SetAccessLogger(cfg.task, model.Entrypoint.AccessLog))
|
||||||
errs.Add(cfg.initNotification(model.Providers.Notification))
|
errs.Add(cfg.initNotification(model.Providers.Notification))
|
||||||
errs.Add(cfg.initAutoCert(model.AutoCert))
|
errs.Add(cfg.initAutoCert(model.AutoCert))
|
||||||
errs.Add(cfg.loadRouteProviders(&model.Providers))
|
errs.Add(cfg.loadRouteProviders(&model.Providers))
|
||||||
|
@ -176,7 +228,8 @@ func (cfg *Config) load() E.Error {
|
||||||
model.MatchDomains[i] = "." + domain
|
model.MatchDomains[i] = "." + domain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entrypoint.SetFindRouteDomains(model.MatchDomains)
|
cfg.entrypoint.SetFindRouteDomains(model.MatchDomains)
|
||||||
|
|
||||||
return errs.Error()
|
return errs.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/homepage"
|
|
||||||
route "github.com/yusing/go-proxy/internal/route"
|
route "github.com/yusing/go-proxy/internal/route"
|
||||||
"github.com/yusing/go-proxy/internal/route/entry"
|
"github.com/yusing/go-proxy/internal/route/provider"
|
||||||
proxy "github.com/yusing/go-proxy/internal/route/provider"
|
|
||||||
"github.com/yusing/go-proxy/internal/route/routes"
|
|
||||||
"github.com/yusing/go-proxy/internal/route/types"
|
"github.com/yusing/go-proxy/internal/route/types"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func DumpEntries() map[string]*types.RawEntry {
|
func (cfg *Config) DumpEntries() map[string]*types.RawEntry {
|
||||||
entries := make(map[string]*types.RawEntry)
|
entries := make(map[string]*types.RawEntry)
|
||||||
instance.providers.RangeAll(func(_ string, p *proxy.Provider) {
|
cfg.providers.RangeAll(func(_ string, p *provider.Provider) {
|
||||||
p.RangeRoutes(func(alias string, r *route.Route) {
|
p.RangeRoutes(func(alias string, r *route.Route) {
|
||||||
entries[alias] = r.Entry
|
entries[alias] = r.Entry
|
||||||
})
|
})
|
||||||
|
@ -22,107 +16,20 @@ func DumpEntries() map[string]*types.RawEntry {
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
func DumpProviders() map[string]*proxy.Provider {
|
func (cfg *Config) DumpProviders() map[string]*provider.Provider {
|
||||||
entries := make(map[string]*proxy.Provider)
|
entries := make(map[string]*provider.Provider)
|
||||||
instance.providers.RangeAll(func(name string, p *proxy.Provider) {
|
cfg.providers.RangeAll(func(name string, p *provider.Provider) {
|
||||||
entries[name] = p
|
entries[name] = p
|
||||||
})
|
})
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
func HomepageConfig() homepage.Config {
|
func (cfg *Config) Statistics() map[string]any {
|
||||||
hpCfg := homepage.NewHomePageConfig()
|
|
||||||
routes.GetHTTPRoutes().RangeAll(func(alias string, r types.HTTPRoute) {
|
|
||||||
en := r.RawEntry()
|
|
||||||
item := en.Homepage
|
|
||||||
if item == nil {
|
|
||||||
item = new(homepage.Item)
|
|
||||||
item.Show = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !item.IsEmpty() {
|
|
||||||
item.Show = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !item.Show {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
item.Alias = alias
|
|
||||||
|
|
||||||
if item.Name == "" {
|
|
||||||
item.Name = strutils.Title(
|
|
||||||
strings.ReplaceAll(
|
|
||||||
strings.ReplaceAll(alias, "-", " "),
|
|
||||||
"_", " ",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if instance.value.Homepage.UseDefaultCategories {
|
|
||||||
if en.Container != nil && item.Category == "" {
|
|
||||||
if category, ok := homepage.PredefinedCategories[en.Container.ImageName]; ok {
|
|
||||||
item.Category = category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.Category == "" {
|
|
||||||
if category, ok := homepage.PredefinedCategories[strings.ToLower(alias)]; ok {
|
|
||||||
item.Category = category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case entry.IsDocker(r):
|
|
||||||
if item.Category == "" {
|
|
||||||
item.Category = "Docker"
|
|
||||||
}
|
|
||||||
item.SourceType = string(proxy.ProviderTypeDocker)
|
|
||||||
case entry.UseLoadBalance(r):
|
|
||||||
if item.Category == "" {
|
|
||||||
item.Category = "Load-balanced"
|
|
||||||
}
|
|
||||||
item.SourceType = "loadbalancer"
|
|
||||||
default:
|
|
||||||
if item.Category == "" {
|
|
||||||
item.Category = "Others"
|
|
||||||
}
|
|
||||||
item.SourceType = string(proxy.ProviderTypeFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
item.AltURL = r.TargetURL().String()
|
|
||||||
hpCfg.Add(item)
|
|
||||||
})
|
|
||||||
return hpCfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func RoutesByAlias(typeFilter ...route.RouteType) map[string]any {
|
|
||||||
rts := make(map[string]any)
|
|
||||||
if len(typeFilter) == 0 || typeFilter[0] == "" {
|
|
||||||
typeFilter = []route.RouteType{route.RouteTypeReverseProxy, route.RouteTypeStream}
|
|
||||||
}
|
|
||||||
for _, t := range typeFilter {
|
|
||||||
switch t {
|
|
||||||
case route.RouteTypeReverseProxy:
|
|
||||||
routes.GetHTTPRoutes().RangeAll(func(alias string, r types.HTTPRoute) {
|
|
||||||
rts[alias] = r
|
|
||||||
})
|
|
||||||
case route.RouteTypeStream:
|
|
||||||
routes.GetStreamRoutes().RangeAll(func(alias string, r types.StreamRoute) {
|
|
||||||
rts[alias] = r
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rts
|
|
||||||
}
|
|
||||||
|
|
||||||
func Statistics() map[string]any {
|
|
||||||
nTotalStreams := 0
|
nTotalStreams := 0
|
||||||
nTotalRPs := 0
|
nTotalRPs := 0
|
||||||
providerStats := make(map[string]proxy.ProviderStats)
|
providerStats := make(map[string]provider.ProviderStats)
|
||||||
|
|
||||||
instance.providers.RangeAll(func(name string, p *proxy.Provider) {
|
cfg.providers.RangeAll(func(name string, p *provider.Provider) {
|
||||||
stats := p.Statistics()
|
stats := p.Statistics()
|
||||||
providerStats[name] = stats
|
providerStats[name] = stats
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ package types
|
||||||
import (
|
import (
|
||||||
"github.com/yusing/go-proxy/internal/net/http/accesslog"
|
"github.com/yusing/go-proxy/internal/net/http/accesslog"
|
||||||
"github.com/yusing/go-proxy/internal/utils"
|
"github.com/yusing/go-proxy/internal/utils"
|
||||||
|
|
||||||
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -24,6 +26,12 @@ type (
|
||||||
AccessLog *accesslog.Config `json:"access_log" validate:"omitempty"`
|
AccessLog *accesslog.Config `json:"access_log" validate:"omitempty"`
|
||||||
}
|
}
|
||||||
NotificationConfig map[string]any
|
NotificationConfig map[string]any
|
||||||
|
|
||||||
|
ConfigInstance interface {
|
||||||
|
Value() *Config
|
||||||
|
Reload() E.Error
|
||||||
|
Statistics() map[string]any
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
|
@ -35,6 +43,11 @@ func DefaultConfig() *Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Validate(data []byte) E.Error {
|
||||||
|
var model Config
|
||||||
|
return utils.DeserializeYAML(data, &model)
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
utils.RegisterDefaultValueFactory(DefaultConfig)
|
utils.RegisterDefaultValueFactory(DefaultConfig)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
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/accesslog"
|
||||||
|
@ -17,32 +16,31 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var findRouteFunc = findRouteAnyDomain
|
type Entrypoint struct {
|
||||||
|
middleware *middleware.Middleware
|
||||||
var (
|
accessLogger *accesslog.AccessLogger
|
||||||
epMiddleware *middleware.Middleware
|
findRouteFunc func(host string) (route.HTTPRoute, error)
|
||||||
epMiddlewareMu sync.Mutex
|
}
|
||||||
|
|
||||||
epAccessLogger *accesslog.AccessLogger
|
|
||||||
epAccessLoggerMu sync.Mutex
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrNoSuchRoute = errors.New("no such route")
|
var ErrNoSuchRoute = errors.New("no such route")
|
||||||
|
|
||||||
func SetFindRouteDomains(domains []string) {
|
func NewEntrypoint() *Entrypoint {
|
||||||
if len(domains) == 0 {
|
return &Entrypoint{
|
||||||
findRouteFunc = findRouteAnyDomain
|
findRouteFunc: findRouteAnyDomain,
|
||||||
} else {
|
|
||||||
findRouteFunc = findRouteByDomains(domains)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetMiddlewares(mws []map[string]any) error {
|
func (ep *Entrypoint) SetFindRouteDomains(domains []string) {
|
||||||
epMiddlewareMu.Lock()
|
if len(domains) == 0 {
|
||||||
defer epMiddlewareMu.Unlock()
|
ep.findRouteFunc = findRouteAnyDomain
|
||||||
|
} else {
|
||||||
|
ep.findRouteFunc = findRouteByDomains(domains)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ep *Entrypoint) SetMiddlewares(mws []map[string]any) error {
|
||||||
if len(mws) == 0 {
|
if len(mws) == 0 {
|
||||||
epMiddleware = nil
|
ep.middleware = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,22 +48,19 @@ func SetMiddlewares(mws []map[string]any) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
epMiddleware = mid
|
ep.middleware = mid
|
||||||
|
|
||||||
logger.Debug().Msg("entrypoint middleware loaded")
|
logger.Debug().Msg("entrypoint middleware loaded")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetAccessLogger(parent task.Parent, cfg *accesslog.Config) (err error) {
|
func (ep *Entrypoint) SetAccessLogger(parent task.Parent, cfg *accesslog.Config) (err error) {
|
||||||
epAccessLoggerMu.Lock()
|
|
||||||
defer epAccessLoggerMu.Unlock()
|
|
||||||
|
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
epAccessLogger = nil
|
ep.accessLogger = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
epAccessLogger, err = accesslog.NewFileAccessLogger(parent, cfg)
|
ep.accessLogger, err = accesslog.NewFileAccessLogger(parent, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -73,28 +68,18 @@ func SetAccessLogger(parent task.Parent, cfg *accesslog.Config) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
func (ep *Entrypoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
mux, err := findRouteFunc(r.Host)
|
mux, err := ep.findRouteFunc(r.Host)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if epAccessLogger != nil {
|
if ep.accessLogger != nil {
|
||||||
epMiddlewareMu.Lock()
|
w = gphttp.NewModifyResponseWriter(w, r, func(resp *http.Response) error {
|
||||||
if epAccessLogger != nil {
|
ep.accessLogger.Log(r, resp)
|
||||||
w = gphttp.NewModifyResponseWriter(w, r, func(resp *http.Response) error {
|
return nil
|
||||||
epAccessLogger.Log(r, resp)
|
})
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
epMiddlewareMu.Unlock()
|
|
||||||
}
|
}
|
||||||
if epMiddleware != nil {
|
if ep.middleware != nil {
|
||||||
epMiddlewareMu.Lock()
|
ep.middleware.ServeHTTP(mux.ServeHTTP, w, r)
|
||||||
if epMiddleware != nil {
|
return
|
||||||
mid := epMiddleware
|
|
||||||
epMiddlewareMu.Unlock()
|
|
||||||
mid.ServeHTTP(mux.ServeHTTP, w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
epMiddlewareMu.Unlock()
|
|
||||||
}
|
}
|
||||||
mux.ServeHTTP(w, r)
|
mux.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
|
|
|
@ -8,18 +8,19 @@ import (
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var r route.HTTPRoute
|
var (
|
||||||
|
r route.HTTPRoute
|
||||||
|
ep = NewEntrypoint()
|
||||||
|
)
|
||||||
|
|
||||||
func run(t *testing.T, match []string, noMatch []string) {
|
func run(t *testing.T, match []string, noMatch []string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
t.Cleanup(routes.TestClear)
|
t.Cleanup(routes.TestClear)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() { ep.SetFindRouteDomains(nil) })
|
||||||
SetFindRouteDomains(nil)
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, test := range match {
|
for _, test := range match {
|
||||||
t.Run(test, func(t *testing.T) {
|
t.Run(test, func(t *testing.T) {
|
||||||
found, err := findRouteFunc(test)
|
found, err := ep.findRouteFunc(test)
|
||||||
ExpectNoError(t, err)
|
ExpectNoError(t, err)
|
||||||
ExpectTrue(t, found == &r)
|
ExpectTrue(t, found == &r)
|
||||||
})
|
})
|
||||||
|
@ -27,7 +28,7 @@ func run(t *testing.T, match []string, noMatch []string) {
|
||||||
|
|
||||||
for _, test := range noMatch {
|
for _, test := range noMatch {
|
||||||
t.Run(test, func(t *testing.T) {
|
t.Run(test, func(t *testing.T) {
|
||||||
_, err := findRouteFunc(test)
|
_, err := ep.findRouteFunc(test)
|
||||||
ExpectError(t, ErrNoSuchRoute, err)
|
ExpectError(t, ErrNoSuchRoute, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -72,7 +73,7 @@ func TestFindRouteExactHostMatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindRouteByDomains(t *testing.T) {
|
func TestFindRouteByDomains(t *testing.T) {
|
||||||
SetFindRouteDomains([]string{
|
ep.SetFindRouteDomains([]string{
|
||||||
".domain.com",
|
".domain.com",
|
||||||
".sub.domain.com",
|
".sub.domain.com",
|
||||||
})
|
})
|
||||||
|
@ -97,7 +98,7 @@ func TestFindRouteByDomains(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindRouteByDomainsExactMatch(t *testing.T) {
|
func TestFindRouteByDomainsExactMatch(t *testing.T) {
|
||||||
SetFindRouteDomains([]string{
|
ep.SetFindRouteDomains([]string{
|
||||||
".domain.com",
|
".domain.com",
|
||||||
".sub.domain.com",
|
".sub.domain.com",
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
|
|
||||||
//nolint:errname
|
//nolint:errname
|
||||||
type withSubject struct {
|
type withSubject struct {
|
||||||
Subject string `json:"subject"`
|
Subjects []string `json:"subjects"`
|
||||||
Err error `json:"err"`
|
Err error `json:"err"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const subjectSep = " > "
|
const subjectSep = " > "
|
||||||
|
@ -30,13 +30,18 @@ func PrependSubject(subject string, err error) error {
|
||||||
case Error:
|
case Error:
|
||||||
return err.Subject(subject)
|
return err.Subject(subject)
|
||||||
}
|
}
|
||||||
return &withSubject{subject, err}
|
return &withSubject{[]string{subject}, err}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err *withSubject) Prepend(subject string) *withSubject {
|
func (err *withSubject) Prepend(subject string) *withSubject {
|
||||||
clone := *err
|
clone := *err
|
||||||
if subject != "" {
|
if subject != "" {
|
||||||
clone.Subject = subject + subjectSep + clone.Subject
|
switch subject[0] {
|
||||||
|
case '[', '(', '{':
|
||||||
|
clone.Subjects[len(clone.Subjects)-1] += subject
|
||||||
|
default:
|
||||||
|
clone.Subjects = append(clone.Subjects, subject)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &clone
|
return &clone
|
||||||
}
|
}
|
||||||
|
@ -50,7 +55,22 @@ func (err *withSubject) Unwrap() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err *withSubject) Error() string {
|
func (err *withSubject) Error() string {
|
||||||
subjects := strings.Split(err.Subject, subjectSep)
|
// subject is in reversed order
|
||||||
subjects[len(subjects)-1] = highlight(subjects[len(subjects)-1])
|
n := len(err.Subjects)
|
||||||
return strings.Join(subjects, subjectSep) + ": " + err.Err.Error()
|
size := 0
|
||||||
|
errStr := err.Err.Error()
|
||||||
|
var sb strings.Builder
|
||||||
|
for _, s := range err.Subjects {
|
||||||
|
size += len(s)
|
||||||
|
}
|
||||||
|
sb.Grow(size + 2 + n*len(subjectSep) + len(errStr))
|
||||||
|
|
||||||
|
for i := n - 1; i > 0; i-- {
|
||||||
|
sb.WriteString(err.Subjects[i])
|
||||||
|
sb.WriteString(subjectSep)
|
||||||
|
}
|
||||||
|
sb.WriteString(highlight(err.Subjects[0]))
|
||||||
|
sb.WriteString(": ")
|
||||||
|
sb.WriteString(errStr)
|
||||||
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,10 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
||||||
"github.com/yusing/go-proxy/internal/net/types"
|
"github.com/yusing/go-proxy/internal/net/types"
|
||||||
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,7 +19,7 @@ type (
|
||||||
}
|
}
|
||||||
CIDRWhitelistOpts struct {
|
CIDRWhitelistOpts struct {
|
||||||
Allow []*types.CIDR `validate:"min=1"`
|
Allow []*types.CIDR `validate:"min=1"`
|
||||||
StatusCode int `json:"status_code" aliases:"status" validate:"omitempty,gte=400,lte=599"`
|
StatusCode int `json:"status_code" aliases:"status" validate:"omitempty,status_code"`
|
||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -30,6 +33,13 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
utils.Validator().RegisterValidation("status_code", func(fl validator.FieldLevel) bool {
|
||||||
|
statusCode := fl.Field().Int()
|
||||||
|
return gphttp.IsStatusCodeValid(int(statusCode))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// setup implements MiddlewareWithSetup.
|
// setup implements MiddlewareWithSetup.
|
||||||
func (wl *cidrWhitelist) setup() {
|
func (wl *cidrWhitelist) setup() {
|
||||||
wl.CIDRWhitelistOpts = cidrWhitelistDefaults
|
wl.CIDRWhitelistOpts = cidrWhitelistDefaults
|
||||||
|
|
|
@ -24,6 +24,18 @@ func TestCIDRWhitelistValidation(t *testing.T) {
|
||||||
"message": testMessage,
|
"message": testMessage,
|
||||||
})
|
})
|
||||||
ExpectNoError(t, err)
|
ExpectNoError(t, err)
|
||||||
|
_, err = CIDRWhiteList.New(OptionsRaw{
|
||||||
|
"allow": []string{"192.168.2.100/32"},
|
||||||
|
"message": testMessage,
|
||||||
|
"status": 403,
|
||||||
|
})
|
||||||
|
ExpectNoError(t, err)
|
||||||
|
_, err = CIDRWhiteList.New(OptionsRaw{
|
||||||
|
"allow": []string{"192.168.2.100/32"},
|
||||||
|
"message": testMessage,
|
||||||
|
"status_code": 403,
|
||||||
|
})
|
||||||
|
ExpectNoError(t, err)
|
||||||
})
|
})
|
||||||
t.Run("missing allow", func(t *testing.T) {
|
t.Run("missing allow", func(t *testing.T) {
|
||||||
_, err := CIDRWhiteList.New(OptionsRaw{
|
_, err := CIDRWhiteList.New(OptionsRaw{
|
||||||
|
|
|
@ -168,24 +168,6 @@ func joinURLPath(a, b *url.URL) (path, rawpath string) {
|
||||||
// URLs to the scheme, host, and base path provided in target. If the
|
// URLs to the scheme, host, and base path provided in target. If the
|
||||||
// target's path is "/base" and the incoming request was for "/dir",
|
// target's path is "/base" and the incoming request was for "/dir",
|
||||||
// the target request will be for /base/dir.
|
// the target request will be for /base/dir.
|
||||||
//
|
|
||||||
// NewReverseProxy does not rewrite the Host header.
|
|
||||||
//
|
|
||||||
// To customize the ReverseProxy behavior beyond what
|
|
||||||
// NewReverseProxy provides, use ReverseProxy directly
|
|
||||||
// with a Rewrite function. The ProxyRequest SetURL method
|
|
||||||
// may be used to route the outbound request. (Note that SetURL,
|
|
||||||
// unlike NewReverseProxy, rewrites the Host header
|
|
||||||
// of the outbound request by default.)
|
|
||||||
//
|
|
||||||
// proxy := &ReverseProxy{
|
|
||||||
// Rewrite: func(r *ProxyRequest) {
|
|
||||||
// r.SetURL(target)
|
|
||||||
// r.Out.Host = r.In.Host // if desired
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
|
|
||||||
func NewReverseProxy(name string, target types.URL, transport http.RoundTripper) *ReverseProxy {
|
func NewReverseProxy(name string, target types.URL, transport http.RoundTripper) *ReverseProxy {
|
||||||
if transport == nil {
|
if transport == nil {
|
||||||
panic("nil transport")
|
panic("nil transport")
|
||||||
|
|
|
@ -35,9 +35,9 @@ type Options struct {
|
||||||
Handler http.Handler
|
Handler http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartServer(opt Options) (s *Server) {
|
func StartServer(parent task.Parent, opt Options) (s *Server) {
|
||||||
s = NewServer(opt)
|
s = NewServer(opt)
|
||||||
s.Start()
|
s.Start(parent)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,11 +83,13 @@ func NewServer(opt Options) (s *Server) {
|
||||||
// If both are not set, this does nothing.
|
// If both are not set, this does nothing.
|
||||||
//
|
//
|
||||||
// Start() is non-blocking.
|
// Start() is non-blocking.
|
||||||
func (s *Server) Start() {
|
func (s *Server) Start(parent task.Parent) {
|
||||||
if s.http == nil && s.https == nil {
|
if s.http == nil && s.https == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task := parent.Subtask("server."+s.Name, false)
|
||||||
|
|
||||||
s.startTime = time.Now()
|
s.startTime = time.Now()
|
||||||
if s.http != nil {
|
if s.http != nil {
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -105,7 +107,7 @@ func (s *Server) Start() {
|
||||||
s.l.Info().Str("addr", s.https.Addr).Msgf("server started")
|
s.l.Info().Str("addr", s.https.Addr).Msgf("server started")
|
||||||
}
|
}
|
||||||
|
|
||||||
task.OnProgramExit("server."+s.Name+".stop", s.stop)
|
task.OnCancel("stop", s.stop)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) stop() {
|
func (s *Server) stop() {
|
||||||
|
@ -113,14 +115,19 @@ func (s *Server) stop() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(task.RootContext(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
if s.http != nil && s.httpStarted {
|
if s.http != nil && s.httpStarted {
|
||||||
s.handleErr("http", s.http.Shutdown(task.RootContext()))
|
s.handleErr("http", s.http.Shutdown(ctx))
|
||||||
s.httpStarted = false
|
s.httpStarted = false
|
||||||
|
s.l.Info().Str("addr", s.http.Addr).Msgf("server stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.https != nil && s.httpsStarted {
|
if s.https != nil && s.httpsStarted {
|
||||||
s.handleErr("https", s.https.Shutdown(task.RootContext()))
|
s.handleErr("https", s.https.Shutdown(ctx))
|
||||||
s.httpsStarted = false
|
s.httpsStarted = false
|
||||||
|
s.l.Info().Str("addr", s.https.Addr).Msgf("server stopped")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,9 +72,9 @@ proxy.app1.host: 10.0.0.254
|
||||||
proxy.app1.port: 80
|
proxy.app1.port: 80
|
||||||
proxy.app1.path_patterns:
|
proxy.app1.path_patterns:
|
||||||
| # Check https://pkg.go.dev/net/http#hdr-Patterns-ServeMux for syntax
|
| # Check https://pkg.go.dev/net/http#hdr-Patterns-ServeMux for syntax
|
||||||
GET / # accept any GET request
|
- GET / # accept any GET request
|
||||||
POST /auth # for /auth and /auth/* accept only POST
|
- POST /auth # for /auth and /auth/* accept only POST
|
||||||
GET /home/{$} # for exactly /home
|
- GET /home/{$} # for exactly /home
|
||||||
proxy.app1.healthcheck.disabled: false
|
proxy.app1.healthcheck.disabled: false
|
||||||
proxy.app1.healthcheck.path: /
|
proxy.app1.healthcheck.path: /
|
||||||
proxy.app1.healthcheck.interval: 5s
|
proxy.app1.healthcheck.interval: 5s
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/internal/route"
|
"github.com/yusing/go-proxy/internal/route"
|
||||||
"github.com/yusing/go-proxy/internal/route/entry"
|
"github.com/yusing/go-proxy/internal/route/entry"
|
||||||
|
"github.com/yusing/go-proxy/internal/route/provider/types"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
"github.com/yusing/go-proxy/internal/watcher"
|
"github.com/yusing/go-proxy/internal/watcher"
|
||||||
)
|
)
|
||||||
|
@ -87,10 +88,10 @@ func (handler *EventHandler) matchAny(events []watcher.Event, route *route.Route
|
||||||
|
|
||||||
func (handler *EventHandler) match(event watcher.Event, route *route.Route) bool {
|
func (handler *EventHandler) match(event watcher.Event, route *route.Route) bool {
|
||||||
switch handler.provider.GetType() {
|
switch handler.provider.GetType() {
|
||||||
case ProviderTypeDocker:
|
case types.ProviderTypeDocker:
|
||||||
return route.Entry.Container.ContainerID == event.ActorID ||
|
return route.Entry.Container.ContainerID == event.ActorID ||
|
||||||
route.Entry.Container.ContainerName == event.ActorName
|
route.Entry.Container.ContainerName == event.ActorName
|
||||||
case ProviderTypeFile:
|
case types.ProviderTypeFile:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// should never happen
|
// should never happen
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
R "github.com/yusing/go-proxy/internal/route"
|
R "github.com/yusing/go-proxy/internal/route"
|
||||||
|
"github.com/yusing/go-proxy/internal/route/provider/types"
|
||||||
|
route "github.com/yusing/go-proxy/internal/route/types"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
W "github.com/yusing/go-proxy/internal/watcher"
|
W "github.com/yusing/go-proxy/internal/watcher"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/events"
|
"github.com/yusing/go-proxy/internal/watcher/events"
|
||||||
|
@ -20,7 +22,7 @@ type (
|
||||||
ProviderImpl `json:"-"`
|
ProviderImpl `json:"-"`
|
||||||
|
|
||||||
name string
|
name string
|
||||||
t ProviderType
|
t types.ProviderType
|
||||||
routes R.Routes
|
routes R.Routes
|
||||||
|
|
||||||
watcher W.Watcher
|
watcher W.Watcher
|
||||||
|
@ -31,24 +33,20 @@ type (
|
||||||
NewWatcher() W.Watcher
|
NewWatcher() W.Watcher
|
||||||
Logger() *zerolog.Logger
|
Logger() *zerolog.Logger
|
||||||
}
|
}
|
||||||
ProviderType string
|
|
||||||
ProviderStats struct {
|
ProviderStats struct {
|
||||||
NumRPs int `json:"num_reverse_proxies"`
|
NumRPs int `json:"num_reverse_proxies"`
|
||||||
NumStreams int `json:"num_streams"`
|
NumStreams int `json:"num_streams"`
|
||||||
Type ProviderType `json:"type"`
|
Type types.ProviderType `json:"type"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ProviderTypeDocker ProviderType = "docker"
|
|
||||||
ProviderTypeFile ProviderType = "file"
|
|
||||||
|
|
||||||
providerEventFlushInterval = 300 * time.Millisecond
|
providerEventFlushInterval = 300 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrEmptyProviderName = errors.New("empty provider name")
|
var ErrEmptyProviderName = errors.New("empty provider name")
|
||||||
|
|
||||||
func newProvider(name string, t ProviderType) *Provider {
|
func newProvider(name string, t types.ProviderType) *Provider {
|
||||||
return &Provider{
|
return &Provider{
|
||||||
name: name,
|
name: name,
|
||||||
t: t,
|
t: t,
|
||||||
|
@ -61,7 +59,7 @@ func NewFileProvider(filename string) (p *Provider, err error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return nil, ErrEmptyProviderName
|
return nil, ErrEmptyProviderName
|
||||||
}
|
}
|
||||||
p = newProvider(strings.ReplaceAll(name, ".", "_"), ProviderTypeFile)
|
p = newProvider(strings.ReplaceAll(name, ".", "_"), types.ProviderTypeFile)
|
||||||
p.ProviderImpl, err = FileProviderImpl(filename)
|
p.ProviderImpl, err = FileProviderImpl(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -75,7 +73,7 @@ func NewDockerProvider(name string, dockerHost string) (p *Provider, err error)
|
||||||
return nil, ErrEmptyProviderName
|
return nil, ErrEmptyProviderName
|
||||||
}
|
}
|
||||||
|
|
||||||
p = newProvider(name, ProviderTypeDocker)
|
p = newProvider(name, types.ProviderTypeDocker)
|
||||||
p.ProviderImpl, err = DockerProviderImpl(name, dockerHost, p.IsExplicitOnly())
|
p.ProviderImpl, err = DockerProviderImpl(name, dockerHost, p.IsExplicitOnly())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -92,7 +90,7 @@ func (p *Provider) GetName() string {
|
||||||
return p.name
|
return p.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) GetType() ProviderType {
|
func (p *Provider) GetType() types.ProviderType {
|
||||||
return p.t
|
return p.t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,9 +169,9 @@ func (p *Provider) Statistics() ProviderStats {
|
||||||
numStreams := 0
|
numStreams := 0
|
||||||
p.routes.RangeAll(func(_ string, r *R.Route) {
|
p.routes.RangeAll(func(_ string, r *R.Route) {
|
||||||
switch r.Type {
|
switch r.Type {
|
||||||
case R.RouteTypeReverseProxy:
|
case route.RouteTypeReverseProxy:
|
||||||
numRPs++
|
numRPs++
|
||||||
case R.RouteTypeStream:
|
case route.RouteTypeStream:
|
||||||
numStreams++
|
numStreams++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
8
internal/route/provider/types/provider_type.go
Normal file
8
internal/route/provider/types/provider_type.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
type ProviderType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProviderTypeDocker ProviderType = "docker"
|
||||||
|
ProviderTypeFile ProviderType = "file"
|
||||||
|
)
|
|
@ -14,11 +14,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
RouteType string
|
Route struct {
|
||||||
Route struct {
|
|
||||||
_ U.NoCopy
|
_ U.NoCopy
|
||||||
impl
|
impl
|
||||||
Type RouteType
|
Type types.RouteType
|
||||||
Entry *RawEntry
|
Entry *RawEntry
|
||||||
}
|
}
|
||||||
Routes = F.Map[string, *Route]
|
Routes = F.Map[string, *Route]
|
||||||
|
@ -34,11 +33,6 @@ type (
|
||||||
RawEntries = types.RawEntries
|
RawEntries = types.RawEntries
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
RouteTypeStream RouteType = "stream"
|
|
||||||
RouteTypeReverseProxy RouteType = "reverse_proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
// function alias.
|
// function alias.
|
||||||
var (
|
var (
|
||||||
NewRoutes = F.NewMap[Routes]
|
NewRoutes = F.NewMap[Routes]
|
||||||
|
@ -59,15 +53,15 @@ func NewRoute(raw *RawEntry) (*Route, E.Error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var t RouteType
|
var t types.RouteType
|
||||||
var rt impl
|
var rt impl
|
||||||
|
|
||||||
switch e := en.(type) {
|
switch e := en.(type) {
|
||||||
case *entry.StreamEntry:
|
case *entry.StreamEntry:
|
||||||
t = RouteTypeStream
|
t = types.RouteTypeStream
|
||||||
rt, err = NewStreamRoute(e)
|
rt, err = NewStreamRoute(e)
|
||||||
case *entry.ReverseProxyEntry:
|
case *entry.ReverseProxyEntry:
|
||||||
t = RouteTypeReverseProxy
|
t = types.RouteTypeReverseProxy
|
||||||
rt, err = NewHTTPRoute(e)
|
rt, err = NewHTTPRoute(e)
|
||||||
default:
|
default:
|
||||||
panic("bug: should not reach here")
|
panic("bug: should not reach here")
|
||||||
|
|
99
internal/route/routes/query.go
Normal file
99
internal/route/routes/query.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yusing/go-proxy/internal/homepage"
|
||||||
|
"github.com/yusing/go-proxy/internal/route/entry"
|
||||||
|
provider "github.com/yusing/go-proxy/internal/route/provider/types"
|
||||||
|
"github.com/yusing/go-proxy/internal/route/types"
|
||||||
|
route "github.com/yusing/go-proxy/internal/route/types"
|
||||||
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HomepageConfig(useDefaultCategories bool) homepage.Config {
|
||||||
|
hpCfg := homepage.NewHomePageConfig()
|
||||||
|
GetHTTPRoutes().RangeAll(func(alias string, r types.HTTPRoute) {
|
||||||
|
en := r.RawEntry()
|
||||||
|
item := en.Homepage
|
||||||
|
if item == nil {
|
||||||
|
item = new(homepage.Item)
|
||||||
|
item.Show = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !item.IsEmpty() {
|
||||||
|
item.Show = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !item.Show {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item.Alias = alias
|
||||||
|
|
||||||
|
if item.Name == "" {
|
||||||
|
item.Name = strutils.Title(
|
||||||
|
strings.ReplaceAll(
|
||||||
|
strings.ReplaceAll(alias, "-", " "),
|
||||||
|
"_", " ",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if useDefaultCategories {
|
||||||
|
if en.Container != nil && item.Category == "" {
|
||||||
|
if category, ok := homepage.PredefinedCategories[en.Container.ImageName]; ok {
|
||||||
|
item.Category = category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.Category == "" {
|
||||||
|
if category, ok := homepage.PredefinedCategories[strings.ToLower(alias)]; ok {
|
||||||
|
item.Category = category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case entry.IsDocker(r):
|
||||||
|
if item.Category == "" {
|
||||||
|
item.Category = "Docker"
|
||||||
|
}
|
||||||
|
item.SourceType = string(provider.ProviderTypeDocker)
|
||||||
|
case entry.UseLoadBalance(r):
|
||||||
|
if item.Category == "" {
|
||||||
|
item.Category = "Load-balanced"
|
||||||
|
}
|
||||||
|
item.SourceType = "loadbalancer"
|
||||||
|
default:
|
||||||
|
if item.Category == "" {
|
||||||
|
item.Category = "Others"
|
||||||
|
}
|
||||||
|
item.SourceType = string(provider.ProviderTypeFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
item.AltURL = r.TargetURL().String()
|
||||||
|
hpCfg.Add(item)
|
||||||
|
})
|
||||||
|
return hpCfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func RoutesByAlias(typeFilter ...route.RouteType) map[string]any {
|
||||||
|
rts := make(map[string]any)
|
||||||
|
if len(typeFilter) == 0 || typeFilter[0] == "" {
|
||||||
|
typeFilter = []route.RouteType{route.RouteTypeReverseProxy, route.RouteTypeStream}
|
||||||
|
}
|
||||||
|
for _, t := range typeFilter {
|
||||||
|
switch t {
|
||||||
|
case route.RouteTypeReverseProxy:
|
||||||
|
GetHTTPRoutes().RangeAll(func(alias string, r types.HTTPRoute) {
|
||||||
|
rts[alias] = r
|
||||||
|
})
|
||||||
|
case route.RouteTypeStream:
|
||||||
|
GetStreamRoutes().RangeAll(func(alias string, r types.StreamRoute) {
|
||||||
|
rts[alias] = r
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rts
|
||||||
|
}
|
8
internal/route/types/route_type.go
Normal file
8
internal/route/types/route_type.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
type RouteType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RouteTypeStream RouteType = "stream"
|
||||||
|
RouteTypeReverseProxy RouteType = "reverse_proxy"
|
||||||
|
)
|
|
@ -8,21 +8,21 @@ GoDoxy v0.8.2 expected changes
|
||||||
Sample service showing this:
|
Sample service showing this:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
hello-world:
|
hello-world:
|
||||||
image: nginxdemos/hello
|
image: nginxdemos/hello
|
||||||
container_name: hello-world
|
container_name: hello-world
|
||||||
restart: "no"
|
restart: "no"
|
||||||
ports:
|
ports:
|
||||||
- "9100:80"
|
- "9100:80"
|
||||||
labels:
|
labels:
|
||||||
proxy.aliases: hello-world
|
proxy.aliases: hello-world
|
||||||
proxy.#1.port: 9100
|
proxy.#1.port: 9100
|
||||||
proxy.idle_timeout: 45s
|
proxy.idle_timeout: 45s
|
||||||
proxy.wake_timeout: 30s
|
proxy.wake_timeout: 30s
|
||||||
proxy.stop_method: stop
|
proxy.stop_method: stop
|
||||||
proxy.stop_timeout: 10s
|
proxy.stop_timeout: 10s
|
||||||
proxy.stop_signal: SIGTERM
|
proxy.stop_signal: SIGTERM
|
||||||
proxy.start_endpoint: "/start"
|
proxy.start_endpoint: "/start"
|
||||||
```
|
```
|
||||||
|
|
||||||
Hitting `/` on this service when the container is down:
|
Hitting `/` on this service when the container is down:
|
||||||
|
@ -38,14 +38,14 @@ GoDoxy v0.8.2 expected changes
|
||||||
> Host: hello-world.godoxy.local
|
> Host: hello-world.godoxy.local
|
||||||
> User-Agent: curl/8.7.1
|
> User-Agent: curl/8.7.1
|
||||||
> Accept: */*
|
> Accept: */*
|
||||||
>
|
>
|
||||||
* Request completely sent off
|
* Request completely sent off
|
||||||
< HTTP/1.1 403 Forbidden
|
< HTTP/1.1 403 Forbidden
|
||||||
< Content-Type: text/plain; charset=utf-8
|
< Content-Type: text/plain; charset=utf-8
|
||||||
< X-Content-Type-Options: nosniff
|
< X-Content-Type-Options: nosniff
|
||||||
< Date: Wed, 08 Jan 2025 02:04:51 GMT
|
< Date: Wed, 08 Jan 2025 02:04:51 GMT
|
||||||
< Content-Length: 71
|
< Content-Length: 71
|
||||||
<
|
<
|
||||||
Forbidden: Container can only be started via configured start endpoint
|
Forbidden: Container can only be started via configured start endpoint
|
||||||
* Connection #0 to host localhost left intact
|
* Connection #0 to host localhost left intact
|
||||||
```
|
```
|
||||||
|
@ -64,16 +64,17 @@ GoDoxy v0.8.2 expected changes
|
||||||
> User-Agent: curl/8.7.1
|
> User-Agent: curl/8.7.1
|
||||||
> Accept: */*
|
> Accept: */*
|
||||||
> X-Goproxy-Check-Redirect: skip
|
> X-Goproxy-Check-Redirect: skip
|
||||||
>
|
>
|
||||||
* Request completely sent off
|
* Request completely sent off
|
||||||
< HTTP/1.1 200 OK
|
< HTTP/1.1 200 OK
|
||||||
< Date: Wed, 08 Jan 2025 02:13:39 GMT
|
< Date: Wed, 08 Jan 2025 02:13:39 GMT
|
||||||
< Content-Length: 0
|
< Content-Length: 0
|
||||||
<
|
<
|
||||||
* Connection #0 to host localhost left intact
|
* Connection #0 to host localhost left intact
|
||||||
```
|
```
|
||||||
|
|
||||||
- Caddyfile like rules
|
- Caddyfile like rules
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
proxy.goaccess.rules: |
|
proxy.goaccess.rules: |
|
||||||
- name: default
|
- name: default
|
||||||
|
@ -92,4 +93,21 @@ GoDoxy v0.8.2 expected changes
|
||||||
- name: block POST and PUT
|
- name: block POST and PUT
|
||||||
on: method POST | method PUT
|
on: method POST | method PUT
|
||||||
do: error 403 Forbidden
|
do: error 403 Forbidden
|
||||||
```
|
```
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
- config reload will now cause all servers to fully restart (i.e. proxy, api, prometheus, etc)
|
||||||
|
|
||||||
|
- multiline-string as list now treated as YAML list, which requires hyphen prefix `-`, i.e.
|
||||||
|
```yaml
|
||||||
|
proxy.app.middlewares.request.hide_headers:
|
||||||
|
- X-Header1
|
||||||
|
- X-Header2
|
||||||
|
````
|
||||||
|
|
||||||
|
- autocert now supports hot-reload
|
||||||
|
|
||||||
|
- Fixes
|
||||||
|
- bug: cert renewal failure no longer causes renew schdueler to stuck forever
|
||||||
|
- bug: access log writes to closed file after config reload
|
||||||
|
|
Loading…
Add table
Reference in a new issue