2021SC@SDUSC
引言
上一期提到,我们可以向服务器发送文件,那我们怎么从服务器获取到这些文件呢?
func NewRouter() *gin.Engine {
r := gin.New()
r.StaticFS("/static", http.Dir(global.AppSetting.UploadSavePath))
...
}
值得注意的是,出于安全性的考虑,在实际项目中,我们不应当将应用服务和文件服务放在一起,也就是将文件资源与应用资源拆分开。
源码分析
r.StaticFS
方法对relativePath
禁用了:
和*
,使得该方法不能接收 URL 参数,然后它调用createStaticHandler
获得一个处理函数,然后传入进新注册的GET
和HEAD
两个路由。
代码中的*filepath
会匹配所有的文件路径,前提是,*filePath
标识符必须在一个路径的最后。
func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes {
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
panic("URL parameters can not be used when serving a static folder")
}
handler := group.createStaticHandler(relativePath, fs)
urlPattern := path.Join(relativePath, "/*filepath")
// Register GET and HEAD handlers
group.GET(urlPattern, handler)
group.HEAD(urlPattern, handler)
return group.returnObj()
}
createStaticHandler
先通过calculateAbsolutePath
方法计算出文件服务的绝对地址,再调用http.StripPrefix
从请求 URL 的路径中删除给定的前缀,并得到一个处理函数,然后它就利用该处理函数调用ServerHTTP
。
实际上,在调用ServerHTTP
时,对c.Writer
和c.Request
的处理逻辑在下文中的http.StripPrefix
中。
func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {
absolutePath := group.calculateAbsolutePath(relativePath)
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
return func(c *Context) {
if _, noListing := fs.(*onlyFilesFS); noListing {
c.Writer.WriteHeader(http.StatusNotFound)
}
file := c.Param("filepath")
// Check if file exists and/or if we have permission to access it
f, err := fs.Open(file)
if err != nil {
c.Writer.WriteHeader(http.StatusNotFound)
c.handlers = group.engine.noRoute
// Reset index
c.index = -1
return
}
f.Close()
fileServer.ServeHTTP(c.Writer, c.Request)
}
}
http.StripPrefix
返回一个处理器,该处理器会将请求中的路径中前缀去除后再交给h
处理。
func StripPrefix(prefix string, h Handler) Handler {
if prefix == "" {
return h
}
return HandlerFunc(func(w ResponseWriter, r *Request) {
p := strings.TrimPrefix(r.URL.Path, prefix)
rp := strings.TrimPrefix(r.URL.RawPath, prefix)
if len(p) < len(r.URL.Path) && (r.URL.RawPath == "" || len(rp) < len(r.URL.RawPath)) {
r2 := new(Request)
*r2 = *r
r2.URL = new(url.URL)
*r2.URL = *r.URL
r2.URL.Path = p
r2.URL.RawPath = rp
h.ServeHTTP(w, r2)
} else {
NotFound(w, r)
}
})
}
path.Join
本质上是字符串的拼接,它创建了一个长度足够的字节数组,然后将参数中的elem
依次添加进路径中,中间用斜杠分隔,遍历完之后调用path.Clean
,让路径看上去规范一点。
func Join(elem ...string) string {
size := 0
for _, e := range elem {
size += len(e)
}
if size == 0 {
return ""
}
buf := make([]byte, 0, size+len(elem)-1)
for _, e := range elem {
if len(buf) > 0 || e != "" {
if len(buf) > 0 {
buf = append(buf, '/')
}
buf = append(buf, e...)
}
}
return Clean(string(buf))
}