All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.ponfee.disjob.registry.rpc.DestinationServerRestTemplate Maven / Gradle / Ivy

/*
 * Copyright 2022-2024 Ponfee (http://www.ponfee.cn/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.ponfee.disjob.registry.rpc;

import cn.ponfee.disjob.common.spring.RestTemplateUtils;
import cn.ponfee.disjob.common.util.Jsons;
import cn.ponfee.disjob.core.base.*;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * Destination server rest template(Method pattern)
 *
 * @author Ponfee
 */
final class DestinationServerRestTemplate {
    private static final Logger LOG = LoggerFactory.getLogger(DestinationServerRestTemplate.class);

    private static final Set RETRYABLE_HTTP_STATUS = ImmutableSet.of(
        // 4xx
        HttpStatus.REQUEST_TIMEOUT,
        //HttpStatus.CONFLICT,
        //HttpStatus.LOCKED,
        //HttpStatus.FAILED_DEPENDENCY,
        HttpStatus.TOO_EARLY,
        //HttpStatus.PRECONDITION_REQUIRED,
        HttpStatus.TOO_MANY_REQUESTS,

        // 5xx
        // 500:Supervisor内部组件超时(如数据库超时)等场景
        //HttpStatus.INTERNAL_SERVER_ERROR,
        HttpStatus.BAD_GATEWAY,
        HttpStatus.SERVICE_UNAVAILABLE,
        HttpStatus.GATEWAY_TIMEOUT,
        HttpStatus.BANDWIDTH_LIMIT_EXCEEDED
    );

    private final RestTemplate restTemplate;
    private final int retryMaxCount;
    private final long retryBackoffPeriod;

    DestinationServerRestTemplate(RestTemplate restTemplate, RetryProperties retry) {
        retry.check();
        this.restTemplate = Objects.requireNonNull(restTemplate);
        this.retryMaxCount = retry.getMaxCount();
        this.retryBackoffPeriod = retry.getBackoffPeriod();
    }

    /**
     * Invoke remote server
     *
     * @param declaringClass    the declaring class
     * @param destinationServer the destination server
     * @param httpMethod        the http method
     * @param returnType        the return type
     * @param args              the arguments
     * @param                return type
     * @return invoked remote http response
     * @throws Exception if occur exception
     */
     T invoke(Class declaringClass, Server destinationServer, String path,
                 HttpMethod httpMethod, Type returnType, Object... args) throws Exception {
        Map authenticationHeaders = null;
        if (destinationServer instanceof Supervisor && declaringClass.isAssignableFrom(SupervisorRpcService.class)) {
            // `ExtendedSupervisorRpcService`声明的接口是供Supervisor之间的调用,因此不需要带认证信息。
            // `SupervisorRpcService`声明的接口是供Worker调用Supervisor,需要带上Worker的认证信息。
            // 但目前Worker调用Supervisor都是用的DiscoveryServerRestTemplate,因此实际上不会走到此处
            authenticationHeaders = Worker.local().createWorkerAuthenticationHeaders();
        }

        String url = destinationServer.buildHttpUrlPrefix() + path;
        Throwable ex = null;
        for (int i = 0; i <= retryMaxCount; i++) {
            try {
                return RestTemplateUtils.invoke(restTemplate, url, httpMethod, returnType, authenticationHeaders, args);
            } catch (Throwable e) {
                ex = e;
                LOG.error("Invoke server rpc failed [{}]: {}, {}, {}", i, url, Jsons.toJson(args), e.getMessage());
                if (isNotRetry(e)) {
                    break;
                }
                if (i < retryMaxCount) {
                    Thread.sleep((i + 1) * retryBackoffPeriod);
                }
            }
        }

        String msg = (ex == null) ? null : ex.getMessage();
        if (StringUtils.isBlank(msg)) {
            msg = "Invoke server rpc error: " + path;
        }
        throw new RpcInvokeException(msg, ex);
    }

    // ----------------------------------------------------------------------------------------static methods

    static boolean isNotRetry(Throwable e) {
        if (e == null) {
            return true;
        }
        // org.springframework.web.client.ResourceAccessException#getCause may be timeout
        Set set = new HashSet<>();
        for (Throwable t = e; t != null && set.add(t); ) {
            if (isTimeoutException(t)) {
                return false;
            }
            t = t.getCause();
        }
        if (!(e instanceof HttpStatusCodeException)) {
            return true;
        }
        return !RETRYABLE_HTTP_STATUS.contains(((HttpStatusCodeException) e).getStatusCode());
    }

    private static boolean isTimeoutException(Throwable e) {
        if (e == null) {
            return false;
        }
        return (e instanceof java.net.SocketTimeoutException)
            || (e instanceof java.net.SocketException)
            || (e instanceof java.rmi.ConnectException)
            || (e instanceof java.rmi.ConnectIOException)
            || (e instanceof org.apache.http.conn.ConnectTimeoutException);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy