前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring cloud zuul的SendResponseFilter做了什么

Spring cloud zuul的SendResponseFilter做了什么

作者头像
java达人
发布于 2019-03-08 06:08:11
发布于 2019-03-08 06:08:11
2.5K00
代码可运行
举报
文章被收录于专栏:java达人java达人
运行总次数:0
代码可运行

源码调试web容器:tomcat

Spring cloud zull 的SendResponseFilter主要工作是将代理请求获取的reponse写入当前response,发送回客户端。以下是源代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class SendResponseFilter extends ZuulFilter {

   private static final Log log = LogFactory.getLog(SendResponseFilter.class);

   private static DynamicBooleanProperty INCLUDE_DEBUG_HEADER = DynamicPropertyFactory
         .getInstance()
         .getBooleanProperty(ZuulConstants.ZUUL_INCLUDE_DEBUG_HEADER, false);

   private static DynamicIntProperty INITIAL_STREAM_BUFFER_SIZE = DynamicPropertyFactory
         .getInstance()
         .getIntProperty(ZuulConstants.ZUUL_INITIAL_STREAM_BUFFER_SIZE, 8192);

   private static DynamicBooleanProperty SET_CONTENT_LENGTH = DynamicPropertyFactory
         .getInstance()
         .getBooleanProperty(ZuulConstants.ZUUL_SET_CONTENT_LENGTH, false);
   private boolean useServlet31 = true;

   public SendResponseFilter() {
      super();
      // To support Servlet API 3.0.1 we need to check if setcontentLengthLong exists
      try {
         HttpServletResponse.class.getMethod("setContentLengthLong");
      } catch(NoSuchMethodException e) {
         useServlet31 = false;
      }
   }

   private ThreadLocal<byte[]> buffers = new ThreadLocal<byte[]>() {
      @Override
      protected byte[] initialValue() {
         return new byte[INITIAL_STREAM_BUFFER_SIZE.get()];
      }
   };

   @Override
   public String filterType() {
      return POST_TYPE;
   }

   @Override
   public int filterOrder() {
      return SEND_RESPONSE_FILTER_ORDER;
   }

   @Override
   public boolean shouldFilter() {
      RequestContext context = RequestContext.getCurrentContext();
      return context.getThrowable() == null
            && (!context.getZuulResponseHeaders().isEmpty()
               || context.getResponseDataStream() != null
               || context.getResponseBody() != null);
   }

   @Override
   public Object run() {
      try {
         addResponseHeaders();
         writeResponse();
      }
      catch (Exception ex) {
         ReflectionUtils.rethrowRuntimeException(ex);
      }
      return null;
   }

   private void writeResponse() throws Exception {
      RequestContext context = RequestContext.getCurrentContext();
      // there is no body to send
      if (context.getResponseBody() == null
            && context.getResponseDataStream() == null) {
         return;
      }
      HttpServletResponse servletResponse = context.getResponse();
      if (servletResponse.getCharacterEncoding() == null) { // only set if not set
         servletResponse.setCharacterEncoding("UTF-8");
      }
      OutputStream outStream = servletResponse.getOutputStream();
      InputStream is = null;
      try {
         if (RequestContext.getCurrentContext().getResponseBody() != null) {
            String body = RequestContext.getCurrentContext().getResponseBody();
            writeResponse(
                  new ByteArrayInputStream(
                        body.getBytes(servletResponse.getCharacterEncoding())),
                  outStream);
            return;
         }
         boolean isGzipRequested = false;
         final String requestEncoding = context.getRequest()
               .getHeader(ZuulHeaders.ACCEPT_ENCODING);

         if (requestEncoding != null
               && HTTPRequestUtils.getInstance().isGzipped(requestEncoding)) {
            isGzipRequested = true;
         }
         is = context.getResponseDataStream();
         InputStream inputStream = is;
         if (is != null) {
            if (context.sendZuulResponse()) {
               // if origin response is gzipped, and client has not requested gzip,
               // decompress stream
               // before sending to client
               // else, stream gzip directly to client
               if (context.getResponseGZipped() && !isGzipRequested) {
                  // If origin tell it's GZipped but the content is ZERO bytes,
                  // don't try to uncompress
                  final Long len = context.getOriginContentLength();
                  if (len == null || len > 0) {
                     try {
                        inputStream = new GZIPInputStream(is);
                     }
                     catch (java.util.zip.ZipException ex) {
                        log.debug(
                              "gzip expected but not "
                                    + "received assuming unencoded response "
                                    + RequestContext.getCurrentContext()
                                    .getRequest().getRequestURL()
                                    .toString());
                        inputStream = is;
                     }
                  }
                  else {
                     // Already done : inputStream = is;
                  }
               }
               else if (context.getResponseGZipped() && isGzipRequested) {
                  servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING, "gzip");
               }
               writeResponse(inputStream, outStream);
            }
         }
      }
      finally {
         /**
         * Closing the wrapping InputStream itself has no effect on closing the underlying tcp connection since it's a wrapped stream. I guess for http
         * keep-alive. When closing the wrapping stream it tries to reach the end of the current request, which is impossible for infinite http streams. So
         * instead of closing the InputStream we close the HTTP response.
         *
         * @author Johannes Edmeier
         */
         try {
            Object zuulResponse = RequestContext.getCurrentContext()
                  .get("zuulResponse");
            if (zuulResponse instanceof Closeable) {
               ((Closeable) zuulResponse).close();
            }
            outStream.flush();
            // The container will close the stream for us
         }
         catch (IOException ex) {
         log.warn("Error while sending response to client: " + ex.getMessage());
         }
      }
   }

   private void writeResponse(InputStream zin, OutputStream out) throws Exception {
      byte[] bytes = buffers.get();
      int bytesRead = -1;
      while ((bytesRead = zin.read(bytes)) != -1) {
         out.write(bytes, 0, bytesRead);
      }
   }

   private void addResponseHeaders() {
      RequestContext context = RequestContext.getCurrentContext();
      HttpServletResponse servletResponse = context.getResponse();
      if (INCLUDE_DEBUG_HEADER.get()) {
         @SuppressWarnings("unchecked")
         List<String> rd = (List<String>) context.get(ROUTING_DEBUG_KEY);
         if (rd != null) {
            StringBuilder debugHeader = new StringBuilder();
            for (String it : rd) {
               debugHeader.append("[[[" + it + "]]]");
            }
            servletResponse.addHeader(X_ZUUL_DEBUG_HEADER, debugHeader.toString());
         }
      }
      List<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders();
      if (zuulResponseHeaders != null) {
         for (Pair<String, String> it : zuulResponseHeaders) {
            servletResponse.addHeader(it.first(), it.second());
         }
      }
      // Only inserts Content-Length if origin provides it and origin response is not
      // gzipped
      if (SET_CONTENT_LENGTH.get()) {
         Long contentLength = context.getOriginContentLength();
         if ( contentLength != null && !context.getResponseGZipped()) {
            if(useServlet31) {
               servletResponse.setContentLengthLong(contentLength);
            } else {
               //Try and set some kind of content length if we can safely convert the Long to an int
               if (isLongSafe(contentLength)) {
                  servletResponse.setContentLength(contentLength.intValue());
               }
            }
         }
      }
   }

   private boolean isLongSafe(long value) {
      return value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE;
   }

}

