Golang Gin作为一个优秀的框架,不仅为我们提供了托管文件的能力,还为我们提供了从io.Reader
,这篇文章除了介绍文件托管的使用和原理外,我们还会利用其托管io.Reader
的能力,反向代理www.baidu.com
网站,也就是说,我们在浏览器里访问http://localhost:8080/
就可以看到百度的网站的内容了,就像百度的镜像一样。
通过这篇文章你可以学到(6000多字大章):
- 托管一个静态文件
- 托管一个目录
- 如何实现FTP服务器效果
- 自定义托管内容类型
- 托管一个Reader
- 静态文件托管原理分析
- Gin是如何禁止目录列表的
- 镜像百度网站
- 封装一个直接拿来用的镜像服务代理
- 多域名API服务聚合(API 网关?),解决CROS跨域问题
托管一个静态文件
在项目的开发中,你可能需要这么一个功能:把服务器上的JS文件暴露出来以供访问,比如让网站调用里面的JS函数等。对于这种情形,我们可以使用Gin提供的StaticFile
方法很方便的完成。
func main() {
router := gin.Default()
router.StaticFile("/adobegc.log", "/tmp/adobegc.log")
router.Run(":8080")
}
通过StaticFile
方法,把文件/tmp/adobegc.log
托管在网络上,并且设置访问路径为/adobegc.log
,这样我们通过http://localhost:8080/adobegc.log
就可以访问这个文件,看到它的内容了。
通过这种方式可以托管任何类型的文件,并且我们不用指定Content-Type
,因为会自动识别。
现在我们又有一个需求,想托管很多静态文件,如果使用StaticFile
方法一个个的设置会很繁琐,有没有更简单的方法呢?接着往下看。
托管一个目录
一般情况下,我们会把我们的静态文件放在一个目录中,比如我们使用Gin做网站开发的时候,可以把CSS、JS和Image这些静态资源文件都放在一个目录中,然后使用Static
方法把整个目录托管,这样就可以自由访问这个目录中的所有文件了。
router.Static("/static", "/tmp")
只需要新增这样一行代码就可以实现,非常简单。Static
方法的第一个参数是设置的相对路径,第二个参数是本机目录的绝对路径。
现在我们就可以通过http://localhost:8080/static/adobegc.log
访问上一节里演示的那个adobegc.log
的内容了,和直接访问http://localhost:8080/adobegc.log
效果是一样的。
实现一个FTP服务器
上一节的例子,如果你在浏览器里访问http://localhost:8080/static/
,你会得到404的错误,这是因为Gin做了安全措施,防止第三方恶意罗列获取你服务器上的所有文件。
但是你的需求正好是要搭建一个类似FTP的服务器,就是想把服务器上的文件件共享给其他人使用,比如下载电影等。这时候你就需要一个可以列出目录的功能了,也就是我们访问http://localhost:8080/static/
可以看到/tmp/
目录下的所有文件(包括文件夹),点击文件夹还可以展开看到里面的文件和文件夹,选择合适的文件进行下载,这样就是一个完整的FTP服务器了。
router.StaticFS("/static1", gin.Dir("/tmp", true))
这里我们用到了StaticFS
方法,也是一行代码就可以搞定,为了和上个例子区分,我这里采用/static1
作为相对路径。
这里的关键点在于gin.Dir
函数的第二个参数,true
代表可以列目录的意思。
// if listDirectory == true, then it works the same as http.Dir() otherwise it returns
// a filesystem that prevents http.FileServer() to list the directory files.
func Dir(root string, listDirectory bool) http.FileSystem
现在我们启动访问http://localhost:8080/static1/
就可以看到文件和文件夹列表了,和FTP服务器是类似的。
自定义托管内容类型
以上的示例都是托管一个静态文件或者目录,我们并没有太多的自定义能力,比如设置内容类型,托管一个文件的部分内容等等。
对于这类需求,Gin为我们提供了Data
方法来实现,以第一节中的adobegc.log
为例。
router.GET("/adobegc.log", func(c *gin.Context) {
data, err := ioutil.ReadFile("/tmp/adobegc.log")
if err != nil {
c.AbortWithError(500, err)
} else {
c.Data(200, "text/plain; charset=utf-8", data)
}
})
这个例子实现的效果和上面的是一样的,不一样的是我们通过c.Data
这个方法来实现,这个方法有三个参数:
func (c *Context) Data(code int, contentType string, data []byte)
这就为为我们自定义提供了便利,比如可以指定contentType
和内容data
,这种能力很有用,比如我们可以把我们储存在数据库中的图片二进制数据,作为一张图片显示在网站上。
功能更强大的Reader托管。
除了可以从一个字节数组[]byte
中读取数据显示外,Gin还为我们提供了从一个io.Reader
中获取数据,并且提供了更强大的自定义能力,它就是DataFromReader
方法。
func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string)
从上面的方法签名我们可以看到我们可以自定义的内容:
- 要显示的内容长度
- 内容的类型
- 一个内容源Reader
- 响应的头信息extraHeaders
尤其是自定义的头信息,可以让我们做很多事情,比如缓存等。这个方法的使用比较简单,和上面的Data
方法差不多,这里不再举例,后面我们会通过镜像百度网站这个示例来演示它的使用。
基于源代码分析原理
会使用和知道原理是两码事,面试的时候,面试官也喜欢问具体的源代码实现,这样才能更看出一个人的能力。接下来我们就基于源代码来分析静态文件是如何托管成文件服务的、Gin如果实现安全的防目录列表的;然后会通过Gin的这些能力,镜像一个百度的网站(反向代理),让你访问localhost:8080
就可以访问百度网站;最后会提供一个封装好的类库(直接拿来用),可以非常方便的通过Gin反向代理任意服务,通过它你可以实现聚和多个域名上的API服务,可以解决浏览器跨域的问题。