项目地址:https://github.com/labstack/echo
官方文档:https://echo.labstack.com/
由于框架更新迭代速度较快,若你阅读本文时已有新版本出现,请到官方文档进行学习。
简介
Echo框架是一个用 go 编写的 Web 框架,它具备可扩展、轻量级等特点。
Echo框架简单高效,几行代码就可以启动一个高性能的 HTTP 服务端。
Echo框架默认包含了MVC框架的 C(Controller)控制器部分,负责 URL 路由和控制器部分。V(View)视图部分和 M(Model)数据操作部分,我们可以使用自己喜欢的工具库来操作。
安装
要求
- 安装 Go
- 设置 GOPATH 环境变量
安装 Echo 包:
1 | cd <PROJECT IN $GOPATH> |
其中...
代表你想要安装的版本比如我这里安装的是 v4 版本,那我输入的命令就是 go get -u github.com/labstack/echo/v4
。
Echo 使用 Go 1.10.x
开发,并通过了 1.9.x
和 1.10.x
的测试。
中间件
在 Go Echo框架中 中间件(Middleware)指的是可以拦截http请求-响应生命周期的特殊函数。在请求-响应生命周期中可以注册多个中间件,每个中间件执行不同的功能,一个中间执行完再轮到下一个中间件执行。
中间件的运行流程如下:
中间件常见的应用场景如下:
- 请求限速
- api 接口签名处理
- 权限校验
- 统一错误处理
如果你想拦截所有请求做一些事情都可以开发一个中间件函数去实现。
常见的中间件
常见的中间件有以下几种:
- Redirect 中间件
- Recover 中间件
- Static 中间件
- Logger 中间件
- Rewrite 中间件
- Session 中间件
- 自定义中间件
下面对它们进行逐一介绍。
Redirect 中间件
Redirect 重定向中间件支持下面几种重定向机制:
http 强制跳转至 https
比如:
访问 http://www.wuster.store 自动跳转到 https://www.wuster.store1
2e := echo.New()
e.Pre(middleware.HTTPSRedirect())www 跳转
比如:
访问 http://wuster.store 自动跳转到 http://www.wuster.store1
2e := echo.New()
e.Pre(middleware.WWWRedirect())https www 跳转
比如:
访问 http://wuster.store 自动跳转到 https://www.wuster.store1
2e := echo.New()
e.Pre(middleware.HTTPSWWWRedirect())
Recover 中间件
Recover 中间件,主要用于拦截 panic 错误并且在控制台打印错误日志,避免 echo 程序直接崩溃。
使用范例如下:
1 | //初始化echo实例 |
一般 echo 应用都会注册 Recover 中间件,避免程序崩溃退出。
Static 中间件
Static 中间件主要用于处理 js、css 之类的静态资源, 具体用法请参考: 处理静态文件。
Logger 中间件
Logger 中间件主要用于打印 http 请求日志。
1 | //初始化echo实例 |
开启 Logger 中间件后,访问 http 请求会打印下面日志:
1 | {"time":"2022-11-10T19:40:54.8818768+08:00","id":"","remote_ip":"::1","host":"localhost:1323","method":"GET","uri":"/","user_agent":"PostmanRuntime/7.29.2","status":200,"error":"","latency":0,"latency_human":"0s","bytes_in":0,"bytes_out":14} |
Rewrite 中间件
url 重定向中间件,我们可以用于将一个 url 重定向到另外一个 url。
范例如下:
1 | //初始化echo实例 |
“*
“星代表任意字符串,$1
代表引用表达式中第一个星(*
)的匹配值,$2
代表第二个,以此类推。
Session 中间件
会话中间件。
自定义中间件
下面以一个简单的统计访问量的例子介绍如何自定义中间件。
1 | package main |
响应头如下:
Context
echo.Context 表示当前 HTTP 请求的上下文。通过路径、路径参数、数据、注册处理器和相关 API 进行请求的读取与响应的输出。由于 Context 是一个接口,也可以轻松地使用自定义 API 进行扩展。
扩展 Context
定义一个自定义 context
1 | type CustomContext stuct{ |
创建一个中间件来扩展默认的 context
1 | e.Use(func(h echo.HandlerFunc) echo.HandlerFunc { |
此中间件应在任何其他中间件之前注册。
在处理器中使用
1 | e.Get("/", func(c echo.Context) error { |
Cookies
Cookie 是用户访问网站时浏览器上存储的小型文本文件,由服务器发送而来。每当用户加载网站时,浏览器都会将 cookie 发送回服务器以通知用户之前的活动。 Cookie 作为一个可靠验证凭据,可用来记录状态信息(比如在线商城购物车中的商品)或记录用户的浏览器活动(包括单击特定按钮,登录或记录访问过的页面)。Cookie 还可以存储用户先前输入的密码和表单内容,例如信用卡号或地址。
在 Go Echo框架中,我们可以通过 net/http 包的 Cookie 结构体初始化一个 cookie,然后通过 echo.Context 上下文对象的 SetCookie 函数往请求结果设置 cookie。
Cookie 属性
属性 | 可选 |
---|---|
Name | No |
Value | No |
Path | Yes |
Domain | Yes |
Expires | Yes |
Secure | Yes |
HTTPOnly | Yes |
Echo 使用 golang 自带的 http.Cookie
对象写入/读取从上下文中的 cookie。
http.Cookie 结构体定义
1 | type Cookie struct { |
创建一个 Cookie
往客户设置一个 cookie 需要两个步骤:
- 初始化 http.Cookie 对象;
- 调用 SetCookie 函数设置 cookie 对象。
范例如下:
1 | func writeCookie(c echo.Context) error{ |
- 使用
new(http.Cookie)
创建Cookie。 - cookie 的属性值会被赋值给
http.Cookie
的可导出属性。 - 最后,使用
c.SetCookie(cookies)
来给 HTTP 响应增加Set-Cookie
头。
读取 cookie
读取 cookie 主要通过 echo.Context 上下文对象的 Cookie 函数进行操作。
范例如下:
1 | // Handler |
- Cookie 通过名称从 HTTP 请求里读取:
c.Cookie("name")
。 - Cookie 的属性可以使用
Getter
方法获取。
下面介绍如何一次性查询所有 cookie:
1 | // Handler |
删除 cookie
删除 cookie,本质上是通过设置 cookie 的过期时间,让 cookie 立刻失效。
范例如下:
1 | // Handler |
错误处理
Echo 提倡通过中间件或处理程序 (handler) 返回 HTTP 错误集中处理。集中式错误处理程序允许我们从统一位置将错误记录到外部服务,并向客户端发送自定义 HTTP 响应。
你可以返回一个标准的 error
或者 echo.*HTTPError
。
例如,当基本身份验证中间件找到无效凭据时,会返回 401未授权错误 (401-Unauthorized),并终止当前的 HTTP 请求。
1 | e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { |
你也可以不带消息内容调用 echo.NewHTTPError()
,这种情况下状态文本会被用作错误信息,例如 Unauthorized
。
默认 HTTP 错误处理程序
Echo 提供了默认的 HTTP 错误处理程序,它用 JSON 格式发送错误。
1 | { |
标准错误 error
的响应是 500 - Internal Server Error
。然而在调试 (debug) 模式模式下,原始的错误信息会被发送。如果错误是 *HTTPError
,则使用设置的状态代码和消息发送响应。如果启用了日志记录,则还会记录错误消息。
自定义 HTTP 错误处理程序
通过 e.HTTPErrorHandler
可以设置自定义的 HTTP 错误处理程序 (error handler) 。
通常默认的 HTTP 错误处理程序已经够用;然而如果要获取不同类型的错误并采取相应的操作,则可以使用自定义 HTTP 错误处理程序,例如发送通知邮件或记录日志到应用中心的场景。最后,你还可以发送自定义的错误页面或 JSON 响应给客户端。
错误页
利用自定义 HTTP 错误处理程序,可以在显示不同种类的错误页面的同时,记录错误日志。错误页的名称可写作 <CODE>.html
,例如 500.html
。你可以在
https://github.com/AndiDittrich/HttpErrorPages看到 Echo 内置的错误页。
1 | func customHTTPErrorHandler(err error, c echo.Context) { |
日志除了记录到 logger,也可以记录到第三方服务,例如 Elasticsearch 或者 Splunk
模板
模板渲染
使用 Context#Render(code int, name string, data interface{}) error
命令渲染带有数据的模板,并发送带有状态代码的 text / html 响应。通过 Echo.Renderer
的设置我们可以使用任何模板引擎。
我们先看下 echo.Renderer 接口定义:
1 | Renderer interface { |
通过实现 echo.Renderer 接口自定义当调用 Render 函数的时候我们使用什么模版引擎来渲染模版。
下面是使用 Go html/template
的示例:
实现
echo.Renderer
接口1
2
3
4
5
6
7
8
9// 自定义的模版引擎 struct
type Template struct {
templates *template.Template
}
// 实现接口,Render函数
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
// 调用模版引擎渲染模版
return t.templates.ExecuteTemplate(w, name, data)
}预编译模板
public/views/hello.html
1
{{define "hello"}}Hello, {{.}}!{{end}}
1
2
3
4
5t := &Template{
//模版引擎支持提前编译模版, 这里对views目录下以html结尾的模版文件进行预编译处理
// 预编译处理的目的是为了优化后期渲染模版文件的速度
templates: template.Must(template.ParseGlob("public/views/*.html")),
}声明模板
1
2
3
4
5
6//初始化echo实例
e := echo.New()
// 向echo实例注册模版引擎
e.Renderer = t
// 初始化路由和控制器函数
e.GET("/hello", Hello)在控制器中渲染模板
1
2
3
4func Hello(c echo.Context) error {
//渲染hello.html模版文件,模版参数为World
return c.Render(http.StatusOK, "hello.html", "World")
}
在控制器中渲染模版并返回 HTML 页面
完成模版引擎设置后,就可以在控制器函数中通过 echo.Context 对象的 Render 函数渲染模版并返回 html 页面。
函数定义:Render(code int, name string, data interface{}) error
参数说明:
参数 | 说明 |
---|---|
code | http状态码 |
name | 模版文件名 |
data | 模版参数,可以是任意类型数据 |
模版文件 views/hello.html 内容:
1 | Hello, {{.}}! |
渲染模版文件:
1 | func Hello(c echo.Context) error { |
渲染结果为:Hello, world!
高级 - 在模版中调用 Echo
在某些情况下,从模板生成 uri 可能很有用,为此,您需要从模板本身调用 Echo#Reverse
。此时,Golang 的 html/template
包并不一定合适这种情况,但我们可以通过两种方法实现它:第一种,给所有的传递到模版的对象提供一个公用的方法;第二种,将 map[string]interface{}
作为参数传递并在自定义渲染器中扩充此模版。鉴于后一种方法的灵活性,这里有一个示例程序: template.html
1 | <html> |
server.go
1 | package main |
测试结果如下:
请求
请求参数
此处介绍四种获取请求参数的方式:绑定 struct 数据、获取 Get 参数、获取 Post 参数以及获取路径参数。
绑定 struct 数据
通过将请求参数绑定到一个 struct 对象的方式获取数据。这种方式获取请求参数支持 json、xml、k/v 键值对等多种方式。
下面例子是将请求参数绑定到 User struct 对象。
1 | // User 结构体定义 |
通过定义 struct 字段的标签,定义请求参数和 struct 字段的关系。
下面对 User 的 Name 字段的标签进行说明。
struct标签说明:
标签 | 说明 |
---|---|
json:”name” | 支持post请求,数据格式为json格式,并且字段名为name |
form:”name” | 支持post请求,并且参数名为name |
query:”name” | 支持get请求,并且参数名为name |
我们可以根据自己的需要选择支持的请求方式和数据类型,例如需要支持 xml 数据格式,可以这样定义字段标签: xml:”name”。
下面看控制器代码:
1 | // Handler |
要在创建User对象后调用 echo.Context 的 Bind 函数将请求参数和 User 对象进行绑定,才可以将请求的实际参数和 struct 字段建立起交互关系。
获取 Get 请求数据
通过 echo.Context 对象的 QueryParam 函数可以直接获取 Get 请求参数。
1 | // Handler |
注:c.QueryParam(“name”) 中的 name 名字必须和 get 传参的名字一致,否则为无效传值。
query 对应 Get 请求
获取 Post 请求数据
通过 echo.Context 对象的 FormValue 函数可以直接获取 Post 请求参数。
1 | // Handler |
通过 FormValue 函数获取参数的值,数据类型都是 String 类型, 如果需要其他类型的数据,需要自己转换数据格式。
获取 path 路径参数
通过 echo.Context 对象的 Param 获取 url 路径参数。
1 | //例子: url 路由规则为 /users/:name , :name 为参数。 |
一个小 demo
1 | package main |
上面改成 GET 传值也行
显示响应信息:
更改上述的路由定义,换为 GET 请求,修改绑定的控制器函数为 GET 请求数据,且将定义的 User 结构体注释掉
1 | e.GET("/", func(c echo.Context) error { |
进行测试得
请求结果
Go Echo框架支持以字符串、json、xml、文件等格式返回请求结果,响应请求。
在 Go Echo框架中,使用 echo.Context 上下文对象来支持多种返回处理结果。
以字符串方式响应请求
Context#String(code int, s string)
用于发送一个带有状态码的纯文本响应。
函数定义:String(code int, s string) error
参数说明:
参数 | 说明 |
---|---|
code | http 状态码 |
s | 返回字符串结果 |
范例如下:
1 | func(c echo.Context) error { |
net/http 包定义了多种常用的状态码常量,例如:http.StatusOK == 200, http.StatusMovedPermanently == 301, http.StatusNotFound == 404等,具体可以参考 net/http包。
以 HTML 格式响应请求
Context#HTML(code int, html string)
用于发送一个带有状态码的简单 HTML 响应。如果你需要动态生成 HTML 内容请查看模版。
示例
1 | func(c echo.Context) error { |
以 json 格式响应请求
函数定义:JSON(code int, i interface{}) error
参数说明:
参数 | 说明 |
---|---|
code | http状态码 |
i | 返回结果对象,通常传入struct对象 |
范例如下:
1 | // User 定义 |
JSON流
Context#JSON()
内部使用 json.Marshal
来转换 JSON 数据,但该方法面对大量的 JSON 数据会显得效率不足,对于这种情况可以直接使用 JSON 流。
示例
1 | func(c echo.Context) error { |
JSON 美化 (JSON Pretty)
Context#JSONPretty(code int, i interface{}, indent string)
可以发送带有缩进(可以使用空格和 tab)的更为好看的 JSON 数据。
示例
1 | func(c echo.Context) error { |
通过在请求URL查询字符串中附加
pretty
,你也可以使用Context#JSON()
来输出带有缩进的 JSON 数据。
示例
将上述的控制器中的 return 改成以下样式
1 | return c.JSONPretty(http.StatusOK, u, "wuhuwuhu") |
测试的以下结果:
通过在请求URL查询字符串中附加
pretty
,你也可以使用Context#JSON()
来输出带有缩进的 JSON 数据。
示例
1 | curl http://localhost:1323/?pretty |
以 xml 格式响应请求
函数定义:XML(code int, i interface{}) error
参数说明:
参数 | 说明 |
---|---|
code | http状态码 |
i | 返回结果对象,通常传入struct对象 |
范例如下:
1 | // User 定义, 默认struct的名字就是xml的根节点名字,这里转换成xml后根节点的名字为User. |
当然也可以使用 XML 美化 (XML Pretty)
示例
1 | func(c echo.Context) error { |
1 |
|
以文件格式响应请求
如果我们需要实现文件下载功能,可以直接返回文件。
echo支持多种方式返回文件:
1 | // 例子1: |
发送流 (Stream)
Context#Stream(code int, contentType string, r io.Reader)
可用于发送带有内容类型 (content type) 、状态代码、io.Reader
的任意类型数据流。
示例
1 | func(c echo.Context) error { |
发送空内容 (No Content)
Context#NoContent(code int)
可用于发送带有状态码的空内容。
示例
1 | func(c echo.Context) error { |
Hooks
响应之前
Context#Response#Before(func())
可以用来注册在写入响应之前调用的函数。
响应之后
Context#Response#After(func())
可以用来注册在写入响应之后调用的函数。但是如果 “Content-Length” 是未知状态,则不会有任何方法会被执行。
示例
1 | func(c echo.Context) error { |
可以在响应之前与之后注册多个方法
示例
按上述进行测试得到结果如下:
可以看到无 Content-Length 值,因为我们在控制器中返回的是空内容
所以经过多次请求后,程序也只打印 before response
。
现在让我们对控制器进行修改
1 | return c.String(http.StatusOK, "欢迎访问 codebaoku.com!") |
让其以字符串方式响应请求
我们可以看到由于存在响应信息 Content-Length 发挥作用
进经过一次访问,可以看到 before response
与 after response
都被打印了出来。
设置 http 响应头(设置 Header)
1 | func(c echo.Context) error { |
路由
基于 radix tree ,Echo 的路由查询速度非常快。路由使用 sync pool 来重用内存,实现无 GC 开销下的零动态内存分配。
路由规则
一条路由规则由三部分组成:
- http 请求方法
- url 路径
- 控制器函数
通过特定的 HTTP 方法,url 路径和一个匹配的处理程序 (handler) 可以注册一个路由。例如,下面的代码则展示了一个注册路由的例子:它包括 Get
的访问方式, /hello
的访问路径,以及发送 Hello World
HTTP 响应的处理程序。
1 | // 业务处理 |
你可以用 Echo.Any(path string, h Handler)
来为所有的 HTTP 方法发送注册 处理程序(handler);如果仅需要为某个特定的方法注册路由,可使用 Echo.Match(methods []string, path string, h Handler)
。
Echo 通过 func(echo.Context) error
定义 handler 方法,其中 echo.Context
已经内嵌了 HTTP 请求和响应接口。
url 路径
echo 框架,url 路径有三种写法:
- 静态url路径
- 带路径参数的url路径
- 带星号(*)模糊匹配参数的url路径
1 | // 例子1, 静态Url路径, 即不带任何参数的url路径 |
Url 路径匹配顺序
上面列出了三种 Url 路径的写法,那么如果我们项目里面三种路径都有使用,而且出现一个 http 请求匹配多种路径的时候,echo 框架选择哪个路径呢?
例如:一个 http Get 请求的路径为 /user/10。
同时匹配下面三种 url 路径定义:
1 | /user/10 |
如果出现上述,一个 http 请求路径匹配多个定义的url路径,echo 框架按下面顺序匹配,先匹配到那个就用那个定义。
- 匹配静态 url 路径 (固定路径)
- 匹配带路径参数的 url 路径 (参数路径)
- 匹配带星号(*)模糊匹配参数的 url 路径 (匹配所有)
实例
1 | e.GET("/users/:id", func(c echo.Context) error { |
上面定义的路由将按下面的优先级顺序匹配:
/users/new
/users/:id
/users/1/files/*
路由可以按照任意顺序定义。
组路由
Echo#Group(prefix string, m ...Middleware) *Group
可以将具有相同前缀的路由归为一组从而定义具有可选中间件的新子路由。除了一些特殊的中间件外,组路由也会继承父中间件。若要在组路由中添加中间件,则需使用 Group.Use(m ...Middleware)
。最后,组路由也可以嵌套。
下面的代码,我们创建了一个 admin 组,它需要对 /admin/*
路由进行基本的 HTTP 身份认证。
1 | g := e.Group("/admin") |
路由命名
每个路由都会返回一个 Route
对象,这个对象可以用来给路由命名。比如:
1 | routeInfo := e.GET("/users/:id", func(c echo.Context) error { |
当你需要在模版生成 uri 但是又无法获取路由的引用,或者多个路由使用相同的处理器(handler)的时候,路由命名就会显得更方便。
构造URI
Echo#URI(handler HandlerFunc, params ...interface{})
可以用来在任何业务处理代码里生成带有特殊参数的 URI 。这样在你重构自己的应用程序的时候,可以很方便的集中处理所有的 URI 。
下面的代码中 e.URI(h, 1)
将生成 /users/1
:
1 | // 业务处理 |
除了 Echo#URI
,还可以使用 Echo#Reverse(name string, params ...interface{})
方法根据路由名生成 uri。比如,当 foobar
进行如下设置时,使用 Echo#Reverse("foobar", 1234)
就会生成 /users/1234
:
1 | // Handler |
路由列表
Echo#Routes() []*Route
会根据路由定义的顺序列出所有已经注册的路由。每一个路由包含 http 方法,路径和对应的处理器(handler)。 示例
1 | // Handlers |
用下面的代码你将所有的路由信息输出到 JSON 文件:
1 | data, err := json.MarshalIndent(e.Routes(), "", " ") |
routes.json
1 | [ |
静态文件
例如图片,JavaScript,CSS,PDF,字体文件等等…
echo.Static 函数定义
我们可以通过 echo.Static 函数初始化 static 中间件。
echo.Static 函数定义:
1 | Static(prefix, root string) *Route |
参数说明:
参数 | 说明 |
---|---|
prefix | 静态资源 url 的前缀 |
root | 静态资源文件的根目录, . 代表当前目录 |
使用 Echo#Static()
Echo#Static(prefix, root string)
使用路径前缀注册一个新路由,以便由根目录提供静态文件。
用法 1
1 | e := echo.New() |
如上所示, assets 目录中 /static/*
路径下的任何文件都会被访问。例如,一个访问 /static/js/main.js
的请求会匹配到 assets/js/main.js
这个文件。
用法 2
1 | e := echo.New() |
如上所示, assets 目录中 /*
路径下的任何文件都会被访问。例如,一个访问 /js/main.js
的请求将会匹配到 assets/js/main.js
文件。
echo.Static 函数范例
1 | //初始化echo实例 |
根据这个例子的设置,如果我们访问 /res/codebaoku.jpg 这个url路径,实际上就是访问 static/codebaoku.jpg 这个路径的内容(即访问 static 目录下面 codebaoku.jpg 文件)
Echo.File 函数定义
我们也可以通过 Echo.File 函数为一个 url 地址绑定一个静态资源文件。
echo.File 函数定义:
1 | File(url, filename string) *Route |
参数说明:
参数 | 说明 |
---|---|
url | 静态资源 url |
filename | 静态资源的文件名 |
使用 Echo#File()
Echo#File(path, file string)
使用路径注册新路由以提供静态文件。
用法 1
使用 public/index.html
提供索引页面
1 | e.File("/", "public/index.html") |
用法 2
使用 images/favicon.ico
提供一个图标
1 | e.File("/favicon.ico", "images/favicon.ico") |
Echo.File 函数范例
1 | // 初始化echo实例 |
测试
测试处理程序 (Testing handler)
GET
/users/:id
下面的处理程序是根据用户的 id 从数据库取到该用户数据,如果用户不存在则返回 404
和提示语句。
创建 User
POST
/users
- 接受 JSON 格式的关键信息
- 创建成功返回
201 - Created
- 发生错误返回
500 - Internal Server Error
获取 User
GET
/users/:email
- 获取成功返回
200 - OK
- 未获取 User 返回
404 - Not Found
- 发生其它错误返回
500 - Internal Server Error
handler.go
1 | package handler |
handler_test.go
1 | package handler |
使用 Form 表单作为关键信息
1 | // import "net/url" |
设置路径 (Path) 参数
1 | c.SetParamNames("id", "email") |
设置查询 (Query) 参数
1 | // import "net/url" |
测试中间件
待定 你可以在这里查看框架自带中间件的测试代码。
参考:
Guide | Echo - High performance, minimalist Go web framework (labstack.com)
Echo框架 教程 - 编程宝库 (codebaoku.com)
HttpErrorPages/HttpErrorPages: Simple HTTP Error Page Generator (github.com)