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