Go搭建静态页面server笔记

Go是一门简洁强大的语言,简单体验之后觉得对于网络和命令行的支持也非常棒,本文介绍一下Go实现静态服务器的大致流程。

基础实现

最近接手了Go By Example的翻译工作,将项目重构后需要本地的测试环境。
由于想要页面的url显示为“https://gobyexample.xgwang.me/hello-world”这种结尾不带“/”的形式,子页面没有带上html,并且有图片资源因此需要一个static server。

根据Golang wiki,实现这个简单server只需要...一行代码:

package main

import "net/http"

func main() {
    panic(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))))
}复制代码

加入log后稍微改写一下,放在我们项目的tools目录下:

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文件

#!/bin/bash
exec go run tools/serve.go复制代码

ok现在只需要tools/serve就可以启动这个服务器了。

404

一切看起来很正常,但如果我们访问一下不存在的某个页面,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 :)

转载于:https://juejin.im/post/5a1132706fb9a0450b65fa7c

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值