filterType:post

filterOrder:1000,是post阶段最后执行的过滤器

shouldFilter:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean shouldFilter() {
      RequestContext context = RequestContext.getCurrentContext();
      return context.getThrowable() == null
            && (!context.getZuulResponseHeaders().isEmpty()
               || context.getResponseDataStream() != null
               || context.getResponseBody() != null);
   }

该过滤器会检查请求上下文中是否包含请求响应相关的头信息(zuulResponseHeaders)、响应数据流(responseDataStream)或是响应体(responseBody),只有在包含它们其中一个且没有异常抛出的时候才会执行处理逻辑。

run:

主要做了两件事

1、添加响应头

addResponseHeaders();

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void addResponseHeaders() {
      RequestContext context = RequestContext.getCurrentContext();
      HttpServletResponse servletResponse = context.getResponse();
      if (INCLUDE_DEBUG_HEADER.get()) {
         @SuppressWarnings("unchecked")
         List<String> rd = (List<String>) context.get(ROUTING_DEBUG_KEY);
         if (rd != null) {
            StringBuilder debugHeader = new StringBuilder();
            for (String it : rd) {
               debugHeader.append("[[[" + it + "]]]");
            }
            servletResponse.addHeader(X_ZUUL_DEBUG_HEADER, debugHeader.toString());
         }
      }
      List<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders();
      if (zuulResponseHeaders != null) {
         for (Pair<String, String> it : zuulResponseHeaders) {
            servletResponse.addHeader(it.first(), it.second());
         }
      }
      // Only inserts Content-Length if origin provides it and origin response is not
      // gzipped
      if (SET_CONTENT_LENGTH.get()) {
         Long contentLength = context.getOriginContentLength();
         if ( contentLength != null && !context.getResponseGZipped()) {
            if(useServlet31) {
               servletResponse.setContentLengthLong(contentLength);
            } else {
               //Try and set some kind of content length if we can safely convert the Long to an int
               if (isLongSafe(contentLength)) {
                  servletResponse.setContentLength(contentLength.intValue());
               }
            }
         }
      }
   }

ZUULINCLUDEDEBUG_HEADER主要涉及到zuul.include-debug-header的配置,如果为true,且请求中带有debug=true或zuul.debug.request配置为true,则 debug信息将会添加到X-Zuul-Debug-Header响应header中,你可以将它作为信息返回给网关调用方,也可以通过调用com.netflix.zuul.context.Debug.getRoutingDebug().自己打印出来,方法可参考:

https://stackoverflow.com/questions/43910195/where-can-i-find-the-debug-information-in-zuul

接着是把zuulResponseHeaders放入到当前response中,那zuulResponseHeaders原先是怎么放到context中的呢,以我们使用SimpleHostRoutingFilter为例子: 其内部执行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
      headers, params, requestEntity);
setResponse(response);

sendResponse:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void setResponse(HttpResponse response) throws IOException {
   RequestContext.getCurrentContext().set("zuulResponse", response);
   this.helper.setResponse(response.getStatusLine().getStatusCode(),
         response.getEntity() == null ? null : response.getEntity().getContent(),
         revertHeaders(response.getAllHeaders()));
}

this.helper.setResponse:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void setResponse(int status, InputStream entity,
      MultiValueMap<String, String> headers) throws IOException {
   RequestContext context = RequestContext.getCurrentContext();
   context.setResponseStatusCode(status);
   if (entity != null) {
      context.setResponseDataStream(entity);
   }

   HttpHeaders httpHeaders = new HttpHeaders();
   for (Entry<String, List<String>> header : headers.entrySet()) {
      List<String> values = header.getValue();
      for (String value : values) {
         httpHeaders.add(header.getKey(), value);
      }
   }
   boolean isOriginResponseGzipped = false;
   if (httpHeaders.containsKey(CONTENT_ENCODING)) {
      List<String> collection = httpHeaders.get(CONTENT_ENCODING);
      for (String header : collection) {
         if (HTTPRequestUtils.getInstance().isGzipped(header)) {
            isOriginResponseGzipped = true;
            break;
         }
      }
   }
   context.setResponseGZipped(isOriginResponseGzipped);

   for (Entry<String, List<String>> header : headers.entrySet()) {
      String name = header.getKey();
      for (String value : header.getValue()) {
         context.addOriginResponseHeader(name, value);
         if (name.equalsIgnoreCase(CONTENT_LENGTH)) {
            context.setOriginContentLength(value);
         }
         if (isIncludedHeader(name)) {
            context.addZuulResponseHeader(name, value);
         }
      }
   }
}

如上述代码所示,根据转发时获取的origin response,将相关信息设置到context中,以备后续使用,这些信息包括:responseStatusCode(响应状态码)、responseDataStream(响应输入流)、responseGZipped(原始响应是否Gzipped)、originResponseHeaders(原始响应头)、originContentLength(原始响应实体长度)、zuulResponseHeaders(从originResponseHeaders中过滤了部分header信息,具体看下面isIncludedHeader方法)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean isIncludedHeader(String headerName) {
   String name = headerName.toLowerCase();
   RequestContext ctx = RequestContext.getCurrentContext();
   if (ctx.containsKey(IGNORED_HEADERS)) {
      Object object = ctx.get(IGNORED_HEADERS);
      if (object instanceof Collection && ((Collection<?>) object).contains(name)) {
         return false;
      }
   }
   switch (name) {
   case "host":
   case "connection":
   case "content-length":
   case "content-encoding":
   case "server":
   case "transfer-encoding":
   case "x-application-context":
      return false;
   default:
      return true;
   }
}

为什么要过滤这些以上这些字段呢,因为这些都是目标主机特定于代理网关的http头,而不是代理网关特定于客户端的http头,像host,serve、connection、content-encoding等,代理网关可能根据客户端请求信息,当前网络状态设置不一样的值。有些像content-length,后面作了特别处理。

最后是设置当前响应的content-length,SETCONTENTLENGTH对应的配置项是zuul.set-content-length,如果是true,并且目标主机提供Content-Length而且响应没有被gzipped压缩时,才插入Content-Length。为什么gzipped压缩时不传入呢,通过后面的run方法内容可知,如果原始response是经过gzip压缩,而网关client没有要求gzip压缩,则在发送给客户端之前先解压响应流,因此此时一旦设置了Content-Length, 便会导致实际的传输长度比Content-Length要长的情况,导致截断。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// if origin response is gzipped, and client has not requested gzip,
               // decompress stream
               // before sending to client
               // else, stream gzip directly to client
               if (context.getResponseGZipped() && !isGzipRequested) {
                  // If origin tell it's GZipped but the content is ZERO bytes,
                  // don't try to uncompress
                  final Long len = context.getOriginContentLength();
                  if (len == null || len > 0) {
                     try {
                        inputStream = new GZIPInputStream(is);
                     }
                     catch (java.util.zip.ZipException ex) {
                        log.debug(
                              "gzip expected but not "
                                    + "received assuming unencoded response "
                                    + RequestContext.getCurrentContext()
                                    .getRequest().getRequestURL()
                                    .toString());
                        inputStream = is;
                     }
                  }
                  else {
                     // Already done : inputStream = is;
                  }
               }
               else if (context.getResponseGZipped() && isGzipRequested) {
                  servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING, "gzip");
               }

可能有些童鞋不大清楚content-length的作用,在此作一个普及:

Content-Length指示出报文中的实体主体的字节大小,它主要为了检测出服务器崩溃导致的报文截尾,并对持久连接的多个报文进行正确分段。 如果主体进行了内容编码,它指示编码后的主体的字节长度。如果不是持久连接,那么不需要知道它正在读取的主体的长度,只需要读到服务器关闭主体连接为止,如果是持久连接,在服务器写主体前,必须知道它的大小并在Content-length中发送,若服务器动态创建内容,则发送前无法知道主体的长度,那怎么办呢?这时可以用transfer-encoding替代,在头部加入 Transfer-Encoding: chunked 之后,就代表这个报文采用了分块编码。这时,报文中的实体需要改为用一系列分块来传输。每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的 CRLF(\r\n),也不包括分块数据结尾的 CRLF。最后一个分块长度值必须为 0,对应的分块数据没有内容,表示实体结束。

(详细可参考https://imququ.com/post/transfer-encoding-header-in-http.html)

那假如SETCONTENTLENGTH为false,既然SendResponseFilter中没看到设置Content-Length的代码,也没设置 transfer-encoding,那么是在哪里作处理的呢,调试代码可知,是在org.apache.pache.catalina.connector.ResponseFacade中处理。

查看org.apache.catalina.connector.CoyoteOutputStream. close源码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
public void close() throws IOException {
    ob.close();
}

ob即outputbuffer,close时内部将执行doFlush:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected void doFlush(boolean realFlush) throws IOException {

    if (suspended) {
        return;
    }

    try {
        doFlush = true;
        if (initial) {
            coyoteResponse.sendHeaders();
            initial = false;
        }
        if (cb.remaining() > 0) {
            flushCharBuffer();
        }
        if (bb.remaining() > 0) {
            flushByteBuffer();
        }
    } finally {
        doFlush = false;
    }

    if (realFlush) {
        coyoteResponse.action(ActionCode.CLIENT_FLUSH, null);
        // If some exception occurred earlier, or if some IOE occurred
        // here, notify the servlet with an IOE
        if (coyoteResponse.isExceptionPresent()) {
            throw new ClientAbortException(coyoteResponse.getErrorException());
        }
    }

}

Http11Processor的prepareResponse方法:

如果存在contentLength,则设置;如果没有,且响应码支持拥有实体,并且使用的是HTTP 1.1,持久连接(Connection: keep-alive),那么我们将使用Transfer-Encoding:chunk。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
protected final void prepareResponse() throws IOException {

    boolean entityBody = true;
    contentDelimitation = false;

    OutputFilter[] outputFilters = outputBuffer.getFilters();

    if (http09 == true) {
        // HTTP/0.9
        outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
        outputBuffer.commit();
        return;
    }

    int statusCode = response.getStatus();
    if (statusCode < 200 || statusCode == 204 || statusCode == 205 ||
            statusCode == 304) {
        // No entity body
        outputBuffer.addActiveFilter
            (outputFilters[Constants.VOID_FILTER]);
        entityBody = false;
        contentDelimitation = true;
        if (statusCode == 205) {
            // RFC 7231 requires the server to explicitly signal an empty
            // response in this case
            response.setContentLength(0);
        } else {
            response.setContentLength(-1);
        }
    }

    MessageBytes methodMB = request.method();
    if (methodMB.equals("HEAD")) {
        // No entity body
        outputBuffer.addActiveFilter
            (outputFilters[Constants.VOID_FILTER]);
        contentDelimitation = true;
    }

    // Sendfile support
    if (endpoint.getUseSendfile()) {
        prepareSendfile(outputFilters);
    }

    // Check for compression
    boolean isCompressible = false;
    boolean useCompression = false;
    if (entityBody && (compressionLevel > 0) && sendfileData == null) {
        isCompressible = isCompressible();
        if (isCompressible) {
            useCompression = useCompression();
        }
        // Change content-length to -1 to force chunking
        if (useCompression) {
            response.setContentLength(-1);
        }
    }

    MimeHeaders headers = response.getMimeHeaders();
    // A SC_NO_CONTENT response may include entity headers
    if (entityBody || statusCode == HttpServletResponse.SC_NO_CONTENT) {
        String contentType = response.getContentType();
        if (contentType != null) {
            headers.setValue("Content-Type").setString(contentType);
        }
        String contentLanguage = response.getContentLanguage();
        if (contentLanguage != null) {
            headers.setValue("Content-Language")
                .setString(contentLanguage);
        }
    }

    long contentLength = response.getContentLengthLong();
    boolean connectionClosePresent = false;
    if (contentLength != -1) {
        headers.setValue("Content-Length").setLong(contentLength);
        outputBuffer.addActiveFilter
            (outputFilters[Constants.IDENTITY_FILTER]);
        contentDelimitation = true;
    } else {
        // If the response code supports an entity body and we're on
        // HTTP 1.1 then we chunk unless we have a Connection: close header
        connectionClosePresent = isConnectionClose(headers);
        if (entityBody && http11 && !connectionClosePresent) {
            outputBuffer.addActiveFilter
                (outputFilters[Constants.CHUNKED_FILTER]);
            contentDelimitation = true;
            headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
        } else {
            outputBuffer.addActiveFilter
                (outputFilters[Constants.IDENTITY_FILTER]);
        }
    }

    if (useCompression) {
        outputBuffer.addActiveFilter(outputFilters[Constants.GZIP_FILTER]);
        headers.setValue("Content-Encoding").setString("gzip");
    }
    // If it might be compressed, set the Vary header
    if (isCompressible) {
        // Make Proxies happy via Vary (from mod_deflate)
        MessageBytes vary = headers.getValue("Vary");
        if (vary == null) {
            // Add a new Vary header
            headers.setValue("Vary").setString("Accept-Encoding");
        } else if (vary.equals("*")) {
            // No action required
        } else {
            // Merge into current header
            headers.setValue("Vary").setString(
                    vary.getString() + ",Accept-Encoding");
        }
    }

    // Add date header unless application has already set one (e.g. in a
    // Caching Filter)
    if (headers.getValue("Date") == null) {
        headers.addValue("Date").setString(
                FastHttpDateFormat.getCurrentDate());
    }

    // FIXME: Add transfer encoding header

    if ((entityBody) && (!contentDelimitation)) {
        // Mark as close the connection after the request, and add the
        // connection: close header
        keepAlive = false;
    }

    // This may disabled keep-alive to check before working out the
    // Connection header.
    checkExpectationAndResponseStatus();

    // If we know that the request is bad this early, add the
    // Connection: close header.
    if (keepAlive && statusDropsConnection(statusCode)) {
        keepAlive = false;
    }
    if (!keepAlive) {
        // Avoid adding the close header twice
        if (!connectionClosePresent) {
            headers.addValue(Constants.CONNECTION).setString(
                    Constants.CLOSE);
        }
    } else if (!http11 && !getErrorState().isError()) {
        headers.addValue(Constants.CONNECTION).setString(Constants.KEEPALIVE);
    }

    // Add server header
    if (server == null) {
        if (serverRemoveAppProvidedValues) {
            headers.removeHeader("server");
        }
    } else {
        // server always overrides anything the app might set
        headers.setValue("Server").setString(server);
    }

    // Build the response header
    try {
        outputBuffer.sendStatus();

        int size = headers.size();
        for (int i = 0; i < size; i++) {
            outputBuffer.sendHeader(headers.getName(i), headers.getValue(i));
        }
        outputBuffer.endHeaders();
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        // If something goes wrong, reset the header buffer so the error
        // response can be written instead.
        outputBuffer.resetHeaderBuffer();
        throw t;
    }

    outputBuffer.commit();
}

private static boolean isConnectionClose(MimeHeaders headers) {
    MessageBytes connection = headers.getValue(Constants.CONNECTION);
    if (connection == null) {
        return false;
    }
    return connection.equals(Constants.CLOSE);
}

private void prepareSendfile(OutputFilter[] outputFilters) {
    String fileName = (String) request.getAttribute(
            org.apache.coyote.Constants.SENDFILE_FILENAME_ATTR);
    if (fileName == null) {
        sendfileData = null;
    } else {
        // No entity body sent here
        outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
        contentDelimitation = true;
        long pos = ((Long) request.getAttribute(
                org.apache.coyote.Constants.SENDFILE_FILE_START_ATTR)).longValue();
        long end = ((Long) request.getAttribute(
                org.apache.coyote.Constants.SENDFILE_FILE_END_ATTR)).longValue();
        sendfileData = socketWrapper.createSendfileData(fileName, pos, end - pos);
    }
}

2、写入响应内容

writeResponse():

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void writeResponse() throws Exception {
      RequestContext context = RequestContext.getCurrentContext();
      // there is no body to send
      if (context.getResponseBody() == null
            && context.getResponseDataStream() == null) {
         return;
      }
      HttpServletResponse servletResponse = context.getResponse();
      if (servletResponse.getCharacterEncoding() == null) { // only set if not set
         servletResponse.setCharacterEncoding("UTF-8");
      }
      OutputStream outStream = servletResponse.getOutputStream();
      InputStream is = null;
      try {
         if (RequestContext.getCurrentContext().getResponseBody() != null) {
            String body = RequestContext.getCurrentContext().getResponseBody();
            writeResponse(
                  new ByteArrayInputStream(
                        body.getBytes(servletResponse.getCharacterEncoding())),
                  outStream);
            return;
         }
         boolean isGzipRequested = false;
         final String requestEncoding = context.getRequest()
               .getHeader(ZuulHeaders.ACCEPT_ENCODING);

         if (requestEncoding != null
               && HTTPRequestUtils.getInstance().isGzipped(requestEncoding)) {
            isGzipRequested = true;
         }
         is = context.getResponseDataStream();
         InputStream inputStream = is;
         if (is != null) {
            if (context.sendZuulResponse()) {
               // if origin response is gzipped, and client has not requested gzip,
               // decompress stream
               // before sending to client
               // else, stream gzip directly to client
               if (context.getResponseGZipped() && !isGzipRequested) {
                  // If origin tell it's GZipped but the content is ZERO bytes,
                  // don't try to uncompress
                  final Long len = context.getOriginContentLength();
                  if (len == null || len > 0) {
                     try {
                        inputStream = new GZIPInputStream(is);
                     }
                     catch (java.util.zip.ZipException ex) {
                        log.debug(
                              "gzip expected but not "
                                    + "received assuming unencoded response "
                                    + RequestContext.getCurrentContext()
                                    .getRequest().getRequestURL()
                                    .toString());
                        inputStream = is;
                     }
                  }
                  else {
                     // Already done : inputStream = is;
                  }
               }
               else if (context.getResponseGZipped() && isGzipRequested) {
                  servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING, "gzip");
               }
               writeResponse(inputStream, outStream);
            }
         }
      }
      finally {
         /**
         * Closing the wrapping InputStream itself has no effect on closing the underlying tcp connection since it's a wrapped stream. I guess for http
         * keep-alive. When closing the wrapping stream it tries to reach the end of the current request, which is impossible for infinite http streams. So
         * instead of closing the InputStream we close the HTTP response.
         *
         * @author Johannes Edmeier
         */
         try {
            Object zuulResponse = RequestContext.getCurrentContext()
                  .get("zuulResponse");
            if (zuulResponse instanceof Closeable) {
               ((Closeable) zuulResponse).close();
            }
            outStream.flush();
            // The container will close the stream for us
         }
         catch (IOException ex) {
         log.warn("Error while sending response to client: " + ex.getMessage());
         }
      }
   }

   private void writeResponse(InputStream zin, OutputStream out) throws Exception {
      byte[] bytes = buffers.get();
      int bytesRead = -1;
      while ((bytesRead = zin.read(bytes)) != -1) {
         out.write(bytes, 0, bytesRead);
      }
   }

