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

com.baidu.discovery.client.api.TianluApi Maven / Gradle / Ivy

package com.baidu.discovery.client.api;

import com.baidu.discovery.client.FormulaContext;
import com.baidu.discovery.client.auth.Authorization;
import com.baidu.discovery.client.bean.InstanceBean;
import com.baidu.discovery.client.bean.ServiceAddBean;
import com.baidu.discovery.client.bean.ServiceBean;
import com.baidu.discovery.client.exception.ServerCommunicationException;
import com.baidu.discovery.client.exception.ServerStatusException;
import com.baidu.discovery.client.model.Instance;
import com.baidu.discovery.client.model.ServiceInfo;
import com.baidu.discovery.client.model.Token;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
 * @author Bowu Dong ([email protected])
 */
public class TianluApi {
    private static final Logger logger = LoggerFactory.getLogger(TianluApi.class);

    public static final String X_BMS_HEADER_PRODUCT_LINE = "X-BMS-PRODUCTLINE-NAME";

    private CloseableHttpClient httpClient;

    private ObjectMapper objectMapper;

    private Authorization authorization;

    public TianluApi() {
        httpClient = HttpClientBuilder.create()
                .build();

        objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        objectMapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        authorization = new Authorization();
    }

    public List getServiceInstances(FormulaContext context, String serviceName) {
        String url = String.format("%s/registry/services/%s/instances",
                context.getClientConfig().getServiceUrl(), serviceId(context, serviceName));
        HttpGet get = new HttpGet(url);
        processHeaders(get);
        addAuthInfo(get, context);

        try (CloseableHttpResponse response = httpClient.execute(get)) {
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) {
                return Collections.emptyList();
            } else if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                throw new ServerStatusException("get service instances error", response);
            }
            byte[] json = EntityUtils.toByteArray(response.getEntity());
            JavaType javaType = objectMapper.getTypeFactory().constructParametricType(List.class, InstanceBean.class);
            List beans = objectMapper.readValue(json, javaType);
            return beans.stream()
                    .map(InstanceBean::getData)
                    .map(data -> {
                Instance.InstanceBuilder builder = Instance.builder();
                builder.instanceId(data.getId())
                        .scheme(data.getScheme())
                        .host(data.getHost())
                        .port(data.getPort())
                        .path(data.getPath())
                        .type(data.getRpcType())
                        .status(data.getStatus())
                        .customs(data.getRpcDesc())
                        .tags(data.getTags())
                        .startTime(data.getStartTime());
                return builder.build();
            }).collect(Collectors.toList());
        } catch (IOException e) {
            throw new ServerCommunicationException("get service instances error!", e);
        }
    }

    public Instance registerInstance(FormulaContext context, Instance instance) {
        prepare(context, instance);
        String url = String.format("%s/registry/services/%s/instances",
                context.getClientConfig().getServiceUrl(),
                serviceId(context, instance.getAppName()));
        HttpPost post = new HttpPost(url);
        processHeaders(post);
        addAuthInfo(post, context);
        InstanceBean.InstanceData data = InstanceBean.InstanceData.builder()
                .id(instance.getInstanceId())
                .scheme(instance.getScheme())
                .host(instance.getHost())
                .port(instance.getPort())
                .rpcType(instance.getType())
                .rpcDesc(instance.getCustoms())
                .startTime(new Date())
                .status(instance.getStatus())
                .idc(instance.getZone())
                .path(instance.getPath())
                .productName(context.getClientConfig().getProductName())
                .serviceId(serviceId(context, instance.getAppName()))
                .tags(instance.getTags())
                .build();
        try {
            byte[] body = objectMapper.writeValueAsBytes(data);
            post.setEntity(new ByteArrayEntity(body));
        } catch (IOException e) {
            throw new ServerCommunicationException("register instance error!", e);
        }

        try (CloseableHttpResponse response = httpClient.execute(post)) {
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
                logger.warn("server returns 401, force refresh product token");
                getProductToken(context, true);
            }
            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
                throw new ServerStatusException("register instance error", data, response);
            }
        } catch (IOException e) {
            throw new ServerCommunicationException("register instance error!", e);
        }
        return instance;
    }

    private void prepare(FormulaContext context, Instance instance) {
        if (!StringUtils.hasText(instance.getZone())) {
            instance.setZone("unknown");
        }
    }

    public Instance removeInstance(FormulaContext context, Instance instance) {
        String url = String.format("%s/registry/services/%s/instances/%s",
                context.getClientConfig().getServiceUrl(),
                serviceId(context, instance.getAppName()),
                instance.getInstanceId());
        HttpDelete delete = new HttpDelete(url);
        processHeaders(delete);
        addAuthInfo(delete, context);
        try (CloseableHttpResponse response = httpClient.execute(delete)) {
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) {
                return instance;
            } else if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                throw new ServerStatusException("delete instance error", response);
            }
        } catch (IOException e) {
            throw new ServerCommunicationException("delete instance error!", e);
        }
        return instance;
    }

    public Token getProductToken(FormulaContext context) {
        return getProductToken(context, false);
    }

    public Token getProductToken(FormulaContext context, boolean force) {
        if (context.getClientConfig().getProductKey() == null) {
            return null;
        }

        String productName = context.getClientConfig().getProductName();

        if (!force && context.getToken(productName) != null
                && context.getToken(productName).getInvalidTime().getTime() > System.currentTimeMillis()) {
            return context.getToken(productName);
        }

        Token prev = force ? context.getToken(productName) : null;

        String url;
        try {
            url = String.format("%s/registry/authentication/http?productKey=%s",
                    context.getClientConfig().getServiceUrl(),
                    URLEncoder.encode(context.getClientConfig().getProductKey(), "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        HttpUriRequest request = new HttpGet(url);
        processHeaders(request);

        try (CloseableHttpResponse response = httpClient.execute(request)) {
            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
                throw new ServerStatusException("get product token error", response);
            }

            byte[] entity = EntityUtils.toByteArray(response.getEntity());
            Token token = objectMapper.readValue(entity, Token.class);
            context.putToken(productName, token);

            if (force) {
                logger.info("FORCE get product key, prev={}, current={}", prev, token);
            }

            return token;
        } catch (UnsupportedEncodingException e) {
            throw new ServerCommunicationException("utf-8 not support", e);
        } catch (IOException e) {
            throw new ServerCommunicationException(e);
        }
    }

    private void processHeaders(HttpUriRequest request) {
        String agent = String.format("Tianlu-Discovery/1.0.0 (Java/%s)",  System.getProperty("java.version"));
        request.setHeader("User-Agent", agent);
        request.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
        request.setHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
    }

    private void addAuthInfo(HttpUriRequest request, FormulaContext context) {
        if (context.getClientConfig().getHeaders() != null) {
            context.getClientConfig().getHeaders().forEach(request::setHeader);
        }

        if (context.getClientConfig().getProductKey() != null) {
            // 天路认证
            String productName = context.getClientConfig().getProductName();
            String auth = String.format("Basic %s", context.getToken(productName).getToken());
            request.setHeader(HttpHeaders.AUTHORIZATION, auth);
        }
//        else {
//            // 云上AK/SK认证
//            authorization.sign(request, context.getClientConfig().getAccessKey(),
//                    context.getClientConfig().getSecretKey());
//        }
        if (request instanceof HttpRequestBase) {
            RequestConfig requestConfig = RequestConfig.custom()
                    .setSocketTimeout(context.getClientConfig().getSocketTimeoutMs())
                    .setConnectTimeout(context.getClientConfig().getConnectTimeoutMs())
                    .build();
            ((HttpRequestBase) request).setConfig(requestConfig);
        }
    }

    public ServiceAddBean addService(FormulaContext context, String serviceName) {
        // 因注册服务时需要携带custom信息,天路registry暂无法使用,改用management接口
        String url = String.format("%s/management/products/%s/services",
                context.getClientConfig().getServiceUrl(),
                context.getClientConfig().getProductName());
        HttpPost request = new HttpPost(url);
        processHeaders(request);
        addAuthInfo(request, context);
        Map service = new HashMap<>();
        service.put("id", serviceId(context, serviceName));
        service.put("productName", context.getClientConfig().getProductName());
        service.put("desc", serviceId(context, serviceName));
        service.put("doc", serviceId(context, serviceName));
        service.put("status", 1);
        service.put("contacts", Collections.singletonList("jarvis"));
        service.put("createTime", new Date());
        service.put("updateTime", new Date());
        service.put("createUser", "jarvis");
        service.put("updateUser", "jarvis");
        Map custom = new HashMap<>();
        // serviceType: SpringCloud or ServiceMesh.
        custom.put("serviceType", "SpringCloud");
        service.put("custom", custom);

        try {
            byte[] json = objectMapper.writeValueAsBytes(service);
            request.setEntity(new ByteArrayEntity(json));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        try (CloseableHttpResponse response = httpClient.execute(request)) {
            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
                throw new ServerStatusException("add service", Collections.singletonList(service), response);
            }
            String body = EntityUtils.toString(response.getEntity());
            logger.debug("add Service return: {}", body);
            return objectMapper.readValue(body, ServiceAddBean.class);
        } catch (IOException e) {
            throw new ServerCommunicationException("add service failed! ", e);
        }
    }

    /**
     * 实例ID采用
     * @param context
     * @param instance
     * @return
     */
    private String serviceId(FormulaContext context, Instance instance) {
        return serviceId(context, instance.getAppName());
    }

    private String serviceId(FormulaContext context, String serviceName) {
        String productName = context.getClientConfig().getProductName();
        ServiceInfo serviceInfo = getServiceInfo(serviceName);
        String resourceIsolation = serviceInfo.getResourceIsolation();
        String originServiceName = serviceInfo.getOriginServiceName();
        // 服务名中不存在时,默认采用配置中的隔离信息
        if (StringUtils.isEmpty(resourceIsolation)) {
            resourceIsolation = context.getClientConfig().getResourceIsolation();
        }
        return String.format("%s_%s_%s", productName, resourceIsolation, originServiceName);
    }

    /**
     * 获取原生服务名
     * @param serviceName eg.workspaceName.serviceName
     * @return The origin ServiceName
     */
    public String getOriginServiceName(String serviceName) {
        return getServiceInfo(serviceName).getOriginServiceName();
    }

    /**
     * 获取服务信息
     * @param serviceName eg.workspaceName.serviceName
     * @return The ServiceInfo contains resource isolation && the origin service name.
     */
    public ServiceInfo getServiceInfo(String serviceName) {
        String resourceIsolation = "";
        String originServiceName = serviceName;
        // 跨workspace交互方式: workspaceName.serviceName
        if (serviceName.contains(".")) {
            String[] serviceInfoArray = serviceName.split("\\.");
            if (serviceInfoArray.length != 2) {
                throw new IllegalArgumentException("Illegal serviceName argument: " + serviceName);
            }
            resourceIsolation = serviceInfoArray[0];
            originServiceName = serviceInfoArray[1];
        }
        ServiceInfo serviceInfo = new ServiceInfo();
        serviceInfo.setResourceIsolation(resourceIsolation);
        serviceInfo.setOriginServiceName(originServiceName);
        return serviceInfo;
    }

    public ServiceBean.ServiceData heartBeat(FormulaContext context, Instance instance) {
        String url = String.format("%s/registry/provider/heartbeat?time=%s",
                context.getClientConfig().getServiceUrl(),
                60
        );
        HttpPut request = new HttpPut(url);
        processHeaders(request);
        addAuthInfo(request, context);
        try {
            String body = objectMapper.writeValueAsString(Collections.singletonList(instance.getInstanceId()));
            request.setEntity(new StringEntity(body));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        try (CloseableHttpResponse response = httpClient.execute(request)) {
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
                logger.warn("server returns 401, force refresh product token");
                getProductToken(context, true);
            } else if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                throw new ServerStatusException("heart beat", url, response);
            }

            String json = EntityUtils.toString(response.getEntity());
            JavaType javaType = objectMapper.getTypeFactory().constructParametricType(List.class,
                    ServiceBean.ServiceData.class);
            List list = objectMapper.readValue(json, javaType);
            logger.debug("heart beat response: {}", json);

            if (list == null || list.stream()
                    .noneMatch(d -> d.getServiceId().contains(serviceId(context, instance)))) {
                // instance fail, registering
                logger.warn("instance {} failed, registering", instance.getInstanceId());
                registerInstance(context, instance);
                return null;
            }

            return CollectionUtils.isEmpty(list) ? null : list.get(0);
        } catch (IOException e) {
            throw new ServerCommunicationException("heart beat error!, url=" + url, e);
        }
    }

    public ServiceBean getService(FormulaContext context, String serviceName) {
        String url = String.format("%s/registry/services/%s",
                context.getClientConfig().getServiceUrl(),
                serviceId(context, serviceName));
        HttpGet get = new HttpGet(url);
        processHeaders(get);
        addAuthInfo(get, context);

        try (CloseableHttpResponse response = httpClient.execute(get)) {
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) {
                return null;
            } else if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                throw new ServerStatusException("get service", response);
            }
            byte[] json = EntityUtils.toByteArray(response.getEntity());
            return objectMapper.readValue(json, ServiceBean.class);
        } catch (IOException e) {
            throw new ServerCommunicationException("get service error!", e);
        }
    }

    public void close() {
        try {
            httpClient.close();
        } catch (IOException e) {
            // do nothing
        }
    }

    public List getServices(FormulaContext context) {
        String url = String.format("%s/management/products/%s/services",
                context.getClientConfig().getServiceUrl(),
                context.getClientConfig().getProductName());
        HttpGet get = new HttpGet(url);
        addAuthInfo(get, context);
        processHeaders(get);
        try (CloseableHttpResponse response = httpClient.execute(get)) {
            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                throw new ServerStatusException("get services error!", response);
            }
            JavaType type = objectMapper.getTypeFactory().constructParametricType(List.class, ServiceBean.class);
            List serviceBeans = objectMapper.readValue(
                    EntityUtils.toByteArray(response.getEntity()), type);
            return serviceBeans.stream()
                    .filter(bean -> belongsTo(bean, context))
                    .map(bean -> bean.getData().getId().replace(serviceId(context, ""), ""))
                    .collect(Collectors.toList());
        } catch (IOException e) {
            throw new ServerCommunicationException("get services error!", e);
        }
    }

    private boolean belongsTo(ServiceBean bean, FormulaContext context) {
        String prefix = serviceId(context, "");
        return bean.getData().getId().startsWith(prefix);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy