RestTemplate related components: Client HttpRequest Interceptor

Keywords: Java Spring OkHttp network

Each sentence

The biggest difference between people who do things and people who dream is their ability to act.

Preface

This article provides an in-depth understanding of Spring's Rest Call Client RestTemplate and explains some of its related components.

Tips: Please pay attention to distinguishing RestTemplate from RedisTemplate.~

ClientHttpRequestFactory

It is a functional interface for creating a Client HttpRequest to send requests based on URI and HttpMethod~

ClientHttpRequestIt represents the requesting client, which inherits fromHttpRequest,HttpOutputMessage,Only oneClientHttpResponse execute() throws IOExceptionMethod. amongNetty,HttpComponents,OkHttp3,HttpUrlConnectionIt's all implemented.~

// @ Since 3.0 RestTemplate is not available until 3.0
@FunctionalInterface
public interface ClientHttpRequestFactory {    

    // Returns a ClientHttpRequest so that the rest request can be sent by calling its execute() method~
    ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException;
}

Its succession tree is as follows:

As you can see intuitively, we can use Apache's HTTP Client, OkHttp3, Netty4, but all of these require additional guides. Spring uses java.net.HttpURLConnection by default.

Latest version of HttpClient: 4.5.10
OkHttp latest version: 4.1.1 (although version number is 4, GAV is still 3 oh: com.squareup.okhttp3)
Netty's latest version: 4.1.39.Final (its version 5 can declare dead)

Spring 4.0 is a new Async Client HttpRequestFactory for asynchronous support (marked as obsolete after Spring 5.0):

// Marked obsolete after Spring 5.0, replaced by org. spring framework. http. client. reactive. ClientHttpConnector (but still available)
@Deprecated
public interface AsyncClientHttpRequestFactory {

    // AsyncClientHttpRequest#executeAsync() returns ListenableFuture < ClientHttpResponse >
    // It can be seen that its asynchronization is achieved through Listenable Future.
    AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException;
}

Create ClientHttpRequest with the factory, and then we send the request without paying attention to the details inside the specific httpClient (pluggable use of two-party libraries, three-party libraries)

SimpleClientHttpRequestFactory

It is Spring's built-in default implementation, using JDK's built-in java.net.URLConnection as the client client client.

public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {

    private static final int DEFAULT_CHUNK_SIZE = 4096;
    @Nullable
    private Proxy proxy; //java.net.Proxy
    private boolean bufferRequestBody = true; // The body is buffered by default
    
    // URLConnection's connect timeout (in milliseconds).
    // If the value is set to 0, it means never timeout @see URLConnection#setConnectTimeout(int)
    private int connectTimeout = -1;
    // URLConnection#setReadTimeout(int) 
    // The timeout rule is the same as above.
    private int readTimeout = -1;
    
    //Set if the underlying URLConnection can be set to 'output streaming' mode.
    private boolean outputStreaming = true;

    // Asynchronous time needs
    @Nullable
    private AsyncListenableTaskExecutor taskExecutor;
    ... // Omit all set methods
    
    @Override
    public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
        
        // Open a HttpURLConnection
        HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
        // Set some parameters to connection, such as timeout, request method, etc.
        prepareConnection(connection, httpMethod.name());

        //SimpleBuffering ClientHttpRequest's excute method ultimately uses connection.connect();
        // Then the response code and response body are obtained from connection.~~~
        if (this.bufferRequestBody) {
            return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
        } else {
            return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
        }
    }

    // createAsyncRequest() method is simple, but asynchronous completion of requests in the thread pool
    ...
}

Note that JDK < 1.8 does't support getOutputStream with HTTP DELETE, that is, if the version of JDK is less than 1.8, then the Delete request does not support body.

Demo Show:

