Go语言学习——如何实现一个过滤器

过滤器使用场景

作业务的时候咱们常常要使用过滤器或者拦截器(听这口音就是从Java过来的)。常见的场景如一个HTTP请求,须要通过鉴权过滤器、白名单校验过滤、参数验证过滤器等重重关卡最终拿到数据。java

Java使用过滤器很简单。XML时代,只要添加一个过滤器配置再新建一个实现了Filter接口的xxxFilter实现类;Java Configuration时代,只要在xxxConfiguration配置类中声明一个Filter注解,若是想设置Filter的执行顺序,加上Order注解就好了。面试

Java的过滤器实在太方便也太好用了。spring

以致于在Java有关过滤器的面试题中,只有相似于“过滤器的使用场景有哪些?”,“过滤器和拦截器有什么区别?“,几乎不多听到”你知道过滤器是怎么实现的吗?“,”若是让你实现一个过滤器,你会怎么作?“这样的题目。ide

使用过滤器的场景特征

如同上面过滤器的例子,咱们发现过滤器有一些特征:函数

一、入参同样,好比HTTP请求的过滤器的入参就是ServletRequest对象spa

二、返回值类型相同,好比都是true或者false,或者是连接到下一个过滤器或者return。code

以下是Java实现的CORS过滤器对象

