基于 gorilla/mux 实现路由匹配和请求分发:服务单页面应用

随着前后端分离的大势所趋,后端应用为前端单页面应用(SPA,通常由 Vue、React 等前端框架构建)提供包含数据的 API 接口,然后由前端代码负责路由跳转和渲染变得越来越流行,gorilla/mux 包也对此功能特性提供了开箱支持。

要演示完整的前后端分离单页面应用,需要先初始化前端应用,以基于 Vue.js 构建前端应用为例,使用 Vue CLI 初始化一个名为 gospa 的 Vue 应用,可以在任意目录下创建(需要安装过 Vue CLI 才能执行以下初始化命令):

vue create gospa
cd gospa
yarn serve

启动应用后,可以按照提示在浏览器通过 http://localhost:8080/ 访问应用首页:

Vue SPA应用

接下来,我们通过后端实现 HTTP 服务器来托管前端应用和资源访问。

对于单页面应用,只需在首次访问时请求后端入口路由初始化前端资源,后续交互都是通过前端代码调用后端 API 接口完成数据渲染,从用户角度来说,页面不会刷新,所以看起来就像只有一个单页面。因此,我们只需要在后端入口处定义一个返回初始化前端资源的 HTML 模板视图即可(这个 HTML 文档中引入了前端应用入口 JavaScript 文件以及必要的 CSS 文件),在 gospa 应用根目录下新建一个 server.go,并编写后端实现代码如下:

package main

import (
    "github.com/gorilla/mux"
    "log"
    "net/http"
    "os"
    "path/filepath"
    "time"
)

// spaHandler 实现了 http.Handler 接口,所以可以用来处理 HTTP 请求
// 其中 staticPath 用于定义前端静态资源目录(包含js、css 文件)
// indexPath 用于定义入口视图模板文件,通常是 index.html
type spaHandler struct {
    staticPath string
    indexPath  string
}

// 处理 SPA 应用请求(主要是首次访问时入口 HTML 文档和相关静态资源文件,暂不涉及 API 接口)
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 获取 URL 路径的绝对路径
    path, err := filepath.Abs(r.URL.Path)
    if err != nil {
        // 如果获取失败,返回 400 响应
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    // 在 URL 路径前加上静态资源根目录
    path = filepath.Join(h.staticPath, path)

    // 检查对应资源文件是否存在
    _, err = os.Stat(path)
    if os.IsNotExist(err) {
        // 文件不存在返回入口 HTML 文档内容作为响应
        http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
        return
    } else if err != nil {
        // 如果期间报错,返回 500 响应
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // 一切顺利,则使用 http.FileServer 处理静态资源请求
    http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)
}

func main()  {
    router := mux.NewRouter()

    spa := spaHandler{staticPath: "dist", indexPath: "index.html"}
    router.PathPrefix("/").Handler(spa)

    srv := &http.Server{
        Handler: router,
        Addr:    "127.0.0.1:8000",
        // 最佳实践:为服务器读写设置超时时间
        WriteTimeout: 15 * time.Second,
        ReadTimeout:  15 * time.Second,
    }

    log.Fatal(srv.ListenAndServe())
}

具体的业务逻辑都已经写在注释里了。

接下来,我们通过 Go Module 管理 Go 依赖:

go mod init gospa
go mod tidy

然后启动这个基于 Go 实现的 HTTP 服务器:

 go run server.go

修改 gospa/src/App.Vue 中传入 HelloWorld 组件的 msg 属性如下:

<HelloWorld msg="Welcome to Your Vue.js + Golang SPA App"/>

gospa 根目录下运行 yarn build,将前端资源编译到 dist 目录下,至此我们的项目目录结构如下所示:

Vue + Go SPA 应用目录结构

第一个红框区域对应的目录就是编译后的前端静态资源和 HTML 视图模板所在目录。

然后在浏览器中访问 http://127.0.0.1:8000,即可看到如下效果:

Vue + Go SPA 应用首页

之前对 msg 变量值的修改生效,并且基于 Go 实现的 HTTP 服务器可以成功服务 SPA 应用。

上一篇: 基于 gorilla/mux 包实现路由定义和请求分发:处理静态资源响应

下一篇: 基于 gorilla/mux 实现路由匹配和请求分发:基于 CORS 处理跨域请求