服务网关ZuulFilter过滤器

服务网关ZuulFilter过滤器

微服务中Zuul服务网关一共定义了四种类型的过滤器:

pre:在请求被路由(转发)之前调用

route:在路由(请求)转发时被调用

error:服务网关发生异常时被调用

post:在路由(转发)请求后调用

我在项目中用到了,pre/error/post三种类型,先记录一下

pre过滤器主要是用来校验各种信息的

import com.alibaba.fastjson.JSONObject;

import com.dkjk.gateway.context.ResponseBean;

import com.dkjk.gateway.domain.DockCompanyService;

import com.dkjk.gateway.domain.UserService;

import com.netflix.zuul.ZuulFilter;

import com.netflix.zuul.context.RequestContext;

import com.netflix.zuul.exception.ZuulException;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;

import org.springframework.stereotype.Component;

import org.springframework.web.bind.annotation.RequestMethod;

import javax.annotation.Resource;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

import java.io.PrintWriter;

/**

* @author: qjc

* @createTime: 2019/4/13 16:08

* @Description: 接口安全验证过滤器

*/

@Component

@Slf4j

public class ValidFilter extends ZuulFilter {

@Override

public String filterType() {

return "pre";

}

@Override

public int filterOrder() {

return 0;

}

@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;

}

@Override

public Object run() throws ZuulException {

RequestContext requestContext = RequestContext.getCurrentContext();

HttpServletRequest request = requestContext.getRequest();

String userToken = request.getHeader("apikey");

if (StringUtils.isBlank(userToken)) {

log.warn("apikey为空");

sendError(requestContext, 99001, "请传输参数apikey");

return null;

}

return null;

}

/**

* 发送错误消息

*

* @param requestContext

* @param status

* @param msg

*/

private void sendError(RequestContext requestContext, int status, String msg) {

//过滤该请求,不往下级服务转发,到此结束不进行路由

requestContext.setSendZuulResponse(false);

HttpServletResponse response = requestContext.getResponse();

response.setHeader("Content-type", "application/json;charset=UTF-8");

response.setCharacterEncoding("UTF-8");

PrintWriter pw = null;

try {

pw = response.getWriter();

pw.write(JSONObject.toJSONString(new ResponseBean(status, msg, null)));

} catch (IOException e) {

log.error(e.getMessage());

} finally {

pw.close();

}

}

}

使用PrintWriter响应给客户端,有时候会报异常

所以改为下面的方式响应给客户端

/**

* 发送错误消息

*

* @param requestContext

* @param status

* @param msg

*/

private void sendError(RequestContext requestContext, int status, String msg, String userToken) {

if (StringUtils.isNotBlank(userToken)) {

//释放锁

Boolean exists = redisUtil.exists(userToken, RedisKeyEnum.USER_ACCOUNT_LOCK.indexdb);

if (exists) {

Long del = redisUtil.del(RedisKeyEnum.USER_ACCOUNT_LOCK.indexdb, userToken);

System.err.println(del);

}

}

requestContext.setSendZuulResponse(false); //不对请求进行路由

requestContext.setResponseStatusCode(status);//设置返回状态码

requestContext.setResponseBody(JSONObject.toJSONString(new ResponseBean(status, msg, null)));//设置返回响应体

requestContext.getResponse().setContentType("application/json;charset=UTF-8");//设置返回响应体格式,可能会乱码

}

这种方式在POSTMan测试时会出现Could not get any response的错误,可以用浏览器,或者代码测试网关错误信息响应,如果正常响应则没问题。

同时也给了处理方式。

post过滤器可以在请求转发后获取请求信息和响应入库,或者日志记录

import com.alibaba.fastjson.JSON;

import com.netflix.zuul.ZuulFilter;

import com.netflix.zuul.context.RequestContext;

import com.netflix.zuul.exception.ZuulException;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.IOUtils;

import org.apache.commons.lang3.StringUtils;

import org.springframework.stereotype.Component;

import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStream;

import java.util.Enumeration;

import java.util.HashMap;

import java.util.Map;

/**

* @author: qjc

* @createTime: 2019/5/6 11:07

* @Description:

*/

@Component

@Slf4j

public class ResponseFilter extends ZuulFilter {

@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);

System.err.println(requestParams);

try {

String responseBoby = IOUtils.toString(stream);

RequestContext.getCurrentContext().setResponseBody(responseBoby);

} 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 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 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过滤器是在服务网关出现异常的时候起作用的

import com.alibaba.fastjson.JSONObject;

import com.dkjk.gateway.context.ResponseBean;

import com.netflix.zuul.ZuulFilter;

import com.netflix.zuul.context.RequestContext;

import com.netflix.zuul.exception.ZuulException;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Component;

import org.springframework.util.ReflectionUtils;

import javax.servlet.http.HttpServletResponse;

import java.io.PrintWriter;

/**

* @author: qjc

* @createTime: 2019/5/30 19:11

* @Description: 处理请求发生错误时过滤器

*/

@Component

@Slf4j

public class ErrorFilter extends ZuulFilter {

@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 != null && 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(new ResponseBean(99999, "系统出现异常", null)));

pw.close();

}

} catch (Exception ex) {

log.error("Exception filtering in custom error filter", ex);

ReflectionUtils.rethrowRuntimeException(ex);

}

return null;

}

}