import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CORSFilter implements Filter {

    @Override
    public void doFilter(ServletRequest reserRealmq, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) reserRealmq;
        HttpServletResponse response = (HttpServletResponse) res;

        String currentOrigin= request.getHeader("Origin");
        if (!StringUtils.isEmpty(currentOrigin)) {
            response.setHeader("Access-Control-Allow-Origin", currentOrigin);
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Cache-Control, Expires, Content-Type, X-E4M-With, Index-Url");
        }

        // return http status 204 if OPTIONS requst
        if ("OPTIONS".equals(request.getMethod())){
            response.setStatus(HttpStatus.NO_CONTENT.value());
        }else {
            chain.doFilter(reserRealmq, res);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {

    }
}

复制代码

凡是具备这种特征的需求,咱们均可以抽象为过滤器进行实现(Java里面称为责任链模式)。接口

下面就来讲说,基于Go语言如何实现一个过滤器。get

简单实现

过滤器本质就是一堆条件断定,最直观的过滤方案就是建立几个方法,针对每一个方法的返回结果断定,若是返回为false则终止请求,若是为true则继续执行下一个过滤器。

package main

import (
	"context"
)

func main() {
	ctx := context.TODO()
  
  if continued := F1(ctx); !continued {
    ...
    return
  }
  
  if continued := F2(ctx); !continued {
    ...
    return
  }
  
  if continued := F3(ctx); !continued {
    ...
    return
  }
}

func F1(ctx context.Context) bool {
  ...
  return true
} 

func F2(ctx context.Context) bool {
  ...
  return true
}

func F3(ctx context.Context) bool {
  ...
  return false
}
复制代码

该版本从功能上说,彻底符合过滤器的要求。

可是从代码层面来讲,有几个问题:

一、复用性较差。main函数中对于各个过滤器的断定,除了函数名不同,其余逻辑都同样,能够考虑抽象重用。

二、可扩展性较差。由于有些代码复用性差,致使代码很差扩展,若是这时候添加、删除过滤器或者调整过滤器执行顺序,代码都须要较大改动才能实现。

三、难以维护。不用多说。

重构实现

package main

import (
	"context"
	"fmt"
)

type MyContext struct {
	context.Context
	KeyValue map[string]bool
}

type FilterFunc func(*MyContext) bool type FilterFuncChain []FilterFunc type CombinedFunc struct {
	CF    FilterFuncChain
	MyCtx *MyContext
}

func main() {
	myContext := MyContext{Context: context.TODO(), KeyValue: map[string]bool{"key": false}}

	cf := CombinedFilter(&myContext, F1, F2, F3);
	DoFilter(cf)
}

func DoFilter(cf *CombinedFunc) {
	for _, f := range cf.CF {
		res := f(cf.MyCtx)
		fmt.Println("result:", res)
		if res == false {
			fmt.Println("stopped")
			return
		}
	}
}

func CombinedFilter(ctx *MyContext, ff ...FilterFunc) *CombinedFunc {
	return &CombinedFunc{
		CF:    ff,
		MyCtx: ctx,
	}
}

func F1(ctx *MyContext) bool {
	ctx.KeyValue["key"] = true
	fmt.Println(ctx.KeyValue["key"])

	return ctx.KeyValue["key"]
}

func F2(ctx *MyContext) bool {
	ctx.KeyValue["key"] = false
	fmt.Println(ctx.KeyValue["key"])

	return ctx.KeyValue["key"]
}

func F3(ctx *MyContext) bool {
	ctx.KeyValue["key"] = false
	fmt.Println(ctx.KeyValue["key"])

	return ctx.KeyValue["key"]
}

复制代码

代码不长,咱们一块块分析。

自定义的Context

这里我使用了自定义的Context,从新定义一个MyContext的结构体,其中组合了标准库中的Context,即具有标准库Context的能力。

这里MyContext是做为数据载体在各个过滤器之间传递。没有用标准库的Context,采用自定义的Context主要是为了说明咱们能够根据须要扩展MyContext,经过扩展MyContext添加任何咱们须要的参数。这里添加的是一个map键值对。咱们能够将每一个过滤器处理的结果存入这个map中,再传递到下一个过滤器。

myContext := MyContext{Context: context.TODO(), KeyValue: map[string]bool{"key": false}}
复制代码

上面的等价写法还能够是

ctx := context.TODO()
myContext := context.WithValue(ctx, "key", "value")
复制代码

这里充分利用了Context的WithValue的用法,有兴趣能够去看下,这是Context建立map键值对的方式。

充分利用Go的type的特性

type FilterFunc func(*MyContext) bool 复制代码

前面在使用过滤的场景特种中提到,过滤器的入参和返回值都是同样的。因此这里咱们利用Go的type特性,将这种过滤器函数定义为一个变量FilterFunc

这一特性对于精简代码起到了关键性的做用。且看

cf := CombinedFilter(&myContext, F1, F2, F3);

func CombinedFilter(ctx *MyContext, ff ...FilterFunc) *CombinedFunc {
	return &CombinedFunc{
		CF:    ff,
		MyCtx: ctx,
	}
}
复制代码

由于这里的F一、F2和F3都有相同入参和返回值,因此抽象为FilterFunc,并使用变长参数的FilterFunc统一接收。

CombinedFilter不只能够加F一、F2和F3,后面还能够有F四、F5...

type FilterFuncChain []FilterFunc
复制代码

这里的抽象也是一样的道理。

若是以前写过Java,这里是否是已经看到了Filter接口的影子。其实这里的FilterFunc能够等价于Java里面的Filter接口,接口是一种约束一种契约,Filter定义了若是要实现该接口必需要实现接口定义的方法。

package javax.servlet;

import java.io.IOException;

/** * A FilterChain is an object provided by the servlet container to the developer * giving a view into the invocation chain of a filtered request for a resource. * Filters use the FilterChain to invoke the next filter in the chain, or if the * calling filter is the last filter in the chain, to invoke the resource at the * end of the chain. * * @see Filter * @since Servlet 2.3 **/

public interface FilterChain {

    /** * Causes the next filter in the chain to be invoked, or if the calling * filter is the last filter in the chain, causes the resource at the end of * the chain to be invoked. * * @param request * the request to pass along the chain. * @param response * the response to pass along the chain. * * @throws IOException if an I/O error occurs during the processing of the * request * @throws ServletException if the processing fails for any other reason * @since 2.3 */
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;

}
复制代码

遍历执行过滤器

由于有了上面的特性,咱们才能将这些过滤器存入切片而后依次执行,以下

func DoFilter(cf *CombinedFunc) {
	for _, f := range cf.CF {
		res := f(cf.MyCtx)
		fmt.Println("result:", res)
		if res == false {
			fmt.Println("stopped")
			return
		}
	}
}
复制代码

在执行的过程当中,若是咱们发现若是返回值为false,则表示没有经过某个过滤器校验,则退出也不会继续执行后面的过滤器。

继续改进

既然MyContext中的map集合能够存储各个Filter的执行状况,并且能够在各个过滤器之间传递,咱们甚至能够省略FilterFunc函数的返回值,改进后以下

package main

import (
	"context"
	"fmt"
)

type MyContext struct {
	context.Context
	KeyValue map[string]bool
}

type FilterFunc func(*MyContext) type FilterFuncChain []FilterFunc type CombinedFunc struct {
	CF    FilterFuncChain
	MyCtx *MyContext
}

func main() {
	myContext := MyContext{Context: context.TODO(), KeyValue: map[string]bool{"key": false}}

	cf := CombinedFilter(&myContext, F1, F2, F3);
	DoFilter(cf)
}

func DoFilter(cf *CombinedFunc) {
	for _, f := range cf.CF {
		f(cf.MyCtx)
		continued :=  cf.MyCtx.KeyValue["key"]
		fmt.Println("result:", continued)
		if !continued {
			fmt.Println("stopped")
			return
		}
	}
}

func CombinedFilter(ctx *MyContext, ff ...FilterFunc) *CombinedFunc {
	return &CombinedFunc{
		CF:    ff,
		MyCtx: ctx,
	}
}

func F1(ctx *MyContext) {
	ctx.KeyValue["key"] = true
	fmt.Println(ctx.KeyValue["key"])
	//return ctx.KeyValue["key"]
}

func F2(ctx *MyContext) {
	ctx.KeyValue["key"] = false
	fmt.Println(ctx.KeyValue["key"])
	//return ctx.KeyValue["key"]
}

func F3(ctx *MyContext) {
	ctx.KeyValue["key"] = false
	fmt.Println(ctx.KeyValue["key"])
	//return ctx.KeyValue["key"]
}

复制代码

总结

基于Go语言造轮子实现一个过滤器的雏形,经过实现一个相对优雅可扩展的过滤器熟悉了type的用法,Context.WithValue的做用。

相关文章
相关标签/搜索