转载请注明出处

今天我们用go来搭建一个文件服务器FileServer,并且我们简单分析一下,它究竟是如何工作的。知其然,并知其所以然!

首先搭建一个最简单的,资源就挂载在服务器的根目录下,并且路由路径为根路径:127.0.0.1:8080/

    http.Handle("/", http.FileServer(http.Dir("sourse")))

    err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println(err)
}

服务器程序和资源结构如下:

打开源码,我们定位到net/http/fs.go文件中,看看http.FileServer是如何定义的

func FileServer(root FileSystem) Handler {
return &fileHandler{root}
}

原来FileServer函数是返回一个Handler,接下来我们再看看fileHandler是怎么定义的

type fileHandler struct {
root FileSystem
}

原来是个结构体,既然是个Handler,那么它一定实现了ServeHttp函数,找找看

func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
upath := r.URL.Path
if !strings.HasPrefix(upath, "/") {
upath = "/" + upath
r.URL.Path = upath
}
serveFile(w, r, f.root, path.Clean(upath), true) //看来关键在这里
}

进入到关键函数serveFile看看,它的函数声明如下:

func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) //最后一个参数表示是否重新定向,在web服务中,它总是true

这里最后一个参数很重要,我们下面会揭示为什么,好啦,看看源码,无关部分我都砍掉:

func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
const indexPage = "/index.html" // redirect .../index.html to .../
// can't use Redirect() because that would make the path absolute,
// which would be a problem running under StripPrefix
if strings.HasSuffix(r.URL.Path, indexPage) {
localRedirect(w, r, "./")
return
} f, err := fs.Open(name)
if err != nil {
msg, code := toHTTPError(err)
Error(w, msg, code)
return
}
defer f.Close() d, err := f.Stat()
if err != nil {
msg, code := toHTTPError(err)
Error(w, msg, code)
return
} if redirect {
// redirect to canonical path: / at end of directory url
// r.URL.Path always begins with /
url := r.URL.Path
if d.IsDir() {
if url[len(url)-] != '/' {
localRedirect(w, r, path.Base(url)+"/") ---------------------------- 1
return
}
} else {
if url[len(url)-] == '/' {
localRedirect(w, r, "../"+path.Base(url)) ---------------------------- 2
return
}
}
} // serveContent will check modification time
sizeFunc := func() (int64, error) { return d.Size(), nil }
serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f) ---------------------------- 3
}

重点看到红色标注部分,现在我们假设我们请求是http://127.0.0.1/abc/d.jpg。那么我们 r.URL.Path的值就是/abc/d.jpg,于是乎,程序进入到1部分(看我蓝色字体标注),path.Base()函数是取函数最后/部分,也就是/d.jpg。现在请求变成了/d.jpg,然后进行重定向,这时浏览器根据重定向内容再次发送请求,这次请求的url.Path是我们上一次处理好的/d.jpg,最后,程序便顺利的进入到了第3部分(见我蓝色字体标注)。serveContent 这个函数是最终向浏览器发送资源文件的

大概的一个处理文件资源请求的流程就是这样子,现在我们来解释一下,为什么

func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) 函数的第四个参数那么重要

因为在web服务中,我们发现它永远都是true,这就导致了我们的url无论是什么,都将会被它cut成只剩最后一部分/xxx.jpg类似的样子。换句话说,假设我们为文本服务器设置的路由格式是/xxx/xxx/xxx/x.jpg的话。
那么文本服务器根本没法正常工作,因为它只认识/xx.jpg的路由格式。 这或许也正是你在网上找相关资料的时候,发现大家转发的内容都是将文本服务器挂载在根节点上。 "/"路由我们通常会将它拿来做网站的入口,这样岂不是很不爽了?那么有没有解决的办法呢? 当然是有的啦,在net/http/server.go文件中,有这么一个函数:
// StripPrefix returns a handler that serves HTTP requests
// by removing the given prefix from the request URL's Path
// and invoking the handler h. StripPrefix handles a
// request for a path that doesn't begin with prefix by
// replying with an HTTP 404 not found error.
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) {
r.URL.Path = p
h.ServeHTTP(w, r)
} else {
NotFound(w, r)
}
})
}

根据注释以及代码来看,它的作用是返回一个Handler,但是这个Handler呢,有点点不一样,不一样在哪里呢,它会过滤掉一部分路由前缀。

比如我们有如下路由:/aaa/bbb/ccc.jpg,那么执行StripPrefix("/aaa/bbb", ..handler)之后,我们将会得到一个新的Handler,这个新Handler的执行函数和原来的handler是一样的,但是这个新Handler在处理路由请求的时候,会自动将/aaa/bbb/ccc.jpg理解为/aaa.jpg

好啦,分析到这里,我们现在再来搭建一个路由路径为/s/下的文件服务器,代码如下:

