Abstract:This article focuses on a scenario where you use the RESTTemplate provided by CSE. In fact, the final invocation logic of the RPC Annotation provided by CSE and the RESTTemplate go the same way.

This article is shared from Huawei Cloud Community “I am a request, where should I go from here (2)”, the original author: Xiang Hao.

Last time we saw how the server handles the request, what is the process of sending the request? This article focuses on a scenario where you use the RESTTemplate provided by CSE. In fact, the final invocation logic of the RPC Annotation provided by CSE and the RESTTemplate go the same way.

use

When using the RESTTemplate provided by CSE, it is initialized like this:

RestTemplate restTemplate = RestTemplateBuilder.create();

restTemplate.getForObject("cse://appId:serviceName/xxx", Object.class);

We can notice two oddities:

  • RestTemplate by RestTemplateBuilder. The create () to obtain, rather than in the Spring.
  • The request path starts with CSE instead of HTTP, HTTPS and requires the application ID and service name of the service to which the service belongs.

parsing

Match the RestTemplate based on the URL

First look at the RestTemplateBuilder. The create (), it returns the org. Apache. Servicecomb. The provider. For springmvc. Reference. RestTemplateWrapper, Is a wrapper class provided by CSE.

/ / org. Apache. Servicecomb. The provider. For springmvc. Reference. RestTemplateWrapper / / used to support both cse calls and call the class of cse RestTemplateWrapper extends RestTemplate { private final List<AcceptableRestTemplate> acceptableRestTemplates = new ArrayList<>(); final RestTemplate defaultRestTemplate = new RestTemplate(); RestTemplateWrapper() { acceptableRestTemplates.add(new CseRestTemplate()); } RestTemplate getRestTemplate(String url) { for (AcceptableRestTemplate template : acceptableRestTemplates) { if (template.isAcceptable(url)) { return template; } } return defaultRestTemplate; }}

AcceptableRestTemplate: This class is abstract and inherits RestTemplate. Currently, its subclass is CSERestTemplate. We can also see that we add a CSERestTemplate to our AcceptableTestTemplates by default on initialization.

To the place to use restTemplate. GetForObject: this method will delegate to the following methods:

public <T> T getForObject(String url, Class<T> responseType, Object... urlVariables) throws RestClientException {
    return getRestTemplate(url).getForObject(url, responseType, urlVariables);
}

You can see that getRestTemplate(URL) is called first, that is, template.isacceptable (URL) is called, and cserestTemplate is returned if there is a match, otherwise the regular RestTemplate is returned. Then look at the acceptable () method again:

Here we see what CSE :// does in the path in order to make a request using CSERestTemplate. We also see why RestTemplateWrapper supports both CSE and non-CSE calls.

Delegate invocation

From the above, we can see that our CSE calls are actually delegated to CSERestTemplate. When we construct CSestTemplate, we initialize several things:

public CseRestTemplate() {
    setMessageConverters(Arrays.asList(new CseHttpMessageConverter()));
    setRequestFactory(new CseClientHttpRequestFactory());
    setUriTemplateHandler(new CseUriTemplateHandler());
}

You need to focus on new CseClientHttpRequestFactory () :

public class CseClientHttpRequestFactory implements ClientHttpRequestFactory { @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { return new CseClientHttpRequest(uri, httpMethod); }}

Finally, we delegate to the CSECLienthTTpRequest class, and here’s the big deal!

Let’s bring our attention back to this sentence: RestTemplate. GetForObject (cse: / / appId “:” serviceName/XXX, Object. The class), from the above we know the logic is based on the url to find the corresponding restTemplate first, GetForObject then call this method, finally this method will be called to: org. Springframework. Web. Client. RestTemplate# the doExecute:

protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException { ClientHttpResponse response = null; try { ClientHttpRequest request = createRequest(url, method); if (requestCallback ! = null) { requestCallback.doWithRequest(request); } response = request.execute(); handleResponse(url, method, response); return (responseExtractor ! = null ? responseExtractor.extractData(response) : null); }}

CreateRequest (url, method) : Will call getRequestFactory().createRequest(url, method), which will eventually call the RequestFactory from which we initialized CSECLienthTpRequest. So this is going to return the ClientTTpRequest class.

Request. The execute () : this method will delegate to org. The apache. Servicecomb. The provider. For springmvc. Reference. CseClientHttpRequest# execute this method.

