概要
fileserver是静态文件服务,其主要功能是根据提供文件查询与文件传输的功能。由golang标准库提供。
入口函数
func FileServer(root FileSystem) Handler {
return &fileHandler{root}
}
type fileHandler struct {
root FileSystem
}
它的入参是一个带有根路径的文件系统,返回值是一个http_handler(处理函数)
FileSystem文件系统,支持访问文件路径,无论使用何种操作系统,文件路径通过‘/’进行分隔
type FileSystem interface {
Open(name string) (File, error) // 打开一个文件
}
type File interface {
io.Closer // 关闭文件
io.Reader // 读文件
io.Seeker // 文件位置信息
Readdir(count int) ([]os.FileInfo, error) // 读文件夹
Stat() (os.FileInfo, error) // 文件状态
}
处理函数
func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
upath := r.URL.Path
if !strings.HasPrefix(upath, "/") { // 改写path
upath = "/" + upath
r.URL.Path = upath
}
serveFile(w, r, f.root, path.Clean(upath), true) // 核心逻辑
}
func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
f, err := fs.Open(name) // 打开文件,全路径是由root+name
d, err := f.Stat()
if d.IsDir() { // 如果path指向的是文件夹
if checkIfModifiedSince(r, d.ModTime()) == condFalse { // 判断文件夹是否更新
writeNotModified(w)
return
}
setLastModified(w, d.ModTime()) // 写入文件夹更新时间
dirList(w, r, f) // 见后
return
}
// 如果path指向的是文件
sizeFunc := func() (int64, error) { return d.Size(), nil }
serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f) // 这部分代码比较复杂,起主要逻辑是通过request中的range信息,多线程读取文件内容,返回写入response中。从中我们可以看到静态服务器是支持断点续传的http服务
}
文件夹列表
func dirList(w ResponseWriter, r *Request, f File) {
dirs, err := f.Readdir(-1) // 读文件夹
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() }) // 按照文件名进行排序
// 写html格式的response
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, "<pre>\n")
for _, d := range dirs {
name := d.Name()
if d.IsDir() { // 如果是子目录
name += "/"
}
url := url.URL{Path: name}
fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
}
fmt.Fprintf(w, "</pre>\n")
}
用于将字符串路径转换为文件系统
主要的功能是兼容不同的os
定义
type Dir string
// name是当前os的文件路径格式
func (d Dir) Open(name string) (File, error) {
dir := string(d) // 根目录
if dir == "" {
dir = "."
}
fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))) // 拼出全路径
f, err := os.Open(fullName)
if err != nil {
return nil, mapDirOpenError(err, fullName)
}
return f, nil
}
辅助中间件
// 该函数对象改写request的path,是一个中间件
func StripPrefix(prefix string, h Handler) Handler {
if prefix == "" {
return h
}
return HandlerFunc(func(w ResponseWriter, r *Request) { // 其定义见后
if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) { // 前缀匹配
r2 := new(Request) // 创建一个新的request
*r2 = *r
r2.URL = new(url.URL)
*r2.URL = *r.URL
r2.URL.Path = p // 改写path
h.ServeHTTP(w, r2) // 调用hander的处理逻辑
} else {
NotFound(w, r)
}
})
}
type HandlerFunc func(ResponseWriter, *Request) // 定义一个函数对象
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { // 该函数对象的ServeHTTP方法是执行自身
f(w, r)
}
put it together
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))