public static void main(String[] args) throws IOException {
    SimpleClientHttpRequestFactory clientFactory  = new SimpleClientHttpRequestFactory();
    
    // Connect Timeout works only when the network is normal, so both are generally set up
    clientFactory.setConnectTimeout(5000); //Setting up a connection timeout of 5 seconds
    clientFactory.setReadTimeout(5000); // Timeout for transferring data (this parameter is useful in case of network jitter)

    ClientHttpRequest client = clientFactory.createRequest(URI.create("https://www.baidu.com"), HttpMethod.GET);
    // Send request
    ClientHttpResponse response = client.execute();
    System.out.println(response.getStatusCode()); //200 OK
    System.out.println(response.getStatusText()); // OK
    System.out.println(response.getHeaders()); //

    // The return content is an InputStream
    byte[] bytes = FileCopyUtils.copyToByteArray(response.getBody());
    System.out.println(new String(bytes, StandardCharsets.UTF_8)); // html of Baidu Home Page Content
}

With regard to the API usage of HttpURLConnection, the following points should be noted:

  1. The HttpURLConnection object cannot be constructed directly. It needs to be obtained by the openConnection() method in the URL class.
  2. The connection () function of HttpURLConnection actually only establishes a TCP connection to the server, and does not actually send HTTP requests. HTTP requests are actually not sent officially until we get the server response data (such as calling getInputStream(), getResponseCode(), etc.).

        1. Configuration information needs to be completed before the connect() method is executed
  3. HttpURLConnection is based on HTTP protocol, and its bottom layer is realized by socket communication. If timeout is not set, in the case of network anomalies, the program may be deadlocked and not continue to execute. == Make sure 100% settings are set==
  4. The content of HTTP body is written through the OutputStream stream. The data written to the stream will not be sent to the network immediately, but will exist in the memory buffer. When the stream is closed, HTTP body will be generated according to the content written.
  5. When the getInputStream() method is called, an input stream is returned from which the server's return information for HTTP requests is read.
  6. HttpURLConnection.connect() is not required. When we need to return a value, such as when we use the HttpURLConnection.getInputStream() method, it automatically sends a request, so there is no need to call the connect() method at all (there is no need to establish Tcp first).

Which underlying http library is used?

We know that HttpURLConnection has some functional deficiencies (simple submission parameters can be satisfied). In most cases, the Web site's pages may not be so simple. These pages are not accessible through a simple URL. They may require users to log in and have the corresponding permissions to access the page. In this case, we need to deal with Session and Cookie. If we intend to use HttpURLConnection to deal with these details, of course, it is possible to achieve, but it is more difficult to handle.

At this point, Apache Open Source provides an HttpClient project that can be used to send HTTP requests and receive HTTP responses (including HttpGet, HttpPost... and other objects that send requests).

It does not cache the server's response, can't execute Javascript code embedded in HTML pages, and can't parse and process the content of pages.

So let Spring use HttpClient as an example to demonstrate the use of tripartite libraries:
1, guide package

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.10</version>
</dependency>

Tips: Requires Apache HttpComponents 4.3 or higher, as of Spring 4.0.

2. Case Use
The content of the case only needs to replace the first sentence of the preceding example with an example using HttpComponents Client HttpRequestFactory, and the rest can be seen successfully without changing. Let's see what this class does.

// @ It appeared after 3.13.1.
public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean {

    private HttpClient httpClient;
    @Nullable
    private RequestConfig requestConfig; // This configuration is the class that can configure the time-out and other cluttered client attributes.
    private boolean bufferRequestBody = true;

    //========= Here are the constructors=========
    public HttpComponentsClientHttpRequestFactory() {
        // HttpClientBuilder.create().useSystemProperties().build();
        // All if this is the case, the configuration timeout can be set as follows:
        // System.setProperty("sun.net.client.defaultConnectTimeout", "5000″);
        this.httpClient = HttpClients.createSystem();
    }
    // Of course, you can throw in your configured Client.
    public HttpComponentsClientHttpRequestFactory(HttpClient httpClient) {
        this.httpClient = httpClient;
    }
    ... // Eliminate setting timeout time... Some get/set of attributes such as
    // Timeout information and everything are stored in `RequestConfig'.


    @Override
    public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
        HttpClient client = getHttpClient(); // Get the client you specified = (or the default system)
        // switch statement logic: HttpMethod = GET - > HttpGet HEAD - > HttppHead...
        HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
        postProcessHttpRequest(httpRequest);
        ...
    }
}

