go是一门简洁强大的语言,简单体验以后以为对于网络和命令行的支持也很是棒,本文介绍一下go实现静态服务器的大体流程。html
最近接手了gobyexample的翻译工做,将项目重构后须要本地的测试环境。
因为想要页面的url显示为“https://gobyexample.xgwang.me/hello-world
”这种结尾不带“/”的形式,子页面没有带上html,而且有图片资源所以须要一个static server。git
根据golang wiki,实现这个简单server只须要...一行代码:github
package main import "net/http" func main() { panic(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc")))) }
加入log后稍微改写一下,放在咱们项目的tools目录下:golang
package main import ( "log" "net/http" ) func main() { // Simple static webserver: port := ":8080" log.Printf("Serving at: http://localhost%s\n", port) err := http.ListenAndServe(port, http.FileServer(http.Dir("public"))) if err != nil { log.Fatal("ListenAndServe fail:", err) } }
再来一个可执行的tools/serve
文件web
#!/bin/bash exec go run tools/serve.go
ok如今只须要tools/serve
就能够启动这个服务器了。bash
一切看起来很正常,但若是咱们访问一下不存在的某个页面,404.html并不会被serve,这是由于go提供的FileServer
并不知道咱们自定义的404页面。
因此咱们须要将http.FileServer
改成一个自定义的Handler
。服务器
写go的时候体验特别好的一点就是go官方团队提供了很opinionated的convention,好比go-get,go-fmt等。
在咱们输入http.FileServer
时会自动在imports
中添加相应的库,跳转到源码后看到了这个函数的实现:网络
type fileHandler struct { root FileSystem } // FileServer returns a handler that serves HTTP requests // with the contents of the file system rooted at root. // // To use the operating system's file system implementation, // use http.Dir: // // http.Handle("/", http.FileServer(http.Dir("/tmp"))) // // As a special case, the returned file server redirects any request // ending in "/index.html" to the same path, without the final // "index.html". func FileServer(root FileSystem) Handler { return &fileHandler{root} } 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) }
因而咱们知道了这里的函数须要返回的Handler有一个ServeHTTP
方法。可是这里的serveFile
并不能直接由http.serveFile
调用:go规定一个package内小写字母开头的均为私有,不能被外部package访问。函数
可是没有关系,咱们能够在fileHandler
上再包装一层代理,在执行完咱们判断文件存在的逻辑后执行原先全部fileHandler.ServeHTTP
的内容,修改后的代码以下:工具
type fileHandler struct { root http.FileSystem h http.Handler } func fileServer(root http.FileSystem, h http.Handler) http.Handler { return &fileHandler{root, h} } func (f *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { path := r.URL.Path if _, err := os.Stat("public/" + path); os.IsNotExist(err) { http.ServeFile(w, r, "public/404.html") return } f.h.ServeHTTP(w, r) } func main() { // Simple static webserver: port := ":8080" log.Printf("Serving at: http://localhost%s\n", port) fs := http.Dir("public") http.Handle("/", fileServer(&fs, http.FileServer(&fs))) err := http.ListenAndServe(port, nil) if err != nil { log.Fatal("ListenAndServe fail:", err) } }
在传入FileSystem
的时候传入指针也避免建立,颇有C的感受。
基本功能都已经实现,但做为一个命令行工具,但愿再进行一些完善。
首先咱们须要支持传参,go对于命令行参数的支持很是棒,只要引入builtin的flag包以后,咱们加入
port := flag.String("port", ":8080", "localhost port to serve") path := flag.String("path", "public", "public files path") flag.Parse()
就能够获得*string
类型的命令行参数,而且天生支持默认值和描述,测试一下go run tools/serve.go -h
,能够获得:
Usage of /var/folders/sd/cwk5fwtd4ms5vflhq5_0_5rr0000gn/T/go-build178666598/command-line-arguments/_obj/exe/serve: -path string public files path (default "public") -port string localhost port to serve (default ":8080")
准备serve文件以前,再输出一下带有格式的信息加粗一下咱们传入的参数:
log.Printf("Serving \x1b[1m%s\x1b[0m at: http://localhost\x1b[1m%s\x1b[0m\n", *path, *port)
这里\x1b[0m
表明“All attributes off(color at startup)”,\x1b[1m
表明“Bold on(enable foreground intensity)”。
go做为静态语言拥有能够与动态语言媲美的灵活性,有完整易用的工具链和丰富的标准库,是2017年增加最快的语言,简单的同时很是强大。
但愿有更多的人能够一块儿学习go,我正在完善Go By Example的翻译,欢迎阅读以及贡献PR!