Make writing a habit together! This is the 15th day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details

How does the Ribbon make web requests

Constitute the determinants of genius should be diligent — Guo Moruo

How does the Ribbon select a service from a registry

Where to analyze

The request is initiated from the execution method, the RestTemplate request is initiated, the interceptor is intercepted, the load balancer is retrieved, the service list is retrieved from the load balancer, the address resolution is performed, and the final network request is made. It is then returned to the RestTemplate for parsing the resulting returned data.

brief

Source code overview diagram

Source code analysis

1. RestTemplate initiates a request

@RestController
@RequestMapping("portal")
public class GoodsController {
// The instance name of the registry to access
private static final String GOODS_SERVICE_URL = 
                                "http://GOODS-APPLICATION/good/getGoods";
@Autowired
private RestTemplate restTemplate;

@RequestMapping(value = "/getGoods",produces = "application/json; charset=UTF-8")
public String getGoods(a) {
    // Make the RestTemplate request access
    ResponseEntity<ResultObject> result = restTemplate
        .getForEntity(GOODS_SERVICE_URL, ResultObject.class);
    // Get the result of the returned request
    ResultObject resultBody = result.getBody();
    System.out.println(resultBody.getData());
    returnresultBody.getStatusMessage(); }}Copy the code

Registry Service list details



The doExecute method in RestTemplate is called

protected <T> T doExecute(URI url, @Nullable HttpMethod method, 
                            @Nullable RequestCallback requestCallback,
                            @Nullable ResponseExtractor<T> responseExtractor) 
                        throws RestClientException {
    // Make a real request access
    response = request.execute();
    // Process the result
    handleResponse(url, method, response);
    // Parse the returned result
    return(responseExtractor ! =null ? responseExtractor.extractData(response) : null); }}Copy the code

2. The first enter InterceptingRequestExecution request intercept

protected final ClientHttpResponse executeInternal(HttpHeaders headers, 
                                          byte[] bufferedOutput) throws IOException {
    // Create an interceptor request execution object
    InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
    // Initiate a visit
    return requestExecution.execute(this, bufferedOutput);
}
Copy the code

The first execution will go to the if module because there are still iterators to execute. When all iterators have been executed, else logic can be used. This is the interception of the request, the request address replacement and selection

private class InterceptingRequestExecution implements ClientHttpRequestExecution {

    private final Iterator<ClientHttpRequestInterceptor> iterator;
    public InterceptingRequestExecution(a) {
        // Assign a value to the interceptor
        // Interceptors this value is set when the LoadBalancerInterceptor first calls
        this.iterator = interceptors.iterator();
    }

    @Override
    public ClientHttpResponse execute(HttpRequest request, byte[] body) 
                                                    throws IOException {
        // The first time I enter it, I will go here because there are values in the iterator, so I will go here,
        // Wait for all iterators to complete the else logic request
        if (this.iterator.hasNext()) {
            ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
            return nextInterceptor.intercept(request, body, this);
        }
        else {
            // The iterator goes here when there is no value in it
            HttpMethod method = request.getMethod();// Get the request method
            ClientHttpRequest delegate = 
                // Create a request method with a method to get the real URL address
                requestFactory.createRequest(request.getURI(), method);
            // Complete the request header
            request.getHeaders().forEach(
                (key, value) -> delegate.getHeaders().addAll(key, value));
            // Complete the request body information
            if (body.length > 0) {
                if (delegate instanceofStreamingHttpOutputMessage) { StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;  streamingOutputMessage. setBody(outputStream -> StreamUtils.copy(body, outputStream)); }else{ StreamUtils.copy(body, delegate.getBody()); }}// Make a real request
            returndelegate.execute(); }}}Copy the code

3. Invoke the load balancer to execute the request

