Skip to content

Commit

Permalink
add doc with blademaster
Browse files Browse the repository at this point in the history
  • Loading branch information
felixhao committed Apr 16, 2019
1 parent 2739a26 commit 0c58cd9
Show file tree
Hide file tree
Showing 12 changed files with 500 additions and 13 deletions.
Binary file added doc/img/bm-arch-2-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/img/bm-arch-2-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/img/bm-handlers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 104 additions & 0 deletions doc/wiki-cn/blademaster-mid.md
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()
}
```
79 changes: 79 additions & 0 deletions doc/wiki-cn/blademaster-mod.md
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

![handler](/doc/img/bm-handlers.png)

初次接触 blademaster 的用户可能会对其 Handler 的流程处理产生不小的疑惑,实际上 bm 对 Handler 对处理非常简单。
将 Router 模块中预先注册的中间件与其他 Handler 合并,放入 Context 的 handlers 字段,并将 index 置 0,然后通过 Next() 方法一个个执行下去。
部分中间件可能想要在过程中中断整个流程,此时可以使用 Abort() 方法提前结束处理。
有些中间件还想在所有 Handler 执行完后再执行部分逻辑,此时可以在自身 Handler 中显式调用 Next() 方法,并将这些逻辑放在调用了 Next() 方法之后。
64 changes: 64 additions & 0 deletions doc/wiki-cn/blademaster-quickstart.md
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)
33 changes: 33 additions & 0 deletions doc/wiki-cn/blademaster.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 背景

在像微服务这样的分布式架构中,经常会有一些需求需要你调用多个服务,但是还需要确保服务的安全性、同一化每次的请求日志或者追踪用户完整的行为等等。要实现这些功能,你可能需要在所有服务中都设置一些相同的属性,虽然这个可以通过一些明确的接入文档来描述或者准入规范来界定,但是这么做的话还是有可能会有一些问题:

1. 你很难让每一个服务都实现上述功能。因为对于开发者而言,他们应当注重的是实现功能。很多项目的开发者经常在一些日常开发中遗漏了这些关键点,经常有人会忘记去打日志或者去记录调用链。但是对于一些大流量的互联网服务而言,一个线上服务一旦发生故障时,即使故障时间很小,其影响面会非常大。一旦有人在关键路径上忘记路记录日志,那么故障的排除成本会非常高,那样会导致影响面进一步扩大。
2. 事实上实现之前叙述的这些功能的成本也非常高。比如说对于鉴权(Identify)这个功能,你要是去一个服务一个服务地去实现,那样的成本也是非常高的。如果说把这个确保认证的责任分担在每个开发者身上,那样其实也会增加大家遗忘或者忽略的概率。

为了解决这样的问题,你可能需要一个框架来帮助你实现这些功能。比如说帮你在一些关键路径的请求上配置必要的鉴权或超时策略。那样服务间的调用会被多层中间件所过滤并检查,确保整体服务的稳定性。

# 设计目标

* 性能优异,不应该惨杂太多业务逻辑的成分
* 方便开发使用,开发对接的成本应该尽可能地小
* 后续鉴权、认证等业务逻辑的模块应该可以通过业务模块的开发接入该框架内
* 默认配置已经是 production ready 的配置,减少开发与线上环境的差异性

# 概览

* 参考`gin`设计整套HTTP框架,去除`gin`中不需要的部分逻辑
* 内置一些必要的中间件,便于业务方可以直接上手使用

# blademaster架构

![bm-arch](/doc/img/bm-arch-2-2.png)

blademaster 由几个非常精简的内部模块组成。其中 Router 用于根据请求的路径分发请求,Context 包含了一个完整的请求信息,Handler 则负责处理传入的 Context,Handlers 为一个列表,一个串一个地执行。
所有的中间件均以 Handler 的形式存在,这样可以保证 blademaster 自身足够精简,且扩展性足够强。

![bm-arch](/doc/img/bm-arch-2-3.png)

blademaster 处理请求的模式非常简单,大部分的逻辑都被封装在了各种 Handler 中。一般而言,业务逻辑作为最后一个 Handler。正常情况下,每个 Handler 按照顺序一个一个串形地执行下去。
但是 Handler 中可以也中断整个处理流程,直接输出 Response。这种模式常被用于校验登陆的中间件中;一旦发现请求不合法,直接响应拒绝。
请求处理的流程中也可以使用 Render 来辅助渲染 Response,比如对于不同的请求需要响应不同的数据格式(JSON、XML),此时可以使用不同的 Render 来简化逻辑。
6 changes: 4 additions & 2 deletions doc/wiki-cn/summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
* [快速开始](quickstart.md)
* [案例](https://github.com/bilibili/kratos-demo)
* [http blademaster](blademaster.md)
* [middleware](blademaster-mid.md)
* [protobuf生成](blademaster-pb.md)
* [bm quickstart](blademaster-quickstart.md)
* [bm module](blademaster-mod.md)
* [bm middleware](blademaster-mid.md)
* [bm protobuf](blademaster-pb.md)
* [grpc warden](warden.md)
* [middleware](warden-mid.md)
* [protobuf生成](warden-pb.md)
Expand Down
7 changes: 7 additions & 0 deletions pkg/net/http/blademaster/middleware/auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#### blademaster/middleware/auth

##### 项目简介

blademaster 的 authorization middleware,主要用于设置路由的认证策略

注:仅仅是个demo,请根据自身业务实现具体鉴权逻辑
Loading

0 comments on commit 0c58cd9

Please sign in to comment.