From 198ae2cd0280fc96a1fed23f2db8ae0be11be48f Mon Sep 17 00:00:00 2001 From: yusing Date: Sat, 10 May 2025 13:12:41 +0800 Subject: [PATCH] refactor(api): restructure existing routes and remove unused debug endpoints and command line arguments --- cmd/main.go | 110 +++-------------- internal/api/handler.go | 12 +- internal/api/v1/list.go | 128 -------------------- internal/api/v1/list_files.go | 41 +++++++ internal/api/v1/list_homepage_categories.go | 13 ++ internal/api/v1/list_homepage_config.go | 13 ++ internal/api/v1/list_icons.go | 23 ++++ internal/api/v1/list_route.go | 23 ++++ internal/api/v1/list_route_providers.go | 12 ++ internal/api/v1/list_routes.go | 14 +++ internal/api/v1/list_routes_by_provider.go | 13 ++ internal/api/v1/query/query.go | 64 ---------- internal/net/gphttp/body.go | 8 +- 13 files changed, 180 insertions(+), 294 deletions(-) delete mode 100644 internal/api/v1/list.go create mode 100644 internal/api/v1/list_files.go create mode 100644 internal/api/v1/list_homepage_categories.go create mode 100644 internal/api/v1/list_homepage_config.go create mode 100644 internal/api/v1/list_icons.go create mode 100644 internal/api/v1/list_route.go create mode 100644 internal/api/v1/list_route_providers.go create mode 100644 internal/api/v1/list_routes.go create mode 100644 internal/api/v1/list_routes_by_provider.go delete mode 100644 internal/api/v1/query/query.go diff --git a/cmd/main.go b/cmd/main.go index 681dbbc..125ec69 100755 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,12 +1,9 @@ package main import ( - "encoding/json" - "log" "os" "sync" - "github.com/yusing/go-proxy/internal/api/v1/query" "github.com/yusing/go-proxy/internal/auth" "github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/config" @@ -18,13 +15,10 @@ import ( "github.com/yusing/go-proxy/internal/metrics/systeminfo" "github.com/yusing/go-proxy/internal/metrics/uptime" "github.com/yusing/go-proxy/internal/net/gphttp/middleware" - "github.com/yusing/go-proxy/internal/route/routes" "github.com/yusing/go-proxy/internal/task" "github.com/yusing/go-proxy/pkg" ) -var rawLogger = log.New(os.Stdout, "", 0) - func parallel(fns ...func()) { var wg sync.WaitGroup for _, fn := range fns { @@ -39,97 +33,29 @@ func parallel(fns ...func()) { func main() { initProfiling() - dnsproviders.InitProviders() - args := pkg.GetArgs(common.MainServerCommandValidator{}) - switch args.Command { - case common.CommandReload: - if err := query.ReloadServer(); err != nil { - gperr.LogFatal("server reload error", err) - } - rawLogger.Println("ok") - return - case common.CommandListIcons: - icons, err := homepage.ListAvailableIcons() - if err != nil { - rawLogger.Fatal(err) - } - printJSON(icons) - return - case common.CommandListRoutes: - routes, err := query.ListRoutes() - if err != nil { - log.Printf("failed to connect to api server: %s", err) - log.Printf("falling back to config file") - } else { - printJSON(routes) - return - } - case common.CommandDebugListMTrace: - trace, err := query.ListMiddlewareTraces() - if err != nil { - log.Fatal(err) - } - printJSON(trace) - return - } + logging.InitLogger(os.Stderr, memlogger.GetMemLogger()) + logging.Info().Msgf("GoDoxy version %s", pkg.GetVersion()) + logging.Trace().Msg("trace enabled") + parallel( + dnsproviders.InitProviders, + homepage.InitIconListCache, + systeminfo.Poller.Start, + middleware.LoadComposeFiles, + ) - if args.Command == common.CommandStart { - logging.InitLogger(os.Stderr, memlogger.GetMemLogger()) - logging.Info().Msgf("GoDoxy version %s", pkg.GetVersion()) - logging.Trace().Msg("trace enabled") - parallel( - homepage.InitIconListCache, - systeminfo.Poller.Start, - ) - - if common.APIJWTSecret == nil { - logging.Warn().Msg("API_JWT_SECRET is not set, using random key") - common.APIJWTSecret = common.RandomJWTKey() - } - } else { - logging.DiscardLogger() - } - - if args.Command == common.CommandValidate { - data, err := os.ReadFile(common.ConfigPath) - if err == nil { - err = config.Validate(data) - } - if err != nil { - log.Fatal("config error: ", err) - } - log.Print("config OK") - return + if common.APIJWTSecret == nil { + logging.Warn().Msg("API_JWT_SECRET is not set, using random key") + common.APIJWTSecret = common.RandomJWTKey() } for _, dir := range common.RequiredDirectories { prepareDirectory(dir) } - middleware.LoadComposeFiles() - - var cfg *config.Config - var err gperr.Error - if cfg, err = config.Load(); err != nil { + cfg, err := config.Load() + if err != nil { gperr.LogWarn("errors in config", err) - err = nil - } - - switch args.Command { - case common.CommandListRoutes: - cfg.StartProxyProviders() - printJSON(routes.ByAlias()) - return - case common.CommandListConfigs: - printJSON(cfg.Value()) - return - case common.CommandDebugListEntries: - printJSON(cfg.DumpRoutes()) - return - case common.CommandDebugListProviders: - printJSON(cfg.DumpRouteProviders()) - return } cfg.Start(&config.StartServersOptions{ @@ -156,11 +82,3 @@ func prepareDirectory(dir string) { } } } - -func printJSON(obj any) { - j, err := json.MarshalIndent(obj, "", " ") - if err != nil { - logging.Fatal().Err(err).Send() - } - rawLogger.Print(string(j)) // raw output for convenience using "jq" -} diff --git a/internal/api/handler.go b/internal/api/handler.go index 7637d95..eb25b37 100644 --- a/internal/api/handler.go +++ b/internal/api/handler.go @@ -71,9 +71,15 @@ func NewHandler(cfg config.ConfigInstance) http.Handler { mux.HandleFunc("GET", "/v1/stats", v1.Stats, true) mux.HandleFunc("POST", "/v1/reload", v1.Reload, true) - mux.HandleFunc("GET", "/v1/list", v1.List, true) - mux.HandleFunc("GET", "/v1/list/{what}", v1.List, true) - mux.HandleFunc("GET", "/v1/list/{what}/{which}", v1.List, true) + mux.HandleFunc("GET", "/v1/list", v1.ListRoutesHandler, true) + mux.HandleFunc("GET", "/v1/list/routes", v1.ListRoutesHandler, true) + mux.HandleFunc("GET", "/v1/list/route/{which}", v1.ListRouteHandler, true) + mux.HandleFunc("GET", "/v1/list/routes_by_provider", v1.ListRoutesByProviderHandler, true) + mux.HandleFunc("GET", "/v1/list/files", v1.ListFilesHandler, true) + mux.HandleFunc("GET", "/v1/list/homepage_config", v1.ListHomepageConfigHandler, true) + mux.HandleFunc("GET", "/v1/list/route_providers", v1.ListRouteProvidersHandler, true) + mux.HandleFunc("GET", "/v1/list/homepage_categories", v1.ListHomepageCategoriesHandler, true) + mux.HandleFunc("GET", "/v1/list/icons", v1.ListIconsHandler, true) mux.HandleFunc("GET", "/v1/file/{type}/{filename}", v1.GetFileContent, true) mux.HandleFunc("POST,PUT", "/v1/file/{type}/{filename}", v1.SetFileContent, true) mux.HandleFunc("POST", "/v1/file/validate/{type}", v1.ValidateFile, true) diff --git a/internal/api/v1/list.go b/internal/api/v1/list.go deleted file mode 100644 index 9928994..0000000 --- a/internal/api/v1/list.go +++ /dev/null @@ -1,128 +0,0 @@ -package v1 - -import ( - "fmt" - "net/http" - "strconv" - "strings" - - "github.com/yusing/go-proxy/internal/common" - config "github.com/yusing/go-proxy/internal/config/types" - "github.com/yusing/go-proxy/internal/homepage" - "github.com/yusing/go-proxy/internal/net/gphttp" - "github.com/yusing/go-proxy/internal/net/gphttp/middleware" - "github.com/yusing/go-proxy/internal/route/routes" - route "github.com/yusing/go-proxy/internal/route/types" - "github.com/yusing/go-proxy/internal/task" - "github.com/yusing/go-proxy/internal/utils" -) - -const ( - ListRoute = "route" - ListRoutes = "routes" - ListRoutesByProvider = "routes_by_provider" - ListFiles = "files" - ListMiddlewares = "middlewares" - ListMiddlewareTraces = "middleware_trace" - ListMatchDomains = "match_domains" - ListHomepageConfig = "homepage_config" - ListRouteProviders = "route_providers" - ListHomepageCategories = "homepage_categories" - ListIcons = "icons" - ListTasks = "tasks" -) - -func List(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) { - what := r.PathValue("what") - if what == "" { - what = ListRoutes - } - which := r.PathValue("which") - - switch what { - case ListRoute: - route := listRoute(which) - if route == nil { - http.NotFound(w, r) - } else { - gphttp.RespondJSON(w, r, route) - } - case ListRoutes: - gphttp.RespondJSON(w, r, routes.ByAlias(route.RouteType(r.FormValue("type")))) - case ListRoutesByProvider: - gphttp.RespondJSON(w, r, routes.ByProvider()) - case ListFiles: - listFiles(w, r) - case ListMiddlewares: - gphttp.RespondJSON(w, r, middleware.All()) - case ListMiddlewareTraces: - gphttp.RespondJSON(w, r, middleware.GetAllTrace()) - case ListMatchDomains: - gphttp.RespondJSON(w, r, cfg.Value().MatchDomains) - case ListHomepageConfig: - gphttp.RespondJSON(w, r, routes.HomepageConfig(r.FormValue("category"), r.FormValue("provider"))) - case ListRouteProviders: - gphttp.RespondJSON(w, r, cfg.RouteProviderList()) - case ListHomepageCategories: - gphttp.RespondJSON(w, r, routes.HomepageCategories()) - case ListIcons: - limit, err := strconv.Atoi(r.FormValue("limit")) - if err != nil { - limit = 0 - } - icons, err := homepage.SearchIcons(r.FormValue("keyword"), limit) - if err != nil { - gphttp.ClientError(w, r, err) - return - } - gphttp.RespondJSON(w, r, icons) - case ListTasks: - gphttp.RespondJSON(w, r, task.DebugTaskList()) - default: - gphttp.BadRequest(w, fmt.Sprintf("invalid what: %s", what)) - } -} - -// if which is "all" or empty, return map[string]Route of all routes -// otherwise, return a single Route with alias which or nil if not found. -func listRoute(which string) any { - if which == "" || which == "all" { - return routes.ByAlias() - } - routes := routes.ByAlias() - route, ok := routes[which] - if !ok { - return nil - } - return route -} - -func listFiles(w http.ResponseWriter, r *http.Request) { - files, err := utils.ListFiles(common.ConfigBasePath, 0, true) - if err != nil { - gphttp.ServerError(w, r, err) - return - } - resp := map[FileType][]string{ - FileTypeConfig: make([]string, 0), - FileTypeProvider: make([]string, 0), - FileTypeMiddleware: make([]string, 0), - } - - for _, file := range files { - t := fileType(file) - file = strings.TrimPrefix(file, common.ConfigBasePath+"/") - resp[t] = append(resp[t], file) - } - - mids, err := utils.ListFiles(common.MiddlewareComposeBasePath, 0, true) - if err != nil { - gphttp.ServerError(w, r, err) - return - } - for _, mid := range mids { - mid = strings.TrimPrefix(mid, common.MiddlewareComposeBasePath+"/") - resp[FileTypeMiddleware] = append(resp[FileTypeMiddleware], mid) - } - gphttp.RespondJSON(w, r, resp) -} diff --git a/internal/api/v1/list_files.go b/internal/api/v1/list_files.go new file mode 100644 index 0000000..439600d --- /dev/null +++ b/internal/api/v1/list_files.go @@ -0,0 +1,41 @@ +package v1 + +import ( + "net/http" + "strings" + + "github.com/yusing/go-proxy/internal/common" + config "github.com/yusing/go-proxy/internal/config/types" + "github.com/yusing/go-proxy/internal/net/gphttp" + "github.com/yusing/go-proxy/internal/utils" +) + +func ListFilesHandler(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) { + files, err := utils.ListFiles(common.ConfigBasePath, 0, true) + if err != nil { + gphttp.ServerError(w, r, err) + return + } + resp := map[FileType][]string{ + FileTypeConfig: make([]string, 0), + FileTypeProvider: make([]string, 0), + FileTypeMiddleware: make([]string, 0), + } + + for _, file := range files { + t := fileType(file) + file = strings.TrimPrefix(file, common.ConfigBasePath+"/") + resp[t] = append(resp[t], file) + } + + mids, err := utils.ListFiles(common.MiddlewareComposeBasePath, 0, true) + if err != nil { + gphttp.ServerError(w, r, err) + return + } + for _, mid := range mids { + mid = strings.TrimPrefix(mid, common.MiddlewareComposeBasePath+"/") + resp[FileTypeMiddleware] = append(resp[FileTypeMiddleware], mid) + } + gphttp.RespondJSON(w, r, resp) +} diff --git a/internal/api/v1/list_homepage_categories.go b/internal/api/v1/list_homepage_categories.go new file mode 100644 index 0000000..8f67a43 --- /dev/null +++ b/internal/api/v1/list_homepage_categories.go @@ -0,0 +1,13 @@ +package v1 + +import ( + "net/http" + + config "github.com/yusing/go-proxy/internal/config/types" + "github.com/yusing/go-proxy/internal/net/gphttp" + "github.com/yusing/go-proxy/internal/route/routes" +) + +func ListHomepageCategoriesHandler(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) { + gphttp.RespondJSON(w, r, routes.HomepageCategories()) +} diff --git a/internal/api/v1/list_homepage_config.go b/internal/api/v1/list_homepage_config.go new file mode 100644 index 0000000..4a5b1f7 --- /dev/null +++ b/internal/api/v1/list_homepage_config.go @@ -0,0 +1,13 @@ +package v1 + +import ( + "net/http" + + config "github.com/yusing/go-proxy/internal/config/types" + "github.com/yusing/go-proxy/internal/net/gphttp" + "github.com/yusing/go-proxy/internal/route/routes" +) + +func ListHomepageConfigHandler(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) { + gphttp.RespondJSON(w, r, routes.HomepageConfig(r.FormValue("category"), r.FormValue("provider"))) +} diff --git a/internal/api/v1/list_icons.go b/internal/api/v1/list_icons.go new file mode 100644 index 0000000..eaef07d --- /dev/null +++ b/internal/api/v1/list_icons.go @@ -0,0 +1,23 @@ +package v1 + +import ( + "net/http" + "strconv" + + config "github.com/yusing/go-proxy/internal/config/types" + "github.com/yusing/go-proxy/internal/homepage" + "github.com/yusing/go-proxy/internal/net/gphttp" +) + +func ListIconsHandler(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) { + limit, err := strconv.Atoi(r.FormValue("limit")) + if err != nil { + limit = 0 + } + icons, err := homepage.SearchIcons(r.FormValue("keyword"), limit) + if err != nil { + gphttp.ClientError(w, r, err) + return + } + gphttp.RespondJSON(w, r, icons) +} diff --git a/internal/api/v1/list_route.go b/internal/api/v1/list_route.go new file mode 100644 index 0000000..56f3238 --- /dev/null +++ b/internal/api/v1/list_route.go @@ -0,0 +1,23 @@ +package v1 + +import ( + "net/http" + + config "github.com/yusing/go-proxy/internal/config/types" + "github.com/yusing/go-proxy/internal/net/gphttp" + "github.com/yusing/go-proxy/internal/route/routes" +) + +func ListRouteHandler(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) { + which := r.PathValue("which") + if which == "" || which == "all" { + gphttp.RespondJSON(w, r, routes.ByAlias()) + return + } + routesMap := routes.ByAlias() + if route, ok := routesMap[which]; ok { + gphttp.RespondJSON(w, r, route) + } else { + gphttp.RespondJSON(w, r, nil) + } +} diff --git a/internal/api/v1/list_route_providers.go b/internal/api/v1/list_route_providers.go new file mode 100644 index 0000000..85f7048 --- /dev/null +++ b/internal/api/v1/list_route_providers.go @@ -0,0 +1,12 @@ +package v1 + +import ( + "net/http" + + config "github.com/yusing/go-proxy/internal/config/types" + "github.com/yusing/go-proxy/internal/net/gphttp" +) + +func ListRouteProvidersHandler(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) { + gphttp.RespondJSON(w, r, cfg.RouteProviderList()) +} diff --git a/internal/api/v1/list_routes.go b/internal/api/v1/list_routes.go new file mode 100644 index 0000000..d74e83b --- /dev/null +++ b/internal/api/v1/list_routes.go @@ -0,0 +1,14 @@ +package v1 + +import ( + "net/http" + + config "github.com/yusing/go-proxy/internal/config/types" + "github.com/yusing/go-proxy/internal/net/gphttp" + "github.com/yusing/go-proxy/internal/route/routes" + route "github.com/yusing/go-proxy/internal/route/types" +) + +func ListRoutesHandler(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) { + gphttp.RespondJSON(w, r, routes.ByAlias(route.RouteType(r.FormValue("type")))) +} diff --git a/internal/api/v1/list_routes_by_provider.go b/internal/api/v1/list_routes_by_provider.go new file mode 100644 index 0000000..f1e080d --- /dev/null +++ b/internal/api/v1/list_routes_by_provider.go @@ -0,0 +1,13 @@ +package v1 + +import ( + "net/http" + + config "github.com/yusing/go-proxy/internal/config/types" + "github.com/yusing/go-proxy/internal/net/gphttp" + "github.com/yusing/go-proxy/internal/route/routes" +) + +func ListRoutesByProviderHandler(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) { + gphttp.RespondJSON(w, r, routes.ByProvider()) +} diff --git a/internal/api/v1/query/query.go b/internal/api/v1/query/query.go deleted file mode 100644 index 0a1d576..0000000 --- a/internal/api/v1/query/query.go +++ /dev/null @@ -1,64 +0,0 @@ -package query - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - - v1 "github.com/yusing/go-proxy/internal/api/v1" - "github.com/yusing/go-proxy/internal/common" - "github.com/yusing/go-proxy/internal/gperr" - "github.com/yusing/go-proxy/internal/net/gphttp" - "github.com/yusing/go-proxy/internal/net/gphttp/middleware" -) - -func ReloadServer() gperr.Error { - resp, err := gphttp.Post(common.APIHTTPURL+"/v1/reload", "", nil) - if err != nil { - return gperr.Wrap(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - failure := gperr.Errorf("server reload status %v", resp.StatusCode) - body, err := io.ReadAll(resp.Body) - if err != nil { - return failure.With(err) - } - reloadErr := string(body) - return failure.Withf(reloadErr) - } - return nil -} - -func List[T any](what string) (_ T, outErr gperr.Error) { - resp, err := gphttp.Get(fmt.Sprintf("%s/v1/list/%s", common.APIHTTPURL, what)) - if err != nil { - outErr = gperr.Wrap(err) - return - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - outErr = gperr.Errorf("list %s: failed, status %v", what, resp.StatusCode) - return - } - var res T - err = json.NewDecoder(resp.Body).Decode(&res) - if err != nil { - outErr = gperr.Wrap(err) - return - } - return res, nil -} - -func ListRoutes() (map[string]map[string]any, gperr.Error) { - return List[map[string]map[string]any](v1.ListRoutes) -} - -func ListMiddlewareTraces() (middleware.Traces, gperr.Error) { - return List[middleware.Traces](v1.ListMiddlewareTraces) -} - -func DebugListTasks() (map[string]any, gperr.Error) { - return List[map[string]any](v1.ListTasks) -} diff --git a/internal/net/gphttp/body.go b/internal/net/gphttp/body.go index 3a94023..b2d0173 100644 --- a/internal/net/gphttp/body.go +++ b/internal/net/gphttp/body.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "errors" - "fmt" "net/http" "github.com/yusing/go-proxy/internal/logging" @@ -23,6 +22,11 @@ func WriteBody(w http.ResponseWriter, body []byte) { } func RespondJSON(w http.ResponseWriter, r *http.Request, data any, code ...int) (canProceed bool) { + if data == nil { + http.NotFound(w, r) + return false + } + if len(code) > 0 { w.WriteHeader(code[0]) } @@ -30,8 +34,6 @@ func RespondJSON(w http.ResponseWriter, r *http.Request, data any, code ...int) var err error switch data := data.(type) { - case string: - _, err = w.Write([]byte(fmt.Sprintf("%q", data))) case []byte: panic("use WriteBody instead") default: