Spring - InterceptingClientHttpRequest

397 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


RestTemplate:本质上是整理参数,调用doExecute函数。
doExecute函数:
    1. 创建请求(ClientHttpRequest)
    2. 请求执行(ClientHttpRequest.execute())
    3. 处理响应(ClientHttpResponse)

InterceptingClientHttpRequest

image.png

AbstractClientHttpRequest

提供文件头属性,使HttpMessage.getHeaders()方法具备真正的意义。

抽象模版方法

   HttpInputMessage.getBody() 和 ClientHttpRequest.execute() 方法根据抽象模版方法,分别提供内部抽象方法,供子类继承实现。
   子类实现支持的getBodyInternal()和executeInternal()方法有this.headers属性加持。

AbstractBufferingClientHttpRequest

ByteArrayOutputStream bufferedOutput:构造ClientHttpRequest之后,通过RequestCallback.doWithRequest(request)方法,将请求体放入bufferedOutput中。

抽象模版方法
    AbstractBufferingClientHttpRequest.executeInternal() 方法 将 【bufferedOutput】 转换成【byte[] bytes】调用内部抽象方法【executeInternal(HttpHeaders headers, byte[] bufferedOutput)】
    子类实现支持的【executeInternal(HttpHeaders headers, byte[] bufferedOutput)】方法可以直接处理请求体的字节数组【byte[] bufferedOutput】
// AbstractBufferingClientHttpRequest.java

package org.springframework.http.client;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.springframework.http.HttpHeaders;

/**
 * Base implementation of {@link ClientHttpRequest} that buffers output
 * in a byte array before sending it over the wire.
 *
 * @author Arjen Poutsma
 * @since 3.0.6
 */
abstract class AbstractBufferingClientHttpRequest extends AbstractClientHttpRequest {

   private ByteArrayOutputStream bufferedOutput = new ByteArrayOutputStream(1024);


   @Override
   protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException {
      return this.bufferedOutput;
   }

   @Override
   protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
      byte[] bytes = this.bufferedOutput.toByteArray();
      if (headers.getContentLength() < 0) {
         headers.setContentLength(bytes.length);
      }
      ClientHttpResponse result = executeInternal(headers, bytes);
      this.bufferedOutput = new ByteArrayOutputStream(0);
      return result;
   }

   /**
    * Abstract template method that writes the given headers and content to the HTTP request.
    * @param headers the HTTP headers
    * @param bufferedOutput the body content
    * @return the response object for the executed request
    */
   protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput)
         throws IOException;


}

InterceptingClientHttpRequest

增加 拦截器集合List<ClientHttpRequestInterceptor> interceptors

ClientHttpRequestExecution

ClientHttpRequestExecution提供execute方法,执行带有请求体的Http报文请求。 如果存在拦截器,执行拦截器方法,如果没有拦截器,执行ClientHttpRequest.execute()方法。

ClientHttpRequestInterceptor提供intercept方法,执行拦截器逻辑,然后继续调用ClientHttpRequestExecution.execute方法。

// ClientHttpRequestExecution.java

package org.springframework.http.client;

import java.io.IOException;

import org.springframework.http.HttpRequest;

/**
 * Represents the context of a client-side HTTP request execution.
 *
 * <p>Used to invoke the next interceptor in the interceptor chain,
 * or - if the calling interceptor is last - execute the request itself.
 *
 * @author Arjen Poutsma
 * @since 3.1
 * @see ClientHttpRequestInterceptor
 */
@FunctionalInterface
public interface ClientHttpRequestExecution {

   /**
    * Execute the request with the given request attributes and body,
    * and return the response.
    * @param request the request, containing method, URI, and headers
    * @param body the body of the request to execute
    * @return the response
    * @throws IOException in case of I/O errors
    */
   ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException;

}

InterceptingRequestExecution

private class InterceptingRequestExecution implements ClientHttpRequestExecution {

   private final Iterator<ClientHttpRequestInterceptor> iterator;

   public InterceptingRequestExecution() {
      this.iterator = interceptors.iterator();
   }

   @Override
   public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
      if (this.iterator.hasNext()) {
         ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
         return nextInterceptor.intercept(request, body, this);
      }
      else {
         HttpMethod method = request.getMethod();
         Assert.state(method != null, "No standard HTTP method");
         ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
         request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
         if (body.length > 0) {
            if (delegate instanceof StreamingHttpOutputMessage) {
               StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
               streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
            }
            else {
               StreamUtils.copy(body, delegate.getBody());
            }
         }
         return delegate.execute();
      }
   }
}
package org.springframework.http.client;

import java.io.IOException;

import org.springframework.http.HttpRequest;

/**
 * Intercepts client-side HTTP requests. Implementations of this interface can be
 * {@linkplain org.springframework.web.client.RestTemplate#setInterceptors registered}
 * with the {@link org.springframework.web.client.RestTemplate RestTemplate},
 * as to modify the outgoing {@link ClientHttpRequest} and/or the incoming
 * {@link ClientHttpResponse}.
 *
 * <p>The main entry point for interceptors is
 * {@link #intercept(HttpRequest, byte[], ClientHttpRequestExecution)}.
 *
 * @author Arjen Poutsma
 * @since 3.1
 */
@FunctionalInterface
public interface ClientHttpRequestInterceptor {

   /**
    * Intercept the given request, and return a response. The given
    * {@link ClientHttpRequestExecution} allows the interceptor to pass on the
    * request and response to the next entity in the chain.
    * <p>A typical implementation of this method would follow the following pattern:
    * <ol>
    * <li>Examine the {@linkplain HttpRequest request} and body</li>
    * <li>Optionally {@linkplain org.springframework.http.client.support.HttpRequestWrapper
    * wrap} the request to filter HTTP attributes.</li>
    * <li>Optionally modify the body of the request.</li>
    * <li><strong>Either</strong>
    * <ul>
    * <li>execute the request using
    * {@link ClientHttpRequestExecution#execute(org.springframework.http.HttpRequest, byte[])},</li>
    * <strong>or</strong>
    * <li>do not execute the request to block the execution altogether.</li>
    * </ul>
    * <li>Optionally wrap the response to filter HTTP attributes.</li>
    * </ol>
    * @param request the request, containing method, URI, and headers
    * @param body the body of the request
    * @param execution the request execution
    * @return the response
    * @throws IOException in case of I/O errors
    */
   ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
         throws IOException;

}