起步
这一小节准备简单聊一聊 Iris 的路由、视图、中间件、模板渲染。主要是为了对 Iris 有个大体的认识,或者说对 web 开发能有个大体的认识。其次是,学习之后利用 Iris 搭建一个简陋的 web 服务器将不在话下,但安全性、实用性、灵活性等等等等——
嗯,想太多!
Go 的官方 http 服务
众所周知,Go 不需要框架,用官方提供的 net/http 就能轻松编写 web 服务。可是就算轻松,又能轻松到哪里去呢?轻松成下面这样:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintf(resp, "hello world")
})
http.ListenAndServe(":8080", nil)
}
http.HandleFunc
的作用是为 pattern 绑定一个 handler。
- 第一个参数是 pattern,是 url 中的 path 部分。
- 第二个参数是 handler,是函数类型,负责处理接收到的请求。
http.ListenAndServe
的作用是设置服务监听端口,并保持服务活动状态。Listen
是监听,Serve
则可以追溯到源码中的 func (srv *Server) Serve(l net.Listener) error
,该函数有一个“死循环”逻辑,一直等着请求来。
// func (srv *Server) Serve(l net.Listener) error 源码
for {
rw, e := l.Accept() // 接收连接
...
}
路由
对于“路由”,我的理解是:道路由此而去。有种指路牌的感觉。url 的 path 部分确实在做这事儿。
协议格式:protocol :// hostname[:port] / path / ;parameters#fragment
譬如 http://www.baodu.com/abc 表示我要去往 /abc;http://www.baodu.com/abc/def 表示我要去往 /abc/def。处理请求的 handler 函数(视图函数)就是目的地的接待员,负责同你互动。
Iris 的路由注册很简单,这里拿暴露出来的、负责响应 http get 请求的接口举例(iris.Application.Get
)。
func main() {
app := iris.Default()
// path = '/'
app.Get("/", func(ctx iris.Context) {
...
})
// path = '/hello'
app.Get("/hello", func(ctx iris.Context) {
...
})
}
老是说要来一场说走就走的旅行,在 http 的世界里也能做到。我们将不为旅客设限,让他在想下的地方下车,想停靠的地方靠站。
// 关注 `{user}`
app.Get("/hello/{user}", func(ctx iris.Context) {
...
})
此时访问 http://127.0.0.1:8080/hello/zhong,去往 /hello/zhong;而如果访问 http://127.0.0.1:8080/hello/ting,则去往 /hello/ting。这种行为我把它叫作 url 的位置传参。
视图
显然,官方提供的 net/http 起了一个好头,因为它足够简洁易用。而后来居上的 web 框架,如果没有兼顾"简洁易用"这一特性,想流行起来就会变得困难。就结论再倒推回去,正因为 Iris 不但性能强大,而且简洁易用,所以它流行起来了。
上一节测试 Iris 是否可以正常使用时,复制粘贴了一段代码,现在可以来分析这段代码。
package main
import "github.com/kataras/iris/v12"
func main() {
app := iris.Default()
app.Get("/ping", func(ctx iris.Context) {
ctx.JSON(iris.Map{
"message": "pong",
})
})
app.Run(iris.Addr(":8080"))
}
iris.Default
返回一个默认的 app 实例,app.Get
负责注册一个支持 get 请求的路由,并为这个路由绑定 handler 函数。iris.Addr
设置监听端口。app.Run
让程序一直不停地跑着。
前面一直在说的 handler 函数,其实就是处理 http 请求的视图函数。我觉得比较好玩儿的是,Iris 支持为一个路由绑定多个 handler。这在源码中可以看到:
// go 源码
func (api *APIBuilder) Get(relativePath string, handlers ...context.Handler) *Route
handlers ...context.Handler
表示可变长参数列表。
使用示例:
func main() {
app := iris.Default()
app.Get("/", func(ctx iris.Context) {
ctx.HTML("<h1>hello</h1>")
ctx.Next()
}, func(ctx iris.Context) {
ctx.HTML("<h1>world</h1>")
ctx.Next()
}, func(ctx iris.Context) {
ctx.HTML("<h1>zty</h1>")
ctx.Next()
})
app.Run(iris.Addr(":8080"))
}
此时浏览器访问 127.0.0.1 页面返回内容是:
hello
world
zty
这里有两点需要注意:
- handlers 的执行顺序是按注册顺序执行。
- 只有在当前 handler 中调用
ctx.Next()
,才会执行后面的 handler。
中间件
用过 Django 的人会知道,中间件的存在能让懒惰程序员省许多心,也能够有更多时间用来划水。可如果你还不知道什么是中间件,在这里我可以非官方地告诉你:在一个地方,总做一件事儿。当然,这只是中间件的一个局部作用。
来个小示例。现在让我们前面的程序跑起来,打开 chrome 控制台,粘贴以下代码并回车。
fetch("http://127.0.0.1:8080").then(resp => console.log(resp)).catch(err => console.log(err))
控制台返回报错信息:Access to fetch at 'http://127.0.0.1:8080/' from origin 'chrome-search://local-ntp' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
其中的原理暂时可以不管。其解决方案就是:为每一个响应添加响应头 Access-Control-Allow-Origin: *
。那是不是要在每个 handler 函数都加上这样一个逻辑:
ctx.Header("Access-Control-Allow-Origin", "*")
代码重复是可耻的。中间件可以让你不可耻。
func main() {
app := iris.Default()
/* 挂载中间件 */
app.Use(MyMiddleWare)
...
app.Run(iris.Addr(":8080"))
}
func MyMiddleWare(ctx iris.Context) {
ctx.Header("Access-Control-Allow-Origin", "*")
ctx.Next()
}
从现在起,每一个 http 请求都会经过中间件 MyMiddleWare(),然后被加上我们想要加上的响应头。app.Use
就负责挂载中间件,也就是让中间件生效。这里尤其需要关注执行顺序:中间件 -> 视图函数。
事实上,通过 iris.Default()
拿到的 app 实例,就已经挂载了两个中间件。这也是终端一直有日志输出的原因。
// go 源码
func Default() *Application {
app := New()
app.Use(recover.New())
app.Use(requestLogger.New())
return app
}
渲染模板
作为一个 web 服务器,返回 html 页面的能力必不可少。
这里先在 main.go 所在目录创建文件夹 views,然后在 views 中创建 hello.html,并写入以下内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1> Hello World </h1>
</body>
</html>
main.go 内容如下:
func main() {
app := iris.Default()
// 制作 html 视图引擎.
// 第一个参数表示 html 文件所在目录
// 第二个参数是文件的扩展类型。我们用 html 文件,所以扩展是 “.html”
htmlEngine := iris.HTML("./views", ".html")
// 开启 html 热加载。每次更新 html 文件的内容后,自动重载内存中的 html
htmlEngine.Reload(true)
// 向应用中注册视图引擎
app.RegisterView(htmlEngine)
app.Get("/hello", func(ctx iris.Context) {
// 返回 html 文件
ctx.View("hello.html")
})
app.Run(iris.Addr(":8080"))
}
调用的 api 的意义在注释中写清楚了,不做赘述。
现在还有一个问题,Iris 该如何在 html 中动态显示变量值呢?譬如这里有一个需求:对当前登录用户说 hello。用户之间的切换借助 url 位置传参来实现。
对于 hello.html 文件修改如下:
<!-- 注意 name 前边有一个小点 -->
<h1> Hello, {{.name}} </h1>
对 main.go 文件修改如下:
func main() {
app := iris.Default()
htmlEngine := iris.HTML("./views", ".html")
htmlEngine.Reload(true)
app.RegisterView(htmlEngine)
app.Get("/hello/{user}", func(ctx iris.Context) {
// 获取 user 的值
name := ctx.Params().GetString("user")
// 将变量 name 的值,绑定到 “name” 上
ctx.ViewData("name", name)
// 渲染 html
ctx.View("hello.html")
})
app.Run(iris.Addr(":8080"))
}
现在访问 http://127.0.0.1:8080/hello/zty,页面显示:
Hello, zty
访问 http://127.0.0.1:8080/hello/zhong,页面显示:
Hello, zhong
最后
现在,基于上述内容就能够快速搭建一个简易 web 服务器了。或许有些地方我写得不清不楚,主要是我欲将这一系列的博客作为笔记而非教程来写。如果浪费了你的时间,请尽情责骂便是。我也应该在此真诚致歉——太不好意思了!
还不快抢沙发