[change] optimizing static http server.
This commit is contained in:
parent
7b312c79c9
commit
95ff65d625
4 changed files with 144 additions and 80 deletions
120
service/fs/fs.go
Normal file
120
service/fs/fs.go
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dir http root path
|
||||||
|
type Dir string
|
||||||
|
|
||||||
|
// DirPWA is the PWA path
|
||||||
|
type DirPWA string
|
||||||
|
|
||||||
|
// Open implements FileSystem using os.Open, opening files for reading rooted
|
||||||
|
// and relative to the directory d.
|
||||||
|
func (d Dir) Open(name string) (http.File, error) {
|
||||||
|
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
|
||||||
|
return nil, errors.New("http: invalid character in file path")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := string(d)
|
||||||
|
if dir == "" {
|
||||||
|
dir = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
name = filepath.FromSlash(path.Clean("/" + name))
|
||||||
|
relName := filepath.Join(dir, name)
|
||||||
|
|
||||||
|
// Close dir views Disable directory listing
|
||||||
|
absName := filepath.Join(application.App.Root(), relName)
|
||||||
|
stat, err := os.Stat(absName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, mapOpenError(err, relName, filepath.Separator, os.Stat)
|
||||||
|
}
|
||||||
|
|
||||||
|
if stat.IsDir() {
|
||||||
|
if _, err := os.Stat(filepath.Join(absName, "index.html")); os.IsNotExist(err) {
|
||||||
|
return nil, mapOpenError(fs.ErrNotExist, relName, filepath.Separator, os.Stat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := application.App.FS(string(d)).Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, mapOpenError(err, relName, filepath.Separator, os.Stat)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open implements FileSystem using os.Open, opening files for reading rooted
|
||||||
|
// and relative to the directory d.
|
||||||
|
func (d DirPWA) Open(name string) (http.File, error) {
|
||||||
|
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
|
||||||
|
return nil, errors.New("http: invalid character in file path")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := string(d)
|
||||||
|
if dir == "" {
|
||||||
|
dir = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
name = filepath.FromSlash(path.Clean("/" + name))
|
||||||
|
relName := filepath.Join(dir, name)
|
||||||
|
|
||||||
|
if filepath.Ext(relName) == "" && relName != dir {
|
||||||
|
relName = filepath.Join(dir, "index.html")
|
||||||
|
name = filepath.Join(string(os.PathSeparator), "index.html")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close dir views Disable directory listing
|
||||||
|
absName := filepath.Join(application.App.Root(), relName)
|
||||||
|
stat, err := os.Stat(absName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, mapOpenError(err, relName, filepath.Separator, os.Stat)
|
||||||
|
}
|
||||||
|
|
||||||
|
if stat.IsDir() {
|
||||||
|
if _, err := os.Stat(filepath.Join(absName, "index.html")); os.IsNotExist(err) {
|
||||||
|
return nil, mapOpenError(fs.ErrNotExist, relName, filepath.Separator, os.Stat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := application.App.FS(string(d)).Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, mapOpenError(err, relName, filepath.Separator, os.Stat)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapOpenError maps the provided non-nil error from opening name
|
||||||
|
// to a possibly better non-nil error. In particular, it turns OS-specific errors
|
||||||
|
// about opening files in non-directories into fs.ErrNotExist. See Issues 18984 and 49552.
|
||||||
|
func mapOpenError(originalErr error, name string, sep rune, stat func(string) (fs.FileInfo, error)) error {
|
||||||
|
if errors.Is(originalErr, fs.ErrNotExist) || errors.Is(originalErr, fs.ErrPermission) {
|
||||||
|
return originalErr
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(name, string(sep))
|
||||||
|
for i := range parts {
|
||||||
|
if parts[i] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fi, err := stat(strings.Join(parts[:i+1], string(sep)))
|
||||||
|
if err != nil {
|
||||||
|
return originalErr
|
||||||
|
}
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return fs.ErrNotExist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return originalErr
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
// Guards middlewares
|
// Guards middlewares
|
||||||
var Guards = map[string]gin.HandlerFunc{
|
var Guards = map[string]gin.HandlerFunc{
|
||||||
"bearer-jwt": guardBearerJWT, // Bearer JWT
|
"bearer-jwt": guardBearerJWT, // Bearer JWT
|
||||||
|
"query-jwt": guardQueryJWT, // Get JWT Token from query string "__tk"
|
||||||
"cross-origin": guardCrossOrigin, // Cross-Origin Resource Sharing
|
"cross-origin": guardCrossOrigin, // Cross-Origin Resource Sharing
|
||||||
"widget-table": table.Guard, // Widget Table Guard
|
"widget-table": table.Guard, // Widget Table Guard
|
||||||
"widget-list": list.Guard, // Widget List Guard
|
"widget-list": list.Guard, // Widget List Guard
|
||||||
|
|
@ -38,6 +39,19 @@ func guardBearerJWT(c *gin.Context) {
|
||||||
c.Set("__sid", claims.SID)
|
c.Set("__sid", claims.SID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JWT Bearer JWT
|
||||||
|
func guardQueryJWT(c *gin.Context) {
|
||||||
|
tokenString := c.Query("__tk")
|
||||||
|
if tokenString == "" {
|
||||||
|
c.JSON(403, gin.H{"code": 403, "message": "No permission"})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := helper.JwtValidate(tokenString)
|
||||||
|
c.Set("__sid", claims.SID)
|
||||||
|
}
|
||||||
|
|
||||||
// CORS Cross Origin
|
// CORS Cross Origin
|
||||||
func guardCrossOrigin(c *gin.Context) {
|
func guardCrossOrigin(c *gin.Context) {
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Middlewares 服务中间件
|
// Middlewares the middlewares
|
||||||
var Middlewares = []func(c *gin.Context){
|
var Middlewares = []func(c *gin.Context){
|
||||||
BinStatic,
|
withStaticFileServer,
|
||||||
}
|
}
|
||||||
|
|
||||||
// BinStatic 静态文件服务
|
// withStaticFileServer static file server
|
||||||
func BinStatic(c *gin.Context) {
|
func withStaticFileServer(c *gin.Context) {
|
||||||
|
|
||||||
length := len(c.Request.URL.Path)
|
length := len(c.Request.URL.Path)
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ func BinStatic(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用内静态文件目录(/ui or public)
|
// static file server
|
||||||
AppFileServer.ServeHTTP(c.Writer, c.Request)
|
AppFileServer.ServeHTTP(c.Writer, c.Request)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,12 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/yaoapp/yao/config"
|
|
||||||
"github.com/yaoapp/yao/data"
|
"github.com/yaoapp/yao/data"
|
||||||
|
"github.com/yaoapp/yao/service/fs"
|
||||||
"github.com/yaoapp/yao/share"
|
"github.com/yaoapp/yao/share"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -27,84 +22,19 @@ var AdminRoot = ""
|
||||||
// AdminRootLen cache
|
// AdminRootLen cache
|
||||||
var AdminRootLen = 0
|
var AdminRootLen = 0
|
||||||
|
|
||||||
// Dir files
|
|
||||||
type Dir string
|
|
||||||
|
|
||||||
// SetupStatic setup static file server
|
// SetupStatic setup static file server
|
||||||
func SetupStatic() error {
|
func SetupStatic() error {
|
||||||
|
|
||||||
// SetAdmin Root
|
// SetAdmin Root
|
||||||
adminRoot()
|
adminRoot()
|
||||||
|
|
||||||
// Static file server
|
|
||||||
AppFileServer = http.FileServer(Dir(filepath.Join(config.Conf.Root, "public")))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open implements FileSystem using os.Open, opening files for reading rooted
|
|
||||||
// and relative to the directory d.
|
|
||||||
func (d Dir) Open(name string) (http.File, error) {
|
|
||||||
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
|
|
||||||
return nil, errors.New("http: invalid character in file path")
|
|
||||||
}
|
|
||||||
|
|
||||||
dir := string(d)
|
|
||||||
if dir == "" {
|
|
||||||
dir = "."
|
|
||||||
}
|
|
||||||
|
|
||||||
name = path.Clean("/" + name)
|
|
||||||
fullName := filepath.Join(dir, filepath.FromSlash(name))
|
|
||||||
if isPWA() {
|
if isPWA() {
|
||||||
if filepath.Ext(fullName) == "" && fullName != dir {
|
AppFileServer = http.FileServer(fs.DirPWA("public"))
|
||||||
fullName = filepath.Join(dir, "index.html")
|
return nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close dir views Disable directory listing
|
AppFileServer = http.FileServer(fs.Dir("public"))
|
||||||
stat, err := os.Stat(fullName)
|
return nil
|
||||||
if err != nil {
|
|
||||||
return nil, mapOpenError(err, fullName, filepath.Separator, os.Stat)
|
|
||||||
}
|
|
||||||
|
|
||||||
if stat.IsDir() {
|
|
||||||
indexFile := filepath.Join(fullName, "index.html")
|
|
||||||
if _, err := os.Stat(indexFile); os.IsNotExist(err) {
|
|
||||||
return nil, mapOpenError(fs.ErrNotExist, fullName, filepath.Separator, os.Stat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(fullName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mapOpenError(err, fullName, filepath.Separator, os.Stat)
|
|
||||||
}
|
|
||||||
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mapOpenError maps the provided non-nil error from opening name
|
|
||||||
// to a possibly better non-nil error. In particular, it turns OS-specific errors
|
|
||||||
// about opening files in non-directories into fs.ErrNotExist. See Issues 18984 and 49552.
|
|
||||||
func mapOpenError(originalErr error, name string, sep rune, stat func(string) (fs.FileInfo, error)) error {
|
|
||||||
if errors.Is(originalErr, fs.ErrNotExist) || errors.Is(originalErr, fs.ErrPermission) {
|
|
||||||
return originalErr
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(name, string(sep))
|
|
||||||
for i := range parts {
|
|
||||||
if parts[i] == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fi, err := stat(strings.Join(parts[:i+1], string(sep)))
|
|
||||||
if err != nil {
|
|
||||||
return originalErr
|
|
||||||
}
|
|
||||||
if !fi.IsDir() {
|
|
||||||
return fs.ErrNotExist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return originalErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// rewrite path
|
// rewrite path
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue