【sduoj】开启文件服务

2021SC@SDUSC

引言

上一期提到,我们可以向服务器发送文件,那我们怎么从服务器获取到这些文件呢?

func NewRouter() *gin.Engine {
    
    
	r := gin.New()
	r.StaticFS("/static", http.Dir(global.AppSetting.UploadSavePath))
	...
}

值得注意的是,出于安全性的考虑,在实际项目中,我们不应当将应用服务和文件服务放在一起,也就是将文件资源与应用资源拆分开。

源码分析

r.StaticFS方法对relativePath禁用了:*,使得该方法不能接收 URL 参数,然后它调用createStaticHandler获得一个处理函数,然后传入进新注册的GETHEAD两个路由。

代码中的*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.Writerc.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))
}

猜你喜欢

转载自blog.csdn.net/weixin_45922876/article/details/120808467