What is actually used is the request completed by HttpClient. In addition, OkHttp3ClientHttpRequestFactory uses okhttp3.OkHttpClient to send requests; Netty4ClientHttpRequestFactory uses io.netty.channel.EventLoopGroup. Here are not all examples.

Since Spring 5.0, Netty4ClientHttpRequestFactory has expired and is recommended to replace it with org. spring framework. http. client. reactive. ReactorClientHttpConnector~

A simple comparison of HttpURLConnection, HttpClient and OkHttpClient:
  • HttpURLConnection:

        - Advantages: JDK built-in support, standard class of java
        - Disadvantage: API is not friendly enough, nothing is encapsulated, and it is too primitive and inconvenient to use. (Actually, sometimes it is a good thing, primitive proves good control.)
  • HttpClient:

        - Advantages: Powerful, API-friendly, high usage, almost become a practical standard (equivalent to the `HttpURLConnection'packaging)
        - Disadvantage: Lower performance (lower than `HttpURLConnection', but improved with connection pool after 4.3), bloated API, which Android has abandoned~
  • OkHttpClient: A New Generation of Http Access Client

        - Advantages: An HTTP client focused on ** performance and ease of use ** (saving broadband, recommended by Android) is designed with the primary goal of being efficient. The latest HTTP protocol versions HTTP/2 and SPDY are supported. If HTTP/2 and SPDY are not available, OkHttp uses connection pools to multiplex connections to improve efficiency.
        - not at all.
    


With regard to Apache HttpClient, Android 5.0 has since abandoned it (too many API s, too heavy), and a lighter HttpUrlConnection is recommended. (Java development or recommended HttpClient)

OkHttp has many advantages: it supports SPDY and can merge multiple requests to the same host; OkHttp implements many technologies such as connection pool, gziping, caching, etc; OkHttp handles many network problems: it automatically recovers from many common connection problems. If your server has multiple IP addresses configured, OkHttp will automatically try the next IP when the first IP connection fails; OkHttp is a Java HTTP+SPDY client development package that also supports Android. By default, OKHttp automatically handles common network problems, such as secondary connections and SSL handshakes. Supports file upload, download, cookie, session, https certificate and almost all other functions. Supporting cancellation of a request

To sum up, whether it's Java or Android, the natural thing I recommend is OkHttp (OkHttp uses Okio for data transmission). Square is its own company, and Square has launched a Retrofit library to double OkHttp's combat effectiveness.~~~

Pooling technology is generally used for long connections. Is it suitable for connection pools like Http?
Since HttpClient 4.3, Pooling HttpClient Connection Manager connection pool has been used to manage holding connections, which can be reused on the same TCP link. HttpClient persists connections through connection pools (so this connection pool is actually a connection pool for tcp). There is a very important concept in it: the concept of Route, which represents a line. For example, baidu.com is a route, 163.com is a route....

Connection pool: It could be an http request or an https request
With pool phone technology, you don't have to create a new connection every time you make a request (shaking hands three times per connection is too inefficient).

AbstractClientHttpRequestFactoryWrapper

For a wrapper abstract class of other ClientHttpRequestFactory, it has the following two subclasses to implement

Intercepting Client HttpRequestFactory (Important)

The concept of Interceptor interception is still important. The Client HttpRequest Interceptor it holds is very important if we want to intercept requests sent out (for example, in full-link manometry, we can use it to set token and so on)

// @since 3.1
public class InterceptingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {
    // Holding all request interceptors
    private final List<ClientHttpRequestInterceptor> interceptors;

    public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory, @Nullable List<ClientHttpRequestInterceptor> interceptors) {
        super(requestFactory);
        // The interceptor allows only constructor settings, and does not provide get methods.~
        this.interceptors = (interceptors != null ? interceptors : Collections.emptyList());
    }

    // The one returned here is an Intercepting Client HttpRequest. Obviously, it must be a Client HttpRequest.~
    @Override
    protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {
        return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
    }

}