由SimpleHostRoutingFilter可知,原始reponse获取的时候已将reponseBody或responseDataStream放入context中,它先判断是否存在responseBody,存在即写入输出流,直接返回,否则将responseDataStream写入,这里responseDataStream可能是一个压缩流,如果原始response是经过gzip压缩,而网关client没有要求gzip压缩,则在发送给客户端之前先解压响应流,否则就直接输出,并设置Content-Encoding:gzip头。

为了释放系统资源,前面通过route过滤器获取的reponse,即zuulResponse需要关闭,关闭其被包装InputStream也许只是为了保持http长连接,本身对底层tcp连接的关闭不起作用。因此,我们关闭HTTP响应而不是关闭InputStream。

先写到这里,有什么需要补充的请留言。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-12-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java达人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
深入理解Zuul之源码解析
Zuul 架构图 在zuul中, 整个请求的过程是这样的,首先将请求给zuulservlet处理,zuulservlet中有一个zuulRunner对象,该对象中初始化了RequestContext:
方志朋
2017/12/29
1.3K0
深入理解Zuul之源码解析
Springcloud之zuul的过滤头部
Springcloud的版本是Greenwich.SR2,Springboot版本是2.1.6.release.
克虏伯
2019/07/08
1.5K0
Spring cloud zuul为什么需要FormBodyWrapperFilter
Spring cloud zuul里面有一些核心过滤器,以前文章大致介绍了下各个过滤器的作用,武林外传—武三通的zuul之惑。这次重点讲解下FormBodyWrapperFilter,先贴出完整源码:
java达人
2018/12/28
2.1K0
Java | zuul 1.x 是如何实现请求转发的
简介实现逻辑源码基于 Servlet 的请求转发ZuulServlet 核心代码ZuulRunner 核心代码RequestContext 核心代码FilterProcessor 核心代码在官方示例中,提供了两个简单的 Route 的 ZuulFilter 实现总结参考
双鬼带单
2021/03/19
7950
Java |  zuul 1.x 是如何实现请求转发的
Spring Cloud Zuul记录接口响应数据
系统在生产环境出现问题时,排查问题最好的方式就是查看日志了,日志的记录尽量详细,这样你才能快速定位问题。
猿天地
2018/08/17
1.4K0
【原创】自己动手写一个服务网关
引言 什么是网关?为什么需要使用网关? 如图所示,在不使用网关的情况下,我们的服务是直接暴露给服务调用方。当调用方增多,势必需要添加定制化访问权限、校验等逻辑。当添加API网关后,再第三方调用端和服
Java高级架构
2018/07/20
9700
Spring Cloud Zuul 那些你不知道的功能点
当@EnableZuulProxy与Spring Boot Actuator配合使用时,Zuul会暴露一个路由管理端点/routes。
猿天地
2019/07/10
9170
Spring Cloud Zuul 那些你不知道的功能点
Spring Cloud Zuul 那些你不知道的功能点
当@EnableZuulProxy与Spring Boot Actuator配合使用时,Zuul会暴露一个路由管理端点/routes。
Bug开发工程师
2019/07/12
1.2K0
Spring Cloud Zuul 那些你不知道的功能点
Spring Cloud实战小贴士:Zuul统一异常处理(三)【Dalston版】
本篇作为《Spring Cloud微服务实战》一书关于Spring Cloud Zuul网关在Dalston版本对异常处理的补充。没有看过本书的读书也不要紧,可以先阅读我之前的两篇博文:《Spring Cloud实战小贴士:Zuul统一异常处理(一)》和《Spring Cloud实战小贴士:Zuul统一异常处理(二)》,这两篇文章都详细介绍和分析了Spring Cloud Zuul在过滤器设计中对异常处理的不足。同时,在这两篇文章中,也针对不足之处做了相应的解决方案。不过,这些方案都是基于Brixton版本
程序猿DD
2018/02/01
8330
Spring Cloud实战小贴士:Zuul统一异常处理(三)【Dalston版】
Springcloud之Zuul的RibbonRoutingFilter
    spring-cloud-netflix-zuul的版本是2.1.2.release.
