路由复用器--gorilla/mux
简介
gorilla/mux
是 gorilla Web 开发工具包中的路由管理库。gorilla Web 开发包是 Go 语言中辅助开发 Web 服务器的工具包。它包括 Web 服务器开发的各个方面,有表单数据处理包gorilla/schema
,有 websocket 通信包gorilla/websocket
,有各种中间件的包gorilla/handlers
,有 session 管理包gorilla/sessions
,有安全的 cookie 包gorilla/securecookie
。本文先介绍gorilla/mux
(下文简称mux
),后续文章会依次介绍上面列举的 gorilla 包。
mux
有以下优势:
实现了标准的
http.Handler
接口,所以可以与net/http
标准库结合使用,非常轻量;可以根据请求的主机名、路径、路径前缀、协议、HTTP 首部、查询字符串和 HTTP 方法匹配处理器,还可以自定义匹配逻辑;
可以在主机名、路径和请求参数中使用变量,还可以为之指定一个正则表达式;
可以传入参数给指定的处理器让其构造出完整的 URL;
支持路由分组,方便管理和维护。
快速使用
本文代码使用 Go Modules。
创建目录并初始化:
$ mkdir -p gorilla/mux && cd gorilla/mux
$ go mod init github.com/darjun/go-daily-lib/gorilla/mux
安装gorilla/mux
库:
$ go get -u github.com/gorilla/gorilla/mux
我现在身边有几本 Go 语言的经典著作:
下面我们编写一个管理图书信息的 Web 服务。图书由 ISBN 唯一标识,ISBN 意为国际标准图书编号(International Standard Book Number)。
首先定义图书的结构:
type Book struct {ISBN string `json:"isbn"`Name string `json:"name"`Authors []string `json:"authors"`Press string `json:"press"`PublishedAt string `json:"published_at"`
}var (mapBooks map[string]*BookslcBooks []*Book
)
定义init()
函数,从文件中加载数据:
func init() {mapBooks = make(map[string]*Book)slcBooks = make([]*Book, 0, 1)data, err := ioutil.ReadFile("../data/books.json")if err != nil {log.Fatalf("failed to read book.json:%v", err)}err = json.Unmarshal(data, &slcBooks)if err != nil {log.Fatalf("failed to unmarshal books:%v", err)}for _, book := range slcBooks {mapBooks[book.ISBN] = book}
}
然后是两个处理函数,分别用于返回整个列表和某一本具体的图书:
func BooksHandler(w http.ResponseWriter, r *http.Request) {enc := json.NewEncoder(w)enc.Encode(slcBooks)
}func BookHandler(w http.ResponseWriter, r *http.Request) {book, ok := mapBooks[mux.Vars(r)["isbn"]]if !ok {http.NotFound(w, r)return}enc := json.NewEncoder(w)enc.Encode(book)
}
注册处理器:
func main() {r := mux.NewRouter()r.HandleFunc("/", BooksHandler)r.HandleFunc("/books/{isbn}", BookHandler)http.Handle("/", r)log.Fatal(http.ListenAndServe(":8080", nil))
}
mux
的使用与net/http
非常类似。首先调用mux.NewRouter()
创建一个类型为*mux.Router
的路由对象,该路由对象注册处理器的方式与标准库的*http.ServeMux
完全相同,即调用HandleFunc()
方法注册类型为func(http.ResponseWriter, *http.Request)
的处理函数,调用Handle()
方法注册实现了http.Handler
接口的处理器对象。上面注册了两个处理函数,一个是显示图书信息列表,一个显示具体某本书的信息。
注意到路径/books/{isbn}
使用了变量,在{}
中间指定变量名,它可以匹配路径中的特定部分。在处理函数中通过mux.Vars(r)
获取请求r
的路由变量,返回map[string]string
,后续可以用变量名访问。如上面的BookHandler
中对变量isbn
的访问。
由于*mux.Router
也实现了http.Handler
接口,所以可以直接将它作为http.Handle("/", r)
的处理器对象参数注册。这里注册的是根路径/
,相当于把所有请求的处理都托管给了*mux.Router
。
最后还是http.ListenAndServe(":8080", nil)
开启一个 Web 服务器,等待接收请求。
运行,在浏览器中键入localhost:8080
,显示书籍列表:
键入localhost:8080/books/978-7-111-55842-2
,显示图书《Go 程序设计语言》的详细信息:
从上面的使用过程中可以看出,mux
库非常轻量,能很好的与标准库net/http
结合使用。
我们还可以使用正则表达式限定变量的模式。ISBN 有固定的模式,现在使用的模式大概是这样:978-7-111-55842-2
(这就是《Go 程序设计语言》一书的 ISBN),即 3个数字-1个数字-3个数字-5个数字-1个数字,用正则表达式表示为\d{3}-\d-\d{3}-\d{5}-\d
。在变量名后添加一个:
分隔变量和正则表达式:
r.HandleFunc("/books/{isbn:\\d{3}-\\d-\\d{3}-\\d{5}-\\d}", BookHandler)
灵活的匹配方式
mux
提供了丰富的匹配请求的方式。相比之下,net/http
只能指定具体的路径,稍显笨拙。
我们可以指定路由的域名或子域名:
r.Host("github.io")
r.Host("{subdomain:[a-zA-Z0-9]+}.github.io")
上面的路由只接受域名github.io
或其子域名的请求,例如我的博客地址darjun.github.io
就是它的一个子域名。指定域名时可以使用正则表达式,上面第二行代码限制子域名的第一部分必须是若干个字母或数字。
指定路径前缀:
// 只处理路径前缀为`/books/`的请求
r.PathPrefix("/books/")
指定请求的方法:
// 只处理 GET/POST 请求
r.Methods("GET", "POST")
使用的协议(HTTP/HTTPS
):
// 只处理 https 的请求
r.Schemes("https")
首部:
// 只处理首部 X-Requested-With 的值为 XMLHTTPRequest 的请求
r.Headers("X-Requested-With", "XMLHTTPRequest")
查询参数(即 URL 中?
后的部分):
// 只处理查询参数包含key=value的请求
r.Queries("key", "value")
最后我们可以组合这些条件:
r.HandleFunc("/", HomeHandler).Host("bookstore.com").Methods("GET").Schemes("http")
除此之外,mux
还允许自定义匹配器。自定义的匹配器就是一个类型为func(r *http.Request, rm *RouteMatch) bool
的函数,根据请求r
中的信息判断是否能否匹配成功。http.Request
结构中包含了非常多的信息:HTTP 方法、HTTP 版本号、URL、首部等。例如,如果我们要求只处理 HTTP/1.1 的请求可以这么写:
r.MatchrFunc(func(r *http.Request, rm *RouteMatch) bool {return r.ProtoMajor == 1 && r.ProtoMinor == 1
})
需要注意的是,mux
会根据路由注册的顺序依次匹配。所以,通常是将特殊的路由放在前面,一般的路由放在后面。如果反过来了,特殊的路由就不会被匹配到了:
r.HandleFunc("/specific", specificHandler)
r.PathPrefix("/").Handler(catchAllHandler)
子路由
有时候对路由进行分组管理,能让程序模块更清晰,更易于维护。现在网站扩展业务,加入了电影相关信息。我们可以定义两个子路由分别管理:
r := mux.NewRouter()
bs := r.PathPrefix("/books").Subrouter()
bs.HandleFunc("/", BooksHandler)
bs.HandleFunc("/{isbn}", BookHandler)ms := r.PathPrefix("/movies").Subrouter()
ms.HandleFunc("/", MoviesHandler)
ms.HandleFunc("/{imdb}", MovieHandler)
子路由一般通过路径前缀来限定,r.PathPrefix()
会返回一个*mux.Route
对象,调用它的Subrouter()
方法创建一个子路由对象*mux.Router
,然后通过该对象的HandleFunc/Handle
方法注册处理函数。
电影没有类似图书的 ISBN 国际统一标准,只有一个民间“准标准”:IMDB。我们采用豆瓣电影中的信息:
定义电影的结构:
type Movie struct {IMDB string `json:"imdb"`Name string `json:"name"`PublishedAt string `json:"published_at"`Duration uint32 `json:"duration"`Lang string `json:"lang"`
}
加载:
var (mapMovies map[string]*MovieslcMovies []*Movie
)func init() {mapMovies = make(map[string]*Movie)slcMovies = make([]*Movie, 0, 1)data, := ioutil.ReadFile("../../data/movies.json")json.Unmarshal(data, &slcMovies)for _, movie := range slcMovies {mapMovies[movie.IMDB] = movie}
}
使用子路由的方式,还可以将各个部分的路由分散到各自的模块去加载,在文件book.go
中定义一个InitBooksRouter()
方法负责注册图书相关的路由:
func InitBooksRouter(r *mux.Router) {bs := r.PathPrefix("/books").Subrouter()bs.HandleFunc("/", BooksHandler)bs.HandleFunc("/{isbn}", BookHandler)
}
在文件movie.go
中定义一个InitMoviesRouter()
方法负责注册电影相关的路由:
func InitMoviesRouter(r *mux.Router) {ms := r.PathPrefix("/movies").Subrouter()ms.HandleFunc("/", MoviesHandler)ms.HandleFunc("/{imdb}", MovieHandler)
}
在main.go
的主函数中:
func main() {r := mux.NewRouter()InitBooksRouter(r)InitMoviesRouter(r)http.Handle("/", r)log.Fatal(http.ListenAndServe(":8080", nil))
}
需要注意的是,子路由匹配是需要包含路径前缀的,也就是说/books/
才能匹配BooksHandler
。
构造路由 URL
我们可以为一个路由起一个名字,例如:
r.HandleFunc("/books/{isbn}", BookHandler).Name("book")
上面的路由中有参数,我们可以传入参数值来构造一个完整的路径:
fmt.Println(r.Get("book").URL("isbn", "978-7-111-55842-2"))
// /books/978-7-111-55842-2 <nil>
返回的是一个*url.URL
对象,其路径部分为/books/978-7-111-55842-2
。这同样适用于主机名和查询参数:
r := mux.Router()
r.Host("{name}.github.io").Path("/books/{isbn}").HandlerFunc(BookHandler).Name("book")url, err := r.Get("book").URL("name", "darjun", "isbn", "978-7-111-55842-2")
路径中所有的参数都需要指定,并且值需要满足指定的正则表达式(如果有的话)。运行输出:
$ go run main.go
http://darjun.github.io/books/978-7-111-55842-2
可以调用URLHost()
只生成主机名部分,URLPath()
只生成路径部分。
中间件
mux
定义了中间件类型MiddlewareFunc
:
type MiddlewareFunc func(http.Handler) http.Handler
所有满足该类型的函数都可以作为mux
的中间件使用,通过调用路由对象*mux.Router
的Use()
方法应用中间件。如果看过我上一篇文章《Go 每日一库之 net/http(基础和中间件)》应该对这种中间件不陌生了。编写中间件一般会将原处理器传入,中间件中会手动调用原处理函数,然后在前后增加通用处理逻辑:
func loggingMiddleware(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {log.Println(r.RequestURI)next.ServeHTTP(w, r)})
}
我在上篇文章中写的 3 个中间件可以直接使用,这就是兼容net/http
的好处:
func main() {logger = log.New(os.Stdout, "[goweb]", log.Lshortfile|log.LstdFlags)r := mux.NewRouter()// 直接使用上一篇文章中定义的中间件r.Use(PanicRecover, WithLogger, Metric)InitBooksRouter(r)InitMoviesRouter(r)http.Handle("/", r)log.Fatal(http.ListenAndServe(":8080", nil))
}
如果不手动调用原处理函数,那么原处理函数就不会执行,这可以用来在校验不通过时直接返回错误。例如,网站需要登录才能访问,而 HTTP 是一个无状态的协议。所以发明了 Cookie 机制用于在客户端和服务器之间记录一些信息。
我们在登录成功之后生成一个键为token
的 Cookie 表示已登录成功,我们可以编写一个中间件来出来这块逻辑,如果 Cookie 不存在或者非法,则重定向到登录界面:
func login(w http.ResponseWriter, r *http.Request) {ptTemplate.ExecuteTemplate(w, "login.tpl", nil)
}func doLogin(w http.ResponseWriter, r *http.Request) {r.ParseForm()username := r.Form.Get("username")password := r.Form.Get("password")if username != "darjun" || password != "handsome" {http.Redirect(w, r, "/login", http.StatusFound)return}token := fmt.Sprintf("username=%s&password=%s", username, password)data := base64.StdEncoding.EncodeToString([]byte(token))http.SetCookie(w, &http.Cookie{Name: "token",Value: data,Path: "/",HttpOnly: true,Expires: time.Now().Add(24 * time.Hour),})http.Redirect(w, r, "/", http.StatusFound)
}
上面为了记录登录状态,我将登录的用户名和密码组合成username=xxx&password=xxx
形式的字符串,对这个字符串进行base64
编码,然后设置到 Cookie 中。Cookie 有效期为 24 小时。同时为了安全只允许 HTTP 访问此 Cookie(JS 脚本不可访问)。当然这种方式安全性很低,这里只是为了演示。登录成功之后重定向到/
。
总结
本文介绍了轻量级的,功能强大的路由库gorilla/mux
。它支持丰富的请求匹配方法,子路由能极大地方便我们管理路由。由于兼容标准库net/http
,所以可以无缝集成到使用net/http
的程序中,利用为net/http
编写的中间件资源。下一篇我们介绍gorilla/handlers
——一些常用的中间件。
- END -
扫码关注公众号「网管叨bi叨」
给网管个星标,第一时间吸我的知识
路由复用器--gorilla/mux相关推荐
- 路由(gorilla/mux)
安装gorilla/mux go get -u github.com/gorilla/mux 默认的http包无法处理复杂的请求路由,例如从url中拆分出参数.该第三方包可方便的处理. 创建路由 r ...
- 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析
目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...
- Go 每日一库之 gorilla/mux
简介 gorilla/mux是 gorilla Web 开发工具包中的路由管理库.gorilla Web 开发包是 Go 语言中辅助开发 Web 服务器的工具包.它包括 Web 服务器开发的各个方面, ...
- 使用gorilla/mux增强Go HTTP服务器的路由能力
今天这篇文章我们将会为我们之前编写的 HTTP服务器加上复杂路由的功能以及对路由进行分组管理.在之前的文章<深入学习用 Go 编写HTTP服务器>中详细地讲了使用 net/http进行路由 ...
- mysql的请求分发,基于 gorilla/mux 实现路由匹配和请求分发:服务单页面应用
基于 gorilla/mux 实现路由匹配和请求分发:服务单页面应用 由 学院君 创建于1年前, 最后更新于 1年前 版本号 #1 1279 views 0 likes 0 collects 随着前后 ...
- gorilla/mux 的学习
原文链接:gorilla/mux的学习 源代码: package mainimport ("encoding/json""fmt""github.co ...
- gorilla/mux类库解析
简介 gorilla/mux实现了一个请求路由和分发的Go框架."mux"的意思是"HTTP request multiplexer",和标准包http.Ser ...
- gorilla/mux的使用
github.com/gorilla/mux: golang自带的http.SeverMux路由实现简单,本质是一个map[string]Handler,是请求路径与该路 径对应的处理函数的映射关系. ...
- Gorilla源码分析之gorilla/mux源码分析
本文公众号文章链接:https://mp.weixin.qq.com/s/LLcPDPtpjNeXAA_ffL3YCg 本文csdn博客链接:http://blog.csdn.net/screscen ...
最新文章
- javascript json对象转字符串形式
- SAP RFC 函数来创建 Java呼叫 学习总结 一步一步的插图
- elementUI的table组件实现setCurrentRow的滚动条定位效果
- JS版数据结构第三篇(链表)
- 2017年09月23日普级组 环
- AtCoder AGC030F Permutation and Minimum (DP、计数)
- IDEA中添加tomcat服务器和创建一个新的web项目
- 条件 推导 迭代 并行
- LeetCode 255. 验证前序遍历序列二叉搜索树(单调栈)*
- keras中文文档_【DL项目实战02】图像识别分类——Keras框架+卷积神经网络CNN(使用VGGNet)
- html5图片剪切板,JavaScript 网页端复制图片到剪切板
- 股票软件开发中全推与点播的区别(自己留作记录的,请csdn的小编别乱删我的文章)...
- [转载] python解析返回结果_python:解析requests返回的response(json格式)说明
- CentOS 6.6 安装 Node.js
- log4j日志级别以及配置
- UNIX 操作系统体系结构调整
- Edge浏览器快捷键
- 软件更新(2005.06.04)
- android融云自定义通知,android融云消息免打扰
- 常见混沌系统—Lorenz模型
热门文章
- 图片数字型的九九乘法表
- ConcurrentHashMap源码跟踪记录
- Cocos2d-x场景功能描述的生命周期
- CentOS6.5+Python2.7+ GIT +IPython
- 审计导致select * 报ORA-01435: user does not exist
- 理解 Python 中的异步编程
- 《OpenACC并行程序设计:性能优化实践指南》一 3.1 性能分析技术和术语
- splObjectStroge的作用,实例化一个数组
- MySQL(root用户)密码重置
- [Android官方API阅读]___System Permissions