The execute() method of InterceptingClientHttpRequest is characterized by: if there is an interceptor, it is given to the interceptor to execute the return nextInterceptor.intercept(request, body, this), otherwise it will go on its own.

ClientHttpRequestInterceptor

With regard to request interceptors, Spring MVC has two most basic implementations built in

==BasicAuthorizationInterceptor==:

// @ since 4.3.1, but after Spring 5.1.1, Basic Authentication Interceptor is recommended
@Deprecated
public class BasicAuthorizationInterceptor implements ClientHttpRequestInterceptor {
    
    private final String username;
    private final String password;
    
    // Note: username is not allowed to include: this character, but password is allowed
    public BasicAuthorizationInterceptor(@Nullable String username, @Nullable String password) {
        Assert.doesNotContain(username, ":", "Username must not contain a colon");
        this.username = (username != null ? username : "");
        this.password = (password != null ? password : "");
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        // After the username password is connected, the bytecode is coded with Base64~
        String token = Base64Utils.encodeToString((this.username + ":" + this.password).getBytes(StandardCharsets.UTF_8));
    
        // Put it in the request header: key is `Authorization', and then execute the sending of the request
        request.getHeaders().add("Authorization", "Basic " + token);
        return execution.execute(request, body);
    }
}

The interceptor has made any changes to the body, just putting the username and password on the request head for you.

Note: If you already have the Authorization key in your header, it won't be covered here. It will be added. But it's not recommended that you have coverage.~

==BasicAuthenticationInterceptor==:
It is used to replace the above class. It is handled using standard authorization headers, referring to HttpHeaders#setBasicAuth, HttpHeaders#AUTHORIZATION

public class BasicAuthenticationInterceptor implements ClientHttpRequestInterceptor {
    private final String username;
    private final String password;
    // Coding, generally not specified
    @Nullable
    private final Charset charset;
    ... // Structural function

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {

        HttpHeaders headers = request.getHeaders();
        // Only when the request does not contain the `Authorization'key will the authorization header be set here.
        if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
            
            // This method is provided after @since 5.1.~~~~~
            // If this key is not included, the standard authorization header (based on user name and password) is set up. There are also three steps in it:
            
            // String credentialsString = username + ":" + password;
            // byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(charset));
            // String encodedCredentials = new String(encodedBytes, charset);
            
            // Note: It eventually calls set (AUTHORIZATION,'Basic'+ encoded Credentials) internally; this method's
            headers.setBasicAuth(this.username, this.password, this.charset);
        }
        return execution.execute(request, body);
    }
}

Note: Although these two request interceptors are provided by Spring, they are not "assembled" by default. If you need them, please assemble them manually.~

BufferingClientHttpRequestFactory

Package other ClientHttpRequest Factory to enable caching. If the caching function is turned on (with switch control), the original Client HttpRequest will be wrapped in Buffering Client HttpRequest Wrapper. This sends the request with a Buffering Client HttpResponseWrapper response.

ResponseErrorHandler

Policy interface used to determine whether a particular response has an error.

// @since 3.0
public interface ResponseErrorHandler {

    // Is there a mistake in response?
    boolean hasError(ClientHttpResponse response) throws IOException;
    // This method is called only if hasError = true
    void handleError(ClientHttpResponse response) throws IOException;
     // @since 5.0
    default void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
        handleError(response);
    }
}

The succession tree is as follows:

DefaultResponseErrorHandler

SpringThe default implementation of this policy interface,RestTemplateThe default error handler is it.

// @since 3.0
public class DefaultResponseErrorHandler implements ResponseErrorHandler {

