前言
大家好,一直以来我都本着用最通俗的话理解核心的知识点, 我认为所有的难点都离不开 基础知识 的铺垫。目前正在出一个SpringCloud长期系列教程,从入门到进阶, 篇幅会较多~
适合人群
- 有一定的Java基础
- 想尝试微服务开发
- 有SpringBoot开发基础
- 想学习或了解SpringCloud
- 想提高自己的同学
大佬可以绕过 ~
背景
如果你是一路看过来的,很高兴你能够耐心看完。之前带大家学了Springboot这门框架,熟练掌握了单体应用的开发,如今微服务开发盛行,对我们的技术要求也是越来越高,薪资也是令人兴奋。这个系列将会带大家学习SpringCloud微服务开发,我会带大家一步一步的入门,耐心看完你一定会有收获~
情景回顾
上期带大家一起认识了Zuul微服务网关以及带大家体验了它的常用配置,本期学习Zuul网关过滤器,我们一起来看一下吧~
PRE过滤器
本期代码依然沿用上期的模块。在实现之前,先给大家简单普及一下它的生命周期的各个阶段,一个有四个阶段 pre、post、route和error
-
PRE:PRE过滤器用于将请求路径与配置的路由规则进行匹配,以找到需要转发的目标地址,并做一些前置加工,比如请求的校验等; -
ROUTING:ROUTING过滤器用于将外部请求转发到具体服务实例上去; -
POST:POST过滤器用于将微服务的响应信息返回到客户端,这个过程种可以对返回数据进行加工处理; -
ERROR:上述的过程发生异常后将调用ERROR过滤器。ERROR过滤器捕获到异常后需要将异常信息返回给客户端,所以最终还是会调用POST过滤器。
下面我们就手动实现一个pre过滤器
@Component
public class PreZuulFilter extends ZuulFilter {
private final Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 对应Zuul生命周期的四个阶段:pre、post、route和error;
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器的优先级,数字越小,优先级越高;
* @return
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 方法返回boolean类型,true时表示是否执行该过滤器的run方法,false则表示不执行;
* @return
*/
@Override
public boolean shouldFilter() {
// 进行跨域请求的时候,并且请求头中有额外参数,比如token,客户端会先发送一个OPTIONS请求来探测后续需要发起的跨域POST请求是否安全可接受
// 所以这个请求就不需要拦截,下面是处理方式
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
if (request.getMethod().equals(RequestMethod.OPTIONS.name())) {
log.info("OPTIONS请求不做拦截操作");
return false;
}
return true;
}
/**
* 过滤器的过滤逻辑
* @return
*/
@Override
public Object run() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
String userToken = request.getHeader("apikey");
if (StringUtils.isBlank(userToken)) {
log.warn("apikey为空");
sendError(requestContext, 99001, "请传输参数apikey", null);
return null;
}
String host = request.getRemoteHost();
String method = request.getMethod();
String uri = request.getRequestURI();
log.info("请求URI:{},HTTP Method:{},请求IP:{}", uri, method, host);
return null;
}
/**
* 发送错误消息
* todo: 如果是重定向到本地 还是会正常返回对应的api数据,状态码会变成返回的状态码
* todo: 如果是转发到其它服务的, 会被拦截下来返回对应的错误信息
*
* @param requestContext
* @param status
* @param msg
*/
private void sendError(RequestContext requestContext, int status, String msg, String userToken) {
requestContext.setSendZuulResponse(false); //不对请求进行路由
requestContext.setResponseStatusCode(status);//设置返回状态码
requestContext.setResponseBody(JSONObject.toJSONString(msg));//设置返回响应体
requestContext.getResponse().setContentType("application/json;charset=UTF-8");//设置返回响应体格式,可能会乱码
}
}
我们要实现的逻辑是run()部分,这个是不是很像我们之前给大家讲的Shiro拦截器,我们可以借助这一过滤器实现自己的特定功能
POST 过滤器
- post过滤器可以在请求转发后获取请求信息和响应,这里给大家写一个例子,可以用来记录请求日志
@Component
public class PostZuulFilter extends ZuulFilter {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 2;
}
@Override
public boolean shouldFilter() {
// 进行跨域请求的时候,并且请求头中有额外参数,比如token,客户端会先发送一个OPTIONS请求来探测后续需要发起的跨域POST请求是否安全可接受
// 所以这个请求就不需要拦截,下面是处理方式
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
if (request.getMethod().equals(RequestMethod.OPTIONS.name())) {
log.info("OPTIONS请求不做拦截操作");
return false;
}
// 如果前面的拦截器不进行路由,那么后面的过滤器就没必要执行
if (!requestContext.sendZuulResponse()) {
return false;
}
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
InputStream stream = requestContext.getResponseDataStream();
if (stream == null) {
return null;
}
HttpServletRequest request = requestContext.getRequest();
String requestParams = getRequestParams(requestContext, request);
log.warn(requestParams);
try {
String responseBody = IOUtils.toString(stream);
RequestContext.getCurrentContext().setResponseBody(responseBody);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//获取请求参数,适用于POST请求/GET请求,以及参数拼接在URL后面的POST请求
private String getRequestParams(RequestContext requestContext, HttpServletRequest request) {
String requestParams = null;
String requestMethod = request.getMethod();
StringBuilder params = new StringBuilder();
Enumeration<String> names = request.getParameterNames();
if (requestMethod.equals("GET")) {
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
params.append(name);
params.append("=");
params.append(request.getParameter(name));
params.append("&");
}
requestParams = params.delete(params.length() - 1, params.length()).toString();
} else {
Map<String, String> res = new HashMap<>();
Enumeration<?> temp = request.getParameterNames();
if (null != temp) {
while (temp.hasMoreElements()) {
String en = (String) temp.nextElement();
String value = request.getParameter(en);
res.put(en, value);
}
requestParams = JSON.toJSONString(res);
}
if (StringUtils.isBlank(requestParams) || "{}".equals(requestParams)) {
BufferedReader br = null;
StringBuilder sb = new StringBuilder("");
try {
br = request.getReader();
String str;
while ((str = br.readLine()) != null) {
sb.append(str);
}
br.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != br) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
requestParams = sb.toString();
}
}
return requestParams;
}
}
Error 过滤器
- error过滤器是在服务网关出现异常的时候起作用的
@Component
public class ErrorZuulFilter extends ZuulFilter {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
//需要在默认的 SendErrorFilter 之前
return 5;
}
@Override
public boolean shouldFilter() {
// 只有在抛出异常时才会进行拦截
return RequestContext.getCurrentContext().containsKey("throwable");
}
@Override
public Object run() {
try {
RequestContext requestContext = RequestContext.getCurrentContext();
Object e = requestContext.get("throwable");
if (e instanceof ZuulException) {
ZuulException zuulException = (ZuulException) e;
// 删除该异常信息,不然在下一个过滤器中还会被执行处理
requestContext.remove("throwable");
// 响应给客户端信息
HttpServletResponse response = requestContext.getResponse();
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter pw = null;
pw = response.getWriter();
pw.write(JSONObject.toJSONString("系统出现异常"));
pw.close();
}
} catch (Exception ex) {
log.error("Exception filtering in custom error filter", ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
}
上面就是常用的过滤器,大家可以启动服务请求一下试试, 也可以借助它实现一些自己想到的小功能,或者整合一些其它的工具库
结束语
本期到这里就结束了, 总结一下,本节主要讲了常用的Zuul网关过滤器, 以及带大家实现了自定义的过滤器, 建议大家自己多去尝试 ~
下期预告
俗话说,没有仪表盘的车不敢开, 微服务也是如此,这么多的服务,我怎么知道它的链路调用关系,下期就给大家介绍一下它的链路追踪方案, 其实开源的产品比较多,比如ZipKin,jaeger, 这里给大家介绍jeager, 它是uber开源的,go语言写的,很轻量,方便集成,下期就带大家集成一下。 关注我,不迷路, 下期不见不散 ~
更文时间
- 工作日(周一 🍉 周五)
- 周末不更 ☀️
- 节假日不定时更