Diagram + source code to explain how Ribbon initiates network requests

Get into the habit of writing together! This is the 15th day of my participation in the "Nuggets Daily New Plan·April Update Challenge", click to view the event details

Diagram + source code to explain how Ribbon initiates network requests

Diligence is the decisive factor for genius - Guo Moruo

Related Articles
Illustration + Source Code Explaining Ribbon Example
Illustration + Source Code Explaining Ribbon Principles
Illustration + Source Code Explaining Ribbon Service List Update
Illustration + Source Code Explaining Ribbon Service Selection Principle

where to analyze

    The designation is also executed from the execution method, and the request is also initiated through RestTemplate , and then intercepted by the interceptor, first obtains the load balancer, then obtains the service list from the load balancer, and then performs address resolution, Make the final request of the network, and then return it to RestTemplate for parsing the result returned data.

sketch

Source code overview

image.png

Source code analysis

1. RestTemplate initiates a request

@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() {
    // 进行 RestTemplate 请求访问
    ResponseEntity<ResultObject> result = restTemplate
        .getForEntity(GOODS_SERVICE_URL, ResultObject.class);
    // 获取返回的请求结果
    ResultObject resultBody = result.getBody();
    System.out.println(resultBody.getData());
    return resultBody.getStatusMessage();
    }
}
复制代码

Registry Service List Details

image.png
     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 {
    // 进行真正的请求访问
    response = request.execute();
    // 处理返回结果
    handleResponse(url, method, response);
    // 进行返回的结果解析
    return (responseExtractor != null ? responseExtractor.extractData(response) : null);
	}
}

复制代码

2. Enter InterceptingRequestExecution for the first time for request interception

protected final ClientHttpResponse executeInternal(HttpHeaders headers, 
                                          byte[] bufferedOutput) throws IOException {
    // 创建一个拦截器请求执行对象
    InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
    // 发起访问
    return requestExecution.execute(this, bufferedOutput);
}
复制代码

    When it is executed for the first time, it will enter the if module, because there are still iterators that are not executed. When all the iterators are executed, the following else logic can be followed, which intercepts the request and replaces the request address. and select

private class InterceptingRequestExecution implements ClientHttpRequestExecution {

    private final Iterator<ClientHttpRequestInterceptor> iterator;
    public InterceptingRequestExecution() {
        // 给拦截器中的值进行赋值
        // interceptors 这个值是LoadBalancerInterceptor初始话的时候进行设置的
        this.iterator = interceptors.iterator();
    }

    @Override
    public ClientHttpResponse execute(HttpRequest request, byte[] body) 
                                                    throws IOException {
        // 第一次进入的时候会走这里,因为迭代器里面有值,所以走这里,
        // 等所有的迭代器都执行完毕后进行 else 逻辑请求
        if (this.iterator.hasNext()) {
            ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
            return nextInterceptor.intercept(request, body, this);
        }
        else {
            // 当迭代器里面的值没有的时候会走这里
            HttpMethod method = request.getMethod();// 获取请求方法
            ClientHttpRequest delegate = 
                // 创建请求方法,其中有一个获取真实URL地址的方法
                requestFactory.createRequest(request.getURI(), method);
            // 进行请求头部 header 完善
            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();
        }
    }
}
复制代码

3. Call the load balancer for request execution

    Because the interceptor is LoadBalancerInterceptor set when LoadBalancerAutoConfiguration is initialized, and it is put into the ClientHttpRequestInterceptor interceptor list and the RestTemplate interceptor is set to LoadBalancerInterceptor

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
        final LoadBalancerInterceptor loadBalancerInterceptor) {
    return restTemplate -> {
        // 将 LoadBalancerInterceptor 拦截器放入 ClientHttpRequestInterceptor 
        // 拦截器列表中
        List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                restTemplate.getInterceptors());
        list.add(loadBalancerInterceptor);
        // 设置RestTemplate拦截器是 LoadBalancerInterceptor
        restTemplate.setInterceptors(list);
    };
}
复制代码