    // Whether there are errors is based on the response code, so please strictly comply with the specifications of the response code ah
    // Simply put, both 4xx and 5xx are considered to be wrong, otherwise they are good references: HttpStatus.Series
    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        int rawStatusCode = response.getRawStatusCode();
        HttpStatus statusCode = HttpStatus.resolve(rawStatusCode);
        return (statusCode != null ? hasError(statusCode) : hasError(rawStatusCode));
    }
    ...
    // Handling errors
    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode());
        if (statusCode == null) {
            throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response));
        }
        handleError(response, statusCode);
    }
    
    // protected method, subclass overrides it
    protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
        String statusText = response.getStatusText();
        HttpHeaders headers = response.getHeaders();
        byte[] body = getResponseBody(response); // Get the body and convert InputStream to a byte array
        Charset charset = getCharset(response); // Note that the code here is taken from the returned contentType~~~
        
        // Throws for client errors, server errors wrapped as HttpClientErrorException and HttpServerErrorException, respectively
        // Exceptions contain state codes, state text, headers, bodies, codes, and so on.~~~~
        switch (statusCode.series()) {
            case CLIENT_ERROR:
                throw HttpClientErrorException.create(statusCode, statusText, headers, body, charset);
            case SERVER_ERROR:
                throw HttpServerErrorException.create(statusCode, statusText, headers, body, charset);
            default:
                throw new UnknownHttpStatusCodeException(statusCode.value(), statusText, headers, body, charset);
        }
    }
    ...
}

Here you can explain why you often see client errors, and then there is a status code + a string of information, because of these two exceptions.

HttpClientErrorException:

public class HttpClientErrorException extends HttpStatusCodeException {
    ...
    public static HttpClientErrorException create(
            HttpStatus statusCode, String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) {

        switch (statusCode) {
            case BAD_REQUEST:
                return new HttpClientErrorException.BadRequest(statusText, headers, body, charset);
            case UNAUTHORIZED:
                return new HttpClientErrorException.Unauthorized(statusText, headers, body, charset);
            case FORBIDDEN:
                return new HttpClientErrorException.Forbidden(statusText, headers, body, charset);
            case NOT_FOUND:
                return new HttpClientErrorException.NotFound(statusText, headers, body, charset);
            case METHOD_NOT_ALLOWED:
                return new HttpClientErrorException.MethodNotAllowed(statusText, headers, body, charset);
            case NOT_ACCEPTABLE:
                return new HttpClientErrorException.NotAcceptable(statusText, headers, body, charset);
            case CONFLICT:
                return new HttpClientErrorException.Conflict(statusText, headers, body, charset);
            case GONE:
                return new HttpClientErrorException.Gone(statusText, headers, body, charset);
            case UNSUPPORTED_MEDIA_TYPE:
                return new HttpClientErrorException.UnsupportedMediaType(statusText, headers, body, charset);
            case TOO_MANY_REQUESTS:
                return new HttpClientErrorException.TooManyRequests(statusText, headers, body, charset);
            case UNPROCESSABLE_ENTITY:
                return new HttpClientErrorException.UnprocessableEntity(statusText, headers, body, charset);
            default:
                return new HttpClientErrorException(statusCode, statusText, headers, body, charset);
        }
    }
    ...
}

It creates different types of returns for different status codes HttpStatus, which is very meaningful in monitoring.

BadRequest, Unauthorized, Forbidden, etc. are all subclasses of HttpClientErrorException

The HttpServer ErrorException code is similar to that of the~

ExtractingResponseErrorHandler

Inherited from DefaultResponseErrorHandler. Today, with RESTful in vogue, Spring 5.0 has begun to offer this type of service. It converts http error response to its corresponding RestClientException using HttpMessageConverter

// @ since 5.0, it's still very late. Inheritance from DefaultResponseErrorHandler 
// If your RestTemplate wants to use it, just call the RestTemplate # setErrorHandler (Response ErrorHandler) setting.
public class ExtractingResponseErrorHandler extends DefaultResponseErrorHandler {
    private List<HttpMessageConverter<?>> messageConverters = Collections.emptyList();
    
    // Caching response codes
    private final Map<HttpStatus, Class<? extends RestClientException>> statusMapping = new LinkedHashMap<>();
    private final Map<HttpStatus.Series, Class<? extends RestClientException>> seriesMapping = new LinkedHashMap<>();

    // The constructor and set method assign values to the two Map s above. Because we can control which status codes should report errors and which should not.~
    // And can be customized: that state code throws our custom exception, which series of state code throws our custom exception, this is very convenient for us to monitor and control.
    ... // Eliminate constructors and set methods...


    // Add caching ~~or it's handed over to the parent class
    @Override
    protected boolean hasError(HttpStatus statusCode) {
        if (this.statusMapping.containsKey(statusCode)) {
            return this.statusMapping.get(statusCode) != null;
        } else if (this.seriesMapping.containsKey(statusCode.series())) {
            return this.seriesMapping.get(statusCode.series()) != null;
        } else {
            return super.hasError(statusCode);
        }
    }

    // What it does: extract: extract
    @Override
    public void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
        if (this.statusMapping.containsKey(statusCode)) {
            extract(this.statusMapping.get(statusCode), response);
        } else if (this.seriesMapping.containsKey(statusCode.series())) {
            extract(this.seriesMapping.get(statusCode.series()), response);
        } else {
            super.handleError(response, statusCode);
        }
    }


    private void extract(@Nullable Class<? extends RestClientException> exceptionClass, ClientHttpResponse response) throws IOException {
        if (exceptionClass == null) {
            return;
        }

        // The ResponseExtractor Return Value Extractor is used here to extract content from the Return Value (this article is about extracting exceptions).
        HttpMessageConverterExtractor<? extends RestClientException> extractor =
                new HttpMessageConverterExtractor<>(exceptionClass, this.messageConverters);
        RestClientException exception = extractor.extractData(response);
        if (exception != null) { // If abnormal information is extracted, it can be thrown out.
            throw exception;
        }
    }
}

If you want to customize the processing logic for request exceptions, you can also customize the implementation of this interface. Of course, it is recommended that you extend it by inheriting DefaultResponseErrorHandler.~

ResponseExtractor

Response extractor: Extract data from Response. After the RestTemplate request is completed, it is used to extract the specified content (such as request header, request Body, etc.) from the Client HttpResponse.~

Its direct implementation seems to be only HttpMessageConverter Extractor, of course, it is also one of the most important implementations, related to HttpMessageConverter.
Before explaining it, look at this: Message Body Client HttpResponseWrapper, which features that it can not only check whether the response has a message body by actually reading the input stream, but also check whether its length is 0 (i.e. empty).

// @ since 4.1.5 is a class with access rights default and is a wrapper for other ClientHttpResponse s
class MessageBodyClientHttpResponseWrapper implements ClientHttpResponse {
    private final ClientHttpResponse response;
    // java.io.PushbackInputStream
    @Nullable
    private PushbackInputStream pushbackInputStream;
    
    // Judge whether there is body in the corresponding
    // If the response code is 1xx or 204; or getHeaders (). getContentLength ()== 0, return false or true
    public boolean hasMessageBody() throws IOException {
        HttpStatus status = HttpStatus.resolve(getRawStatusCode());
        if (status != null && (status.is1xxInformational() || status == HttpStatus.NO_CONTENT || status == HttpStatus.NOT_MODIFIED)) {
            return false;
        }
        if (getHeaders().getContentLength() == 0) {
            return false;
        }
        return true;
    }

    // Above is the Content Length to determine whether there is a body or not. Here, the flow is used to determine whether there is a body or not.
    // If response. getBody () === null, return true
    // If there is content in the stream, it is eventually packaged in a new Pushback InputStream (body).~~~
    public boolean hasEmptyMessageBody() throws IOException {
        ...
    }
    
