Iris - 入门食用指南

Iris 2019-12-15 6279 字 1500 浏览 点赞

起步

这一小节准备简单聊一聊 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 服务器了。或许有些地方我写得不清不楚,主要是我欲将这一系列的博客作为笔记而非教程来写。如果浪费了你的时间,请尽情责骂便是。我也应该在此真诚致歉——太不好意思了!

感谢



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论