forked from go-kratos/kratos
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
500 additions
and
13 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# 背景 | ||
|
||
基于bm的handler机制,可以自定义很多middleware(中间件)进行通用的业务处理,比如用户登录鉴权。接下来就以鉴权为例,说明middleware的写法和用法。 | ||
|
||
# 写自己的中间件 | ||
|
||
middleware本质上就是一个handler,如下代码: | ||
```go | ||
// Handler responds to an HTTP request. | ||
type Handler interface { | ||
ServeHTTP(c *Context) | ||
} | ||
|
||
// HandlerFunc http request handler function. | ||
type HandlerFunc func(*Context) | ||
|
||
// ServeHTTP calls f(ctx). | ||
func (f HandlerFunc) ServeHTTP(c *Context) { | ||
f(c) | ||
} | ||
``` | ||
|
||
1. 实现了`Handler`接口,可以作为engine的全局中间件使用:`engine.User(YourHandler)` | ||
2. 声明为`HandlerFunc`方法,可以作为router的局部中间件使用:`e.GET("/path", YourHandlerFunc)` | ||
|
||
简单示例代码如下: | ||
|
||
```go | ||
type Demo struct { | ||
Key string | ||
Value string | ||
} | ||
// ServeHTTP implements from Handler interface | ||
func (d *Demo) ServeHTTP(ctx *bm.Context) { | ||
ctx.Set(d.Key, d.Value) | ||
} | ||
|
||
e := bm.DefaultServer(nil) | ||
d := &Demo{} | ||
|
||
// Handler使用如下: | ||
e.Use(d) | ||
|
||
// HandlerFunc使用如下: | ||
e.GET("/path", d.ServeHTTP) | ||
|
||
// 或者只有方法 | ||
myHandler := func(ctx *bm.Context) { | ||
// some code | ||
} | ||
e.GET("/path", myHandler) | ||
``` | ||
|
||
|
||
# 全局中间件 | ||
|
||
在blademaster的`server.go`代码中,有以下代码: | ||
|
||
```go | ||
func DefaultServer(conf *ServerConfig) *Engine { | ||
engine := NewServer(conf) | ||
engine.Use(Recovery(), Trace(), Logger()) | ||
return engine | ||
} | ||
``` | ||
|
||
会默认创建一个bm engine,并注册`Recovery(), Trace(), Logger()`三个middlerware,用于全局handler处理。优先级从前到后。 | ||
如果需要自定义默认全局执行的middleware,可以使用`NewServer`方法创建一个无middleware的engine对象。 | ||
如果想要将自定义的middleware注册进全局,可以继续调用Use方法如下: | ||
|
||
```go | ||
engine.Use(YourMiddleware()) | ||
``` | ||
|
||
此方法会将`YourMiddleware`追加到已有的全局middleware后执行。 | ||
|
||
# 局部中间件 | ||
|
||
先来看一段示例(代码再pkg/net/http/blademaster/middleware/auth模块下): | ||
|
||
```go | ||
func Example() { | ||
myHandler := func(ctx *bm.Context) { | ||
mid := metadata.Int64(ctx, metadata.Mid) | ||
ctx.JSON(fmt.Sprintf("%d", mid), nil) | ||
} | ||
|
||
authn := auth.New(&auth.Config{DisableCSRF: false}) | ||
|
||
e := bm.DefaultServer(nil) | ||
|
||
// "/user"接口必须保证登录用户才能访问,那么我们加入"auth.User"来确保用户鉴权通过,才能进入myHandler进行业务逻辑处理 | ||
e.GET("/user", authn.User, myHandler) | ||
// "/guest"接口访客用户就可以访问,但如果登录用户我们需要知道mid,那么我们加入"auth.Guest"来尝试鉴权获取mid,但肯定会继续执行myHandler进行业务逻辑处理 | ||
e.GET("/guest", authn.Guest, myHandler) | ||
|
||
// "/owner"开头的所有接口,都需要进行登录鉴权才可以被访问,那可以创建一个group并加入"authn.User" | ||
o := e.Group("/owner", authn.User) | ||
o.GET("/info", myHandler) // 该group创建的router不需要再显示的加入"authn.User" | ||
o.POST("/modify", myHandler) // 该group创建的router不需要再显示的加入"authn.User" | ||
|
||
e.Start() | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# Context | ||
|
||
以下是 blademaster 中 Context 对象结构体声明的代码片段: | ||
```go | ||
// Context is the most important part. It allows us to pass variables between | ||
// middleware, manage the flow, validate the JSON of a request and render a | ||
// JSON response for example. | ||
type Context struct { | ||
context.Context | ||
|
||
Request *http.Request | ||
Writer http.ResponseWriter | ||
|
||
// flow control | ||
index int8 | ||
handlers []HandlerFunc | ||
|
||
// Keys is a key/value pair exclusively for the context of each request. | ||
Keys map[string]interface{} | ||
|
||
Error error | ||
|
||
method string | ||
engine *Engine | ||
} | ||
``` | ||
|
||
* 首先可以看到 blademaster 的 Context 结构体中会 embed 一个标准库中的 Context 实例,bm 中的 Context 也是直接通过该实例来实现标准库中的 Context 接口。 | ||
* Request 和 Writer 字段用于获取当前请求的与输出响应。 | ||
* index 和 handlers 用于 handler 的流程控制;handlers 中存储了当前请求需要执行的所有 handler,index 用于标记当前正在执行的 handler 的索引位。 | ||
* Keys 用于在 handler 之间传递一些额外的信息。 | ||
* Error 用于存储整个请求处理过程中的错误。 | ||
* method 用于检查当前请求的 Method 是否与预定义的相匹配。 | ||
* engine 字段指向当前 blademaster 的 Engine 实例。 | ||
|
||
以下为 Context 中所有的公开的方法: | ||
```go | ||
// 用于 Handler 的流程控制 | ||
func (c *Context) Abort() | ||
func (c *Context) AbortWithStatus(code int) | ||
func (c *Context) Bytes(code int, contentType string, data ...[]byte) | ||
func (c *Context) IsAborted() bool | ||
func (c *Context) Next() | ||
|
||
// 用户获取或者传递请求的额外信息 | ||
func (c *Context) RemoteIP() (cip string) | ||
func (c *Context) Set(key string, value interface{}) | ||
func (c *Context) Get(key string) (value interface{}, exists bool) | ||
|
||
// 用于校验请求的 payload | ||
func (c *Context) Bind(obj interface{}) error | ||
func (c *Context) BindWith(obj interface{}, b binding.Binding) error | ||
|
||
// 用于输出响应 | ||
func (c *Context) Render(code int, r render.Render) | ||
func (c *Context) Redirect(code int, location string) | ||
func (c *Context) Status(code int) | ||
func (c *Context) String(code int, format string, values ...interface{}) | ||
func (c *Context) XML(data interface{}, err error) | ||
func (c *Context) JSON(data interface{}, err error) | ||
func (c *Context) JSONMap(data map[string]interface{}, err error) | ||
func (c *Context) Protobuf(data proto.Message, err error) | ||
``` | ||
|
||
所有方法基本上可以分为三类: | ||
|
||
* 流程控制 | ||
* 额外信息传递 | ||
* 请求处理 | ||
* 响应处理 | ||
|
||
# Handler | ||
|
||
 | ||
|
||
初次接触 blademaster 的用户可能会对其 Handler 的流程处理产生不小的疑惑,实际上 bm 对 Handler 对处理非常简单。 | ||
将 Router 模块中预先注册的中间件与其他 Handler 合并,放入 Context 的 handlers 字段,并将 index 置 0,然后通过 Next() 方法一个个执行下去。 | ||
部分中间件可能想要在过程中中断整个流程,此时可以使用 Abort() 方法提前结束处理。 | ||
有些中间件还想在所有 Handler 执行完后再执行部分逻辑,此时可以在自身 Handler 中显式调用 Next() 方法,并将这些逻辑放在调用了 Next() 方法之后。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# 准备工作 | ||
|
||
推荐使用[kratos tool](kratos-tool.md)快速生成项目,如我们生成一个叫`kratos-demo`的项目。 | ||
|
||
生成目录结构如下: | ||
``` | ||
├── CHANGELOG.md | ||
├── CONTRIBUTORS.md | ||
├── LICENSE | ||
├── README.md | ||
├── cmd | ||
│ ├── cmd | ||
│ └── main.go | ||
├── configs | ||
│ ├── application.toml | ||
│ ├── grpc.toml | ||
│ ├── http.toml | ||
│ ├── log.toml | ||
│ ├── memcache.toml | ||
│ ├── mysql.toml | ||
│ └── redis.toml | ||
├── go.mod | ||
├── go.sum | ||
└── internal | ||
├── dao | ||
│ └── dao.go | ||
├── model | ||
│ └── model.go | ||
├── server | ||
│ └── http | ||
│ └── http.go | ||
└── service | ||
└── service.go | ||
``` | ||
|
||
# 路由 | ||
|
||
创建项目成功后,进入`internal/server/http`目录下,打开`http.go`文件,其中有默认生成的`blademaster`模板。其中: | ||
```go | ||
engine = bm.DefaultServer(hc.Server) | ||
initRouter(engine) | ||
if err := engine.Start(); err != nil { | ||
panic(err) | ||
} | ||
``` | ||
是bm默认创建的`engine`及启动代码,我们看`initRouter`初始化路由方法,默认实现了: | ||
```go | ||
func initRouter(e *bm.Engine) { | ||
e.Ping(ping) // engine自带的"/ping"接口,用于负载均衡检测服务健康状态 | ||
g := e.Group("/kratos-demo") // e.Group 创建一组 "/kratos-demo" 起始的路由组 | ||
{ | ||
g.GET("/start", howToStart) // g.GET 创建一个 "kratos-demo/start" 的路由,默认处理Handler为howToStart方法 | ||
} | ||
} | ||
``` | ||
|
||
bm的handler方法,结构如下: | ||
```go | ||
func howToStart(c *bm.Context) // handler方法默认传入bm的Context对象 | ||
``` | ||
|
||
# 扩展阅读 | ||
|
||
[bm模块说明](blademaster-mod.md) [bm中间件](blademaster-mid.md) [bm基于pb生成](blademaster-pb.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# 背景 | ||
|
||
在像微服务这样的分布式架构中,经常会有一些需求需要你调用多个服务,但是还需要确保服务的安全性、同一化每次的请求日志或者追踪用户完整的行为等等。要实现这些功能,你可能需要在所有服务中都设置一些相同的属性,虽然这个可以通过一些明确的接入文档来描述或者准入规范来界定,但是这么做的话还是有可能会有一些问题: | ||
|
||
1. 你很难让每一个服务都实现上述功能。因为对于开发者而言,他们应当注重的是实现功能。很多项目的开发者经常在一些日常开发中遗漏了这些关键点,经常有人会忘记去打日志或者去记录调用链。但是对于一些大流量的互联网服务而言,一个线上服务一旦发生故障时,即使故障时间很小,其影响面会非常大。一旦有人在关键路径上忘记路记录日志,那么故障的排除成本会非常高,那样会导致影响面进一步扩大。 | ||
2. 事实上实现之前叙述的这些功能的成本也非常高。比如说对于鉴权(Identify)这个功能,你要是去一个服务一个服务地去实现,那样的成本也是非常高的。如果说把这个确保认证的责任分担在每个开发者身上,那样其实也会增加大家遗忘或者忽略的概率。 | ||
|
||
为了解决这样的问题,你可能需要一个框架来帮助你实现这些功能。比如说帮你在一些关键路径的请求上配置必要的鉴权或超时策略。那样服务间的调用会被多层中间件所过滤并检查,确保整体服务的稳定性。 | ||
|
||
# 设计目标 | ||
|
||
* 性能优异,不应该惨杂太多业务逻辑的成分 | ||
* 方便开发使用,开发对接的成本应该尽可能地小 | ||
* 后续鉴权、认证等业务逻辑的模块应该可以通过业务模块的开发接入该框架内 | ||
* 默认配置已经是 production ready 的配置,减少开发与线上环境的差异性 | ||
|
||
# 概览 | ||
|
||
* 参考`gin`设计整套HTTP框架,去除`gin`中不需要的部分逻辑 | ||
* 内置一些必要的中间件,便于业务方可以直接上手使用 | ||
|
||
# blademaster架构 | ||
|
||
 | ||
|
||
blademaster 由几个非常精简的内部模块组成。其中 Router 用于根据请求的路径分发请求,Context 包含了一个完整的请求信息,Handler 则负责处理传入的 Context,Handlers 为一个列表,一个串一个地执行。 | ||
所有的中间件均以 Handler 的形式存在,这样可以保证 blademaster 自身足够精简,且扩展性足够强。 | ||
|
||
 | ||
|
||
blademaster 处理请求的模式非常简单,大部分的逻辑都被封装在了各种 Handler 中。一般而言,业务逻辑作为最后一个 Handler。正常情况下,每个 Handler 按照顺序一个一个串形地执行下去。 | ||
但是 Handler 中可以也中断整个处理流程,直接输出 Response。这种模式常被用于校验登陆的中间件中;一旦发现请求不合法,直接响应拒绝。 | ||
请求处理的流程中也可以使用 Render 来辅助渲染 Response,比如对于不同的请求需要响应不同的数据格式(JSON、XML),此时可以使用不同的 Render 来简化逻辑。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#### blademaster/middleware/auth | ||
|
||
##### 项目简介 | ||
|
||
blademaster 的 authorization middleware,主要用于设置路由的认证策略 | ||
|
||
注:仅仅是个demo,请根据自身业务实现具体鉴权逻辑 |
Oops, something went wrong.