At this point we know that the previous call will eventually delegate to the method cSeclienthTTPrequest# execute.

Cse call

Following the above analysis:

public ClientHttpResponse execute() { path = findUriPath(uri); requestMeta = createRequestMeta(method.name(), uri); QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri.getRawSchemeSpecificPart()); queryParams = queryStringDecoder.parameters(); Object[] args = this.collectArguments(); Return this.invoke(args); return this.invoke(args); }

CreateRequestMeta (Method.name (), URI) : This is mainly based on the MicroServiceName to get the information to call the service, and will get the information into the Map. Service information is as follows:

As you can see, there is a lot of information in it, such as application name, service name, and YAML information for the interface.

This.collecTarguments () : A hidden checkpoint verifies that the arguments passed in conform to the interface’s definition. Mainly through the method: org.apache.servicecomb.com mon. Rest. Codec. RestCodec# restToArgs, if do not conform to the whole process is over.

Prepare invocation

To get to the interface from the above analysis shows that the required parameters would call this method: after org. Apache. Servicecomb. The provider. For springmvc. Reference. CseClientHttpRequest# invoke:

private CseClientHttpResponse invoke(Object[] args) {
    Invocation invocation = prepareInvocation(args);
    Response response = doInvoke(invocation);
 
    if (response.isSuccessed()) {
        return new CseClientHttpResponse(response);
    }
 
    throw ExceptionFactory.convertConsumerException(response.getResult());
}

Prepare-Invocation (ARGS) : This method will prepare the Invocation, the Invocation was analyzed in the last video, but the Invocation was for the server side, so of course we have to work for the consumer side

protected Invocation prepareInvocation(Object[] args) {
    Invocation invocation =
        InvocationFactory.forConsumer(requestMeta.getReferenceConfig(),
            requestMeta.getOperationMeta(),
            args);
 
    return invocation;
}

As can be seen from its name, it serves the consumer side. In fact, the main difference between ForProvider and ForConsumer is that the loading Handler is different. This time, the loading Handler is as follows:

  • The class org. Apache. Servicecomb. QPS. ConsumerQpsFlowControlHandler (flow control)
  • The class org. Apache. Servicecomb. Loadbalance. LoadbalanceHandler (load)
  • The class org. Apache. Servicecomb. Bizkeeper. ConsumerBizkeeperHandler (fault tolerance)
  • Class org. Apache. Servicecomb. Core. Handler. Impl. TransportClientHandler (called, the default to load)

The first three handlers can be referenced in this microservice governance column

DoInvoke (Invocation) : Once the invocation is initialized, the invocation starts. Will call to this method: org. Apache. Servicecomb. Core. The provider. The consumer. InvokerUtils# innerSyncInvoke

So far, these actions are the difference between the RESTTemplate and RPC calls in CSE. It is clear that the RESTTemplate approach only supports synchronous (InnerSyncInvoke), while RPC can support asynchronous (ReactiveInvoke)

public static Response innerSyncInvoke(Invocation invocation) {
 
    invocation.next(respExecutor::setResponse);
}

So we know that the invocation is driven by the liability chain

Start the liability chain of the invocation

Well, here’s our old friend again: Invocation. Next, it’s a classic liability chain with the 4 handlers. I’m not going to analyze the first three, I’m going to jump to TransportClientHandler.

// org.apache.servicecomb.core.handler.impl.TransportClientHandler
public void handle(Invocation invocation, AsyncResponse asyncResp) throws Exception {
    Transport transport = invocation.getTransport();
    transport.send(invocation, asyncResp);
}

Invocation. GetTransport () : Gets the address of the request, and the request will be sent with the same IP :port.

Transport. Send (Invocation, AsyncResp) : The chain is

org.apache.servicecomb.transport.rest.vertx.VertxRestTransport#send

  • – > org. Apache. Servicecomb. Transport. Rest. Client. RestTransportClient# send (here will initialize HttpClientWithContext, analysis below)
  • – > org. Apache. Servicecomb. Transport. Rest. Client. HTTP. RestClientInvocation# invoke (really sends the request)