func main() {

    http.Handle("/s/", http.StripPrefix("/s/", http.FileServer(http.Dir("sourse"))))  

    err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println(err)
}
}

[原]Golang FileServer的更多相关文章

  1. 理解Go Context机制

    1 什么是Context 最近在公司分析gRPC源码,proto文件生成的代码,接口函数第一个参数统一是ctx context.Context接口,公司不少同事都不了解这样设计的出发点是什么,其实我也 ...

  2. 爬虫技术:scrapy 知识点一

    ---恢复内容开始--- 1.scrapy框架 每一步的解释: step1:引擎从爬虫器获取要爬行的初始请求. step2:引擎在调度程序中调度请求,引擎把这个初始请求传递给调度器,并向调度器索要下一 ...

  3. Golang http包下FileServer的使用

    FileServer文档:https://godoc.org/net/http#FileServer 今天看到http的 Handle 方法,所以就像试试,就找到FileServer FileServ ...

  4. golang中浮点型底层存储原理和decimal使用方法

    var price float32 = 39.29 float64和float32类似,只是用于表示各部分的位数不同而已,其中:sign=1位,exponent=11位,fraction=52位,也就 ...

  5. Golang/Go goroutine调度器原理/实现【原】

    Go语言在2016年再次拿下TIBOE年度编程语言称号,这充分证明了Go语言这几年在全世界范围内的受欢迎程度.如果要对世界范围内的gopher发起一次“你究竟喜欢Go的哪一点”的调查,我相信很多Gop ...

  6. Golang接口(interface)三个特性(译文)

    The Laws of Reflection 原文地址 第一次翻译文章,请各路人士多多指教! 类型和接口 因为映射建设在类型的基础之上,首先我们对类型进行全新的介绍. go是一个静态性语言,每个变量都 ...

  7. golang笔记——函数与方法

    如果你遇到没有函数体的函数声明,表示该函数不是以Go实现的. package math func Sin(x float64) float //implemented in assembly lang ...

  8. GoLang之方法与接口

    GoLang之方法与接口 Go语言没有沿袭传统面向对象编程中的诸多概念,比如继承.虚函数.构造函数和析构函数.隐藏的this指针等. 方法 Go 语言中同时有函数和方法.方法就是一个包含了接受者的函数 ...

  9. 【GoLang】golang 面向对象编程 & 面向接口编程

    005.面向对象&接口编程 1 面向函数编程 1.1 将数据作为参数传递到函数入参 1.2 对象与函数是分离的 2 面向对象编程 2.1 使用者看起来函数作为对象的属性而非参数 2.2 函数属 ...

随机推荐

  1. Saltstack-自动化部署

    Saltstack概述 Salt一种全新的基础设施管理方式,部署轻松,在几分钟内可运行起来,扩展性好,很容易管理上万台服务器,速度够快,服务器之间秒级通讯. salt底层采用动态的连接总线, 使其可以 ...

  2. NSProgress

    苹果公司在 iOS 7 and OS X 10.9引入NSProgress类,目标是建立一个标准的机制用来报告长时间运行的任务的进度.NSProgress引入之后,其最重要的作用是可以在一个app的多 ...

  3. msg url

  4. RocEDU.阅读.写作《你的灯亮着吗?》

    <你的灯亮着吗?> 一.对本书的认识 这本书的作者就如何训练思维能力指点迷津.书中提及的观点包括"问题是理想状态和现实状态之间的差别",以及"无论表面上表现的 ...

  5. initializer for conditional binding must have optional type not AVAudioPlayer

    if let buttonBeep = self.setupAudioPlayerWithFile("ButtonTap", type: "wav") {    ...

  6. 一个bug案例分析

    Bug描述: 某大型系统的一个提供基础数据服务的子系统A进行了一次升级.升级的内容为:优化了失败重传功能,在优化的同时,开发人员发现传输数据的时间戳精度只是精确到了秒,于是顺手把精度改成了1/100秒 ...

  7. vs2010 “SetScrollSizes”: 找不到标识符

    SetScrollSizes是CScrollView的成员函数,你的view类必须从CScrollView派生

  8. 开始学习node.js了,第一节,fs文件系统 【fs.rename】重命名文件/文件夹

    var fs=require('fs');fs.rename('c:\\a','c:\\a2',function(err){ if(err) console.log('error:'+err);}); ...

  9. Promise 学习笔记 - 时间支配者

    本文同步自我的个人博客:http://www.52cik.com/2015/11/08/promise.html JavaScript 的 promises 事实标准称为 Promises/A+.ES ...

  10. redis async client 与自有框架集成

    hiredis的异步接口已经支持ae libuv libev 和 libevent集成,具体头文件可以参见redis/deps/hiredis/adapters,样例参见redis/deps/hire ...