From a0ef35627b99ffa7b3748043d572f9a7954c6dbe Mon Sep 17 00:00:00 2001 From: NanamiAdmin Date: Tue, 5 May 2026 15:45:51 +0800 Subject: [PATCH] feat(web): add static file serving and update API routes - Implement embedded static file serving for frontend assets - Update API routes to use '/api' prefix - Add web handler for static file serving with proper content types - Modify .gitignore and README to reflect new web/dist directory structure --- .gitignore | 2 +- README.md | 2 ++ router.go | 65 ++++++++++++++++++----------------- web/embed.go | 6 ++++ web/handler.go | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 33 deletions(-) create mode 100644 web/embed.go create mode 100644 web/handler.go diff --git a/.gitignore b/.gitignore index 0f6d5f9..9c5380a 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,7 @@ go.work.sum /frp_client /configs -/dist +/web/dist agent.md super-frpc super-frpc.exe diff --git a/README.md b/README.md index a651a10..82a7b39 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ Create a `config.json` file in the project root: ## Build +**You have to put the compiled frontend in the `web/dist` directory before building the backend.** + ```bash go build -o super-frpc.exe ``` diff --git a/router.go b/router.go index 71fd2c9..dc1996d 100644 --- a/router.go +++ b/router.go @@ -4,49 +4,50 @@ import ( "fmt" "net/http" "super-frpc/frpLogger" - "super-frpc/utils" + "super-frpc/handlers" "super-frpc/postLog" "super-frpc/session" - "super-frpc/handlers" + "super-frpc/utils" + "super-frpc/web" ) func setupRoutes() { systemLogHandler := postLog.NewLogSocketHandler(postLog.GetLogBroadcaster()) - http.HandleFunc("/system/getLogs", systemLogHandler.Handle) - http.HandleFunc("/system/getStatus", GetStatusHandler) - http.HandleFunc("/system/getSoftwareInfo", GetSoftwareInfoHandler) - http.HandleFunc("/system/settings/get", handlers.GetSettingsHandler) - http.HandleFunc("/system/settings/set", handlers.SetSettingsHandler) + http.HandleFunc("/api/system/getLogs", systemLogHandler.Handle) + http.HandleFunc("/api/system/getStatus", GetStatusHandler) + http.HandleFunc("/api/system/getSoftwareInfo", GetSoftwareInfoHandler) + http.HandleFunc("/api/system/settings/get", handlers.GetSettingsHandler) + http.HandleFunc("/api/system/settings/set", handlers.SetSettingsHandler) - http.HandleFunc("/register", handlers.RegisterHandler) - http.HandleFunc("/login", handlers.LoginHandler) - http.HandleFunc("/logout", handlers.LogoutHandler) + http.HandleFunc("/api/register", handlers.RegisterHandler) + http.HandleFunc("/api/login", handlers.LoginHandler) + http.HandleFunc("/api/logout", handlers.LogoutHandler) - http.HandleFunc("/userMgr/create", handlers.CreateUserHandler) - http.HandleFunc("/userMgr/remove", handlers.RemoveUserHandler) - http.HandleFunc("/userMgr/list", handlers.ListUserHandler) - http.HandleFunc("/userMgr/modify", handlers.ModifyUserHandler) - http.HandleFunc("/userMgr/modifyType", handlers.ModifyUserTypeHandler) + http.HandleFunc("/api/userMgr/create", handlers.CreateUserHandler) + http.HandleFunc("/api/userMgr/remove", handlers.RemoveUserHandler) + http.HandleFunc("/api/userMgr/list", handlers.ListUserHandler) + http.HandleFunc("/api/userMgr/modify", handlers.ModifyUserHandler) + http.HandleFunc("/api/userMgr/modifyType", handlers.ModifyUserTypeHandler) - http.HandleFunc("/sessionMgr/list", handlers.ListActiveSessionsHandler) - http.HandleFunc("/sessionMgr/remove", handlers.RemoveSessionHandler) + http.HandleFunc("/api/sessionMgr/list", handlers.ListActiveSessionsHandler) + http.HandleFunc("/api/sessionMgr/remove", handlers.RemoveSessionHandler) - http.HandleFunc("/frpcAct/instanceMgr/create", handlers.CreateInstanceHandler) - http.HandleFunc("/frpcAct/instanceMgr/delete", handlers.DeleteInstanceHandler) - http.HandleFunc("/frpcAct/instanceMgr/modify", handlers.ModifyInstanceHandler) - http.HandleFunc("/frpcAct/instanceMgr/list", handlers.ListInstancesHandler) - http.HandleFunc("/frpcAct/instanceMgr/start", handlers.StartInstanceHandler) - http.HandleFunc("/frpcAct/instanceMgr/stop", handlers.StopInstanceHandler) - http.HandleFunc("/frpcAct/instanceMgr/restart", handlers.RestartInstanceHandler) - http.HandleFunc("/frpcAct/instanceMgr/status", handlers.GetInstanceStatusHandler) - http.HandleFunc("/frpcAct/instanceMgr/getInfo", handlers.GetInstanceInfoHandler) - http.HandleFunc("/frpcAct/instanceMgr/logs", frpLogger.NewInstanceLogHandler(session.ValidateTokenFromMap).ServeHTTP) - http.HandleFunc("/frpcAct/proxyMgr/create", handlers.CreateProxyHandler) - http.HandleFunc("/frpcAct/proxyMgr/modify", handlers.ModifyProxyHandler) - http.HandleFunc("/frpcAct/proxyMgr/delete", handlers.DeleteProxyHandler) - http.HandleFunc("/frpcAct/proxyMgr/list", handlers.ListProxiesHandler) + http.HandleFunc("/api/frpcAct/instanceMgr/create", handlers.CreateInstanceHandler) + http.HandleFunc("/api/frpcAct/instanceMgr/delete", handlers.DeleteInstanceHandler) + http.HandleFunc("/api/frpcAct/instanceMgr/modify", handlers.ModifyInstanceHandler) + http.HandleFunc("/api/frpcAct/instanceMgr/list", handlers.ListInstancesHandler) + http.HandleFunc("/api/frpcAct/instanceMgr/start", handlers.StartInstanceHandler) + http.HandleFunc("/api/frpcAct/instanceMgr/stop", handlers.StopInstanceHandler) + http.HandleFunc("/api/frpcAct/instanceMgr/restart", handlers.RestartInstanceHandler) + http.HandleFunc("/api/frpcAct/instanceMgr/status", handlers.GetInstanceStatusHandler) + http.HandleFunc("/api/frpcAct/instanceMgr/getInfo", handlers.GetInstanceInfoHandler) + http.HandleFunc("/api/frpcAct/instanceMgr/logs", frpLogger.NewInstanceLogHandler(session.ValidateTokenFromMap).ServeHTTP) + http.HandleFunc("/api/frpcAct/proxyMgr/create", handlers.CreateProxyHandler) + http.HandleFunc("/api/frpcAct/proxyMgr/modify", handlers.ModifyProxyHandler) + http.HandleFunc("/api/frpcAct/proxyMgr/delete", handlers.DeleteProxyHandler) + http.HandleFunc("/api/frpcAct/proxyMgr/list", handlers.ListProxiesHandler) - http.HandleFunc("/", NotFoundHandler) + http.HandleFunc("/", web.ServeStatic) postLog.Info("Routes setup successfully") diff --git a/web/embed.go b/web/embed.go new file mode 100644 index 0000000..65206e0 --- /dev/null +++ b/web/embed.go @@ -0,0 +1,6 @@ +package web + +import "embed" + +//go:embed dist/* +var StaticFiles embed.FS diff --git a/web/handler.go b/web/handler.go new file mode 100644 index 0000000..e10a5e1 --- /dev/null +++ b/web/handler.go @@ -0,0 +1,93 @@ +package web + +import ( + "io/fs" + "net/http" + "path" + "strings" +) + +var staticFS http.FileSystem + +func init() { + subFS, err := fs.Sub(StaticFiles, "dist") + if err != nil { + panic(err) + } + staticFS = http.FS(subFS) +} + +func ServeStatic(w http.ResponseWriter, r *http.Request) { + urlPath := r.URL.Path + + if urlPath == "/" || urlPath == "/index.html" || strings.HasPrefix(urlPath, "/assets/") { + if urlPath == "/" { + urlPath = "/index.html" + } + + filePath := strings.TrimPrefix(urlPath, "/") + f, err := staticFS.Open(filePath) + if err != nil { + serveIndexHTML(w) + return + } + defer f.Close() + + stat, err := f.Stat() + if err != nil { + serveIndexHTML(w) + return + } + + if stat.IsDir() { + serveIndexHTML(w) + return + } + + setContentType(w, path.Ext(filePath)) + http.ServeContent(w, r, filePath, stat.ModTime(), f.(http.File)) + return + } + + serveIndexHTML(w) +} + +func serveIndexHTML(w http.ResponseWriter) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Header().Set("Cache-Control", "no-cache") + data, _ := StaticFiles.ReadFile("dist/index.html") + w.Write(data) +} + +func setContentType(w http.ResponseWriter, ext string) { + switch ext { + case ".html": + w.Header().Set("Content-Type", "text/html; charset=utf-8") + case ".css": + w.Header().Set("Content-Type", "text/css; charset=utf-8") + case ".js": + w.Header().Set("Content-Type", "application/javascript; charset=utf-8") + case ".json": + w.Header().Set("Content-Type", "application/json; charset=utf-8") + case ".png": + w.Header().Set("Content-Type", "image/png") + case ".jpg", ".jpeg": + w.Header().Set("Content-Type", "image/jpeg") + case ".gif": + w.Header().Set("Content-Type", "image/gif") + case ".svg": + w.Header().Set("Content-Type", "image/svg+xml") + case ".ico": + w.Header().Set("Content-Type", "image/x-icon") + case ".woff": + w.Header().Set("Content-Type", "font/woff") + case ".woff2": + w.Header().Set("Content-Type", "font/woff2") + case ".ttf": + w.Header().Set("Content-Type", "font/ttf") + case ".eot": + w.Header().Set("Content-Type", "application/vnd.ms-fontobject") + default: + w.Header().Set("Content-Type", "application/octet-stream") + } +}