谈谈Pixiu的Filter
Filter的生命周期
Pixiu作为一个面向云原生的gateway,通过简单的配置即可代理Http to Dubbo 2、Tripe甚至是Spring Cloud的请求。那Filter是怎样运行的呢?
首先Filter Plugin向Filter Manager注册自己**,然后Filter Manager根据配置创建好Filter Factory并持有它们,等待请求来临时,Manager创建一个一次性的用于此次请求的Filter Chain,然后利用Factory创建好Decode/Encode Filter并把它们加入链中,然后按照顺序去运行Decode Filter,然后去请求Upstream**,拿到Response再反向运行Encode Filter,让Filter可以访问到Response。
几个关键的概念:
Filter Manager
Filter的Manger。。。
// FilterManager manage filters
type FilterManager struct {
filters map[string]HttpFilterFactory
filtersArray []*HttpFilterFactory
}
Filter Plugin:定义了Filter的(唯一的)名字和描述如何去创建一个Filter Factory。
其实结合Filter Factory的定义,可以认为Plugin是Filter Factory的Factory
// HttpFilterPlugin describe plugin
HttpFilterPlugin interface {
// Kind returns the unique kind name to represent itself.
Kind() string
// CreateFilterFactory return the filter factory
CreateFilterFactory() (HttpFilterFactory, error)
}
Filter Factory:定义了Filter自身的配置,并且在请求来临时创建真实的Filter并把它添加到FilterChain中
- Config() 的目的是能让Filter Manager能够有机会把配置交给Factory(此时golang泛型还没有落地)
- Apply() 在配置被注入到Factory后,有机会对config做一些检查和提前做一些初始化的工作
- PrepareFilterChain() 创建Filter并加入Filter Chain
// HttpFilterFactory describe http filter
HttpFilterFactory interface {
// Config Expose the config so that Filter Manger can inject it, so it must be a pointer
Config() interface{}
// Apply After the config is injected, check it or make it to default
Apply() error
// PrepareFilterChain create filter and append it to FilterChain
//
// Be Careful !!! Do not pass the Factory's config pointer to the Filter instance,
// Factory's config may be updated by FilterManager
PrepareFilterChain(ctx *http.HttpContext, chain FilterChain) error
}
Decode/Encode Filter:Filter分为两个部分,Decode在实际请求Upstream之前,所以可以做一些鉴权、限流,把请求在gateway层拦截掉。Eecode则运行在获得Upstream的Response之后,所以可以对返回Log甚至修改Response。
一个Filter可以即是Decode Filter,又是Encode Filter,没有限制!
假设有A、B、C三个Filter,都是Decode/Encode Filter,如果配置的顺序是A、B、C,那么运行将会是下面这样
在Decode阶段 A->B->C,而在Encode阶段,顺序将会反过来!C->B->A
// decode filters will be invoked in the config order: A、B、C, and decode filters will be
// invoked in the reverse order: C、B、A
HttpDecodeFilter interface {
Decode(ctx *http.HttpContext) FilterStatus
}
// HttpEncodeFilter after invoke upstream,
// decode filters will be invoked in the reverse order
HttpEncodeFilter interface {
Encode(ctx *http.HttpContext) FilterStatus
}
更详细的,每个Decode/Encode Filter可以返回一个FilterStatus来决定继续还是就在这里停下!比如JWT鉴权,token无效时就要及时把401返回给Downstream。当然Decode Filter发出的停止命令只会终止Decode阶段,至于为什么?想想如何做一个Access Log Filter,能在请求失败时也把失败的结果记录下吧来!
怎样编写一个自定义Filter
我们来尝试写一个简单的Filter,这个Filter将会有简单的配置,在Decode阶段把请求的Body Log出来,并翻转后作为Mock的返回值。最后在Encode阶段根据配置把返回值Log出来。
1.首先创建一个Filter
type DemoFilter struct {
logPrefix string
}
func (f *DemoFilter) Decode(ctx *contexthttp.HttpContext) filter.FilterStatus {
body, _ := ioutil.ReadAll(ctx.Request.Body)
logger.Infof("request body: %s", body)
//reverse res str
runes := []rune(string(body))
for i := 0; i < len(runes)/2; i += 1 {
runes[i], runes[len(runes)-1-i] = runes[len(runes)-1-i], runes[i]
}
reverse := string(runes)
//mock response
ctx.SendLocalReply(200, []byte(reverse))
return filter.Stop
}
func (f *DemoFilter) Encode(ctx *contexthttp.HttpContext) filter.FilterStatus {
res := ctx.SourceResp.(string)
logger.Infof("%s: %s", f.logPrefix, res)
return filter.Continue
}
2.创建Filter Factory
type (
DemoFilterFactory struct {
conf *Config
}
// Config describe the config of Filter
Config struct {
LogPrefix string `yaml:"logPrefix,omitempty"`
}
)
func (f *DemoFilterFactory) PrepareFilterChain(ctx *contexthttp.HttpContext, chain filter.FilterChain) error {
demo := &DemoFilter{logPrefix: f.conf.LogPrefix}
chain.AppendDecodeFilters(demo)
chain.AppendEncodeFilters(demo)
return nil
}
func (f *DemoFilterFactory) Config() interface{} {
return f.conf
}
func (f *DemoFilterFactory) Apply() error {
return nil
}
3.创建Filter Plugin,并注册自己
//important
func init() {
filter.RegisterHttpFilter(&Plugin{})
}
type Plugin struct {
}
func (p *Plugin) Kind() string {
return "dgp.filters.demo"
}
func (p *Plugin) CreateFilterFactory() (filter.HttpFilterFactory, error) {
return &DemoFilterFactory{conf: &Config{}}, nil
}
4.配置文件中配置此Filter,并启动Pixiu
static_resources:
listeners:
- name: "net/http"
protocol_type: "HTTP"
address:
socket_address:
address: "0.0.0.0"
port: 8888
filter_chains:
filters:
- name: dgp.filter.httpconnectionmanager
config:
route_config:
routes:
- match:
prefix: "/"
http_filters:
- name: dgp.filters.demo
config:
5.访问并查看日志与结果
curl localhost:8888/demo -d "eiv al tse’c"
c’est la vie%
日志
2022-02-19T20:20:11.900+0800 INFO demo/demo.go:62 request body: eiv al tse’c
2022-02-19T20:20:11.900+0800 INFO demo/demo.go:71 : eiv al tse’c