Because at the time of initialization LoadBalancerAutoConfiguration set the interceptor is LoadBalancerInterceptor, And put it in the ClientHttpRequestInterceptor interceptor list and set the set RestTemplate interceptor is LoadBalancerInterceptor

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
        final LoadBalancerInterceptor loadBalancerInterceptor) {
    return restTemplate -> {
        / / add ClientHttpRequestInterceptor LoadBalancerInterceptor blocker
        // List of interceptors
        List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                restTemplate.getInterceptors());
        list.add(loadBalancerInterceptor);
        // Set RestTemplate interceptor to LoadBalancerInterceptor
        restTemplate.setInterceptors(list);
    };
}
Copy the code



So the interceptor method is the Intercept method of the LoadBalancerInterceptor

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
        final ClientHttpRequestExecution execution) throws IOException {
    // Get the requested URL
    final URI originalUri = request.getURI();
    // Get the requested serviceName
    String serviceName = originalUri.getHost();
    LoadBalancerClient loadBalancer calls the execute method of LoadBalancerClient
    return this.loadBalancer.execute(serviceName,
            this.requestFactory.createRequest(request, body, execution));
}
Copy the code



The execute method of the LoadBalancerClient is called, which involves obtaining the load balancer and the service instance of the load balancer through the default polling algorithm

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
        throws IOException {
    / / get the load balancer, the default is the dynamic load balancer DomainExtractingServerList service list
    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    // Use the polling algorithm to obtain the service instance in the load equalizer
    Server server = getServer(loadBalancer, hint);
    RibbonServer ribbonServer = new RibbonServer(serviceId, server,
            isSecure(server, serviceId),
            serverIntrospector(serviceId).getMetadata(server));
    // Initiate execution
    return execute(serviceId, ribbonServer, request);
}
Copy the code

4. Obtain service instances from load balancers.

// Get load balancer, default is dynamic service list load balancer
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// Use the polling algorithm to obtain the service instance in the load equalizer
Server server = getServer(loadBalancer, hint);
Copy the code

5. Enter InterceptingRequestExecution for request again

When all iterators have been executed, we go to the else, otherwise we go to the if logic

private class InterceptingRequestExecution implements ClientHttpRequestExecution {
    private final Iterator<ClientHttpRequestInterceptor> iterator;
    public InterceptingRequestExecution(a) {
        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 {
            // When all iterators have been executed, it will go inside
            HttpMethod method = request.getMethod();
            // Create the request object
            ClientHttpRequest delegate = 
                requestFactory.createRequest(request.getURI(), method);
            // Complete the request header
            request.getHeaders().forEach(
                (key, value) -> delegate.getHeaders().addAll(key, value));
            // Complete the request body information
            if (body.length > 0) {
                if (delegate instanceofStreamingHttpOutputMessage) { StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;  streamingOutputMessage .setBody(outputStream -> StreamUtils.copy(body, outputStream)); }else{ StreamUtils.copy(body, delegate.getBody()); }}// Initiate a real network request
            returndelegate.execute(); }}}Copy the code

6. Parse the real URL

ServiceRequestWrapper encapsulates the address resolution method, which is also created during the execution of the interceptor



The address resolution method is actually called in the RibbonLoadBalancerClientreconstructURImethods

@Override
public URI getURI(a) {
    URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
    return uri;
}
Copy the code

Detailed process for resolving an address

@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
    String serviceId = instance.getServiceId(); // GOODS-APPLICATION
    RibbonLoadBalancerContext context = this.clientFactory
            .getLoadBalancerContext(serviceId);
    URI uri;
    Server server;
    if (instance instanceof RibbonServer) {
        RibbonServer ribbonServer = (RibbonServer) instance;
        server = ribbonServer.getServer(); / / 192.168.2.100.9200
        // http://GOODS-APPLICATION/good/getGoods
        uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
    }
    return context.reconstructURIWithServer(server, uri);
}
Copy the code

Take the service instance and URI native address as parameters, and call reconstructURIWithServer of LoadBalancerContext for address resolution and replacement to get the final actual address to be accessed. Is http://GOODS-APPLICATION/good/getGoods into http://192.168.2.100:9200/good/getGoods like this

