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:
|
||||
- hadolint@2.12.1-beta
|
||||
- actionlint@1.7.6
|
||||
- checkov@3.2.347
|
||||
- checkov@3.2.350
|
||||
- git-diff-check
|
||||
- gofmt@1.20.4
|
||||
- golangci-lint@1.62.2
|
||||
- golangci-lint@1.63.4
|
||||
- osv-scanner@1.9.2
|
||||
- oxipng@9.1.3
|
||||
- prettier@3.4.2
|
||||
|
|
50
cmd/main.go
50
cmd/main.go
|
@ -3,23 +3,19 @@ package main
|
|||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"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/common"
|
||||
"github.com/yusing/go-proxy/internal/config"
|
||||
"github.com/yusing/go-proxy/internal/entrypoint"
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
"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/server"
|
||||
"github.com/yusing/go-proxy/internal/route/routes"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/pkg"
|
||||
)
|
||||
|
@ -97,16 +93,16 @@ func main() {
|
|||
switch args.Command {
|
||||
case common.CommandListRoutes:
|
||||
cfg.StartProxyProviders()
|
||||
printJSON(config.RoutesByAlias())
|
||||
printJSON(routes.RoutesByAlias())
|
||||
return
|
||||
case common.CommandListConfigs:
|
||||
printJSON(config.Value())
|
||||
printJSON(cfg.Value())
|
||||
return
|
||||
case common.CommandDebugListEntries:
|
||||
printJSON(config.DumpEntries())
|
||||
printJSON(cfg.DumpEntries())
|
||||
return
|
||||
case common.CommandDebugListProviders:
|
||||
printJSON(config.DumpProviders())
|
||||
printJSON(cfg.DumpProviders())
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -114,7 +110,7 @@ func main() {
|
|||
logging.Warn().Msg("API JWT secret is empty, authentication is disabled")
|
||||
}
|
||||
|
||||
cfg.StartProxyProviders()
|
||||
cfg.Start()
|
||||
config.WatchChanges()
|
||||
|
||||
sig := make(chan os.Signal, 1)
|
||||
|
@ -122,44 +118,12 @@ func main() {
|
|||
signal.Notify(sig, syscall.SIGTERM)
|
||||
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
|
||||
<-sig
|
||||
|
||||
// grafully shutdown
|
||||
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) {
|
||||
|
|
|
@ -8,38 +8,41 @@ import (
|
|||
"github.com/yusing/go-proxy/internal/api/v1/auth"
|
||||
. "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"
|
||||
)
|
||||
|
||||
type ServeMux struct{ *http.ServeMux }
|
||||
|
||||
func NewServeMux() ServeMux {
|
||||
return ServeMux{http.NewServeMux()}
|
||||
}
|
||||
|
||||
func (mux ServeMux) HandleFunc(method, endpoint string, handler http.HandlerFunc) {
|
||||
mux.ServeMux.HandleFunc(method+" "+endpoint, checkHost(handler))
|
||||
}
|
||||
|
||||
func NewHandler() http.Handler {
|
||||
mux := NewServeMux()
|
||||
func NewHandler(cfg config.ConfigInstance) http.Handler {
|
||||
mux := ServeMux{http.NewServeMux()}
|
||||
mux.HandleFunc("GET", "/v1", v1.Index)
|
||||
mux.HandleFunc("GET", "/v1/version", v1.GetVersion)
|
||||
mux.HandleFunc("POST", "/v1/login", auth.LoginHandler)
|
||||
mux.HandleFunc("GET", "/v1/logout", auth.LogoutHandler)
|
||||
mux.HandleFunc("POST", "/v1/logout", auth.LogoutHandler)
|
||||
mux.HandleFunc("POST", "/v1/reload", v1.Reload)
|
||||
mux.HandleFunc("GET", "/v1/list", auth.RequireAuth(v1.List))
|
||||
mux.HandleFunc("GET", "/v1/list/{what}", auth.RequireAuth(v1.List))
|
||||
mux.HandleFunc("GET", "/v1/list/{what}/{which}", auth.RequireAuth(v1.List))
|
||||
mux.HandleFunc("POST", "/v1/reload", useCfg(cfg, v1.Reload))
|
||||
mux.HandleFunc("GET", "/v1/list", auth.RequireAuth(useCfg(cfg, v1.List)))
|
||||
mux.HandleFunc("GET", "/v1/list/{what}", auth.RequireAuth(useCfg(cfg, 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("POST", "/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/stats", v1.Stats)
|
||||
mux.HandleFunc("GET", "/v1/stats/ws", v1.StatsWS)
|
||||
mux.HandleFunc("GET", "/v1/stats", useCfg(cfg, v1.Stats))
|
||||
mux.HandleFunc("GET", "/v1/stats/ws", useCfg(cfg, v1.StatsWS))
|
||||
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.
|
||||
func checkHost(f http.HandlerFunc) http.HandlerFunc {
|
||||
if common.IsDebug {
|
||||
|
@ -55,4 +58,4 @@ func checkHost(f http.HandlerFunc) http.HandlerFunc {
|
|||
LogDebug(r).Interface("headers", r.Header).Msg("API request")
|
||||
f(w, r)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
||||
"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"
|
||||
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
||||
"github.com/yusing/go-proxy/internal/route/provider"
|
||||
|
|
|
@ -6,9 +6,10 @@ import (
|
|||
|
||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
||||
"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/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/utils"
|
||||
)
|
||||
|
@ -24,7 +25,7 @@ const (
|
|||
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")
|
||||
if what == "" {
|
||||
what = ListRoutes
|
||||
|
@ -40,7 +41,7 @@ func List(w http.ResponseWriter, r *http.Request) {
|
|||
U.RespondJSON(w, r, route)
|
||||
}
|
||||
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:
|
||||
listFiles(w, r)
|
||||
case ListMiddlewares:
|
||||
|
@ -48,9 +49,9 @@ func List(w http.ResponseWriter, r *http.Request) {
|
|||
case ListMiddlewareTraces:
|
||||
U.RespondJSON(w, r, middleware.GetAllTrace())
|
||||
case ListMatchDomains:
|
||||
U.RespondJSON(w, r, config.Value().MatchDomains)
|
||||
U.RespondJSON(w, r, cfg.Value().MatchDomains)
|
||||
case ListHomepageConfig:
|
||||
U.RespondJSON(w, r, config.HomepageConfig())
|
||||
U.RespondJSON(w, r, routes.HomepageConfig(cfg.Value().Homepage.UseDefaultCategories))
|
||||
case ListTasks:
|
||||
U.RespondJSON(w, r, task.DebugTaskList())
|
||||
default:
|
||||
|
@ -60,9 +61,9 @@ func List(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func listRoute(which string) any {
|
||||
if which == "" || which == "all" {
|
||||
return config.RoutesByAlias()
|
||||
return routes.RoutesByAlias()
|
||||
}
|
||||
routes := config.RoutesByAlias()
|
||||
routes := routes.RoutesByAlias()
|
||||
route, ok := routes[which]
|
||||
if !ok {
|
||||
return nil
|
||||
|
|
|
@ -4,11 +4,11 @@ import (
|
|||
"net/http"
|
||||
|
||||
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) {
|
||||
if err := config.Reload(); err != nil {
|
||||
func Reload(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
||||
if err := cfg.Reload(); err != nil {
|
||||
U.HandleErr(w, r, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -9,25 +9,25 @@ import (
|
|||
"github.com/coder/websocket/wsjson"
|
||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
||||
"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"
|
||||
)
|
||||
|
||||
func Stats(w http.ResponseWriter, r *http.Request) {
|
||||
U.RespondJSON(w, r, getStats())
|
||||
func Stats(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
||||
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
|
||||
|
||||
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")
|
||||
originPats = []string{"*"}
|
||||
} else {
|
||||
originPats = make([]string, len(config.Value().MatchDomains))
|
||||
for i, domain := range config.Value().MatchDomains {
|
||||
originPats = make([]string, len(cfg.Value().MatchDomains))
|
||||
for i, domain := range cfg.Value().MatchDomains {
|
||||
originPats[i] = "*" + domain
|
||||
}
|
||||
originPats = append(originPats, localAddresses...)
|
||||
|
@ -52,7 +52,7 @@ func StatsWS(w http.ResponseWriter, r *http.Request) {
|
|||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
stats := getStats()
|
||||
stats := getStats(cfg)
|
||||
if err := wsjson.Write(ctx, conn, stats); err != nil {
|
||||
U.LogError(r).Msg("failed to write JSON")
|
||||
return
|
||||
|
@ -62,9 +62,9 @@ func StatsWS(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
var startTime = time.Now()
|
||||
|
||||
func getStats() map[string]any {
|
||||
func getStats(cfg config.ConfigInstance) map[string]any {
|
||||
return map[string]any{
|
||||
"proxies": config.Statistics(),
|
||||
"proxies": cfg.Statistics(),
|
||||
"uptime": strutils.FormatDuration(time.Since(startTime)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,6 @@ func (p *Provider) Setup() (err E.Error) {
|
|||
}
|
||||
}
|
||||
|
||||
p.ScheduleRenewal()
|
||||
|
||||
for _, expiry := range p.GetExpiries() {
|
||||
logger.Info().Msg("certificate expire on " + strutils.FormatTime(expiry))
|
||||
break
|
||||
|
|
|
@ -7,12 +7,15 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/api"
|
||||
"github.com/yusing/go-proxy/internal/autocert"
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
"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/logging"
|
||||
"github.com/yusing/go-proxy/internal/metrics"
|
||||
"github.com/yusing/go-proxy/internal/net/http/server"
|
||||
"github.com/yusing/go-proxy/internal/notif"
|
||||
proxy "github.com/yusing/go-proxy/internal/route/provider"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
|
@ -26,7 +29,9 @@ type Config struct {
|
|||
value *types.Config
|
||||
providers F.Map[string, *proxy.Provider]
|
||||
autocertProvider *autocert.Provider
|
||||
task *task.Task
|
||||
entrypoint *entrypoint.Entrypoint
|
||||
|
||||
task *task.Task
|
||||
}
|
||||
|
||||
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.`
|
||||
)
|
||||
|
||||
var Validate = types.Validate
|
||||
|
||||
func GetInstance() *Config {
|
||||
return instance
|
||||
}
|
||||
|
||||
func newConfig() *Config {
|
||||
return &Config{
|
||||
value: types.DefaultConfig(),
|
||||
providers: F.NewMapOf[string, *proxy.Provider](),
|
||||
task: task.RootTask("config", false),
|
||||
value: types.DefaultConfig(),
|
||||
providers: F.NewMapOf[string, *proxy.Provider](),
|
||||
entrypoint: entrypoint.NewEntrypoint(),
|
||||
task: task.RootTask("config", false),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,11 +74,6 @@ func Load() (*Config, E.Error) {
|
|||
return instance, instance.load()
|
||||
}
|
||||
|
||||
func Validate(data []byte) E.Error {
|
||||
var model types.Config
|
||||
return utils.DeserializeYAML(data, &model)
|
||||
}
|
||||
|
||||
func MatchDomains() []string {
|
||||
return instance.value.MatchDomains
|
||||
}
|
||||
|
@ -101,6 +104,7 @@ func OnConfigChange(ev []events.Event) {
|
|||
}
|
||||
|
||||
if err := Reload(); err != nil {
|
||||
logger.Warn().Msg("using last config")
|
||||
// recovered in event queue
|
||||
panic(err)
|
||||
}
|
||||
|
@ -122,15 +126,19 @@ func Reload() E.Error {
|
|||
// -> replace config -> start new subtasks
|
||||
instance.task.Finish("config changed")
|
||||
instance = newCfg
|
||||
instance.StartProxyProviders()
|
||||
instance.Start()
|
||||
return nil
|
||||
}
|
||||
|
||||
func Value() types.Config {
|
||||
return *instance.value
|
||||
func (cfg *Config) Value() *types.Config {
|
||||
return instance.value
|
||||
}
|
||||
|
||||
func GetAutoCertProvider() *autocert.Provider {
|
||||
func (cfg *Config) Reload() E.Error {
|
||||
return Reload()
|
||||
}
|
||||
|
||||
func (cfg *Config) AutoCertProvider() *autocert.Provider {
|
||||
return instance.autocertProvider
|
||||
}
|
||||
|
||||
|
@ -138,6 +146,26 @@ func (cfg *Config) Task() *task.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() {
|
||||
errs := cfg.providers.CollectErrorsParallel(
|
||||
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 {
|
||||
const errMsg = "config load error"
|
||||
|
||||
|
@ -164,8 +216,8 @@ func (cfg *Config) load() E.Error {
|
|||
|
||||
// errors are non fatal below
|
||||
errs := E.NewBuilder(errMsg)
|
||||
errs.Add(entrypoint.SetMiddlewares(model.Entrypoint.Middlewares))
|
||||
errs.Add(entrypoint.SetAccessLogger(cfg.task, model.Entrypoint.AccessLog))
|
||||
errs.Add(cfg.entrypoint.SetMiddlewares(model.Entrypoint.Middlewares))
|
||||
errs.Add(cfg.entrypoint.SetAccessLogger(cfg.task, model.Entrypoint.AccessLog))
|
||||
errs.Add(cfg.initNotification(model.Providers.Notification))
|
||||
errs.Add(cfg.initAutoCert(model.AutoCert))
|
||||
errs.Add(cfg.loadRouteProviders(&model.Providers))
|
||||
|
@ -176,7 +228,8 @@ func (cfg *Config) load() E.Error {
|
|||
model.MatchDomains[i] = "." + domain
|
||||
}
|
||||
}
|
||||
entrypoint.SetFindRouteDomains(model.MatchDomains)
|
||||
cfg.entrypoint.SetFindRouteDomains(model.MatchDomains)
|
||||
|
||||
return errs.Error()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/homepage"
|
||||
route "github.com/yusing/go-proxy/internal/route"
|
||||
"github.com/yusing/go-proxy/internal/route/entry"
|
||||
proxy "github.com/yusing/go-proxy/internal/route/provider"
|
||||
"github.com/yusing/go-proxy/internal/route/routes"
|
||||
"github.com/yusing/go-proxy/internal/route/provider"
|
||||
"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)
|
||||
instance.providers.RangeAll(func(_ string, p *proxy.Provider) {
|
||||
cfg.providers.RangeAll(func(_ string, p *provider.Provider) {
|
||||
p.RangeRoutes(func(alias string, r *route.Route) {
|
||||
entries[alias] = r.Entry
|
||||
})
|
||||
|
@ -22,107 +16,20 @@ func DumpEntries() map[string]*types.RawEntry {
|
|||
return entries
|
||||
}
|
||||
|
||||
func DumpProviders() map[string]*proxy.Provider {
|
||||
entries := make(map[string]*proxy.Provider)
|
||||
instance.providers.RangeAll(func(name string, p *proxy.Provider) {
|
||||
func (cfg *Config) DumpProviders() map[string]*provider.Provider {
|
||||
entries := make(map[string]*provider.Provider)
|
||||
cfg.providers.RangeAll(func(name string, p *provider.Provider) {
|
||||
entries[name] = p
|
||||
})
|
||||
return entries
|
||||
}
|
||||
|
||||
func HomepageConfig() homepage.Config {
|
||||
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 {
|
||||
func (cfg *Config) Statistics() map[string]any {
|
||||
nTotalStreams := 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()
|
||||
providerStats[name] = stats
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ package types
|
|||
import (
|
||||
"github.com/yusing/go-proxy/internal/net/http/accesslog"
|
||||
"github.com/yusing/go-proxy/internal/utils"
|
||||
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
)
|
||||
|
||||
type (
|
||||
|
@ -24,6 +26,12 @@ type (
|
|||
AccessLog *accesslog.Config `json:"access_log" validate:"omitempty"`
|
||||
}
|
||||
NotificationConfig map[string]any
|
||||
|
||||
ConfigInstance interface {
|
||||
Value() *Config
|
||||
Reload() E.Error
|
||||
Statistics() map[string]any
|
||||
}
|
||||
)
|
||||
|
||||
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() {
|
||||
utils.RegisterDefaultValueFactory(DefaultConfig)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
||||
"github.com/yusing/go-proxy/internal/net/http/accesslog"
|
||||
|
@ -17,32 +16,31 @@ import (
|
|||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||
)
|
||||
|
||||
var findRouteFunc = findRouteAnyDomain
|
||||
|
||||
var (
|
||||
epMiddleware *middleware.Middleware
|
||||
epMiddlewareMu sync.Mutex
|
||||
|
||||
epAccessLogger *accesslog.AccessLogger
|
||||
epAccessLoggerMu sync.Mutex
|
||||
)
|
||||
type Entrypoint struct {
|
||||
middleware *middleware.Middleware
|
||||
accessLogger *accesslog.AccessLogger
|
||||
findRouteFunc func(host string) (route.HTTPRoute, error)
|
||||
}
|
||||
|
||||
var ErrNoSuchRoute = errors.New("no such route")
|
||||
|
||||
func SetFindRouteDomains(domains []string) {
|
||||
if len(domains) == 0 {
|
||||
findRouteFunc = findRouteAnyDomain
|
||||
} else {
|
||||
findRouteFunc = findRouteByDomains(domains)
|
||||
func NewEntrypoint() *Entrypoint {
|
||||
return &Entrypoint{
|
||||
findRouteFunc: findRouteAnyDomain,
|
||||
}
|
||||
}
|
||||
|
||||
func SetMiddlewares(mws []map[string]any) error {
|
||||
epMiddlewareMu.Lock()
|
||||
defer epMiddlewareMu.Unlock()
|
||||
func (ep *Entrypoint) SetFindRouteDomains(domains []string) {
|
||||
if len(domains) == 0 {
|
||||
ep.findRouteFunc = findRouteAnyDomain
|
||||
} else {
|
||||
ep.findRouteFunc = findRouteByDomains(domains)
|
||||
}
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) SetMiddlewares(mws []map[string]any) error {
|
||||
if len(mws) == 0 {
|
||||
epMiddleware = nil
|
||||
ep.middleware = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -50,22 +48,19 @@ func SetMiddlewares(mws []map[string]any) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
epMiddleware = mid
|
||||
ep.middleware = mid
|
||||
|
||||
logger.Debug().Msg("entrypoint middleware loaded")
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetAccessLogger(parent task.Parent, cfg *accesslog.Config) (err error) {
|
||||
epAccessLoggerMu.Lock()
|
||||
defer epAccessLoggerMu.Unlock()
|
||||
|
||||
func (ep *Entrypoint) SetAccessLogger(parent task.Parent, cfg *accesslog.Config) (err error) {
|
||||
if cfg == nil {
|
||||
epAccessLogger = nil
|
||||
ep.accessLogger = nil
|
||||
return
|
||||
}
|
||||
|
||||
epAccessLogger, err = accesslog.NewFileAccessLogger(parent, cfg)
|
||||
ep.accessLogger, err = accesslog.NewFileAccessLogger(parent, cfg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -73,28 +68,18 @@ func SetAccessLogger(parent task.Parent, cfg *accesslog.Config) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
mux, err := findRouteFunc(r.Host)
|
||||
func (ep *Entrypoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
mux, err := ep.findRouteFunc(r.Host)
|
||||
if err == nil {
|
||||
if epAccessLogger != nil {
|
||||
epMiddlewareMu.Lock()
|
||||
if epAccessLogger != nil {
|
||||
w = gphttp.NewModifyResponseWriter(w, r, func(resp *http.Response) error {
|
||||
epAccessLogger.Log(r, resp)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
epMiddlewareMu.Unlock()
|
||||
if ep.accessLogger != nil {
|
||||
w = gphttp.NewModifyResponseWriter(w, r, func(resp *http.Response) error {
|
||||
ep.accessLogger.Log(r, resp)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if epMiddleware != nil {
|
||||
epMiddlewareMu.Lock()
|
||||
if epMiddleware != nil {
|
||||
mid := epMiddleware
|
||||
epMiddlewareMu.Unlock()
|
||||
mid.ServeHTTP(mux.ServeHTTP, w, r)
|
||||
return
|
||||
}
|
||||
epMiddlewareMu.Unlock()
|
||||
if ep.middleware != nil {
|
||||
ep.middleware.ServeHTTP(mux.ServeHTTP, w, r)
|
||||
return
|
||||
}
|
||||
mux.ServeHTTP(w, r)
|
||||
return
|
||||
|
|
|
@ -8,18 +8,19 @@ import (
|
|||
. "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) {
|
||||
t.Helper()
|
||||
t.Cleanup(routes.TestClear)
|
||||
t.Cleanup(func() {
|
||||
SetFindRouteDomains(nil)
|
||||
})
|
||||
t.Cleanup(func() { ep.SetFindRouteDomains(nil) })
|
||||
|
||||
for _, test := range match {
|
||||
t.Run(test, func(t *testing.T) {
|
||||
found, err := findRouteFunc(test)
|
||||
found, err := ep.findRouteFunc(test)
|
||||
ExpectNoError(t, err)
|
||||
ExpectTrue(t, found == &r)
|
||||
})
|
||||
|
@ -27,7 +28,7 @@ func run(t *testing.T, match []string, noMatch []string) {
|
|||
|
||||
for _, test := range noMatch {
|
||||
t.Run(test, func(t *testing.T) {
|
||||
_, err := findRouteFunc(test)
|
||||
_, err := ep.findRouteFunc(test)
|
||||
ExpectError(t, ErrNoSuchRoute, err)
|
||||
})
|
||||
}
|
||||
|
@ -72,7 +73,7 @@ func TestFindRouteExactHostMatch(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFindRouteByDomains(t *testing.T) {
|
||||
SetFindRouteDomains([]string{
|
||||
ep.SetFindRouteDomains([]string{
|
||||
".domain.com",
|
||||
".sub.domain.com",
|
||||
})
|
||||
|
@ -97,7 +98,7 @@ func TestFindRouteByDomains(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFindRouteByDomainsExactMatch(t *testing.T) {
|
||||
SetFindRouteDomains([]string{
|
||||
ep.SetFindRouteDomains([]string{
|
||||
".domain.com",
|
||||
".sub.domain.com",
|
||||
})
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
|
||||
//nolint:errname
|
||||
type withSubject struct {
|
||||
Subject string `json:"subject"`
|
||||
Err error `json:"err"`
|
||||
Subjects []string `json:"subjects"`
|
||||
Err error `json:"err"`
|
||||
}
|
||||
|
||||
const subjectSep = " > "
|
||||
|
@ -30,13 +30,18 @@ func PrependSubject(subject string, err error) error {
|
|||
case Error:
|
||||
return err.Subject(subject)
|
||||
}
|
||||
return &withSubject{subject, err}
|
||||
return &withSubject{[]string{subject}, err}
|
||||
}
|
||||
|
||||
func (err *withSubject) Prepend(subject string) *withSubject {
|
||||
clone := *err
|
||||
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
|
||||
}
|
||||
|
@ -50,7 +55,22 @@ func (err *withSubject) Unwrap() error {
|
|||
}
|
||||
|
||||
func (err *withSubject) Error() string {
|
||||
subjects := strings.Split(err.Subject, subjectSep)
|
||||
subjects[len(subjects)-1] = highlight(subjects[len(subjects)-1])
|
||||
return strings.Join(subjects, subjectSep) + ": " + err.Err.Error()
|
||||
// subject is in reversed order
|
||||
n := len(err.Subjects)
|
||||
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/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/utils"
|
||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||
)
|
||||
|
||||
|
@ -16,7 +19,7 @@ type (
|
|||
}
|
||||
CIDRWhitelistOpts struct {
|
||||
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
|
||||
}
|
||||
)
|
||||
|
@ -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.
|
||||
func (wl *cidrWhitelist) setup() {
|
||||
wl.CIDRWhitelistOpts = cidrWhitelistDefaults
|
||||
|
|
|
@ -24,6 +24,18 @@ func TestCIDRWhitelistValidation(t *testing.T) {
|
|||
"message": testMessage,
|
||||
})
|
||||
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) {
|
||||
_, 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
|
||||
// target's path is "/base" and the incoming request was for "/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 {
|
||||
if transport == nil {
|
||||
panic("nil transport")
|
||||
|
|
|
@ -35,9 +35,9 @@ type Options struct {
|
|||
Handler http.Handler
|
||||
}
|
||||
|
||||
func StartServer(opt Options) (s *Server) {
|
||||
func StartServer(parent task.Parent, opt Options) (s *Server) {
|
||||
s = NewServer(opt)
|
||||
s.Start()
|
||||
s.Start(parent)
|
||||
return s
|
||||
}
|
||||
|
||||
|
@ -83,11 +83,13 @@ func NewServer(opt Options) (s *Server) {
|
|||
// If both are not set, this does nothing.
|
||||
//
|
||||
// Start() is non-blocking.
|
||||
func (s *Server) Start() {
|
||||
func (s *Server) Start(parent task.Parent) {
|
||||
if s.http == nil && s.https == nil {
|
||||
return
|
||||
}
|
||||
|
||||
task := parent.Subtask("server."+s.Name, false)
|
||||
|
||||
s.startTime = time.Now()
|
||||
if s.http != nil {
|
||||
go func() {
|
||||
|
@ -105,7 +107,7 @@ func (s *Server) Start() {
|
|||
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() {
|
||||
|
@ -113,14 +115,19 @@ func (s *Server) stop() {
|
|||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(task.RootContext(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
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.l.Info().Str("addr", s.http.Addr).Msgf("server stopped")
|
||||
}
|
||||
|
||||
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.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.path_patterns:
|
||||
| # Check https://pkg.go.dev/net/http#hdr-Patterns-ServeMux for syntax
|
||||
GET / # accept any GET request
|
||||
POST /auth # for /auth and /auth/* accept only POST
|
||||
GET /home/{$} # for exactly /home
|
||||
- GET / # accept any GET request
|
||||
- POST /auth # for /auth and /auth/* accept only POST
|
||||
- GET /home/{$} # for exactly /home
|
||||
proxy.app1.healthcheck.disabled: false
|
||||
proxy.app1.healthcheck.path: /
|
||||
proxy.app1.healthcheck.interval: 5s
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
E "github.com/yusing/go-proxy/internal/error"
|
||||
"github.com/yusing/go-proxy/internal/route"
|
||||
"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/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 {
|
||||
switch handler.provider.GetType() {
|
||||
case ProviderTypeDocker:
|
||||
case types.ProviderTypeDocker:
|
||||
return route.Entry.Container.ContainerID == event.ActorID ||
|
||||
route.Entry.Container.ContainerName == event.ActorName
|
||||
case ProviderTypeFile:
|
||||
case types.ProviderTypeFile:
|
||||
return true
|
||||
}
|
||||
// should never happen
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
"github.com/rs/zerolog"
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
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"
|
||||
W "github.com/yusing/go-proxy/internal/watcher"
|
||||
"github.com/yusing/go-proxy/internal/watcher/events"
|
||||
|
@ -20,7 +22,7 @@ type (
|
|||
ProviderImpl `json:"-"`
|
||||
|
||||
name string
|
||||
t ProviderType
|
||||
t types.ProviderType
|
||||
routes R.Routes
|
||||
|
||||
watcher W.Watcher
|
||||
|
@ -31,24 +33,20 @@ type (
|
|||
NewWatcher() W.Watcher
|
||||
Logger() *zerolog.Logger
|
||||
}
|
||||
ProviderType string
|
||||
ProviderStats struct {
|
||||
NumRPs int `json:"num_reverse_proxies"`
|
||||
NumStreams int `json:"num_streams"`
|
||||
Type ProviderType `json:"type"`
|
||||
NumRPs int `json:"num_reverse_proxies"`
|
||||
NumStreams int `json:"num_streams"`
|
||||
Type types.ProviderType `json:"type"`
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
ProviderTypeDocker ProviderType = "docker"
|
||||
ProviderTypeFile ProviderType = "file"
|
||||
|
||||
providerEventFlushInterval = 300 * time.Millisecond
|
||||
)
|
||||
|
||||
var ErrEmptyProviderName = errors.New("empty provider name")
|
||||
|
||||
func newProvider(name string, t ProviderType) *Provider {
|
||||
func newProvider(name string, t types.ProviderType) *Provider {
|
||||
return &Provider{
|
||||
name: name,
|
||||
t: t,
|
||||
|
@ -61,7 +59,7 @@ func NewFileProvider(filename string) (p *Provider, err error) {
|
|||
if name == "" {
|
||||
return nil, ErrEmptyProviderName
|
||||
}
|
||||
p = newProvider(strings.ReplaceAll(name, ".", "_"), ProviderTypeFile)
|
||||
p = newProvider(strings.ReplaceAll(name, ".", "_"), types.ProviderTypeFile)
|
||||
p.ProviderImpl, err = FileProviderImpl(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -75,7 +73,7 @@ func NewDockerProvider(name string, dockerHost string) (p *Provider, err error)
|
|||
return nil, ErrEmptyProviderName
|
||||
}
|
||||
|
||||
p = newProvider(name, ProviderTypeDocker)
|
||||
p = newProvider(name, types.ProviderTypeDocker)
|
||||
p.ProviderImpl, err = DockerProviderImpl(name, dockerHost, p.IsExplicitOnly())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -92,7 +90,7 @@ func (p *Provider) GetName() string {
|
|||
return p.name
|
||||
}
|
||||
|
||||
func (p *Provider) GetType() ProviderType {
|
||||
func (p *Provider) GetType() types.ProviderType {
|
||||
return p.t
|
||||
}
|
||||
|
||||
|
@ -171,9 +169,9 @@ func (p *Provider) Statistics() ProviderStats {
|
|||
numStreams := 0
|
||||
p.routes.RangeAll(func(_ string, r *R.Route) {
|
||||
switch r.Type {
|
||||
case R.RouteTypeReverseProxy:
|
||||
case route.RouteTypeReverseProxy:
|
||||
numRPs++
|
||||
case R.RouteTypeStream:
|
||||
case route.RouteTypeStream:
|
||||
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 (
|
||||
RouteType string
|
||||
Route struct {
|
||||
Route struct {
|
||||
_ U.NoCopy
|
||||
impl
|
||||
Type RouteType
|
||||
Type types.RouteType
|
||||
Entry *RawEntry
|
||||
}
|
||||
Routes = F.Map[string, *Route]
|
||||
|
@ -34,11 +33,6 @@ type (
|
|||
RawEntries = types.RawEntries
|
||||
)
|
||||
|
||||
const (
|
||||
RouteTypeStream RouteType = "stream"
|
||||
RouteTypeReverseProxy RouteType = "reverse_proxy"
|
||||
)
|
||||
|
||||
// function alias.
|
||||
var (
|
||||
NewRoutes = F.NewMap[Routes]
|
||||
|
@ -59,15 +53,15 @@ func NewRoute(raw *RawEntry) (*Route, E.Error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var t RouteType
|
||||
var t types.RouteType
|
||||
var rt impl
|
||||
|
||||
switch e := en.(type) {
|
||||
case *entry.StreamEntry:
|
||||
t = RouteTypeStream
|
||||
t = types.RouteTypeStream
|
||||
rt, err = NewStreamRoute(e)
|
||||
case *entry.ReverseProxyEntry:
|
||||
t = RouteTypeReverseProxy
|
||||
t = types.RouteTypeReverseProxy
|
||||
rt, err = NewHTTPRoute(e)
|
||||
default:
|
||||
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:
|
||||
|
||||
```yaml
|
||||
hello-world:
|
||||
image: nginxdemos/hello
|
||||
container_name: hello-world
|
||||
restart: "no"
|
||||
ports:
|
||||
- "9100:80"
|
||||
labels:
|
||||
proxy.aliases: hello-world
|
||||
proxy.#1.port: 9100
|
||||
proxy.idle_timeout: 45s
|
||||
proxy.wake_timeout: 30s
|
||||
proxy.stop_method: stop
|
||||
proxy.stop_timeout: 10s
|
||||
proxy.stop_signal: SIGTERM
|
||||
proxy.start_endpoint: "/start"
|
||||
hello-world:
|
||||
image: nginxdemos/hello
|
||||
container_name: hello-world
|
||||
restart: "no"
|
||||
ports:
|
||||
- "9100:80"
|
||||
labels:
|
||||
proxy.aliases: hello-world
|
||||
proxy.#1.port: 9100
|
||||
proxy.idle_timeout: 45s
|
||||
proxy.wake_timeout: 30s
|
||||
proxy.stop_method: stop
|
||||
proxy.stop_timeout: 10s
|
||||
proxy.stop_signal: SIGTERM
|
||||
proxy.start_endpoint: "/start"
|
||||
```
|
||||
|
||||
Hitting `/` on this service when the container is down:
|
||||
|
@ -38,14 +38,14 @@ GoDoxy v0.8.2 expected changes
|
|||
> Host: hello-world.godoxy.local
|
||||
> User-Agent: curl/8.7.1
|
||||
> Accept: */*
|
||||
>
|
||||
>
|
||||
* Request completely sent off
|
||||
< HTTP/1.1 403 Forbidden
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
< X-Content-Type-Options: nosniff
|
||||
< Date: Wed, 08 Jan 2025 02:04:51 GMT
|
||||
< Content-Length: 71
|
||||
<
|
||||
<
|
||||
Forbidden: Container can only be started via configured start endpoint
|
||||
* Connection #0 to host localhost left intact
|
||||
```
|
||||
|
@ -64,16 +64,17 @@ GoDoxy v0.8.2 expected changes
|
|||
> User-Agent: curl/8.7.1
|
||||
> Accept: */*
|
||||
> X-Goproxy-Check-Redirect: skip
|
||||
>
|
||||
>
|
||||
* Request completely sent off
|
||||
< HTTP/1.1 200 OK
|
||||
< Date: Wed, 08 Jan 2025 02:13:39 GMT
|
||||
< Content-Length: 0
|
||||
<
|
||||
<
|
||||
* Connection #0 to host localhost left intact
|
||||
```
|
||||
|
||||
- Caddyfile like rules
|
||||
|
||||
```yaml
|
||||
proxy.goaccess.rules: |
|
||||
- name: default
|
||||
|
@ -92,4 +93,21 @@ GoDoxy v0.8.2 expected changes
|
|||
- name: block POST and PUT
|
||||
on: method POST | method PUT
|
||||
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