Static files support clean url
All checks were successful
release-nightly / build-docker (push) Successful in 1m1s

This commit is contained in:
LittleSheep 2023-11-25 23:00:34 +08:00
parent 78c51bb432
commit ad3b36bc2a
3 changed files with 135 additions and 43 deletions

128
pkg/sign/responder.go Normal file
View File

@ -0,0 +1,128 @@
package sign
import (
"errors"
"fmt"
"io/fs"
"net/http"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/proxy"
"github.com/gofiber/fiber/v2/utils"
"github.com/samber/lo"
"github.com/spf13/viper"
"github.com/valyala/fasthttp"
)
func makeHypertextResponse(ctx *fiber.Ctx, upstream *UpstreamConfig) error {
timeout := time.Duration(viper.GetInt64("performance.network_timeout")) * time.Millisecond
return proxy.Do(ctx, upstream.MakeURI(ctx), &fasthttp.Client{
ReadTimeout: timeout,
WriteTimeout: timeout,
})
}
func makeFileResponse(ctx *fiber.Ctx, upstream *UpstreamConfig) error {
uri, queries := upstream.GetRawURI()
root := http.Dir(uri)
method := ctx.Method()
// We only serve static assets for GET and HEAD methods
if method != fiber.MethodGet && method != fiber.MethodHead {
return ctx.Next()
}
// Strip prefix
prefix := ctx.Route().Path
path := strings.TrimPrefix(ctx.Path(), prefix)
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
// Add prefix
if queries.Get("prefix") != "" {
path = queries.Get("prefix") + path
}
if len(path) > 1 {
path = utils.TrimRight(path, '/')
}
file, err := root.Open(path)
if err != nil && errors.Is(err, fs.ErrNotExist) {
if queries.Get("suffix") != "" {
file, err = root.Open(path + queries.Get("suffix"))
}
if err != nil && queries.Get("fallback") != "" {
file, err = root.Open(queries.Get("fallback"))
}
}
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return ctx.Status(fiber.StatusNotFound).Next()
}
return fmt.Errorf("failed to open: %w", err)
}
stat, err := file.Stat()
if err != nil {
return fmt.Errorf("failed to stat: %w", err)
}
// Serve index if path is directory
if stat.IsDir() {
indexPath := utils.TrimRight(path, '/') + queries.Get("index")
index, err := root.Open(indexPath)
if err == nil {
indexStat, err := index.Stat()
if err == nil {
file = index
stat = indexStat
}
}
}
ctx.Status(fiber.StatusOK)
modTime := stat.ModTime()
contentLength := int(stat.Size())
// Set Content-Type header
if queries.Get("charset") == "" {
ctx.Type(filepath.Ext(stat.Name()))
} else {
ctx.Type(filepath.Ext(stat.Name()), queries.Get("charset"))
}
// Set Last-Modified header
if !modTime.IsZero() {
ctx.Set(fiber.HeaderLastModified, modTime.UTC().Format(http.TimeFormat))
}
// Set Cache-Control header
if method == fiber.MethodGet {
maxAge, err := strconv.Atoi(queries.Get("maxAge"))
if lo.Ternary(err != nil, maxAge, 0) > 0 {
ctx.Set(fiber.HeaderCacheControl, "public, max-age="+queries.Get("maxAge"))
}
ctx.Response().SetBodyStream(file, contentLength)
return nil
}
if method == fiber.MethodHead {
ctx.Request().ResetBody()
ctx.Response().SkipBody = true
ctx.Response().Header.SetContentLength(contentLength)
if err := file.Close(); err != nil {
return fmt.Errorf("failed to close: %w", err)
}
return nil
}
return ctx.Next()
}

View File

@ -2,16 +2,9 @@ package sign
import ( import (
"errors" "errors"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/filesystem"
"github.com/gofiber/fiber/v2/middleware/proxy"
"github.com/samber/lo"
"github.com/spf13/viper"
"github.com/valyala/fasthttp"
"math/rand" "math/rand"
"net/http"
"strconv" "github.com/gofiber/fiber/v2"
"time"
) )
type AppConfig struct { type AppConfig struct {
@ -28,43 +21,16 @@ func (v *AppConfig) Forward(ctx *fiber.Ctx, site SiteConfig) error {
switch upstream.GetType() { switch upstream.GetType() {
case UpstreamTypeHypertext: case UpstreamTypeHypertext:
timeout := time.Duration(viper.GetInt64("performance.network_timeout")) * time.Millisecond return makeHypertextResponse(ctx, upstream)
return proxy.Do(ctx, upstream.MakeURI(ctx), &fasthttp.Client{
ReadTimeout: timeout,
WriteTimeout: timeout,
})
case UpstreamTypeFile: case UpstreamTypeFile:
uri, queries := upstream.GetRawURI() return makeFileResponse(ctx, upstream)
fs := filesystem.New(filesystem.Config{
Root: http.Dir(uri),
ContentTypeCharset: queries.Get("charset"),
Index: func() string {
val := queries.Get("index")
return lo.Ternary(len(val) > 0, val, "index.html")
}(),
NotFoundFile: func() string {
val := queries.Get("fallback")
return lo.Ternary(len(val) > 0, val, "404.html")
}(),
Browse: func() bool {
browse, err := strconv.ParseBool(queries.Get("browse"))
return lo.Ternary(err == nil, browse, false)
}(),
MaxAge: func() int {
age, err := strconv.Atoi(queries.Get("maxAge"))
return lo.Ternary(err == nil, age, 3600)
}(),
})
return fs(ctx)
default: default:
return fiber.ErrBadGateway return fiber.ErrBadGateway
} }
} }
type SiteConfig struct { type SiteConfig struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"`
Rules []RouterRuleConfig `json:"rules"` Rules []RouterRuleConfig `json:"rules"`
Transformers []RequestTransformerConfig `json:"transformers"` Transformers []RequestTransformerConfig `json:"transformers"`
Upstreams []UpstreamConfig `json:"upstreams"` Upstreams []UpstreamConfig `json:"upstreams"`

View File

@ -15,10 +15,8 @@ const (
) )
type UpstreamConfig struct { type UpstreamConfig struct {
ID string `json:"id"` ID string `json:"id"`
URI string `json:"uri"`
Name string `json:"name"`
URI string `json:"uri"`
} }
func (v *UpstreamConfig) GetType() string { func (v *UpstreamConfig) GetType() string {