    ...  // The rest of the interface methods are delegated~
    @Override
    public InputStream getBody() throws IOException {
        return (this.pushbackInputStream != null ? this.pushbackInputStream : this.response.getBody());
    }
}

Its function is to provide two methods, hasMessageBody and hasEmpty MessageBody, to facilitate the determination of body content after packaging.

// @ since 3.0 Generic T: the data type
public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
    // java.lang.reflect.Type
    private final Type responseType;
    // This generic is also T, a Class for data.~
    // The calss might be the responseType above.
    @Nullable
    private final Class<T> responseClass;
    // Important: Converters for message parsing
    private final List<HttpMessageConverter<?>> messageConverters;
    ... // Ellipsis constructor


    // Extracting values from ClientHttpResponse
    @Override
    @SuppressWarnings({"unchecked", "rawtypes", "resource"})
    public T extractData(ClientHttpResponse response) throws IOException {
        MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
        // If there is no message body (incorrect status code or empty message body is considered to be null)
        if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
            return null;
        }
    
        // If the content-type is not specified in the response header, the default is its MediaType.APPLICATION_OCTET_STREAM.
        MediaType contentType = getContentType(responseWrapper);
        
        // Traverse through all message Converters and select a message converter based on contentType
        // Finally, return message Converter. read ((Class) this. responseClass, responseWrapper)
        ...
    }
}

Its processing logic is very simple to understand: a message converter is found using contentType, and eventually HttpMessageConverter.read() reads the message out and converts it into Java objects.

It also has two internal classes implemented as follows (both private internal classes of RestTemplate):

RestTemplate: 

    // Extracted as `ResponseEntity'and ultimately delegated to HttpMessageConverter Extractor
    private class ResponseEntityResponseExtractor<T> implements ResponseExtractor<ResponseEntity<T>> {

        @Nullable
        private final HttpMessageConverterExtractor<T> delegate;

        public ResponseEntityResponseExtractor(@Nullable Type responseType) {
            // Obviously: it makes sense only if the return value of the request is not null~
            if (responseType != null && Void.class != responseType) {
                this.delegate = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
            } else {
                this.delegate = null;
            }
        }

        // Data extraction. delegate.extractData(response). Then a new Response Entity comes out and wraps it in.
        // If the tree has a return value (delegate=null), it is an instance of `ResponseEntity', and body is null.
        @Override
        public ResponseEntity<T> extractData(ClientHttpResponse response) throws IOException {
            if (this.delegate != null) {
                T body = this.delegate.extractData(response);
                return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(body);
            }
            else {
                return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).build();
            }
        }
    }

    // Extract request header
    private static class HeadersExtractor implements ResponseExtractor<HttpHeaders> {
        @Override
        public HttpHeaders extractData(ClientHttpResponse response) {
            return response.getHeaders();
        }
    }

UriTemplateHandler

This component is used to define how to extend uri templates with variables.

// @ since 4.2 appeared later.  
// @see RestTemplate#setUriTemplateHandler(UriTemplateHandler)
public interface UriTemplateHandler {
    URI expand(String uriTemplate, Map<String, ?> uriVariables);
    URI expand(String uriTemplate, Object... uriVariables);
}

Ultimately, URI processing is delegated to UriComponents Builder. If there are still some doubts about this, it's very strong. Reference here

Recommended reading

Are you familiar with the use and principles of RestTemplate? [Enjoy Spring MVC]

summary

The components introduced here are the first to understand RestTemplate's essential components. Because RestTemplate is frequently used and often needs to be tuned, I hope you can get a better understanding of it, which is also the purpose of my series.

== If you are interested in Spring, Spring Boot, MyBatis and other source code analysis, you can add me wx: fsx641385712, invite you to join the group and fly together manually.==
== If you are interested in Spring, Spring Boot, MyBatis and other source code analysis, you can add me wx: fsx641385712, invite you to join the group and fly together manually.==

Posted by jonker on Mon, 16 Sep 2019 21:27:59 -0700