diff --git a/Dockerfile b/Dockerfile index bb3b2666..6fff17d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,7 @@ LABEL maintainer="mingcheng" COPY . /answer WORKDIR /answer - -RUN make install-ui-packages ui -RUN mv ui/build /tmp -CMD ls -al /tmp -RUN du -sh /tmp/build - +RUN make install-ui-packages ui && mv ui/build /tmp FROM golang:1.18 AS golang-builder LABEL maintainer="aichy" @@ -23,7 +18,7 @@ ENV GOPRIVATE git.backyard.segmentfault.com # Build COPY . ${BUILD_DIR} WORKDIR ${BUILD_DIR} -COPY --from=node-builder /tmp/build ${BUILD_DIR}/web/html +COPY --from=node-builder /tmp/build ${BUILD_DIR}/ui/build RUN make clean build && \ cp answer /usr/bin/answer && \ mkdir -p /tmp/cache && chmod 777 /tmp/cache && \ @@ -31,8 +26,6 @@ RUN make clean build && \ mkdir -p /data/upfiles && chmod 777 /data/upfiles && \ mkdir -p /data/i18n && chmod 777 /data/i18n && cp -r i18n/*.yaml /data/i18n - - FROM debian:bullseye ENV TZ "Asia/Shanghai" RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list \ diff --git a/internal/base/server/http.go b/internal/base/server/http.go index b021fe4a..e4a2cf54 100644 --- a/internal/base/server/http.go +++ b/internal/base/server/http.go @@ -11,7 +11,7 @@ func NewHTTPServer(debug bool, staticRouter *router.StaticRouter, answerRouter *router.AnswerAPIRouter, swaggerRouter *router.SwaggerRouter, - viewRouter *router.ViewRouter, + viewRouter *router.UIRouter, authUserMiddleware *middleware.AuthUserMiddleware) *gin.Engine { if debug { @@ -22,7 +22,7 @@ func NewHTTPServer(debug bool, r := gin.New() r.GET("/healthz", func(ctx *gin.Context) { ctx.String(200, "OK") }) - viewRouter.RegisterViewRouter(r) + viewRouter.Register(r) rootGroup := r.Group("") swaggerRouter.Register(rootGroup) diff --git a/internal/router/ui.go b/internal/router/ui.go new file mode 100644 index 00000000..ff4d23ba --- /dev/null +++ b/internal/router/ui.go @@ -0,0 +1,78 @@ +package router + +import ( + "embed" + "fmt" + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/ui" + "github.com/segmentfault/pacman/log" + "io/fs" + "net/http" + "os" +) + +const UIIndexFilePath = "build/index.html" +const UIStaticPath = "build/static" + +// UIRouter is an interface that provides ui static file routers +type UIRouter struct { +} + +// NewUIRouter creates a new UIRouter instance with the embed resources +func NewUIRouter() *UIRouter { + return &UIRouter{} +} + +// _resource is an interface that provides static file, it's a private interface +type _resource struct { + fs embed.FS +} + +// Open to implement the interface by http.FS required +func (r *_resource) Open(name string) (fs.File, error) { + name = fmt.Sprintf(UIStaticPath+"/%s", name) + log.Debugf("open static path %s", name) + return r.fs.Open(name) +} + +// Register a new static resource which generated by ui directory +func (a *UIRouter) Register(r *gin.Engine) { + staticPath := os.Getenv("ANSWER_STATIC_PATH") + info, err := os.Stat(staticPath) + + // if ANSWER_STATIC_PATH is set and not empty, ignore embed resource + if staticPath != "" { + if err != nil || !info.IsDir() { + log.Error(err) + } else { + log.Debugf("registering static path %s", staticPath) + + r.LoadHTMLGlob(staticPath + "/*.html") + r.Static("/static", staticPath+"/static") + r.NoRoute(func(c *gin.Context) { + c.HTML(http.StatusOK, "index.html", gin.H{}) + }) + + // return immediately if the static path is set + return + } + } + + // handle the static file by default ui static files + r.StaticFS("/static", http.FS(&_resource{ + fs: ui.Build, + })) + + // specify the not router for default routes and redirect + r.NoRoute(func(c *gin.Context) { + index, err := ui.Build.ReadFile(UIIndexFilePath) + if err != nil { + log.Error(err) + c.Status(http.StatusNotFound) + return + } + + c.Header("content-type", "text/html;charset=utf-8") + c.String(http.StatusOK, string(index)) + }) +} diff --git a/internal/router/ui_test.go b/internal/router/ui_test.go new file mode 100644 index 00000000..1ec160e9 --- /dev/null +++ b/internal/router/ui_test.go @@ -0,0 +1,36 @@ +package router + +import ( + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +func TestUIRouter_Register(t *testing.T) { + r := gin.Default() + + NewUIRouter().Register(r) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestUIRouter_Static(t *testing.T) { + r := gin.Default() + + NewUIRouter().Register(r) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/static/version.txt", nil) + + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "OK", w.Body.String()) +} diff --git a/internal/router/view_router.go b/internal/router/view_router.go deleted file mode 100644 index 36876932..00000000 --- a/internal/router/view_router.go +++ /dev/null @@ -1,67 +0,0 @@ -package router - -import ( - "embed" - "errors" - "io/fs" - "net/http" - "os" - "path" - "path/filepath" - "strings" - - "github.com/gin-gonic/gin" - "github.com/segmentfault/answer/web" -) - -// RegisterViewRouter -type ViewRouter struct { -} - -// NewRegisterViewRouter -func NewViewRouter() *ViewRouter { - return &ViewRouter{} -} - -type Resource struct { - fs embed.FS - path string -} - -func NewResource() *Resource { - return &Resource{ - fs: web.Static, - path: "html", - } -} - -func (r *Resource) Open(name string) (fs.File, error) { - if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) { - return nil, errors.New("http: invalid character in file path") - } - fullName := filepath.Join(r.path, filepath.FromSlash(path.Clean("/static/"+name))) - file, err := r.fs.Open(fullName) - return file, err -} - -func (a *ViewRouter) RegisterViewRouter(r *gin.Engine) { - //export answer_html_static_path="../../web/static" - //export answer_html_page_path="../../web" - static := os.Getenv("answer_html_static_path") - index := os.Getenv("answer_html_page_path") - if len(static) > 0 && len(index) > 0 { - r.LoadHTMLGlob(index + "/*.html") - r.Static("/static", static) - r.NoRoute(func(c *gin.Context) { - c.HTML(http.StatusOK, "index.html", gin.H{}) - }) - return - } else { - r.StaticFS("/static", http.FS(NewResource())) - r.NoRoute(func(c *gin.Context) { - c.Header("content-type", "text/html;charset=utf-8") - c.String(200, string(web.Html)) - }) - } - -} diff --git a/web/html/index.html b/ui/build/index.html similarity index 100% rename from web/html/index.html rename to ui/build/index.html diff --git a/ui/static.go b/ui/static.go new file mode 100644 index 00000000..74002b6b --- /dev/null +++ b/ui/static.go @@ -0,0 +1,9 @@ +package ui + +import ( + "embed" + _ "embed" +) + +//go:embed build +var Build embed.FS diff --git a/web/html.go b/web/html.go deleted file mode 100644 index bbaa02fa..00000000 --- a/web/html.go +++ /dev/null @@ -1,9 +0,0 @@ -package web - -import "embed" - -//go:embed html/index.html -var Html []byte - -//go:embed html/static -var Static embed.FS