public void invoke(Invocation invocation, AsyncResponse asyncResp) throws Exception { createRequest(ipPort, path); clientRequest.putHeader(org.apache.servicecomb.core.Const.TARGET_MICROSERVICE, invocation.getMicroserviceName()); RestClientRequestImpl restClientRequest = new RestClientRequestImpl(clientRequest, httpClientWithContext.context(), asyncResp, throwableHandler); invocation.getHandlerContext().put(RestConst.INVOCATION_HANDLER_REQUESTCLIENT, restClientRequest); Buffer requestBodyBuffer = restClientRequest.getBodyBuffer(); HttpServletRequestEx requestEx = new VertxClientRequestToHttpServletRequest(clientRequest, requestBodyBuffer); invocation.getInvocationStageTrace().startClientFiltersRequest(); // Set the filter.BeforeSendRequest method for (HttpClientFilter: httpClientFilters) { if (filter.enabled()) { filter.beforeSendRequest(invocation, requestEx); }} / / thread from the business transfer to the network thread to send / / httpClientWithContext runOnContext}

CreateRequest (ipPort, path) : Initialize the HttpClientRequest with the parameter. The ClientRequest is initialized with a responseHandler, which is the processing of the response.

Note org.apache.servicecomb.com mon. Rest. Filter. HttpClientFilter# afterReceiveResponse call is the stage here, Through callback org. Apache. Servicecomb. Transport. Rest. Client. HTTP. RestClientInvocation# processResponseBody triggered this method (when creating responseHandle Created when r)

And org.apache.servicecomb.com mon. Rest. Filter. HttpClientFilter# beforeSendRequest: this method of trigger we can clearly see the sending request execution.

RequestEx: Note that its type is HttpServletRequestex, and even though the name has a Servlet in it, opening it reveals that many of the methods we use in Tomcat throw exceptions directly, which is also a mistake mistake!

HttpClientWithContext. RunOnContext: used to send the request of logic, but still a bit around here, under the following key analysis

httpClientWithContext.runOnContext

First look at the definition of HttpClientWithContext:

public class HttpClientWithContext { public interface RunHandler { void run(HttpClient httpClient); } private HttpClient httpClient; private Context context; public HttpClientWithContext(HttpClient httpClient, Context context) { this.httpClient = httpClient; this.context = context; } public void runOnContext(RunHandler handler) { context.runOnContext((v) -> { handler.run(httpClient); }); }}

We can see from the above that sending a request calls this method: RunOnContext, which takes the RunHandler interface, is then passed in as a lambda, which takes the HttpClient parameter, which in turn is initialized in the constructor of HttpClientWithContext. The constructor is the org. Apache. Servicecomb. Transport. Rest. Client. RestTransportClient# send this method initialized (call org. Apache. Servicecomb. Transpor T.r est. Client. RestTransportClient# findHttpClientPool this method).

But let’s look at where it’s called:

/ / thread from the business transfer to network thread to send httpClientWithContext. RunOnContext (httpClient - > { clientRequest.setTimeout(operationMeta.getConfig().getMsRequestTimeout()); processServiceCombHeaders(invocation, operationMeta); try { restClientRequest.end(); } catch (Throwable e) { LOGGER.error(invocation.getMarker(), "send http request failed, local:{}, remote: {}.", getLocalAddress(), ipPort, e); fail((ConnectionBase) clientRequest.connection(), e); }});

In fact, HttpClient is not used in this logic. In fact, the action of sending a request is triggered by RestClientRequest.end (), which is the class RestClientRequestImpl in CSE. It then wraps the HttpClientRequest (provided in VertX), which means that RestClientRequest.end () is finally delegated to HttpClientRequest.end().

So how is the HttpClientRequest initialized? It’s initialized in the createRequest(ipPort, path) method, Namely in the calling org. Apache. Servicecomb. Transport. Rest. Client. HTTP. RestClientInvocation# invoke method at the entrance.

The initialization logic is as follows:

clientRequest = httpClientWithContext.getHttpClient().request(method, 
requestOptions, this::handleResponse)

HttpClientWithContext. The getHttpClient () : this method returns the HttpClient, saying the HttpClient will show up, used to initialize the we send the request of Mr Key: HttpClientRequest. The overall logic for sending a request should be clear at this point.

conclusion

Whether you use a RESTTemplate or an RPC annotation to send a request, the underlying logic is the same. It matches the request information to the other side’s service information, and then goes through a series of handlers, such as flow limit, load, fault tolerance, etc. (which is also a good extension mechanism), and finally goes to the TransportClientHandler. Then, the request is initialized according to the condition. After being processed by the HttpClientFilter, the request will be delegated to VertX’s HttpClientRequest to actually issue the request.

Click on the attention, the first time to understand Huawei cloud fresh technology ~