image.png
    所以此时走的拦截器方法就是 LoadBalancerInterceptor 的 intercept 方法

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
        final ClientHttpRequestExecution execution) throws IOException {
    // 获取请求的URL地址
    final URI originalUri = request.getURI();
    // 获取请求的 serviceName
    String serviceName = originalUri.getHost();
    // LoadBalancerClient loadBalancer 调用的是 LoadBalancerClient的execute方法
    return this.loadBalancer.execute(serviceName,
            this.requestFactory.createRequest(request, body, execution));
}
复制代码

image.png
    这里调用的是 LoadBalancerClient的execute方法,这里面涉及到了获取负载均衡器,以及通过默认的轮询算法获取负载均衡器中的服务实例

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
        throws IOException {
    // 获取负载均衡器,默认是动态服务列表负载均衡器 DomainExtractingServerList
    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    // 通过轮询算法获取载均衡器中的服务实例
    Server server = getServer(loadBalancer, hint);
    RibbonServer ribbonServer = new RibbonServer(serviceId, server,
            isSecure(server, serviceId),
            serverIntrospector(serviceId).getMetadata(server));
    // 发起执行
    return execute(serviceId, ribbonServer, request);
}
复制代码

image.png

4. 获取负载均衡器里面的服务实例【默认是轮询算法获取实例】

// 获取负载均衡器,默认是动态服务列表负载均衡器
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 通过轮询算法获取载均衡器中的服务实例
Server server = getServer(loadBalancer, hint);
复制代码

image.png

5. 再一次进入 InterceptingRequestExecution 进行请求

    当所有迭代器都执行完后才会走到 else 这里面,否则会走 if 的逻辑里面

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();
            // 创建请求对象
            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();
        }
    }
}
复制代码

6. 解析真正的URL地址

    ServiceRequestWrapper 封装了解析地址的方法,这个方法也是在拦截器执行的过程中创建的
image.pngimage.png
    解析地址的方法其实是调用的 RibbonLoadBalancerClient 中的 reconstructURI方法

@Override
public URI getURI() {
    URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
    return uri;
}
复制代码

解析地址详细流程

@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);
}
复制代码

    根据服务实例和URI原生地址为参数,并且调用 LoadBalancerContextreconstructURIWithServer进行地址解析替换得出最终的实际要访问的地址,也就是 http://GOODS-APPLICATION/good/getGoods 转换成http://192.168.2.100:9200/good/getGoods 这个样子

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
    }
    // 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());
    }
    // 创建新的URI
    URI newURI = new URI(sb.toString());
    // http://192.168.2.100:9200/good/getGoods
    return newURI;            

}
复制代码

7. 进行 ClientHttpRequest 请求创建

    创建 SimpleBufferingClientHttpRequest 请求对象,进行后续的方法访问

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) {
        // 创建请求对象
        return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
    }
}
复制代码

8. 进行网络访问

    delegate.execute(), in fact, the final go is the executeInternal method in SimpleBufferingClientHttpRequest

@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
    // 进行构造请求
    addHeaders(this.connection, headers);
    // 进行请求访问
    this.connection.connect();
    // 进行返回结果的封装
    return new SimpleClientHttpResponse(this.connection);
}
复制代码

9. Perform result analysis

    ResponseEntityResponseExtractor Private class inside RestTemplate, call the extractData method of this class for final result parsing

@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();
    }
}
复制代码

Analysis result display

image.png

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() {
        ResponseEntity<ResultObject> result = restTemplate
            .getForEntity(GOODS_SERVICE_URL, ResultObject.class);
        ResultObject resultBody = result.getBody();
        System.out.println(resultBody.getData());
        return resultBody.getStatusMessage();
    }
}
复制代码

output result

image.png

return result

image.png

summary

    In the final analysis, LoadBalancerAutoConfiguration has set its own interceptor in the interceptor requested by RestTemplate when it initializes, which is LoadBalancerInterceptor, so the subsequent methods are processed by this interceptor, such as selecting a load balancer, Obtain the service instance in the load balancer, etc., and then access it through the tool class HttpURLConnection, and parse the returned result through a class inside RestTemplate and return it to the caller

Guess you like

Origin juejin.im/post/7086632160785858596