系统在生产环境出现问题时,排查问题最好的方式就是查看日志了,日志的记录尽量详细,这样你才能快速定位问题。

    下面带大家学习如何在 Zuul 中输出请求响应的信息来辅助我们解决一些问题。

    熟悉 Zuul 的朋友都知道,Zuul 中有 4 种类型过滤器,每种都有特定的使用场景,要想记录响应数据,那么必须是在请求路由到了具体的服务之后,返回了才有数据,这种需求就适合用 post 过滤器来实现了。代码如下所示。

    1. HttpServletRequest req = (HttpServletRequest) RequestContext.getCurrentContext().getRequest();
    2. System.err.println("REQUEST:: " + req.getScheme() + " " + req.getRemoteAddr() + ":" + req.getRemotePort());
    3. StringBuilder params = new StringBuilder("?");
    4. // 获取URL参数
    5. Enumeration<String> names = req.getParameterNames();
    6. if (req.getMethod().equals("GET")) {
    7. while (names.hasMoreElements()) {
    8. String name = (String) names.nextElement();
    9. params.append(name);
    10. params.append("=");
    11. params.append(req.getParameter(name));
    12. params.append("&");
    13. }
    14. }
    15. if (params.length() > 0) {
    16. params.delete(params.length() - 1, params.length());
    17. }
    18. System.err.println(
    19. "REQUEST:: > " + req.getMethod() + " " + req.getRequestURI() + params + " " + req.getProtocol());
    20. Enumeration<String> headers = req.getHeaderNames();
    21. while (headers.hasMoreElements()) {
    22. String name = (String) headers.nextElement();
    23. String value = req.getHeader(name);
    24. System.err.println("REQUEST:: > " + name + ":" + value);
    25. }
    26. final RequestContext ctx = RequestContext.getCurrentContext();
    27. // 获取请求体参数
    28. if (!ctx.isChunkedRequestBody()) {
    29. ServletInputStream inp = null;
    30. try {
    31. inp = ctx.getRequest().getInputStream();
    32. String body = null;
    33. if (inp != null) {
    34. body = IOUtils.toString(inp);
    35. System.err.println("REQUEST:: > " + body);
    36. }
    37. } catch (IOException e) {
    38. e.printStackTrace();
    39. }
    40. }

    输出效果如图 1 所示。
    image.png
    获取响应内容的第一种方式,代码如下所示。

    1. try {
    2. Object zuulResponse = RequestContext.getCurrentContext().get("zuulResponse");
    3. if (zuulResponse != null) {
    4. RibbonHttpResponse resp = (RibbonHttpResponse) zuulResponse;
    5. String body = IOUtils.toString(resp.getBody());
    6. System.err.println("RESPONSE:: > " + body);
    7. resp.close();
    8. RequestContext.getCurrentContext().setResponseBody(body);
    9. }
    10. } catch (IOException e) {
    11. e.printStackTrace();
    12. }

    获取响应内容的第二种方式,代码如下所示。

    1. public static void main(String[] args) {
    2. InputStream stream = RequestContext.getCurrentContext().getResponseDataStream();
    3. try {
    4. if (stream != null) {
    5. String body = IOUtils.toString(stream);
    6. System.err.println("RESPONSE:: > " + body);
    7. RequestContext.getCurrentContext().setResponseBody(body);
    8. }
    9. } catch (IOException e) {
    10. e.printStackTrace();
    11. }
    12. }

    为什么上面两种方式可以取到响应内容?

    在 RibbonRoutingFilter 或者 SimpleHostRoutingFilter 中可以看到下面一段代码,代码如下所示。

    1. public Object run() {
    2. RequestContext context = RequestContext.getCurrentContext();
    3. this.helper.addIgnoredHeaders();
    4. try {
    5. RibbonCommandContext commandContext = buildCommandContext(context);
    6. ClientHttpResponse response = forward(commandContext);
    7. setResponse(response);
    8. return response;
    9. } catch (ZuulException ex) {
    10. throw new ZuulRuntimeException(ex);
    11. } catch (Exception ex) {
    12. throw new ZuulRuntimeException(ex);
    13. }
    14. }

    forward() 方法对服务调用,拿到响应结果,通过 setResponse() 方法进行响应的设置,代码如下所示。

    1. protected void setResponse(ClientHttpResponse resp) throws ClientException, IOException {
    2. RequestContext.getCurrentContext().set("zuulResponse", resp);
    3. this.helper.setResponse(resp.getStatusCode().value(), resp.getBody() == null ? null : resp.getBody(),
    4. resp.getHeaders());
    5. }

    上面第一行代码就可以解释我们的第一种获取的方法,这里直接把响应内容加到了 RequestContext 中。

    第二种方式的解释就在 helper.setResponse 的逻辑里面了,代码如下所示。

    1. public void setResponse(int status, InputStream entity, MultiValueMap<String, String> headers) throws IOException {
    2. RequestContext context = RequestContext.getCurrentContext();
    3. context.setResponseStatusCode(status);
    4. if (entity != null) {
    5. context.setResponseDataStream(entity);
    6. }
    7. // .....
    8. }