克虏伯
2019/10/05
1.5K0
zuul源码分析-探究原生zuul的工作原理
最近在项目中使用了SpringCloud,基于Zuul搭建了一个提供加解密、鉴权等功能的网关服务。鉴于之前没怎么使用过Zuul,于是顺便仔细阅读了它的源码。实际上,Zuul原来提供的功能是很单一的:通过一个统一的Servlet入口(ZuulServlet,或者Filter入口,使用ZuulServletFilter)拦截所有的请求,然后通过内建的com.netflix.zuul.IZuulFilter链对请求做拦截和过滤处理。ZuulFilter和javax.servlet.Filter的原理相似,但是它们本质并不相同。javax.servlet.Filter在Web应用中是独立的组件,ZuulFilter是ZuulServlet处理请求时候调用的,后面会详细分析。
Throwable
2020/06/23
1.8K0
zuul源码分析-探究原生zuul的工作原理
Spring Cloud 之服务网关 Zuul (三) 灰度发布
常见的发布方式有灰度发布、蓝绿发布、金丝雀发布及 AB 发布等. 所谓灰度发布是指, 我们要发布版本了, 在不确定正确性的情况下, 我们选择先部分节点升级, 然后让一些特定的流量进入到这些新节点,完成测试后再全量发布. 灰度发布有多种方式, 本文主要介绍基于 Eureka 的元数据(metadata)的方式实现
芥末鱿鱼
2020/09/22
9420
Spring Cloud实战小贴士:Zuul处理Cookie和重定向
由于我们在之前所有的入门教程中,对于HTTP请求都采用了简单的接口实现。而实际使用过程中,我们的HTTP请求要复杂的多,比如当我们将Spring Cloud Zuul作为API网关接入网站类应用时,往往都会碰到下面这两个非常常见的问题: - 会话无法保持 - 重定向后的HOST错误 本文将帮助大家分析问题原因并给出解决这两个常见问题的方法。 会话保持问题 通过跟踪一个HTTP请求经过Zuul到具体服务,再到返回结果的全过程。我们很容易就能发现,在传递的过程中,HTTP请求头信息中的Cookie和Author
程序猿DD
2018/02/01
2.4K0
Spring Cloud实战小贴士:Zuul处理Cookie和重定向
SpringCloud之zuul源码解析 原
    zuul各版本实现存在一些微小的变化,总的实现思想未改变,以spring-cloud-netflix-core-1.3.6.RELEASE为例
