-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstatic.go
132 lines (117 loc) · 4.21 KB
/
static.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package kanggo
import (
"fmt"
"github.com/7836246/kanggo/constants"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
// StaticConfig 配置结构体,定义静态文件服务的选项
type StaticConfig struct {
Compress bool // 是否启用压缩,减少传输体积,默认值 false
ByteRange bool // 是否支持字节范围请求,默认值 false
Browse bool // 是否启用目录浏览,允许用户查看文件夹中的内容,默认值 false
Download bool // 是否启用文件下载,启用后所有文件将以附件形式下载,默认值 false
Index string // 用于提供目录的索引文件的名称,例如 "index.html",默认值为 "index.html"
CacheDuration time.Duration // 非活动文件处理程序的缓存持续时间,使用负值禁用此选项,默认值 10 秒
MaxAge int // 设置文件响应的 Cache-Control HTTP 头的值,MaxAge 以秒为单位,默认值 0
ModifyResponse func(http.ResponseWriter, *http.Request) // 自定义函数,允许修改响应,默认值为 nil
Next func(*Context) bool // 定义一个函数,当返回 true 时跳过此中间件,默认值为 nil
}
// NewStaticConfig 返回一个带有默认值的 StaticConfig 配置实例
func NewStaticConfig() StaticConfig {
return StaticConfig{
Compress: false,
ByteRange: false,
Browse: false,
Download: false,
Index: "index.html",
CacheDuration: 10 * time.Second,
MaxAge: 0,
ModifyResponse: nil,
Next: nil,
}
}
// Static 注册一个静态文件服务路由
func (k *KangGo) Static(prefix, root string, config ...StaticConfig) *KangGo {
cfg := NewStaticConfig()
if len(config) > 0 {
cfg = config[0]
}
// 确保前缀总是以 '/' 开头
if !strings.HasPrefix(prefix, "/") {
prefix = "/" + prefix
}
// 去除前缀中的尾部斜杠,确保前缀统一
prefix = strings.TrimSuffix(prefix, "/")
handler := func(ctx *Context) error {
if cfg.Next != nil && cfg.Next(ctx) {
return nil
}
// 获取相对路径
relativePath := strings.TrimPrefix(ctx.Request.URL.Path, prefix)
relativePath = strings.TrimPrefix(relativePath, "/")
filePath := filepath.Join(root, relativePath)
// 检查文件或目录是否存在
info, err := os.Stat(filePath)
if os.IsNotExist(err) {
http.NotFound(ctx.Writer, ctx.Request)
return nil
} else if err != nil {
http.Error(ctx.Writer, "500 服务器内部错误", http.StatusInternalServerError)
return nil
}
// 处理目录请求
if info.IsDir() {
indexFile := filepath.Join(filePath, cfg.Index)
if _, err := os.Stat(indexFile); err == nil {
http.ServeFile(ctx.Writer, ctx.Request, indexFile)
return nil
}
if cfg.Browse {
return browseDirectory(ctx.Writer, filePath)
}
http.Error(ctx.Writer, "403 禁止访问目录", http.StatusForbidden)
return nil
}
// 处理文件请求
if cfg.MaxAge > 0 {
ctx.Writer.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(cfg.MaxAge))
}
if cfg.ByteRange {
ctx.Writer.Header().Set("Accept-Ranges", "bytes")
}
if cfg.Download {
ctx.Writer.Header().Set("Content-Disposition", "attachment")
}
if cfg.ModifyResponse != nil {
cfg.ModifyResponse(ctx.Writer, ctx.Request)
}
http.ServeFile(ctx.Writer, ctx.Request, filePath)
return nil
}
k.Router.Handle(constants.MethodGet, prefix+"/*", handler)
return k
}
// browseDirectory 提供简单的目录浏览功能
func browseDirectory(w http.ResponseWriter, dirPath string) error {
files, err := os.ReadDir(dirPath)
if err != nil {
http.Error(w, "500 服务器内部错误", http.StatusInternalServerError)
return err
}
// 构建简单的目录列表 HTML
fmt.Fprintf(w, "<html><body><h1>目录浏览</h1><ul>")
for _, file := range files {
name := file.Name()
if file.IsDir() {
name += "/"
}
fmt.Fprintf(w, "<li><a href=\"%s\">%s</a></li>", name, name)
}
fmt.Fprint(w, "</ul></body></html>")
return nil
}