diff --git a/tools/gen/internal/generator/generator.go b/tools/gen/internal/generator/generator.go index 8a3b7f1..ed6d8dc 100644 --- a/tools/gen/internal/generator/generator.go +++ b/tools/gen/internal/generator/generator.go @@ -104,7 +104,9 @@ func Geerate(app *types.Applicetion) (err error) { } writer.Reset() } - + if err = writeFile(shortName+".go", []byte("package "+shortName)); err != nil { + return + } err = scanDir(protoDir, "third_party", func(filename string) error { if fp, openerr := protoDir.Open(filename); openerr != nil { return openerr diff --git a/transport/http/fs.go b/transport/http/fs.go new file mode 100644 index 0000000..a346390 --- /dev/null +++ b/transport/http/fs.go @@ -0,0 +1,153 @@ +package http + +import ( + "io/fs" + "net/http" + "os" + "path" + "strings" + "time" +) + +type ( + filesystem struct { + fs http.FileSystem + modtime time.Time + prefix string + indexFile string + denyDirectory bool + } + + httpFile struct { + fp http.File + modtime time.Time + } + + httpFileInfo struct { + name string + size int64 + mode fs.FileMode + isDir bool + modtime time.Time + } +) + +func (fi *httpFileInfo) Name() string { + return fi.name +} + +func (fi *httpFileInfo) Size() int64 { + return fi.size +} + +func (fi *httpFileInfo) Mode() fs.FileMode { + return fi.mode +} + +func (fi *httpFileInfo) ModTime() time.Time { + return fi.modtime +} + +func (fi *httpFileInfo) IsDir() bool { + return fi.isDir +} + +func (fi *httpFileInfo) Sys() any { + return nil +} + +func (file *httpFile) Close() error { + return file.fp.Close() +} + +func (file *httpFile) Read(p []byte) (n int, err error) { + return file.fp.Read(p) +} + +func (file *httpFile) Seek(offset int64, whence int) (int64, error) { + return file.fp.Seek(offset, whence) +} + +func (file *httpFile) Readdir(count int) ([]fs.FileInfo, error) { + return file.fp.Readdir(count) +} + +func (file *httpFile) Stat() (fs.FileInfo, error) { + fi, err := file.fp.Stat() + if err != nil { + return nil, err + } + return newFileInfo(fi, file.modtime), nil +} + +func (fs *filesystem) DenyAccessDirectory() { + fs.denyDirectory = true +} + +func (fs *filesystem) SetPrefix(prefix string) { + if prefix != "" { + if prefix[0] != '/' { + prefix = "/" + prefix + } + prefix = strings.TrimRight(prefix, "/") + fs.prefix = prefix + } +} + +func (fs *filesystem) SetIndexFile(indexFile string) { + fs.indexFile = indexFile +} + +func (fs *filesystem) Open(name string) (http.File, error) { + var ( + needRetry bool + ) + if name == "" || name == "/" { + needRetry = true + } + if fs.prefix != "" { + if !strings.HasPrefix(name, fs.prefix) { + name = path.Join(fs.prefix, name) + } + } + fp, err := fs.fs.Open(name) + if err != nil { + return nil, err + } + if fs.denyDirectory { + state, err := fp.Stat() + if err != nil { + return nil, err + } + if state.IsDir() { + if needRetry { + if fs.indexFile != "" { + return fs.Open(path.Join(name, fs.indexFile)) + } + } + return nil, os.ErrPermission + } + } + return &httpFile{fp: fp, modtime: fs.modtime}, nil +} + +func newFS(modtime time.Time, fs http.FileSystem) *filesystem { + return &filesystem{ + fs: fs, + modtime: modtime, + } +} + +func newFileInfo(fi fs.FileInfo, modtime time.Time) *httpFileInfo { + return &httpFileInfo{ + name: fi.Name(), + size: fi.Size(), + mode: fi.Mode(), + isDir: fi.IsDir(), + modtime: modtime, + } +} + +func FileSystem(s fs.FS) http.FileSystem { + return http.FS(s) +} diff --git a/transport/http/server.go b/transport/http/server.go index 49d0ba1..1bfcf02 100644 --- a/transport/http/server.go +++ b/transport/http/server.go @@ -6,7 +6,9 @@ import ( "net/http" "net/http/pprof" "net/url" + "path" "sync" + "time" "git.nobla.cn/golang/aeus/metadata" "git.nobla.cn/golang/aeus/middleware" @@ -25,6 +27,7 @@ type Server struct { engine *gin.Engine once sync.Once listener net.Listener + fs *filesystem middlewares []middleware.Middleware } @@ -87,6 +90,26 @@ func (s *Server) Use(middlewares ...middleware.Middleware) { s.middlewares = append(s.middlewares, middlewares...) } +func (s *Server) Webroot(prefix string, fs http.FileSystem) { + s.fs = newFS(time.Now(), fs) + s.fs.SetPrefix(prefix) + s.fs.DenyAccessDirectory() + s.fs.SetIndexFile("/index.html") +} + +func (s *Server) notFoundHandle(ctx *gin.Context) { + if s.fs != nil && ctx.Request.Method == http.MethodGet { + uri := path.Clean(ctx.Request.URL.Path) + if fp, err := s.fs.Open(uri); err == nil { + http.ServeContent(ctx.Writer, ctx.Request, path.Base(uri), s.fs.modtime, fp) + fp.Close() + ctx.Abort() + return + } + } + ctx.JSON(http.StatusNotFound, newResponse(errors.NotFound, "Not Found", nil)) +} + func (s *Server) requestInterceptor() gin.HandlerFunc { return func(ginCtx *gin.Context) { ctx := ginCtx.Request.Context() @@ -158,6 +181,7 @@ func (s *Server) Start(ctx context.Context) (err error) { if err = s.createListener(); err != nil { return } + s.engine.NoRoute(s.notFoundHandle) s.opts.logger.Info(ctx, "http server listen on: %s", s.uri.Host) if s.opts.certFile != "" && s.opts.keyFile != "" { s.uri.Scheme = "https"