用户2603479
2018/08/16
6660
Spring Cloud内置的Zuul过滤器详解
开端 Spring Cloud默认为Zuul编写并启用了一些过滤器,这些过滤器有什么作用呢?我们不妨按照@EnableZuulServer、@EnableZuulProxy两个注解进行展开,相信大家对这两个注解都不陌生(至少都见过吧)。如果觉得陌生也没有关系,可将@EnableZuulProxy简单理解为@EnableZuulServer的增强版。事实上,当Zuul与Eureka、Ribbon等组件配合使用时,@EnableZuulProxy是我们常用的注解。 在Spring Cloud的官方文档中,只说@
用户1516716
2018/04/02
7230
Spring Cloud内置的Zuul过滤器详解
zuul源码分析之Request生命周期管理
zuul是可以认为是一种API-Gateway。zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。其原理就是在zuul把Request route到源web-service的时候,处理一些逻辑,比如Authentication,Load Shedding等。 下图是zuul的核心框架。对于框架中的核心类将一一分析。
天涯泪小武
2021/12/09
5200
zuul源码分析之Request生命周期管理
spring cloud 学习(6) - zuul 微服务网关
微服务架构体系中,通常一个业务系统会有很多的微服务,比如:OrderService、ProductService、UserService...,为了让调用更简单,一般会在这些服务前端再封装一层,类似下
菩提树下的杨过
2018/01/18
1.5K0
spring cloud 学习(6) - zuul 微服务网关
Springcloud之zuul的zuulFilter
Springcloud的版本是Greenwich.SR2,Springboot版本是2.1.6.release.
克虏伯
2019/07/03
1.3K0
Spring Cloud Zuul 综合使用
从上图中可以看到,Zuul是我们整个系统的入口。当我们有参数校验的需求时,我们就可以利用Zuul的Pre过滤器,进行参数的校验。例如我现在希望请求都一律带上token参数,否则拒绝请求。在项目中创建一个filter包,在该包中新建一个TokenFilter劳累并继承ZuulFilter,代码如下:
端碗吹水
2020/09/23
4400
Spring Cloud Zuul 综合使用
Spring Cloud实战小贴士:Zuul统一异常处理(一)
在上一篇《Spring Cloud源码分析(四)Zuul:核心过滤器》一文中,我们详细介绍了Spring Cloud Zuul中自己实现的一些核心过滤器,以及这些过滤器在请求生命周期中的不同作用。我们会发现在这些核心过滤器中并没有实现error阶段的过滤器。那么这些过滤器可以用来做什么呢?接下来,本文将介绍如何利用error过滤器来实现统一的异常处理。 过滤器中抛出异常的问题 首先,我们可以来看看默认情况下,过滤器中抛出异常Spring Cloud Zuul会发生什么现象。我们创建一个pre类型的过滤器,并
程序猿DD
2018/02/01
1.2K0
相关推荐
深入理解Zuul之源码解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档