public URI reconstructURIWithServer(Server server, URI original) {
    String host = server.getHost(); / / 192.168.2.100
    int port = server.getPort();/ / 9200
    String scheme = server.getScheme();// null
    if (host.equals(original.getHost()) 
            && port == original.getPort()
            && scheme == original.getScheme()) {
        return original;
    }
    if (scheme == null) {
        scheme = original.getScheme(); // http
    }
    if (scheme == null) {
        scheme = deriveSchemeAndPortFromPartialUri(original).first();
    }

    StringBuilder sb = new StringBuilder();
    sb.append(scheme).append(": / /");// http://
    if(! Strings.isNullOrEmpty(original.getRawUserInfo())) { sb.append(original.getRawUserInfo()).append("@");
    }
    sb.append(host);/ / http://192.168.2.100
    if (port >= 0) {
        sb.append(":").append(port);/ / 192.168.2.100:9200
    }
    / / the original getRawPath ()/good/getGoods
    sb.append(original.getRawPath());/ / http://192.168.2.100:9200/good/getGoods
    if(! Strings.isNullOrEmpty(original.getRawQuery())) { sb.append("?").append(original.getRawQuery());
    }
    if(! Strings.isNullOrEmpty(original.getRawFragment())) { sb.append("#").append(original.getRawFragment());
    }
    // Create a new URI
    URI newURI = new URI(sb.toString());
    / / http://192.168.2.100:9200/good/getGoods
    return newURI;            

}
Copy the code

7. Create a ClientHttpRequest request

Create SimpleBufferingClientHttpRequest request object, the method of conducting follow-up visit

public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
/ / sun.net.www.protocol.http.HttpURLConnection:http://192.168.2.100:9200/good/getGoods
    HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
    prepareConnection(connection, httpMethod.name());
    if (this.bufferRequestBody) {
        // Create the request object
        return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming); }}Copy the code

8. Access the network

Delegate. The execute (), but eventually walked in SimpleBufferingClientHttpRequest executeInternal method

@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
    // Make the construction request
    addHeaders(this.connection, headers);
    // Request access
    this.connection.connect();
    // Encapsulate the return result
    return new SimpleClientHttpResponse(this.connection);
}
Copy the code

9. Parse the results

ResponseEntityResponseExtractor RestTemplate internal private class, call the class extractData method for the final result of parsing

@Override
public ResponseEntity<T> extractData(ClientHttpResponse response) throws IOException {
    if (this.delegate ! =null) {
        // The real method requests a result resolution
        T body = this.delegate.extractData(response);
        // Encapsulate the return result
        return ResponseEntity.status(response.getRawStatusCode()).
            headers(response.getHeaders()).body(body);
    }
    else {
        returnResponseEntity.status(response.getRawStatusCode()). headers(response.getHeaders()).build(); }}Copy the code

Display of analytical results

10. Return the result

@RestController
@RequestMapping("portal")
public class GoodsController {
    private static final String GOODS_SERVICE_URL = 
            "http://GOODS-APPLICATION/good/getGoods";
    @Autowired
    private RestTemplate restTemplate;
    @RequestMapping(value = "/getGoods", produces = "application/json; charset=UTF-8")
    public String getGoods(a) {
        ResponseEntity<ResultObject> result = restTemplate
            .getForEntity(GOODS_SERVICE_URL, ResultObject.class);
        ResultObject resultBody = result.getBody();
        System.out.println(resultBody.getData());
        returnresultBody.getStatusMessage(); }}Copy the code

The output

Returns the result

summary

At the end of the day is initialized in LoadBalancerAutoConfiguration has to RestTemplate request interceptor set up his own interceptor, is LoadBalancerInterceptor, The interceptor handles the following methods, such as selecting the load balancer, retrieving the service instance from the load balancer, and then returning the result to the caller via an HttpURLConnection utility class that parses it through a class inside the RestTemplate