diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..eb800b81fba --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,26 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Sample code: + + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Information** + +Java chassis version: + + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..bbcbbe7d615 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/q---a.md b/.github/ISSUE_TEMPLATE/q---a.md new file mode 100644 index 00000000000..3624412c5d7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/q---a.md @@ -0,0 +1,23 @@ +--- +name: Q & A +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + + +**Describe the problem** +A clear and concise description of what the bug is. + +**Sample code** +A clear and concise description of what the bug is. + +**Information** + +Java chassis version: + + +**Additional context** +Add any other context about the problem here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 478ce6a246d..b8f6e71dbe3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,11 +21,11 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" - package-ecosystem: "maven" directory: "/" schedule: - interval: "daily" + interval: "weekly" open-pull-requests-limit: 20 ignore: - dependency-name: "asciidoctorj" diff --git a/.github/workflows/checkstyle.yml b/.github/workflows/checkstyle.yml index fb1e00c9881..dab1228a695 100644 --- a/.github/workflows/checkstyle.yml +++ b/.github/workflows/checkstyle.yml @@ -21,7 +21,7 @@ name: checkstyle on: pull_request: branches: - - master + - 2.8.x jobs: checkstyle: diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 9b0866b8028..6037e44da11 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -21,9 +21,9 @@ name: Java CI with Maven on: push: - branches: [ master ] + branches: [ 2.8.x ] pull_request: - branches: [ master ] + branches: [ 2.8.x ] jobs: build: @@ -38,6 +38,10 @@ jobs: with: java-version: '8' distribution: 'temurin' + - name: Set up Maven + uses: stCarolas/setup-maven@v4.5 + with: + maven-version: 3.8.4 - uses: actions/cache@v3 with: path: ~/.m2/repository diff --git a/.github/workflows/rat_check.yml b/.github/workflows/rat_check.yml index c2996d2b093..0148c9020e9 100644 --- a/.github/workflows/rat_check.yml +++ b/.github/workflows/rat_check.yml @@ -21,10 +21,10 @@ name: rat check on: push: branches: - - master + - 2.8.x pull_request: branches: - - master + - 2.8.x jobs: rat_check: diff --git a/.github/workflows/spotbugs.yml b/.github/workflows/spotbugs.yml index 0b8620e1dfb..dddae01a952 100644 --- a/.github/workflows/spotbugs.yml +++ b/.github/workflows/spotbugs.yml @@ -21,7 +21,7 @@ name: spot bugs on: pull_request: branches: - - master + - 2.8.x jobs: spotbugs: diff --git a/.github/workflows/unit-test-jdk11.yml b/.github/workflows/unit-test-jdk11.yml index 2f7b6c6bf47..39f644a2dc2 100644 --- a/.github/workflows/unit-test-jdk11.yml +++ b/.github/workflows/unit-test-jdk11.yml @@ -22,7 +22,7 @@ name: Unit Test Jdk11 on: pull_request: branches: - - master + - 2.8.x jobs: unit-tests: diff --git a/.github/workflows/unit-test-jdk17.yml b/.github/workflows/unit-test-jdk17.yml index 05e82d3d54d..20102a3aa58 100644 --- a/.github/workflows/unit-test-jdk17.yml +++ b/.github/workflows/unit-test-jdk17.yml @@ -22,7 +22,7 @@ name: Unit Test Jdk17 on: pull_request: branches: - - master + - 2.8.x jobs: unit-tests: diff --git a/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterAddressManager.java b/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterAddressManager.java index 3b346153a0b..62dcb7f8da9 100644 --- a/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterAddressManager.java +++ b/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterAddressManager.java @@ -28,8 +28,9 @@ public class ConfigCenterAddressManager extends AbstractAddressManager { - public ConfigCenterAddressManager(String projectName, List addresses, EventBus eventBus) { - super(projectName, addresses); + public ConfigCenterAddressManager(String projectName, List addresses, String ownRegion, + String ownAvailableZone, EventBus eventBus) { + super(projectName, addresses, ownRegion, ownAvailableZone); eventBus.register(this); } diff --git a/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterClient.java b/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterClient.java index a8ee3b99ec0..1b6c4e70fc4 100644 --- a/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterClient.java +++ b/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterClient.java @@ -18,7 +18,9 @@ package org.apache.servicecomb.config.center.client; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; @@ -30,10 +32,14 @@ import org.apache.servicecomb.http.client.common.HttpResponse; import org.apache.servicecomb.http.client.common.HttpTransport; import org.apache.servicecomb.http.client.common.HttpUtils; +import org.apache.servicecomb.http.client.event.OperationEvents.UnAuthorizedOperationEvent; +import org.apache.servicecomb.http.client.utils.ServiceCombServiceAvailableUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.eventbus.EventBus; public class ConfigCenterClient implements ConfigCenterOperation { private static final Logger LOGGER = LoggerFactory.getLogger(ConfigCenterClient.class); @@ -48,24 +54,34 @@ public class ConfigCenterClient implements ConfigCenterOperation { public static final String DARK_LAUNCH = "darklaunch@"; + private static final String ADDRESS_CHECK_PATH = "/v3/default/configuration/health?mode=readiness"; + private final HttpTransport httpTransport; private final ConfigCenterAddressManager addressManager; + private final Map> dimensionConfigNames = new HashMap<>(); + + private EventBus eventBus; + public ConfigCenterClient(ConfigCenterAddressManager addressManager, HttpTransport httpTransport) { this.addressManager = addressManager; this.httpTransport = httpTransport; } + public void setEventBus(EventBus eventBus) { + this.eventBus = eventBus; + addressManager.setEventBus(eventBus); + } + @Override - public QueryConfigurationsResponse queryConfigurations(QueryConfigurationsRequest request) { + public QueryConfigurationsResponse queryConfigurations(QueryConfigurationsRequest request, String address) { String dimensionsInfo = buildDimensionsInfo(request, true); QueryConfigurationsResponse queryConfigurationsResponse = new QueryConfigurationsResponse(); Map configurations = new HashMap<>(); String uri = null; - String address = addressManager.address(); try { uri = address + "/configuration/items?dimensionsInfo=" + HttpUtils.encodeURLParam(dimensionsInfo) + "&revision=" + request.getRevision(); @@ -76,6 +92,7 @@ public QueryConfigurationsResponse queryConfigurations(QueryConfigurationsReques HttpRequest.GET); HttpResponse httpResponse = httpTransport.doRequest(httpRequest); + recordAndSendUnAuthorizedEvent(httpResponse, address); if (httpResponse.getStatusCode() == HttpStatus.SC_OK) { Map> allConfigMap = HttpUtils.deserialize( httpResponse.getContent(), @@ -88,31 +105,38 @@ public QueryConfigurationsResponse queryConfigurations(QueryConfigurationsReques if (allConfigMap.get(APPLICATION_CONFIG) != null) { configurations.putAll(allConfigMap.get(APPLICATION_CONFIG)); + logConfigurationNames(APPLICATION_CONFIG, allConfigMap.get(APPLICATION_CONFIG)); } if (allConfigMap.get(buildDimensionsInfo(request, false)) != null) { configurations.putAll(allConfigMap.get(buildDimensionsInfo(request, false))); + logConfigurationNames(buildDimensionsInfo(request, false), + allConfigMap.get(buildDimensionsInfo(request, false))); } if (allConfigMap.get(buildDarkLaunchDimensionsInfo(request)) != null) { configurations.putAll(allConfigMap.get(buildDarkLaunchDimensionsInfo(request))); + logConfigurationNames(buildDarkLaunchDimensionsInfo(request), + allConfigMap.get(buildDarkLaunchDimensionsInfo(request))); } if (allConfigMap.get(dimensionsInfo) != null) { configurations.putAll(allConfigMap.get(dimensionsInfo)); + logConfigurationNames(dimensionsInfo, allConfigMap.get(dimensionsInfo)); } queryConfigurationsResponse.setConfigurations(configurations); queryConfigurationsResponse.setChanged(true); - addressManager.recordSuccessState(address); return queryConfigurationsResponse; } else if (httpResponse.getStatusCode() == HttpStatus.SC_NOT_MODIFIED) { queryConfigurationsResponse.setChanged(false); - addressManager.recordSuccessState(address); + return queryConfigurationsResponse; + } else if (httpResponse.getStatusCode() == HttpStatus.SC_TOO_MANY_REQUESTS) { + LOGGER.warn("rate limited, keep the local dimension [{}] configs unchanged.", dimensionsInfo); + queryConfigurationsResponse.setChanged(false); return queryConfigurationsResponse; } else if (httpResponse.getStatusCode() == HttpStatus.SC_BAD_REQUEST) { throw new OperationException("Bad request for query configurations."); } else { - addressManager.recordFailState(address); throw new OperationException( "read response failed. status:" + httpResponse.getStatusCode() @@ -124,10 +148,57 @@ public QueryConfigurationsResponse queryConfigurations(QueryConfigurationsReques } catch (IOException e) { addressManager.recordFailState(address); LOGGER.error("query configuration from {} failed, message={}", uri, e.getMessage()); - throw new OperationException("", e); + throw new OperationException("query configuration failed!", e); + } + } + + private void recordAndSendUnAuthorizedEvent(HttpResponse response, String address) { + if (this.eventBus != null && response.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + LOGGER.warn("query configuration unauthorized from server [{}], message [{}]", address, response.getMessage()); + addressManager.recordFailState(address); + this.eventBus.post(new UnAuthorizedOperationEvent(address)); + } else { + addressManager.recordSuccessState(address); } } + /** + * Only the name of the new configuration item is printed. + * No log is printed when the configuration content is updated. + * + * @param dimension dimension + * @param configs configs + */ + private void logConfigurationNames(String dimension, Map configs) { + if (CollectionUtils.isEmpty(configs)) { + return; + } + List configNames = dimensionConfigNames.get(dimension); + if (configNames == null) { + configNames = new ArrayList<>(); + } + StringBuilder names = new StringBuilder(); + for (String key : configs.keySet()) { + if (configNames.contains(key)) { + continue; + } + names.append(key).append(","); + configNames.add(key); + } + if (names.length() == 0) { + return; + } + dimensionConfigNames.put(dimension, configNames); + String fileNames = names.substring(0, names.length() - 1); + LOGGER.info("pulling dimension [{}] configurations success, get config names: [{}].", + dimension, fileNames); + } + + @Override + public void checkAddressAvailable(String address) { + ServiceCombServiceAvailableUtils.checkAddressAvailable(addressManager, address, httpTransport, ADDRESS_CHECK_PATH); + } + private String buildDimensionsInfo(QueryConfigurationsRequest request, boolean withVersion) { String result = request.getServiceName() + DEFAULT_APP_SEPARATOR diff --git a/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterManager.java b/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterManager.java index 9a89b92a6dc..0ecded5226d 100644 --- a/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterManager.java +++ b/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterManager.java @@ -17,6 +17,7 @@ package org.apache.servicecomb.config.center.client; +import java.util.List; import java.util.Map; import org.apache.servicecomb.config.center.client.model.ConfigCenterConfiguration; @@ -45,13 +46,16 @@ public class ConfigCenterManager extends AbstractTask { private final ConfigCenterConfiguration configCenterConfiguration; - public ConfigCenterManager(ConfigCenterClient configCenterClient, EventBus eventBus, - ConfigConverter configConverter, ConfigCenterConfiguration configCenterConfiguration) { + private final ConfigCenterAddressManager configCenterAddressManager; + + public ConfigCenterManager(ConfigCenterClient configCenterClient, EventBus eventBus, ConfigConverter configConverter, + ConfigCenterConfiguration configCenterConfiguration, ConfigCenterAddressManager configCenterAddressManager) { super("config-center-configuration-task"); this.configCenterClient = configCenterClient; this.eventBus = eventBus; this.configConverter = configConverter; this.configCenterConfiguration = configCenterConfiguration; + this.configCenterAddressManager = configCenterAddressManager; } public void setQueryConfigurationsRequest(QueryConfigurationsRequest queryConfigurationsRequest) { @@ -60,6 +64,8 @@ public void setQueryConfigurationsRequest(QueryConfigurationsRequest queryConfig public void startConfigCenterManager() { this.startTask(new PollConfigurationTask(0)); + schedulerCheckAddressAvailable("cc-addr-check", new CheckConfigCenterAddressTask(), + configCenterConfiguration.getRefreshIntervalInMillis()); } class PollConfigurationTask implements Task { @@ -72,7 +78,8 @@ public PollConfigurationTask(int failCount) { @Override public void execute() { try { - QueryConfigurationsResponse response = configCenterClient.queryConfigurations(queryConfigurationsRequest); + QueryConfigurationsResponse response = configCenterClient.queryConfigurations(queryConfigurationsRequest, + configCenterAddressManager.address()); if (response.isChanged()) { queryConfigurationsRequest.setRevision(response.getRevision()); Map lastData = configConverter.updateData(response.getConfigurations()); @@ -82,9 +89,23 @@ public void execute() { } startTask(new BackOffSleepTask(configCenterConfiguration.getRefreshIntervalInMillis(), new PollConfigurationTask(0))); } catch (Exception e) { - LOGGER.error("get configurations from ConfigCenter failed, and will try again.", e); + LOGGER.warn("get configurations from ConfigCenter failed, and will try again, cause message: {}. current " + + "fail does not affect the obtained historical configuration.", e.getMessage()); startTask(new BackOffSleepTask(failCount + 1, new PollConfigurationTask(failCount + 1))); } } } + + class CheckConfigCenterAddressTask implements Runnable { + @Override + public void run() { + List isolationAddresses = configCenterAddressManager.getIsolationAddresses(); + if (isolationAddresses.isEmpty()) { + return; + } + for (String address : isolationAddresses) { + configCenterClient.checkAddressAvailable(address); + } + } + } } diff --git a/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterOperation.java b/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterOperation.java index 5e3a2207067..919e9f30cd7 100644 --- a/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterOperation.java +++ b/clients/config-center-client/src/main/java/org/apache/servicecomb/config/center/client/ConfigCenterOperation.java @@ -25,8 +25,16 @@ public interface ConfigCenterOperation { /** * 根据查询条件查询配置项。 * @param request 查询的维度(project, application, serviceName, version) 和 revision 信息。 + * @param address 查询的配置中心地址。 * @return 如果存在配置变更,返回全量的配置项, changed = true。 如果没有变更, 返回 null, changed = false, * @throws OperationException If some problems happened to contact service center or non http 200 returned. */ - QueryConfigurationsResponse queryConfigurations(QueryConfigurationsRequest request); + QueryConfigurationsResponse queryConfigurations(QueryConfigurationsRequest request, String address); + + /** + * Check config center isolation address available + * + * @param address isolation address + */ + void checkAddressAvailable(String address); } diff --git a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/KieClient.java b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/KieClient.java index 1e099e030f1..175baadfbbf 100644 --- a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/KieClient.java +++ b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/KieClient.java @@ -17,11 +17,16 @@ package org.apache.servicecomb.config.kie.client; +import com.google.common.eventbus.EventBus; + import java.io.StringReader; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.stream.Collectors; @@ -41,15 +46,20 @@ import org.apache.servicecomb.http.client.common.HttpResponse; import org.apache.servicecomb.http.client.common.HttpTransport; import org.apache.servicecomb.http.client.common.HttpUtils; +import org.apache.servicecomb.http.client.event.OperationEvents.UnAuthorizedOperationEvent; +import org.apache.servicecomb.http.client.utils.ServiceCombServiceAvailableUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; import org.springframework.core.io.ByteArrayResource; +import org.springframework.util.CollectionUtils; public class KieClient implements KieConfigOperation { private static final Logger LOGGER = LoggerFactory.getLogger(KieClient.class); + private static final String ADDRESS_CHECK_PATH = "/v1/health"; + protected HttpTransport httpTransport; protected String revision = "0"; @@ -60,15 +70,23 @@ public class KieClient implements KieConfigOperation { public static final String DEFAULT_KIE_API_VERSION = "v1"; + private final Map> dimensionConfigNames = new HashMap<>(); + + private EventBus eventBus; + public KieClient(KieAddressManager addressManager, HttpTransport httpTransport, KieConfiguration kieConfiguration) { this.httpTransport = httpTransport; this.addressManager = addressManager; this.kieConfiguration = kieConfiguration; } + public void setEventBus(EventBus eventBus) { + this.eventBus = eventBus; + addressManager.setEventBus(eventBus); + } + @Override - public ConfigurationsResponse queryConfigurations(ConfigurationsRequest request) { - String address = addressManager.address(); + public ConfigurationsResponse queryConfigurations(ConfigurationsRequest request, String address) { String url = buildUrl(request, address); try { if (kieConfiguration.isEnableLongPolling()) { @@ -77,15 +95,16 @@ public ConfigurationsResponse queryConfigurations(ConfigurationsRequest request) HttpRequest httpRequest = new HttpRequest(url, null, null, HttpRequest.GET); HttpResponse httpResponse = httpTransport.doRequest(httpRequest); + recordAndSendUnAuthorizedEvent(httpResponse, address); ConfigurationsResponse configurationsResponse = new ConfigurationsResponse(); if (httpResponse.getStatusCode() == HttpStatus.SC_OK) { revision = httpResponse.getHeader("X-Kie-Revision"); KVResponse allConfigList = HttpUtils.deserialize(httpResponse.getContent(), KVResponse.class); + logConfigurationNames(request.getLabelsQuery(), allConfigList.getData()); Map configurations = getConfigByLabel(allConfigList); configurationsResponse.setConfigurations(configurations); configurationsResponse.setChanged(true); configurationsResponse.setRevision(revision); - addressManager.recordSuccessState(address); return configurationsResponse; } if (httpResponse.getStatusCode() == HttpStatus.SC_BAD_REQUEST) { @@ -93,19 +112,70 @@ public ConfigurationsResponse queryConfigurations(ConfigurationsRequest request) } if (httpResponse.getStatusCode() == HttpStatus.SC_NOT_MODIFIED) { configurationsResponse.setChanged(false); - addressManager.recordSuccessState(address); return configurationsResponse; } - addressManager.recordFailState(address); + if (httpResponse.getStatusCode() == HttpStatus.SC_TOO_MANY_REQUESTS) { + LOGGER.warn("rate limited, keep the local dimension [{}] configs unchanged.", request.getLabelsQuery()); + configurationsResponse.setChanged(false); + return configurationsResponse; + } throw new OperationException( "read response failed. status:" + httpResponse.getStatusCode() + "; message:" + httpResponse.getMessage() + "; content:" + httpResponse.getContent()); } catch (Exception e) { + addressManager.recordFailState(address); LOGGER.error("query configuration from {} failed, message={}", url, e.getMessage()); throw new OperationException("read response failed. ", e); } } + private void recordAndSendUnAuthorizedEvent(HttpResponse response, String address) { + if (this.eventBus != null && response.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + LOGGER.warn("query configuration unauthorized from server [{}], message [{}]", address, response.getMessage()); + addressManager.recordFailState(address); + this.eventBus.post(new UnAuthorizedOperationEvent(address)); + } else { + addressManager.recordSuccessState(address); + } + } + + /** + * Only the name of the new configuration item is printed. + * No log is printed when the configuration content is updated. + * + * @param dimension dimension + * @param data configs-data + */ + private void logConfigurationNames(String dimension, List data) { + if (CollectionUtils.isEmpty(data)) { + return; + } + List configNames = dimensionConfigNames.get(dimension); + if (configNames == null) { + configNames = new ArrayList<>(); + } + StringBuilder names = new StringBuilder(); + for (KVDoc doc : data) { + if (configNames.contains(doc.getKey())) { + continue; + } + names.append(doc.getKey()).append(","); + configNames.add(doc.getKey()); + } + if (names.length() == 0) { + return; + } + dimensionConfigNames.put(dimension, configNames); + String fileNames = names.substring(0, names.length() - 1); + LOGGER.info("pulling dimension [{}] configurations success, get config names: [{}].", + dimension, fileNames); + } + + @Override + public void checkAddressAvailable(String address) { + ServiceCombServiceAvailableUtils.checkAddressAvailable(addressManager, address, httpTransport, ADDRESS_CHECK_PATH); + } + private String buildUrl(ConfigurationsRequest request, String currentAddress) { StringBuilder sb = new StringBuilder(); sb.append(currentAddress); @@ -126,6 +196,7 @@ private String buildUrl(ConfigurationsRequest request, String currentAddress) { private Map getConfigByLabel(KVResponse resp) { Map resultMap = new HashMap<>(); resp.getData().stream() + .sorted(Comparator.comparing(KVDoc::getUpdateTime, Comparator.nullsFirst(Comparator.naturalOrder()))) .filter(doc -> doc.getStatus() == null || ConfigConstants.STATUS_ENABLED.equalsIgnoreCase(doc.getStatus())) .map(this::processValueType) .collect(Collectors.toList()) diff --git a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/KieConfigManager.java b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/KieConfigManager.java index 7c1eaf92383..f5a6dfd070c 100644 --- a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/KieConfigManager.java +++ b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/KieConfigManager.java @@ -27,6 +27,7 @@ import org.apache.servicecomb.config.kie.client.model.ConfigurationsRequest; import org.apache.servicecomb.config.kie.client.model.ConfigurationsRequestFactory; import org.apache.servicecomb.config.kie.client.model.ConfigurationsResponse; +import org.apache.servicecomb.config.kie.client.model.KieAddressManager; import org.apache.servicecomb.config.kie.client.model.KieConfiguration; import org.apache.servicecomb.http.client.task.AbstractTask; import org.apache.servicecomb.http.client.task.Task; @@ -51,9 +52,11 @@ public class KieConfigManager extends AbstractTask { private final KieConfiguration kieConfiguration; + private final KieAddressManager kieAddressManager; + + public KieConfigManager(KieConfigOperation configKieClient, EventBus eventBus, - KieConfiguration kieConfiguration, - ConfigConverter configConverter) { + KieConfiguration kieConfiguration, ConfigConverter configConverter, KieAddressManager kieAddressManager) { super("config-center-configuration-task"); this.configurationsRequests = ConfigurationsRequestFactory.buildConfigurationRequests(kieConfiguration); this.configurationsRequests.sort(ConfigurationsRequest::compareTo); @@ -61,30 +64,46 @@ public KieConfigManager(KieConfigOperation configKieClient, EventBus eventBus, this.eventBus = eventBus; this.configConverter = configConverter; this.kieConfiguration = kieConfiguration; + this.kieAddressManager = kieAddressManager; } public void firstPull() { + Map data = new HashMap<>(); try { - Map data = new HashMap<>(); - this.configurationsRequests.forEach(r -> { - r.setRevision(ConfigurationsRequest.INITIAL_REVISION); - ConfigurationsResponse response = configKieClient.queryConfigurations(r); - if (response.isChanged()) { - r.setRevision(response.getRevision()); - r.setLastRawData(response.getConfigurations()); - data.putAll(response.getConfigurations()); - } - }); - this.configConverter.updateData(data); - } catch (RuntimeException e) { + firstQueryConfigurations(data); + } catch (Exception e) { if (this.kieConfiguration.isFirstPullRequired()) { throw e; } else { - LOGGER.warn("first pull failed, and ignore {}", e.getMessage()); + LOGGER.warn("first pull failed!"); } } } + private void firstQueryConfigurations(Map data) { + for (int i = 0; i < 3;) { + String address = kieAddressManager.address(); + try { + this.configurationsRequests.forEach(r -> { + r.setRevision(ConfigurationsRequest.INITIAL_REVISION); + ConfigurationsResponse response = configKieClient.queryConfigurations(r, address); + if (response.isChanged()) { + r.setRevision(response.getRevision()); + r.setLastRawData(response.getConfigurations()); + data.putAll(response.getConfigurations()); + } + }); + this.configConverter.updateData(data); + break; + } catch (Exception e) { + if (i == 2) { + throw e; + } + LOGGER.warn("firstQueryConfigurations failed, config address {} and ignore {}", address, e.getMessage()); + } + i++; + } + } private void onDataChanged() { Map latestData = new HashMap<>(); this.configurationsRequests.forEach(r -> latestData.putAll(r.getLastRawData())); @@ -106,6 +125,8 @@ protected void initTaskPool(String taskName) { public void startConfigKieManager() { this.configurationsRequests.forEach((t) -> this.startTask(new PollConfigurationTask(0, t))); + schedulerCheckAddressAvailable("kie-addr-check", new CheckKieAddressTask(), + kieConfiguration.getRefreshIntervalInMillis()); } class PollConfigurationTask implements Task { @@ -121,7 +142,8 @@ public PollConfigurationTask(int failCount, ConfigurationsRequest configurations @Override public void execute() { try { - ConfigurationsResponse response = configKieClient.queryConfigurations(configurationsRequest); + ConfigurationsResponse response = configKieClient.queryConfigurations(configurationsRequest, + kieAddressManager.address()); if (response.isChanged()) { configurationsRequest.setRevision(response.getRevision()); configurationsRequest.setLastRawData(response.getConfigurations()); @@ -133,10 +155,24 @@ public void execute() { startTask(new BackOffSleepTask(kieConfiguration.getRefreshIntervalInMillis(), new PollConfigurationTask(0, this.configurationsRequest))); } } catch (Exception e) { - LOGGER.error("get configurations from KieConfigCenter failed, and will try again.", e); + LOGGER.warn("get configurations from KieConfigCenter failed, and will try again, cause message: {}. current " + + "fail does not affect the obtained historical configuration.", e.getMessage()); startTask( new BackOffSleepTask(failCount + 1, new PollConfigurationTask(failCount + 1, this.configurationsRequest))); } } } + + class CheckKieAddressTask implements Runnable { + @Override + public void run() { + List isolationAddresses = kieAddressManager.getIsolationAddresses(); + if (isolationAddresses.isEmpty()) { + return; + } + for (String address : isolationAddresses) { + configKieClient.checkAddressAvailable(address); + } + } + } } diff --git a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/KieConfigOperation.java b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/KieConfigOperation.java index 364774fd22a..135ad0c220a 100644 --- a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/KieConfigOperation.java +++ b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/KieConfigOperation.java @@ -26,8 +26,16 @@ public interface KieConfigOperation { /** * 根据查询条件查询配置项。 * @param request 查询的维度(project, application, serviceName, version) 和 revision 信息。 + * @param address 查询的配置中心地址。 * @return 如果存在配置变更,返回全量的配置项, changed = true。 如果没有变更, 返回 null, changed = false, * @throws OperationException If some problems happened to contact service center or non http 200 returned. */ - ConfigurationsResponse queryConfigurations(ConfigurationsRequest request); + ConfigurationsResponse queryConfigurations(ConfigurationsRequest request, String address); + + /** + * Check kie isolation address available + * + * @param address isolation address + */ + void checkAddressAvailable(String address); } diff --git a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ConfigConstants.java b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ConfigConstants.java index 81585ecdff6..eeac88e70fa 100644 --- a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ConfigConstants.java +++ b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ConfigConstants.java @@ -37,4 +37,10 @@ public class ConfigConstants { public static final String KEY_POLLINGWAITSEC = "pollingWaitInSeconds"; + public static final String CLIENT_CONNECT_TIMEOUT = "servicecomb.kie.client.timeout.connect"; + + public static final String CLIENT_REQUEST_TIMEOUT = "servicecomb.kie.client.timeout.request"; + + public static final String CLIENT_SOCKET_TIMEOUT = "servicecomb.kie.client.timeout.socket"; + } diff --git a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ConfigurationsRequest.java b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ConfigurationsRequest.java index b0e9344e492..b22f7ecaa23 100644 --- a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ConfigurationsRequest.java +++ b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ConfigurationsRequest.java @@ -80,6 +80,7 @@ public ConfigurationsRequest setLastRawData(Map lastRawData) { @Override public int compareTo(ConfigurationsRequest o) { + // Higher priority, query the last return o.getOrder() - this.order; } } diff --git a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ConfigurationsRequestFactory.java b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ConfigurationsRequestFactory.java index 4f2b4325c0f..20c6ba3bacb 100644 --- a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ConfigurationsRequestFactory.java +++ b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ConfigurationsRequestFactory.java @@ -32,13 +32,13 @@ public class ConfigurationsRequestFactory { private static final String KEY_VERSION = "version"; - private static final int VERSION_ORDER = 50; + private static final int CUSTOM_ORDER = 100; - private static final int SERVICE_ORDER = 100; + private static final int VERSION_ORDER = 200; - private static final int APP_ORDER = 200; + private static final int SERVICE_ORDER = 300; - private static final int CUSTOM_ORDER = 300; + private static final int APP_ORDER = 400; public static List buildConfigurationRequests(KieConfiguration configuration) { List result = new ArrayList<>(); diff --git a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/KVDoc.java b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/KVDoc.java index 4078557d3be..3059a581c61 100644 --- a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/KVDoc.java +++ b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/KVDoc.java @@ -44,6 +44,9 @@ public class KVDoc { private String status; + @JsonAlias("update_time") + private long updateTime; + public String getStatus() { return status; } @@ -115,4 +118,12 @@ public void setValue(String value) { public String getValueType() { return valueType; } + + public long getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(long updateTime) { + this.updateTime = updateTime; + } } diff --git a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/KieAddressManager.java b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/KieAddressManager.java index 0742c11fb10..d8b6582d846 100644 --- a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/KieAddressManager.java +++ b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/KieAddressManager.java @@ -27,8 +27,8 @@ public class KieAddressManager extends AbstractAddressManager { - public KieAddressManager(List addresses, EventBus eventBus) { - super(addresses); + public KieAddressManager(List addresses, String ownRegion, String ownAvailableZone, EventBus eventBus) { + super(addresses, ownRegion, ownAvailableZone); eventBus.register(this); } diff --git a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ValueType.java b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ValueType.java index b38a1e28f8e..23cbce3b367 100644 --- a/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ValueType.java +++ b/clients/config-kie-client/src/main/java/org/apache/servicecomb/config/kie/client/model/ValueType.java @@ -24,5 +24,6 @@ public enum ValueType { string, text, json, - properties + properties, + xml } diff --git a/clients/config-kie-client/src/test/java/org/apache/servicecomb/config/kie/client/model/KieAddressManagerTest.java b/clients/config-kie-client/src/test/java/org/apache/servicecomb/config/kie/client/model/KieAddressManagerTest.java index 0e4f5793060..4fd547a2ce2 100644 --- a/clients/config-kie-client/src/test/java/org/apache/servicecomb/config/kie/client/model/KieAddressManagerTest.java +++ b/clients/config-kie-client/src/test/java/org/apache/servicecomb/config/kie/client/model/KieAddressManagerTest.java @@ -17,6 +17,7 @@ package org.apache.servicecomb.config.kie.client.model; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -36,10 +37,13 @@ class KieAddressManagerTest { private static KieAddressManager addressManager1; @Test - public void kieAddressManagerTest() { + public void kieAddressManagerTest() throws NoSuchFieldException, IllegalAccessException { addresses.add("http://127.0.0.1:30103"); addresses.add("https://127.0.0.2:30103"); - addressManager1 = new KieAddressManager(addresses, new EventBus()); + addressManager1 = new KieAddressManager(addresses, "", "", new EventBus()); + Field addressManagerField = addressManager1.getClass().getSuperclass().getDeclaredField("index"); + addressManagerField.setAccessible(true); + addressManagerField.set(addressManager1, 0); Assertions.assertNotNull(addressManager1); @@ -51,7 +55,6 @@ public void kieAddressManagerTest() { Assertions.assertEquals("http://127.0.0.1:30103", addressManager1.address()); } - @Test public void onRefreshEndpointEvent() { List addressAZ = new ArrayList<>(); @@ -61,7 +64,7 @@ public void onRefreshEndpointEvent() { Map> zoneAndRegion = new HashMap<>(); zoneAndRegion.put("sameZone", addressAZ); zoneAndRegion.put("sameRegion", addressRG); - addressManager1 = new KieAddressManager(addresses, new EventBus()); + addressManager1 = new KieAddressManager(addresses, "", "", new EventBus()); RefreshEndpointEvent event = new RefreshEndpointEvent(zoneAndRegion, "KIE"); addressManager1.refreshEndpoint(event, "KIE"); diff --git a/clients/dashboard-client/src/main/java/org/apache/servicecomb/dashboard/client/DashboardAddressManager.java b/clients/dashboard-client/src/main/java/org/apache/servicecomb/dashboard/client/DashboardAddressManager.java index 9dd8c6a3448..e80c537bc46 100644 --- a/clients/dashboard-client/src/main/java/org/apache/servicecomb/dashboard/client/DashboardAddressManager.java +++ b/clients/dashboard-client/src/main/java/org/apache/servicecomb/dashboard/client/DashboardAddressManager.java @@ -29,8 +29,8 @@ public class DashboardAddressManager extends AbstractAddressManager { - public DashboardAddressManager(List addresses, EventBus eventBus) { - super(addresses); + public DashboardAddressManager(List addresses, String ownRegion, String ownAvailableZone, EventBus eventBus) { + super(addresses, ownRegion, ownAvailableZone); eventBus.register(this); } diff --git a/clients/dashboard-client/src/test/java/org/apache/servicecomb/dashboard/client/AddressManagerTest.java b/clients/dashboard-client/src/test/java/org/apache/servicecomb/dashboard/client/AddressManagerTest.java index 0703e0dc6aa..448f2d7170f 100644 --- a/clients/dashboard-client/src/test/java/org/apache/servicecomb/dashboard/client/AddressManagerTest.java +++ b/clients/dashboard-client/src/test/java/org/apache/servicecomb/dashboard/client/AddressManagerTest.java @@ -17,6 +17,7 @@ package org.apache.servicecomb.dashboard.client; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -34,11 +35,16 @@ class AddressManagerTest { private static DashboardAddressManager addressManager1; + private static int index; + @Test - public void kieAddressManagerTest() { + public void kieAddressManagerTest() throws IllegalAccessException, NoSuchFieldException { addresses.add("http://127.0.0.1:30103"); addresses.add("https://127.0.0.2:30103"); - addressManager1 = new DashboardAddressManager(addresses, new EventBus()); + addressManager1 = new DashboardAddressManager(addresses, "", "", new EventBus()); + Field addressManagerField = addressManager1.getClass().getSuperclass().getDeclaredField("index"); + addressManagerField.setAccessible(true); + addressManagerField.set(addressManager1, 0); Assertions.assertNotNull(addressManager1); @@ -50,7 +56,6 @@ public void kieAddressManagerTest() { Assertions.assertEquals("http://127.0.0.1:30103", addressManager1.address()); } - @Test public void onRefreshEndpointEvent() { List addressAZ = new ArrayList<>(); @@ -60,7 +65,7 @@ public void onRefreshEndpointEvent() { Map> zoneAndRegion = new HashMap<>(); zoneAndRegion.put("sameZone", addressAZ); zoneAndRegion.put("sameRegion", addressRG); - addressManager1 = new DashboardAddressManager(addresses, new EventBus()); + addressManager1 = new DashboardAddressManager(addresses, "", "", new EventBus()); RefreshEndpointEvent event = new RefreshEndpointEvent(zoneAndRegion, "CseMonitoring"); addressManager1.refreshEndpoint(event, "CseMonitoring"); diff --git a/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/common/AbstractAddressManager.java b/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/common/AbstractAddressManager.java index a126a855fa9..508b523a0bc 100644 --- a/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/common/AbstractAddressManager.java +++ b/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/common/AbstractAddressManager.java @@ -17,31 +17,24 @@ package org.apache.servicecomb.http.client.common; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; import java.net.URI; -import java.net.URISyntaxException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Random; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.servicecomb.http.client.event.EngineConnectChangedEvent; import org.apache.servicecomb.http.client.event.RefreshEndpointEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; import com.google.common.annotations.VisibleForTesting; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.common.eventbus.EventBus; public class AbstractAddressManager { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAddressManager.class); @@ -52,66 +45,113 @@ public class AbstractAddressManager { private static final String V3_PREFIX = "/v3/"; - private static final int DEFAULT_METRICS_WINDOW_TIME = 1; + private static final String ZONE = "availableZone"; + + private static final String REGION = "region"; private static final int ISOLATION_THRESHOLD = 3; - private List addresses = new ArrayList<>(); + private volatile List addresses = new ArrayList<>(); - private int index = 0; + // when all addresses are isolation, it will use this for polling. + private final List defaultAddress = new ArrayList<>(); - private String projectName; + private final List defaultIsolationAddress = new ArrayList<>(); - // if address in same zone will be true; others will be false. - private final Map addressCategory = new HashMap<>(); + private int index; + + private String projectName; // recording continuous times of failure of an address. private final Map addressFailureStatus = new ConcurrentHashMap<>(); - // recording address isolation status, if isolated will be false - private final Map addressIsolated = new ConcurrentHashMap<>(); - - // recording address isolation status, if isolated will be false - private Cache addressIsolationStatus = CacheBuilder.newBuilder() - .maximumSize(100) - .expireAfterWrite(1, TimeUnit.MINUTES) - .build(); - private volatile List availableZone = new ArrayList<>(); + private final List isolationZoneAddress = new ArrayList<>(); + private volatile List availableRegion = new ArrayList<>(); - private final List defaultAddress = new ArrayList<>(); + private final List isolationRegionAddress = new ArrayList<>(); private boolean addressAutoRefreshed = false; private final Object lock = new Object(); - private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1, - new ThreadFactoryBuilder() - .setNameFormat("check-available-address-%d") - .build()); + private final Random random = new Random(); - public AbstractAddressManager(List addresses) { + private EventBus eventBus; + + public AbstractAddressManager(List addresses, String ownRegion, String ownAvailableZone) { this.projectName = DEFAULT_PROJECT; - this.addresses.addAll(addresses); - this.defaultAddress.addAll(addresses); + parseAndInitAddresses(addresses, ownRegion, ownAvailableZone, false); + this.index = !addresses.isEmpty() ? getRandomIndex() : 0; + } + + /** + * address support config with region/availableZone info, to enable engine affinity calls during startup + * address may be like: + * https://192.168.20.13:30110?region=region1&availableZone=az + * https://192.168.20.13:30100?region=region1&availableZone=az + * When address have no datacenter information, roundRobin using address + * + * @param addresses engine addresses + * @param ownRegion microservice region + * @param ownAvailableZone microservice zone + * @param isFormat is need format + */ + private void parseAndInitAddresses(List addresses, String ownRegion, String ownAvailableZone, + boolean isFormat) { + if (CollectionUtils.isEmpty(addresses)) { + return; + } + List tempList = new ArrayList<>(); + addressAutoRefreshed = addresses.stream().anyMatch(addr -> addr.contains(ZONE) || addr.contains(REGION)); + for (String address : addresses) { + // Compatible IpPortManager init address is 127.0.0.1:30100 + if (!address.startsWith("http")) { + tempList.add(address); + continue; + } + URLEndPoint endpoint = new URLEndPoint(address); + tempList.add(endpoint.toString()); + buildAffinityAddress(endpoint, ownRegion, ownAvailableZone); + } + this.addresses.addAll(isFormat ? this.transformAddress(tempList) : tempList); + this.defaultAddress.addAll(isFormat ? this.transformAddress(tempList) : tempList); } - public AbstractAddressManager(String projectName, List addresses) { + private void buildAffinityAddress(URLEndPoint endpoint, String ownRegion, String ownAvailableZone) { + if (addressAutoRefreshed) { + if (regionAndAZMatch(ownRegion, ownAvailableZone, endpoint.getFirst(REGION), endpoint.getFirst(ZONE))) { + availableZone.add(endpoint.toString()); + } else { + availableRegion.add(endpoint.toString()); + } + } + } + + public AbstractAddressManager(String projectName, List addresses, String ownRegion, String ownAvailableZone) { this.projectName = StringUtils.isEmpty(projectName) ? DEFAULT_PROJECT : projectName; - this.addresses = this.transformAddress(addresses); - this.defaultAddress.addAll(this.addresses); + parseAndInitAddresses(addresses, ownRegion, ownAvailableZone, true); + this.index = !addresses.isEmpty() ? getRandomIndex() : 0; } - @VisibleForTesting - Cache getAddressIsolationStatus() { - return addressIsolationStatus; + private int getRandomIndex() { + return random.nextInt(addresses.size()); } - @VisibleForTesting - void setAddressIsolationStatus(Cache addressIsolationStatus) { - this.addressIsolationStatus = addressIsolationStatus; + public void refreshEndpoint(RefreshEndpointEvent event, String key) { + if (null == event || !event.getName().equals(key)) { + return; + } + + availableZone = event.getSameZone().stream().map(this::normalizeUri).collect(Collectors.toList()); + availableRegion = event.getSameRegion().stream().map(this::normalizeUri).collect(Collectors.toList()); + addressAutoRefreshed = true; + } + + protected String normalizeUri(String endpoint) { + return new URLEndPoint(endpoint).toString(); } @VisibleForTesting @@ -131,25 +171,10 @@ public List getAvailableRegion() { return availableRegion; } - private void startCheck() { - executorService.scheduleAtFixedRate(this::checkHistory, - 0, - DEFAULT_METRICS_WINDOW_TIME, - TimeUnit.MINUTES); - } - public String formatUrl(String url, boolean absoluteUrl, String address) { return absoluteUrl ? address + url : formatAddress(address) + url; } - public String address() { - if (!addressAutoRefreshed) { - return getDefaultAddress(); - } else { - return getAvailableZoneAddress(); - } - } - public boolean sslEnabled() { return address().startsWith("https://"); } @@ -158,10 +183,6 @@ protected List transformAddress(List addresses) { return addresses.stream().map(this::formatAddress).collect(Collectors.toList()); } - protected String getUrlPrefix(String address) { - return address + V3_PREFIX; - } - protected String formatAddress(String address) { try { return getUrlPrefix(address) + HttpUtils.encodeURLParam(this.projectName); @@ -170,28 +191,36 @@ protected String formatAddress(String address) { } } - private String getDefaultAddress() { - List addresses = getAvailableAddress(defaultAddress); - if (!addresses.isEmpty()) { - return getCurrentAddress(addresses); + protected String getUrlPrefix(String address) { + return address + V3_PREFIX; + } + + public String address() { + if (!addressAutoRefreshed) { + return getDefaultAddress(); + } else { + return getAvailableZoneAddress(); } - return getInitAddress(); } - private String getAvailableZoneAddress() { - List addresses = getAvailableZoneIpPorts(); + private String getDefaultAddress() { if (!addresses.isEmpty()) { return getCurrentAddress(addresses); } - return getInitAddress(); + LOGGER.warn("all addresses are isolation, please check server status."); + // when all addresses are isolation, it will use all default address for polling. + return getCurrentAddress(defaultAddress); } - // when all available address is fail, it will use all the initial addresses for polling. - private String getInitAddress() { - if (addresses.isEmpty()) { - return null; + private String getAvailableZoneAddress() { + List zoneOrRegionAddress = getZoneOrRegionAddress(); + if (!zoneOrRegionAddress.isEmpty()) { + return getCurrentAddress(zoneOrRegionAddress); } - return getCurrentAddress(addresses); + LOGGER.warn("all auto discovery addresses are isolation, please check server status."); + + // when all available address are isolation, it will use config addresses for polling. + return getDefaultAddress(); } private String getCurrentAddress(List addresses) { @@ -204,38 +233,48 @@ private String getCurrentAddress(List addresses) { } } - private List getAvailableZoneIpPorts() { + private List getZoneOrRegionAddress() { List results = new ArrayList<>(); if (!availableZone.isEmpty()) { - results.addAll(getAvailableAddress(availableZone)); + results.addAll(availableZone); } else { - results.addAll(getAvailableAddress(availableRegion)); + results.addAll(availableRegion); } return results; } - private List getAvailableAddress(List endpoints) { - return endpoints.stream().filter(uri -> !addressIsolated.containsKey(uri) || addressIsolated.get(uri)) - .collect(Collectors.toList()); - } - - protected String normalizeUri(String endpoint) { - return new URLEndPoint(endpoint).toString(); - } - - public void refreshEndpoint(RefreshEndpointEvent event, String key) { - if (null == event || !event.getName().equals(key)) { + public void recordSuccessState(String address) { + resetFailureStatus(address); + if (addressAutoRefreshed) { + if (isolationZoneAddress.remove(address)) { + LOGGER.warn("restore same region address [{}]", address); + if (eventBus != null && availableZone.isEmpty()) { + eventBus.post(new EngineConnectChangedEvent()); + } + availableZone.add(address); + return; + } + if (isolationRegionAddress.remove(address)) { + LOGGER.warn("restore same zone address [{}]", address); + availableRegion.add(address); + } return; } + if (defaultIsolationAddress.remove(address)) { + LOGGER.warn("restore default address [{}]", address); + addresses.add(address); + } + } - availableZone = event.getSameZone().stream().map(this::normalizeUri).collect(Collectors.toList()); - availableRegion = event.getSameRegion().stream().map(this::normalizeUri).collect(Collectors.toList()); - availableZone.forEach(address -> addressCategory.put(address, true)); - availableRegion.forEach(address -> addressCategory.put(address, false)); - startCheck(); - addressAutoRefreshed = true; + public void resetFailureStatus(String address) { + addressFailureStatus.put(address, 0); } + /** + * Only authentication failure, IO, and timeout exception record as failed. + * + * @param address request address + */ public void recordFailState(String address) { synchronized (lock) { if (!addressFailureStatus.containsKey(address)) { @@ -251,96 +290,76 @@ public void recordFailState(String address) { } } - public void recordSuccessState(String address) { - addressFailureStatus.put(address, 0); - } - + //Query whether the current address belongs to the same AZ or the same region through AZMap, + // and delete it from the record. At the same time, add records in history and cache @VisibleForTesting - protected void checkHistory() { - addressIsolated.keySet().stream().filter(this::judgeIsolation).forEach(s -> { - if (telnetTest(s)) { - rejoinAddress(s); - } else { - addressIsolationStatus.put(s, false); + void removeAddress(String address) { + if (!addressAutoRefreshed) { + if (addresses.remove(address)) { + LOGGER.warn("isolation default address [{}]", address); + defaultIsolationAddress.add(address); } - }); - } - - private Boolean judgeIsolation(String address) { - try { - return addressIsolationStatus.get(address, () -> true); - } catch (ExecutionException e) { - return true; + return; + } + if (availableZone.remove(address)) { + LOGGER.warn("isolation same zone address [{}]", address); + isolationZoneAddress.add(address); + if (eventBus != null && availableZone.isEmpty() && !availableRegion.isEmpty()) { + eventBus.post(new EngineConnectChangedEvent()); + } + return; + } + if (availableRegion.remove(address)) { + LOGGER.warn("isolation same region address [{}]", address); + isolationRegionAddress.add(address); } } - protected boolean telnetTest(String address) { - URI ipPort = parseIpPortFromURI(address); - try (Socket s = new Socket()) { - s.connect(new InetSocketAddress(ipPort.getHost(), ipPort.getPort()), 3000); - return true; - } catch (IOException e) { - LOGGER.warn("ping endpoint {} failed, It will be quarantined again.", address); - } - return false; + public void setEventBus(EventBus eventBus) { + this.eventBus = eventBus; } - private URI parseIpPortFromURI(String uri) { - try { - return new URI(uri); - } catch (URISyntaxException e) { - return null; - } + public List getIsolationAddresses() { + List isolationAddresses = new ArrayList<>(defaultIsolationAddress); + isolationAddresses.addAll(isolationZoneAddress); + isolationAddresses.addAll(isolationRegionAddress); + return isolationAddresses; } - //Query whether the current address belongs to the same AZ or region through azmap, - // add it to the sequence of, and delete the record in history - @VisibleForTesting - void rejoinAddress(String address) { - if (!addressAutoRefreshed) { - defaultAddress.add(address); - addressFailureStatus.put(address, 0); - addressIsolated.remove(address); - return; + public String compareAndGetAddress(String host) { + for (String address : defaultAddress) { + if (isAddressHostSame(address, host)) { + return address; + } } + return ""; + } - if (addressCategory.get(address) == null) { - LOGGER.warn("may not happen {}-{}", addressCategory.size(), address); - return; + private boolean isAddressHostSame(String address, String host) { + if (StringUtils.isEmpty(host)) { + return false; } - - if (addressCategory.get(address)) { - availableZone.add(address); - } else { - availableRegion.add(address); + try { + URI uri = new URI(address); + return host.equals(uri.getHost()); + } catch (Exception e) { + LOGGER.warn("Exception occurred while constructing URI using the address [{}]", address); } - addressFailureStatus.put(address, 0); - addressIsolated.remove(address); + return false; } - //Query whether the current address belongs to the same AZ or the same region through AZMap, - // and delete it from the record. At the same time, add records in history and cache - @VisibleForTesting - void removeAddress(String address) { - if (!addressAutoRefreshed) { - defaultAddress.remove(address); - addressIsolated.put(address, false); - addressIsolationStatus.put(address, false); - return; - } + private boolean regionAndAZMatch(String ownRegion, String ownAvailableZone, String engineRegion, + String engineAvailableZone) { + return ownRegion.equalsIgnoreCase(engineRegion) && ownAvailableZone.equals(engineAvailableZone); + } - if (addressCategory.get(address) == null) { - LOGGER.warn("may not happen {}-{}", addressCategory.size(), address); - return; + public void refreshAffinityAddress(Set sameZone, Set sameRegion) { + addressAutoRefreshed = true; + if (!sameZone.isEmpty()) { + availableZone.addAll(sameZone); } - - if (addressCategory.get(address)) { - availableZone.remove(address); - } else { - availableRegion.remove(address); + if (!sameRegion.isEmpty()) { + availableRegion.addAll(sameRegion); } - - addressIsolated.put(address, false); - addressIsolationStatus.put(address, false); } } diff --git a/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/common/HttpTransportImpl.java b/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/common/HttpTransportImpl.java index da87892929c..4f03f112f7c 100644 --- a/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/common/HttpTransportImpl.java +++ b/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/common/HttpTransportImpl.java @@ -18,6 +18,7 @@ package org.apache.servicecomb.http.client.common; import java.io.IOException; +import java.net.URI; import java.util.Map; import org.apache.http.client.HttpClient; @@ -87,7 +88,7 @@ public HttpResponse doRequest(HttpRequest httpRequest) throws IOException { globalHeaders.forEach(httpRequest::addHeader); } - httpRequest.getHeaders().putAll(requestAuthHeaderProvider.loadAuthHeader(createSignRequest())); + httpRequest.getHeaders().putAll(requestAuthHeaderProvider.loadAuthHeader(createSignRequest(httpRequest.getUrl()))); //get Http response org.apache.http.HttpResponse response = httpClient.execute(httpRequest.getRealRequest()); @@ -98,9 +99,15 @@ public HttpResponse doRequest(HttpRequest httpRequest) throws IOException { response.getAllHeaders()); } - private static SignRequest createSignRequest() { - // Now the implementations do not process SignRequest, so return null. Maybe future will use it. - return null; + private static SignRequest createSignRequest(String url) { + try { + URI uri = URI.create(url); + SignRequest signRequest = new SignRequest(); + signRequest.setEndpoint(uri); + return signRequest; + } catch (Exception e) { + return null; + } } @Override diff --git a/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/event/EngineConnectChangedEvent.java b/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/event/EngineConnectChangedEvent.java new file mode 100644 index 00000000000..b2029f5c38c --- /dev/null +++ b/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/event/EngineConnectChangedEvent.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.http.client.event; + +public class EngineConnectChangedEvent { +} diff --git a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/OperationEvents.java b/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/event/OperationEvents.java similarity index 79% rename from clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/OperationEvents.java rename to clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/event/OperationEvents.java index 62274519ea4..69896f750ec 100644 --- a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/OperationEvents.java +++ b/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/event/OperationEvents.java @@ -15,10 +15,18 @@ * limitations under the License. */ -package org.apache.servicecomb.service.center.client; +package org.apache.servicecomb.http.client.event; public abstract class OperationEvents { public static class UnAuthorizedOperationEvent extends OperationEvents { + private final String address; + public UnAuthorizedOperationEvent(String address) { + this.address = address; + } + + public String getAddress() { + return address; + } } } diff --git a/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/task/AbstractTask.java b/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/task/AbstractTask.java index deba2d8521d..42d94bbbe06 100644 --- a/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/task/AbstractTask.java +++ b/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/task/AbstractTask.java @@ -20,6 +20,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; @@ -29,7 +30,7 @@ public class AbstractTask { public class BackOffSleepTask implements Task { private static final long BASE = 3000; - private static final long MAX = 10 * 60 * 10000; + private static final long MAX = 10 * 60 * 1000; long waitTime; @@ -63,6 +64,8 @@ public void execute() { private volatile boolean running = true; + private ScheduledExecutorService addrCheckExecutor; + protected AbstractTask(String taskName) { initTaskPool(taskName); Runtime.getRuntime().addShutdownHook(new Thread(AbstractTask.this::stop, taskName + "-shutdown-hook")); @@ -73,6 +76,13 @@ protected void initTaskPool(String taskName) { new Thread(task, taskName)); } + protected void schedulerCheckAddressAvailable(String taskName, Runnable task, long delayTime) { + if (addrCheckExecutor == null) { + addrCheckExecutor = Executors.newScheduledThreadPool(1, (t) -> new Thread(t, taskName)); + } + addrCheckExecutor.scheduleWithFixedDelay(task, delayTime, delayTime, TimeUnit.MILLISECONDS); + } + protected void startTask(Task task) { if (!running) { return; @@ -96,6 +106,10 @@ public void stop() { running = false; this.taskPool.shutdown(); this.taskPool.awaitTermination(10, TimeUnit.SECONDS); + if (addrCheckExecutor != null) { + this.addrCheckExecutor.shutdown(); + this.addrCheckExecutor.awaitTermination(10, TimeUnit.SECONDS); + } } catch (InterruptedException e) { LOGGER.warn("tasks not shutdown in time {}", e.getMessage()); } diff --git a/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/utils/ServiceCombServiceAvailableUtils.java b/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/utils/ServiceCombServiceAvailableUtils.java new file mode 100644 index 00000000000..3f7af48d757 --- /dev/null +++ b/clients/http-client-common/src/main/java/org/apache/servicecomb/http/client/utils/ServiceCombServiceAvailableUtils.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.http.client.utils; + +import org.apache.http.HttpStatus; +import org.apache.servicecomb.http.client.common.AbstractAddressManager; +import org.apache.servicecomb.http.client.common.HttpRequest; +import org.apache.servicecomb.http.client.common.HttpResponse; +import org.apache.servicecomb.http.client.common.HttpTransport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; + +public class ServiceCombServiceAvailableUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceCombServiceAvailableUtils.class); + + public static void checkAddressAvailable(AbstractAddressManager addressManager, String address, + HttpTransport httpTransport, String path) { + String formatUrl = addressManager.formatUrl(path, true, address); + HttpRequest httpRequest = new HttpRequest(formatUrl, null, null, HttpRequest.GET); + try { + HttpResponse response = httpTransport.doRequest(httpRequest); + if (response.getStatusCode() == HttpStatus.SC_OK) { + addressManager.recordSuccessState(address); + return; + } + + // old server does not provide the check api, using TCP checks whether the server is ready. + if (response.getStatusCode() == HttpStatus.SC_NOT_FOUND && telnetCheckAddress(address)) { + LOGGER.warn("[{}] path does not provide, tcp check address ready!", path); + addressManager.recordSuccessState(address); + } + } catch (IOException e) { + LOGGER.error("check isolation address [{}] available error!", address); + } + } + + public static boolean telnetCheckAddress(String address) { + URI ipPort = parseIpPortFromURI(address); + if (ipPort == null) { + return false; + } + try (Socket s = new Socket()) { + s.connect(new InetSocketAddress(ipPort.getHost(), ipPort.getPort()), 3000); + return true; + } catch (IOException e) { + LOGGER.warn("ping endpoint {} failed, It will be quarantined again.", address); + } + return false; + } + + private static URI parseIpPortFromURI(String address) { + try { + String newAddress = address.startsWith("http") ? address : "https://" + address; + return new URI(newAddress); + } catch (URISyntaxException e) { + LOGGER.error("build uri error with address [{}].", address); + return null; + } + } +} diff --git a/clients/http-client-common/src/test/java/org/apache/servicecomb/http/client/common/AbstractAddressManagerTest.java b/clients/http-client-common/src/test/java/org/apache/servicecomb/http/client/common/AbstractAddressManagerTest.java index 080e86f1657..b14d6827cb8 100644 --- a/clients/http-client-common/src/test/java/org/apache/servicecomb/http/client/common/AbstractAddressManagerTest.java +++ b/clients/http-client-common/src/test/java/org/apache/servicecomb/http/client/common/AbstractAddressManagerTest.java @@ -17,6 +17,7 @@ package org.apache.servicecomb.http.client.common; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -31,9 +32,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; - public class AbstractAddressManagerTest { private static final List addresses = new ArrayList<>(); @@ -45,12 +43,21 @@ public class AbstractAddressManagerTest { private static AbstractAddressManager addressManager3; @BeforeEach - public void setUp() { + public void setUp() throws NoSuchFieldException, IllegalAccessException { addresses.add("http://127.0.0.1:30103"); addresses.add("https://127.0.0.2:30103"); - addressManager1 = new AbstractAddressManager(addresses); - addressManager2 = new AbstractAddressManager("project", addresses); - addressManager3 = new AbstractAddressManager(null, addresses); + addressManager1 = new AbstractAddressManager(addresses, "", ""); + addressManager2 = new AbstractAddressManager("project", addresses, "", ""); + addressManager3 = new AbstractAddressManager(null, addresses, "", ""); + Field addressManagerField = addressManager1.getClass().getDeclaredField("index"); + addressManagerField.setAccessible(true); + addressManagerField.set(addressManager1, 0); + addressManagerField = addressManager2.getClass().getDeclaredField("index"); + addressManagerField.setAccessible(true); + addressManagerField.set(addressManager2, 0); + addressManagerField = addressManager3.getClass().getDeclaredField("index"); + addressManagerField.setAccessible(true); + addressManagerField.set(addressManager3, 0); } @AfterEach @@ -81,12 +88,7 @@ public void recordStateTest() throws ExecutionException { zoneAndRegion.put("sameZone", addressAZ); zoneAndRegion.put("sameRegion", addressRG); RefreshEndpointEvent event = new RefreshEndpointEvent(zoneAndRegion, "TEST"); - AbstractAddressManager addressManager = new AbstractAddressManager(addresses) { - @Override - protected boolean telnetTest(String address) { - return true; - } - }; + AbstractAddressManager addressManager = new AbstractAddressManager(addresses, "", "") {}; addressManager.refreshEndpoint(event, "TEST"); @@ -99,7 +101,7 @@ protected boolean telnetTest(String address) { Assertions.assertEquals("http://127.0.0.3:30100", addressManager.address()); // test fail 2 times ,it will not be isolated - addressManager.recordSuccessState(address); + addressManager.resetFailureStatus(address); Assertions.assertEquals("http://127.0.0.3:30100", addressManager.address()); // test recodeStatus times @@ -112,20 +114,8 @@ protected boolean telnetTest(String address) { addressManager.recordFailState(address); Assertions.assertEquals("http://127.0.0.4:30100", addressManager.address()); - // mock cacheAddress status refresh after 10 minute - Cache cache = CacheBuilder.newBuilder() - .maximumSize(100) - .expireAfterWrite(10, TimeUnit.MINUTES) - .build(); - cache.put("http://127.0.0.3:30100", true); - - addressManager.setAddressIsolationStatus(cache); - Cache result = addressManager.getAddressIsolationStatus(); - Assertions.assertEquals(true, result.get("http://127.0.0.3:30100", () -> false)); - // test restore isolation - addressManager.checkHistory(); - addressManager.rejoinAddress("http://127.0.0.3:30100"); + addressManager.recordSuccessState("http://127.0.0.3:30100"); Assertions.assertEquals("http://127.0.0.3:30100", addressManager.address()); Assertions.assertEquals("http://127.0.0.3:30100", addressManager.address()); } @@ -134,7 +124,7 @@ protected boolean telnetTest(String address) { @Test public void testMultipleThread() throws Exception { - AbstractAddressManager addressManager = new AbstractAddressManager(addresses); + AbstractAddressManager addressManager = new AbstractAddressManager(addresses, "", ""); String address = "http://127.0.0.3:30100"; CountDownLatch latch = new CountDownLatch(2); @@ -312,4 +302,27 @@ public void normalizeIPV6Test() { uri = addressManager1.normalizeUri("rest://[2008::7:957f:b2d6:1af4:a1f8]:30100"); Assertions.assertEquals("http://[2008::7:957f:b2d6:1af4:a1f8]:30100", uri); } + + @Test + public void compareAndGetAddressTest() { + List testAddr = new ArrayList<>(); + testAddr.add("https://192.168.20.160:30100"); + testAddr.add("https://127.0.0.1:30100"); + testAddr.add("https://127.0.0.3:30100"); + AbstractAddressManager manager = new AbstractAddressManager(testAddr, "", ""); + Assertions.assertTrue(manager.compareAndGetAddress("192.168.20.16").isEmpty()); + Assertions.assertEquals("https://192.168.20.160:30100", manager.compareAndGetAddress("192.168.20.160")); + } + + @Test + public void AddressAffinityTest() { + List testAddr = new ArrayList<>(); + testAddr.add("https://192.168.20.160:30100?region=region1&availableZone=zone1"); + testAddr.add("https://127.0.0.1:30100"); + AbstractAddressManager manager = new AbstractAddressManager(testAddr, "region1", "zone1"); + Assertions.assertEquals("https://192.168.20.160:30100", manager.address()); + + AbstractAddressManager manager2 = new AbstractAddressManager("default", testAddr, "region1", "zone1"); + Assertions.assertEquals("https://192.168.20.160:30100", manager2.address()); + } } diff --git a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/DiscoveryEvents.java b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/DiscoveryEvents.java index e45720f5ebb..db0d12b1ce1 100644 --- a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/DiscoveryEvents.java +++ b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/DiscoveryEvents.java @@ -20,8 +20,15 @@ import java.util.List; import org.apache.servicecomb.service.center.client.model.MicroserviceInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; public abstract class DiscoveryEvents { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public static class InstanceChangedEvent extends DiscoveryEvents { private final String appName; @@ -52,6 +59,45 @@ public List getInstances() { * internal events to ask for a immediate instance pull */ public static class PullInstanceEvent extends DiscoveryEvents { + private static final Logger LOGGER = LoggerFactory.getLogger(PullInstanceEvent.class); + + private final String appId; + + private final String serviceName; + + public PullInstanceEvent(String message) { + JsonNode messageNode = parseJsonString(message); + this.appId = getContextFromNode(messageNode, "appId"); + this.serviceName = getContextFromNode(messageNode, "serviceName"); + } + + public String getAppId() { + return appId; + } + + public String getServiceName() { + return serviceName; + } + + private JsonNode parseJsonString(String message) { + try { + return OBJECT_MAPPER.readTree(message); + } catch (Exception e) { + LOGGER.error("parse message [{}] failed!", message, e); + return null; + } + } + private String getContextFromNode(JsonNode messageNode, String itemKey) { + if (messageNode == null) { + return ""; + } + try { + return messageNode.get("key").get(itemKey).asText(); + } catch (Exception e) { + LOGGER.error("get [{}] context from node [{}] failed!", itemKey, e); + return ""; + } + } } } diff --git a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterAddressManager.java b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterAddressManager.java index a5fd128ab1e..f955c148af3 100644 --- a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterAddressManager.java +++ b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterAddressManager.java @@ -26,8 +26,9 @@ import com.google.common.eventbus.Subscribe; public class ServiceCenterAddressManager extends AbstractAddressManager { - public ServiceCenterAddressManager(String projectName, List addresses, EventBus eventBus) { - super(projectName, addresses); + public ServiceCenterAddressManager(String projectName, List addresses, String ownRegion, + String ownAvailableZone, EventBus eventBus) { + super(projectName, addresses, ownRegion, ownAvailableZone); eventBus.register(this); } diff --git a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterClient.java b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterClient.java index 09f30dc93da..1a9bcbc2a27 100755 --- a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterClient.java +++ b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterClient.java @@ -25,6 +25,7 @@ import java.util.Map; import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.utils.URIBuilder; import org.apache.servicecomb.http.client.auth.RequestAuthHeaderProvider; import org.apache.servicecomb.http.client.common.HttpConfiguration.SSLProperties; @@ -32,13 +33,13 @@ import org.apache.servicecomb.http.client.common.HttpTransport; import org.apache.servicecomb.http.client.common.HttpTransportFactory; import org.apache.servicecomb.http.client.common.HttpUtils; -import org.apache.servicecomb.service.center.client.OperationEvents.UnAuthorizedOperationEvent; import org.apache.servicecomb.service.center.client.exception.OperationException; import org.apache.servicecomb.service.center.client.model.CreateMicroserviceInstanceRequest; import org.apache.servicecomb.service.center.client.model.CreateMicroserviceRequest; import org.apache.servicecomb.service.center.client.model.CreateSchemaRequest; import org.apache.servicecomb.service.center.client.model.ErrorMessage; import org.apache.servicecomb.service.center.client.model.FindMicroserviceInstancesResponse; +import org.apache.servicecomb.service.center.client.model.Framework; import org.apache.servicecomb.service.center.client.model.GetSchemaListResponse; import org.apache.servicecomb.service.center.client.model.GetSchemaResponse; import org.apache.servicecomb.service.center.client.model.HeartbeatsRequest; @@ -58,6 +59,7 @@ import org.apache.servicecomb.service.center.client.model.UpdatePropertiesRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; import com.google.common.eventbus.EventBus; @@ -65,16 +67,23 @@ public class ServiceCenterClient implements ServiceCenterOperation { private static final Logger LOGGER = LoggerFactory.getLogger(ServiceCenterClient.class); + private static final String CLIENT_CONNECT_TIMEOUT = "servicecomb.service.client.timeout.connect"; + + private static final String CLIENT_REQUEST_TIMEOUT = "servicecomb.service.client.timeout.request"; + + private static final String CLIENT_SOCKET_TIMEOUT = "servicecomb.service.client.timeout.socket"; + private final ServiceCenterRawClient httpClient; - private EventBus eventBus; + private ServiceCenterAddressManager addressManager; public ServiceCenterClient(ServiceCenterRawClient httpClient) { this.httpClient = httpClient; } public ServiceCenterClient setEventBus(EventBus eventBus) { - this.eventBus = eventBus; + addressManager.setEventBus(eventBus); + this.httpClient.setEventBus(eventBus); return this; } @@ -82,14 +91,27 @@ public ServiceCenterClient(ServiceCenterAddressManager addressManager, SSLProperties sslProperties, RequestAuthHeaderProvider requestAuthHeaderProvider, String tenantName, - Map extraGlobalHeaders) { - HttpTransport httpTransport = HttpTransportFactory.createHttpTransport(sslProperties, requestAuthHeaderProvider); + Map extraGlobalHeaders, Environment environment) { + HttpTransport httpTransport = HttpTransportFactory.createHttpTransport(sslProperties, requestAuthHeaderProvider, + buildRequestConfig(environment)); httpTransport.addHeaders(extraGlobalHeaders); this.httpClient = new ServiceCenterRawClient.Builder() .setTenantName(tenantName) .setAddressManager(addressManager) .setHttpTransport(httpTransport).build(); + this.addressManager = addressManager; + } + + private RequestConfig buildRequestConfig(Environment environment) { + RequestConfig.Builder builder = HttpTransportFactory.defaultRequestConfig(); + if (environment == null) { + return builder.build(); + } + builder.setConnectTimeout(environment.getProperty(CLIENT_CONNECT_TIMEOUT, int.class, 5000)); + builder.setConnectionRequestTimeout(environment.getProperty(CLIENT_REQUEST_TIMEOUT, int.class, 5000)); + builder.setSocketTimeout(environment.getProperty(CLIENT_SOCKET_TIMEOUT, int.class, 5000)); + return builder.build(); } @Override @@ -99,7 +121,6 @@ public MicroserviceInstancesResponse getServiceCenterInstances() { if (response.getStatusCode() == HttpStatus.SC_OK) { return HttpUtils.deserialize(response.getContent(), MicroserviceInstancesResponse.class); } - sendUnAuthorizedEvent(response); throw new OperationException( "get service-center instances fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -120,7 +141,6 @@ public RegisteredMicroserviceResponse registerMicroservice(Microservice microser if (response.getStatusCode() == HttpStatus.SC_OK) { return HttpUtils.deserialize(response.getContent(), RegisteredMicroserviceResponse.class); } - sendUnAuthorizedEvent(response); throw new OperationException( "register service fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -138,7 +158,6 @@ public MicroservicesResponse getMicroserviceList() { if (response.getStatusCode() == HttpStatus.SC_OK) { return HttpUtils.deserialize(response.getContent(), MicroservicesResponse.class); } - sendUnAuthorizedEvent(response); throw new OperationException( "get service List fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -163,7 +182,6 @@ public RegisteredMicroserviceResponse queryServiceId(Microservice microservice) if (response.getStatusCode() == HttpStatus.SC_OK) { return HttpUtils.deserialize(response.getContent(), RegisteredMicroserviceResponse.class); } - sendUnAuthorizedEvent(response); LOGGER.info("Query serviceId fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() + "; content = " + response.getContent()); @@ -186,7 +204,6 @@ public Microservice getMicroserviceByServiceId(String serviceId) { .deserialize(response.getContent(), MicroserviceResponse.class); return microserviceResponse.getService(); } - sendUnAuthorizedEvent(response); throw new OperationException( "get service message fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -208,7 +225,6 @@ public RegisteredMicroserviceInstanceResponse registerMicroserviceInstance(Micro if (response.getStatusCode() == HttpStatus.SC_OK) { return HttpUtils.deserialize(response.getContent(), RegisteredMicroserviceInstanceResponse.class); } - sendUnAuthorizedEvent(response); throw new OperationException( "register service instance fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -221,8 +237,7 @@ public RegisteredMicroserviceInstanceResponse registerMicroserviceInstance(Micro @Override public FindMicroserviceInstancesResponse findMicroserviceInstance(String consumerId, String appId, String serviceName, - String versionRule, - String revision) { + String versionRule, String revision) { try { Map headers = new HashMap<>(); headers.put("X-ConsumerId", consumerId); @@ -244,7 +259,11 @@ public FindMicroserviceInstancesResponse findMicroserviceInstance(String consume result.setModified(false); return result; } - sendUnAuthorizedEvent(response); + if (response.getStatusCode() == HttpStatus.SC_TOO_MANY_REQUESTS) { + LOGGER.warn("rate limited, keep the local service {}#{} instance cache unchanged.", appId, serviceName); + result.setModified(false); + return result; + } throw new OperationException( "get service instances list fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -263,7 +282,6 @@ public MicroserviceInstancesResponse getMicroserviceInstanceList(String serviceI if (response.getStatusCode() == HttpStatus.SC_OK) { return HttpUtils.deserialize(response.getContent(), MicroserviceInstancesResponse.class); } - sendUnAuthorizedEvent(response); throw new OperationException( "get service instances list fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -284,7 +302,6 @@ public MicroserviceInstance getMicroserviceInstance(String serviceId, String ins .deserialize(response.getContent(), MicroserviceInstanceResponse.class); return instanceResponse.getInstance(); } - sendUnAuthorizedEvent(response); throw new OperationException( "get service instance message fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -304,7 +321,6 @@ public void deleteMicroserviceInstance(String serviceId, String instanceId) { LOGGER.info("Delete service instance successfully."); return; } - sendUnAuthorizedEvent(response); throw new OperationException( "delete service instance fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -324,7 +340,6 @@ public boolean updateMicroserviceInstanceStatus(String serviceId, String instanc if (response.getStatusCode() == HttpStatus.SC_OK) { return true; } - sendUnAuthorizedEvent(response); throw new OperationException( "update service instance status fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -343,7 +358,6 @@ public void sendHeartBeats(HeartbeatsRequest heartbeatsRequest) { if (response.getStatusCode() == HttpStatus.SC_OK) { return; } - sendUnAuthorizedEvent(response); throw new OperationException( "heartbeats fails, statusCode = " + response.getStatusCode() + "; message = " + response.getMessage() + "; content = " + response.getContent()); @@ -363,7 +377,6 @@ public boolean sendHeartBeat(String serviceId, String instanceId) { if (response.getStatusCode() == HttpStatus.SC_OK) { return true; } - sendUnAuthorizedEvent(response); throw new OperationException( "heartbeats fails, statusCode = " + response.getStatusCode() + "; message = " + response.getMessage() + "; content = " + response.getContent()); @@ -388,7 +401,6 @@ public List getServiceSchemasList(String serviceId, boolean withCont .deserialize(response.getContent(), GetSchemaListResponse.class); return getSchemaResponse.getSchemas(); } - sendUnAuthorizedEvent(response); throw new OperationException( "get service schemas list fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -415,7 +427,6 @@ public String getServiceSchemaContext(String serviceId, String schemaId) { GetSchemaResponse getSchemaResponse = HttpUtils.deserialize(response.getContent(), GetSchemaResponse.class); return getSchemaResponse.getSchema(); } - sendUnAuthorizedEvent(response); throw new OperationException( "get service schema context fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -435,7 +446,6 @@ public boolean registerSchema(String serviceId, String schemaId, CreateSchemaReq if (response.getStatusCode() == HttpStatus.SC_OK) { return true; } - sendUnAuthorizedEvent(response); throw new OperationException( "update service schema fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -458,7 +468,6 @@ public boolean updateServiceSchemaContext(String serviceId, SchemaInfo schemaInf if (response.getStatusCode() == HttpStatus.SC_OK) { return true; } - sendUnAuthorizedEvent(response); throw new OperationException( "update service schema fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -478,7 +487,6 @@ public boolean batchUpdateServiceSchemaContext(String serviceId, ModifySchemasRe if (response.getStatusCode() == HttpStatus.SC_OK) { return true; } - sendUnAuthorizedEvent(response); throw new OperationException( "update service schema fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -489,18 +497,13 @@ public boolean batchUpdateServiceSchemaContext(String serviceId, ModifySchemasRe } } - private void sendUnAuthorizedEvent(HttpResponse response) { - if (this.eventBus != null && response.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { - this.eventBus.post(new UnAuthorizedOperationEvent()); - } - } - @Override - public RbacTokenResponse queryToken(RbacTokenRequest request) { + public RbacTokenResponse queryToken(RbacTokenRequest request, String host) { try { + String queryAddress = addressManager.compareAndGetAddress(host); HttpResponse response = httpClient .postHttpRequestAbsoluteUrl("/v4/token", null, - HttpUtils.serialize(request)); + HttpUtils.serialize(request), queryAddress); if (response.getStatusCode() == HttpStatus.SC_OK) { RbacTokenResponse result = HttpUtils.deserialize(response.getContent(), RbacTokenResponse.class); result.setStatusCode(HttpStatus.SC_OK); @@ -530,16 +533,16 @@ public RbacTokenResponse queryToken(RbacTokenRequest request) { } @Override - public boolean updateMicroserviceProperties(String serviceId, Map serviceProperties) { + public boolean updateMicroserviceProperties(String serviceId, Map serviceProperties, Framework framework) { try { UpdatePropertiesRequest request = new UpdatePropertiesRequest(); request.setProperties(serviceProperties); + request.setFramework(framework); HttpResponse response = httpClient.putHttpRequest( "/registry/microservices/" + serviceId + "/properties", null, HttpUtils.serialize(request)); if (response.getStatusCode() == HttpStatus.SC_OK) { return true; } - sendUnAuthorizedEvent(response); throw new OperationException( "update service instance status fails, statusCode = " + response.getStatusCode() + "; message = " + response .getMessage() @@ -549,4 +552,15 @@ public boolean updateMicroserviceProperties(String serviceId, Map isolationAddresses = addressManager.getIsolationAddresses(); + if (isolationAddresses.isEmpty()) { + return; + } + for (String address : isolationAddresses) { + httpClient.checkAddressAvailable(address); + } + } } diff --git a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterDiscovery.java b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterDiscovery.java index ccc8fb1697a..6a58d1203db 100644 --- a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterDiscovery.java +++ b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterDiscovery.java @@ -17,14 +17,16 @@ package org.apache.servicecomb.service.center.client; -import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Random; +import java.util.Timer; +import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; +import org.apache.commons.lang3.StringUtils; import org.apache.servicecomb.http.client.task.AbstractTask; import org.apache.servicecomb.http.client.task.Task; import org.apache.servicecomb.service.center.client.DiscoveryEvents.InstanceChangedEvent; @@ -101,6 +103,10 @@ public static class SubscriptionValue { private final Object lock = new Object(); + private final Random random = new Random(); + + private Timer timer; + public ServiceCenterDiscovery(ServiceCenterClient serviceCenterClient, EventBus eventBus) { super("service-center-discovery-task"); this.serviceCenterClient = serviceCenterClient; @@ -150,16 +156,68 @@ public void onPullInstanceEvent(PullInstanceEvent event) { return; } pullInstanceTaskOnceInProgress = true; - startTask(new PullInstanceOnceTask()); + if (StringUtils.isEmpty(event.getAppId()) || StringUtils.isEmpty(event.getServiceName())) { + // If the application or service name cannot be resolved, pulled all services. + startTask(new PullInstanceOnceTask()); + return; + } + try { + String appId = event.getAppId(); + String serviceName = event.getServiceName(); + if (!refreshTargetServiceSuccess(appId, serviceName)) { + int positive = random.nextInt(300); + int sign = random.nextBoolean() ? 1 : -1; + long delayTime = 2000L + sign * positive; + if (timer == null) { + timer = new Timer("event-retry-pull-task"); + } + timer.schedule(new PullTargetServiceTask(appId, serviceName), delayTime); + } + } finally { + pullInstanceTaskOnceInProgress = false; + } + } + + class PullTargetServiceTask extends TimerTask { + private final String appId; + + private final String serviceName; + + public PullTargetServiceTask(String appId, String serviceName) { + this.appId = appId; + this.serviceName = serviceName; + } + + @Override + public void run() { + refreshTargetServiceSuccess(appId, serviceName); + } + } + + private boolean refreshTargetServiceSuccess(String appId, String serviceName) { + SubscriptionKey currentKey = new SubscriptionKey(appId, serviceName); + if (instancesCache.get(currentKey) == null) { + // No pull during the service startup phase. + return true; + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("pull [{}#{}] instances from service center", appId, serviceName); + } + String originRev = instancesCache.get(currentKey).revision; + pullInstance(currentKey, instancesCache.get(currentKey), true); + String currentRev = instancesCache.get(currentKey).revision; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("current revision: [{}], origin revision: [{}]", currentRev, originRev); + } + return !originRev.equals(currentRev); } - private List pullInstance(SubscriptionKey k, SubscriptionValue v, boolean sendChangedEvent) { + private void pullInstance(SubscriptionKey k, SubscriptionValue v, boolean sendChangedEvent) { if (myselfServiceId == null) { // registration not ready - return Collections.emptyList(); + return; } - List failedKeys = new ArrayList<>(); try { FindMicroserviceInstancesResponse instancesResponse = serviceCenterClient .findMicroserviceInstance(myselfServiceId, k.appId, k.serviceName, ALL_VERSION, v.revision); @@ -186,14 +244,9 @@ private List pullInstance(SubscriptionKey k, SubscriptionValue } } } catch (Exception e) { - LOGGER.error("find service {}#{} instance failed.", k.appId, k.serviceName, e); - if (!(e.getCause() instanceof IOException)) { - // for IOException, do not remove cache, or when service center - // not available, invocation between microservices will fail. - failedKeys.add(k); - } + LOGGER.warn("find service {}#{} instance failed, remaining local instances cache [{}], cause message: {}", + k.appId, k.serviceName, instanceToString(v.instancesCache), e.getMessage()); } - return failedKeys; } private void setMicroserviceInfo(List instances) { @@ -216,10 +269,17 @@ class PullInstanceTask implements Task { public void execute() { pullAllInstance(); - startTask(new BackOffSleepTask(pollInterval, new PullInstanceTask())); + startTask(new BackOffSleepTask(buildPollIntervalWithSalt(), new PullInstanceTask())); } } + private long buildPollIntervalWithSalt() { + int positive = random.nextInt(5); + int sign = random.nextBoolean() ? 1 : -1; + long currentPollInterval = pollInterval + sign * positive * 1000; + return currentPollInterval > 0 ? currentPollInterval : pollInterval; + } + class PullInstanceOnceTask implements Task { @Override public void execute() { @@ -232,13 +292,9 @@ public void execute() { } private synchronized void pullAllInstance() { - List failedInstances = new ArrayList<>(); - instancesCache.forEach((k, v) -> failedInstances.addAll(pullInstance(k, v, true))); - if (failedInstances.isEmpty()) { - return; - } - failedInstances.forEach(instancesCache::remove); - failedInstances.clear(); + instancesCache.forEach((k, v) -> { + pullInstance(k, v, true); + }); } private static String instanceToString(List instances) { @@ -252,7 +308,7 @@ private static String instanceToString(List instances) { sb.append(endpoint.length() > 64 ? endpoint.substring(0, 64) : endpoint); sb.append("|"); } - sb.append(instance.getServiceName()); + sb.append(instance.getStatus()); sb.append("|"); } sb.append("#"); diff --git a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterOperation.java b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterOperation.java index 5521a732244..15dd1b5de35 100644 --- a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterOperation.java +++ b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterOperation.java @@ -23,6 +23,7 @@ import org.apache.servicecomb.service.center.client.exception.OperationException; import org.apache.servicecomb.service.center.client.model.CreateSchemaRequest; import org.apache.servicecomb.service.center.client.model.FindMicroserviceInstancesResponse; +import org.apache.servicecomb.service.center.client.model.Framework; import org.apache.servicecomb.service.center.client.model.HeartbeatsRequest; import org.apache.servicecomb.service.center.client.model.Microservice; import org.apache.servicecomb.service.center.client.model.MicroserviceInstance; @@ -113,8 +114,7 @@ public interface ServiceCenterOperation { * @throws OperationException If some problems happened to contact service center or non http 200 returned.n */ FindMicroserviceInstancesResponse findMicroserviceInstance(String consumerId, String appId, String serviceName, - String versionRule, - String revision); + String versionRule, String revision); /** * Delete a microservice instance @@ -184,7 +184,7 @@ boolean updateMicroserviceInstanceStatus(String serviceId, String instanceId, * @return if heartbeat is successful * @throws OperationException If some problems happened to contact service center or non http 200 returned. */ - RbacTokenResponse queryToken(RbacTokenRequest request); + RbacTokenResponse queryToken(RbacTokenRequest request, String host); /** * Update properties of microservice @@ -192,5 +192,11 @@ boolean updateMicroserviceInstanceStatus(String serviceId, String instanceId, * @return if update is successful * @throws OperationException If some problems happened to contact service center or non http 200 returned. */ - boolean updateMicroserviceProperties(String microserviceId, Map serviceProperties); + boolean updateMicroserviceProperties(String microserviceId, Map serviceProperties, + Framework framework); + + /** + * Check serviceCenter isolation address available + */ + void checkIsolationAddressAvailable(); } diff --git a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterRawClient.java b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterRawClient.java index 6c293004cc0..a76f86ddd79 100755 --- a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterRawClient.java +++ b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterRawClient.java @@ -21,23 +21,33 @@ import java.util.HashMap; import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpStatus; import org.apache.servicecomb.http.client.common.HttpRequest; import org.apache.servicecomb.http.client.common.HttpResponse; import org.apache.servicecomb.http.client.common.HttpTransport; +import org.apache.servicecomb.http.client.event.OperationEvents.UnAuthorizedOperationEvent; +import org.apache.servicecomb.http.client.utils.ServiceCombServiceAvailableUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.eventbus.EventBus; + public class ServiceCenterRawClient { private static final Logger LOGGER = LoggerFactory.getLogger(ServiceCenterRawClient.class); private static final String HEADER_TENANT_NAME = "x-domain-name"; + private static final String ADDRESS_CHECK_PATH = "/v4/default/registry/health/readiness"; + private final String tenantName; private final HttpTransport httpTransport; private final ServiceCenterAddressManager addressManager; + private EventBus eventBus; + private ServiceCenterRawClient(String tenantName, HttpTransport httpTransport, ServiceCenterAddressManager addressManager) { this.httpTransport = httpTransport; @@ -46,40 +56,35 @@ private ServiceCenterRawClient(String tenantName, HttpTransport httpTransport, } public HttpResponse getHttpRequest(String url, Map headers, String content) throws IOException { - return doHttpRequest(url, false, headers, content, HttpRequest.GET); + return doHttpRequest(url, false, headers, content, HttpRequest.GET, ""); } - public HttpResponse postHttpRequestAbsoluteUrl(String url, Map headers, String content) + public HttpResponse postHttpRequestAbsoluteUrl(String url, Map headers, String content, String address) throws IOException { - return doHttpRequest(url, true, headers, content, HttpRequest.POST); + return doHttpRequest(url, true, headers, content, HttpRequest.POST, address); } public HttpResponse postHttpRequest(String url, Map headers, String content) throws IOException { - return doHttpRequest(url, false, headers, content, HttpRequest.POST); + return doHttpRequest(url, false, headers, content, HttpRequest.POST, ""); } public HttpResponse putHttpRequest(String url, Map headers, String content) throws IOException { - return doHttpRequest(url, false, headers, content, HttpRequest.PUT); + return doHttpRequest(url, false, headers, content, HttpRequest.PUT, ""); } public HttpResponse deleteHttpRequest(String url, Map headers, String content) throws IOException { - return doHttpRequest(url, false, headers, content, HttpRequest.DELETE); + return doHttpRequest(url, false, headers, content, HttpRequest.DELETE, ""); } private HttpResponse doHttpRequest(String url, boolean absoluteUrl, Map headers, String content, - String method) - throws IOException { - String address = addressManager.address(); + String method, String queryAddress) throws IOException { + String address = StringUtils.isEmpty(queryAddress) ? addressManager.address() : queryAddress; String formatUrl = addressManager.formatUrl(url, absoluteUrl, address); - if (headers == null) { - headers = new HashMap<>(); - } - headers.put(HEADER_TENANT_NAME, tenantName); - HttpRequest httpRequest = new HttpRequest(formatUrl, headers, content, method); - + HttpRequest httpRequest = buildHttpRequest(formatUrl, headers, content, method); + HttpResponse httpResponse; try { - HttpResponse httpResponse = httpTransport.doRequest(httpRequest); - addressManager.recordSuccessState(address); + httpResponse = httpTransport.doRequest(httpRequest); + recordAndSendUnAuthorizedEvent(httpResponse, address); return httpResponse; } catch (IOException e) { addressManager.recordFailState(address); @@ -88,7 +93,9 @@ private HttpResponse doHttpRequest(String url, boolean absoluteUrl, Map headers, String content, String method) { + if (headers == null) { + headers = new HashMap<>(); + } + headers.put(HEADER_TENANT_NAME, tenantName); + return new HttpRequest(url, headers, content, method); + } + + public void setEventBus(EventBus eventBus) { + this.eventBus = eventBus; + } + public static class Builder { private String tenantName; diff --git a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterRegistration.java b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterRegistration.java index c9340e4d380..97d414b2640 100644 --- a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterRegistration.java +++ b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterRegistration.java @@ -104,6 +104,7 @@ public ServiceCenterRegistration setSchemaInfos(List schemaInfos) { public void startRegistration() { startTask(new RegisterMicroserviceTask(0)); + schedulerCheckAddressAvailable("sc-addr-check", new CheckAddressTask(), heartBeatInterval); } class RegisterMicroserviceTask implements Task { @@ -137,13 +138,13 @@ public void execute() { microservice.setProperties(newMicroservice.getProperties()); microservice.getProperties().putAll(propertiesTemp); if (serviceCenterClient.updateMicroserviceProperties(serviceResponse.getServiceId(), - microservice.getProperties())) { + microservice.getProperties(), microservice.getFramework())) { LOGGER.info( - "microservice is already registered. Update microservice properties successfully. properties=[{}]", - microservice.getProperties()); + "microservice is already registered. Update microservice properties successfully. properties=[{}], " + + "frameworkVersion [{}]", microservice.getProperties(), microservice.getFramework().getVersion()); } else { - LOGGER.error("microservice is already registered. Update microservice properties failed. properties=[{}]", - microservice.getProperties()); + LOGGER.error("microservice is already registered. Update microservice properties failed. properties=[{}], " + + "frameworkVersion [{}]", microservice.getProperties(), microservice.getFramework().getVersion()); } microservice.setServiceId(serviceResponse.getServiceId()); @@ -276,7 +277,7 @@ public void execute() { } if (!serviceCenterClient.sendHeartBeat(microservice.getServiceId(), microserviceInstance.getInstanceId())) { - LOGGER.error("send heart failed, and will try again."); + LOGGER.warn("send heart failed, and will try again."); eventBus.post(new HeartBeatEvent(false, microservice, microserviceInstance)); startTask(new BackOffSleepTask(failedCount + 1, new SendHeartBeatTask(failedCount + 1))); } else { @@ -286,10 +287,22 @@ public void execute() { new BackOffSleepTask(Math.max(heartBeatInterval, heartBeatRequestTimeout), new SendHeartBeatTask(0))); } } catch (Exception e) { - LOGGER.error("send heart failed, and will try again.", e); + // If heartbeat failures three times, log error stack help troubleshooting. Others just log message as warn. + if (failedCount == 2) { + LOGGER.error("send heart failed, and will try again.", e); + } else { + LOGGER.warn("send heart failed, and will try again. message [{}]", e.getMessage()); + } eventBus.post(new HeartBeatEvent(false, microservice, microserviceInstance)); startTask(new BackOffSleepTask(failedCount + 1, new SendHeartBeatTask(failedCount + 1))); } } } + + class CheckAddressTask implements Runnable { + @Override + public void run() { + serviceCenterClient.checkIsolationAddressAvailable(); + } + } } diff --git a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterWatch.java b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterWatch.java index a8be8b98b78..a7d678e30f2 100644 --- a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterWatch.java +++ b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/ServiceCenterWatch.java @@ -17,12 +17,14 @@ package org.apache.servicecomb.service.center.client; +import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.servicecomb.foundation.auth.SignRequest; import org.apache.servicecomb.http.client.auth.RequestAuthHeaderProvider; import org.apache.servicecomb.http.client.common.HttpConfiguration.SSLProperties; import org.apache.servicecomb.http.client.common.WebSocketListener; @@ -107,7 +109,7 @@ private void startWatch() { Map headers = new HashMap<>(); headers.put("x-domain-name", this.tenantName); headers.putAll(this.extraGlobalHeaders); - headers.putAll(this.requestAuthHeaderProvider.loadAuthHeader(null)); + headers.putAll(this.requestAuthHeaderProvider.loadAuthHeader(createSignRequest(address))); currentServerUri = convertAddress(address); LOGGER.info("start watch to address {}", currentServerUri); webSocketTransport = new WebSocketTransport(currentServerUri, sslProperties, @@ -121,6 +123,17 @@ private void startWatch() { }); } + private SignRequest createSignRequest(String url) { + try { + URI uri = URI.create(url); + SignRequest signRequest = new SignRequest(); + signRequest.setEndpoint(uri); + return signRequest; + } catch (Exception e) { + return null; + } + } + private String convertAddress(String address) { String url = String.format(WATCH, project, serviceId); if (address.startsWith(HTTP)) { @@ -164,7 +177,7 @@ private void backOff() { @Override public void onMessage(String s) { LOGGER.info("web socket receive message [{}], start query instance", s); - this.eventBus.post(new PullInstanceEvent()); + this.eventBus.post(new PullInstanceEvent(s)); } @Override diff --git a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/model/UpdatePropertiesRequest.java b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/model/UpdatePropertiesRequest.java index b10586208a8..d7d4b093930 100644 --- a/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/model/UpdatePropertiesRequest.java +++ b/clients/service-center-client/src/main/java/org/apache/servicecomb/service/center/client/model/UpdatePropertiesRequest.java @@ -22,6 +22,8 @@ public class UpdatePropertiesRequest { private Map properties; + private Framework framework; + public Map getProperties() { return properties; } @@ -29,4 +31,12 @@ public Map getProperties() { public void setProperties(Map properties) { this.properties = properties; } + + public Framework getFramework() { + return framework; + } + + public void setFramework(Framework framework) { + this.framework = framework; + } } diff --git a/clients/service-center-client/src/test/java/org/apache/servicecomb/service/center/client/ServiceCenterAddressManagerTest.java b/clients/service-center-client/src/test/java/org/apache/servicecomb/service/center/client/ServiceCenterAddressManagerTest.java index f5c316cd848..d425cc5d449 100644 --- a/clients/service-center-client/src/test/java/org/apache/servicecomb/service/center/client/ServiceCenterAddressManagerTest.java +++ b/clients/service-center-client/src/test/java/org/apache/servicecomb/service/center/client/ServiceCenterAddressManagerTest.java @@ -41,7 +41,7 @@ class ServiceCenterAddressManagerTest { @Test public void getUrlPrefix() { addresses.add("http://127.0.0.1:30103"); - addressManager1 = new ServiceCenterAddressManager("project", addresses, new EventBus()); + addressManager1 = new ServiceCenterAddressManager("project", addresses, "", "", new EventBus()); Assertions.assertNotNull(addressManager1); @@ -55,7 +55,7 @@ public void getUrlPrefix() { @Test public void formatUrlTest() { addresses.add("http://127.0.0.1:30103"); - addressManager1 = new ServiceCenterAddressManager("project", addresses, new EventBus()); + addressManager1 = new ServiceCenterAddressManager("project", addresses, "", "", new EventBus()); Assertions.assertNotNull(addressManager1); String address = addressManager1.address(); @@ -76,7 +76,7 @@ public void onRefreshEndpointEvent() { Map> zoneAndRegion = new HashMap<>(); zoneAndRegion.put("sameZone", addressAZ); zoneAndRegion.put("sameRegion", addressRG); - addressManager1 = new ServiceCenterAddressManager("project", addresses, new EventBus()); + addressManager1 = new ServiceCenterAddressManager("project", addresses, "", "", new EventBus()); RefreshEndpointEvent event = new RefreshEndpointEvent(zoneAndRegion, "SERVICECENTER"); addressManager1.refreshEndpoint(event, "SERVICECENTER"); diff --git a/clients/service-center-client/src/test/java/org/apache/servicecomb/service/center/client/ServiceCenterClientTest.java b/clients/service-center-client/src/test/java/org/apache/servicecomb/service/center/client/ServiceCenterClientTest.java index 12ad9cda3ab..1c39853bcde 100755 --- a/clients/service-center-client/src/test/java/org/apache/servicecomb/service/center/client/ServiceCenterClientTest.java +++ b/clients/service-center-client/src/test/java/org/apache/servicecomb/service/center/client/ServiceCenterClientTest.java @@ -23,6 +23,7 @@ import java.util.List; import org.apache.servicecomb.http.client.common.HttpResponse; +import org.apache.servicecomb.service.center.client.model.Framework; import org.apache.servicecomb.service.center.client.model.HeartbeatsRequest; import org.apache.servicecomb.service.center.client.model.InstancesRequest; import org.apache.servicecomb.service.center.client.model.Microservice; @@ -508,7 +509,7 @@ public void testUpdateMicroserviceProperties() throws IOException { ServiceCenterClient serviceCenterClient = new ServiceCenterClient(serviceCenterRawClient); boolean result = serviceCenterClient - .updateMicroserviceProperties("111", new HashMap()); + .updateMicroserviceProperties("111", new HashMap(), new Framework()); Assertions.assertTrue(result); } diff --git a/clients/service-center-client/src/test/java/org/apache/servicecomb/service/center/client/ServiceCenterRawClientTest.java b/clients/service-center-client/src/test/java/org/apache/servicecomb/service/center/client/ServiceCenterRawClientTest.java index 29337e31a44..0cda29fc573 100755 --- a/clients/service-center-client/src/test/java/org/apache/servicecomb/service/center/client/ServiceCenterRawClientTest.java +++ b/clients/service-center-client/src/test/java/org/apache/servicecomb/service/center/client/ServiceCenterRawClientTest.java @@ -41,7 +41,8 @@ public class ServiceCenterRawClientTest { public void TestDefaultParameter() throws IOException { HttpTransport httpTransport = Mockito.mock(HttpTransport.class); - ServiceCenterAddressManager addressManager = new ServiceCenterAddressManager(PROJECT_NAME, Arrays.asList("http://127.0.0.1:30100"), new EventBus()); + ServiceCenterAddressManager addressManager = new ServiceCenterAddressManager(PROJECT_NAME, + Arrays.asList("http://127.0.0.1:30100"), "", "", new EventBus()); ServiceCenterRawClient client = new ServiceCenterRawClient.Builder() .setHttpTransport(httpTransport) .setAddressManager(addressManager) diff --git a/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/core/element/impl/DurationMillisecondAccessItem.java b/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/core/element/impl/DurationMillisecondAccessItem.java index e0bde4ad306..9d570312000 100644 --- a/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/core/element/impl/DurationMillisecondAccessItem.java +++ b/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/core/element/impl/DurationMillisecondAccessItem.java @@ -26,12 +26,19 @@ public class DurationMillisecondAccessItem implements AccessLogItem { @Override public void appendServerFormattedItem(ServerAccessLogEvent accessLogEvent, StringBuilder builder) { - builder.append(accessLogEvent.getMilliEndTime() - accessLogEvent.getMilliStartTime()); + builder.append(calc(accessLogEvent.getMilliEndTime(), accessLogEvent.getMilliStartTime())); } @Override public void appendClientFormattedItem(InvocationFinishEvent finishEvent, StringBuilder builder) { - builder.append((finishEvent.getInvocation().getInvocationStageTrace().getFinish() - + builder.append(calc(finishEvent.getInvocation().getInvocationStageTrace().getFinish(), finishEvent.getInvocation().getInvocationStageTrace().getStartSend()) / 1000_000); } + + private long calc(long end, long begin) { + if (begin == 0 || end == 0) { + return 0; + } + return end - begin; + } } diff --git a/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/core/element/impl/DurationSecondAccessItem.java b/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/core/element/impl/DurationSecondAccessItem.java index 2da34026d5e..9dcd015fc92 100644 --- a/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/core/element/impl/DurationSecondAccessItem.java +++ b/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/core/element/impl/DurationSecondAccessItem.java @@ -28,12 +28,19 @@ public class DurationSecondAccessItem implements AccessLogItem { @Override public void appendServerFormattedItem(ServerAccessLogEvent accessLogEvent, StringBuilder builder) { - builder.append((accessLogEvent.getMilliEndTime() - accessLogEvent.getMilliStartTime()) / 1000); + builder.append(calc(accessLogEvent.getMilliEndTime(), accessLogEvent.getMilliStartTime()) / 1000); } @Override public void appendClientFormattedItem(InvocationFinishEvent finishEvent, StringBuilder builder) { - builder.append((finishEvent.getInvocation().getInvocationStageTrace().getFinish() - + builder.append(calc(finishEvent.getInvocation().getInvocationStageTrace().getFinish(), finishEvent.getInvocation().getInvocationStageTrace().getStartSend()) / 1000_000_000); } + + private long calc(long end, long begin) { + if (begin == 0 || end == 0) { + return 0; + } + return end - begin; + } } diff --git a/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/ws/WebSocketAccessLogConfig.java b/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/ws/WebSocketAccessLogConfig.java new file mode 100644 index 00000000000..107c1e1c065 --- /dev/null +++ b/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/ws/WebSocketAccessLogConfig.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.common.accessLog.ws; + +import com.netflix.config.DynamicPropertyFactory; + +public class WebSocketAccessLogConfig { + private static final String BASE = "servicecomb.accesslog.ws."; + + private static final String SERVER_BASE = BASE + "server."; + + private static final String CLIENT_BASE = BASE + "client."; + + private static final String SERVER_LOG_ENABLED = SERVER_BASE + "enabled"; + + private static final String CLIENT_LOG_ENABLED = CLIENT_BASE + "enabled"; + + private boolean serverLogEnabled; + + private boolean clientLogEnabled; + + public static final WebSocketAccessLogConfig INSTANCE = new WebSocketAccessLogConfig(); + + private WebSocketAccessLogConfig() { + clientLogEnabled = DynamicPropertyFactory + .getInstance().getBooleanProperty(CLIENT_LOG_ENABLED, false).get(); + serverLogEnabled = DynamicPropertyFactory + .getInstance().getBooleanProperty(SERVER_LOG_ENABLED, false).get(); + } + + public boolean isServerLogEnabled() { + return serverLogEnabled; + } + + public boolean isClientLogEnabled() { + return clientLogEnabled; + } +} diff --git a/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/ws/WebSocketAccessLogGenerator.java b/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/ws/WebSocketAccessLogGenerator.java new file mode 100644 index 00000000000..5772c79378a --- /dev/null +++ b/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/ws/WebSocketAccessLogGenerator.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.common.accessLog.ws; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import org.apache.servicecomb.core.event.WebSocketActionEvent; + +/** + * Similar to {@link org.apache.servicecomb.common.accessLog.core.AccessLogGenerator}, + * this is an access log generator for WebSocket protocol. + */ +public class WebSocketAccessLogGenerator { + + public static final String DEFAULT_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + + public static final Locale DEFAULT_LOCALE = Locale.US; + + public static final TimeZone TIME_ZONE = TimeZone.getDefault(); + + private final ThreadLocal datetimeFormatHolder = new ThreadLocal<>(); + + public String generateServerLog(WebSocketActionEvent actionEvent) { + return generateLog(actionEvent); + } + + public String generateClientLog(WebSocketActionEvent actionEvent) { + return generateLog(actionEvent); + } + + private String generateLog(WebSocketActionEvent actionEvent) { + return actionEvent.getInvocationType() + + "|" + + actionEvent.getOperationMeta().getMicroserviceQualifiedName() + + "|" + + formatTimestampToDateTimeStr(actionEvent.getActionStartTimestamp()) + + "|" + + actionEvent.getTraceId() + + "|" + + actionEvent.getConnectionId() + + "|" + + actionEvent.getActionType() + + "|" + + (actionEvent.getActionStartTimestamp() - actionEvent.getScheduleStartTimestamp()) + + "|" + + (actionEvent.getActionEndTimestamp() - actionEvent.getActionStartTimestamp()) + + "|" + + actionEvent.getHandleThreadName() + + "|" + + actionEvent.getDataSize(); + } + + private String formatTimestampToDateTimeStr(long timestamp) { + return getDatetimeFormat() + .format(new Date(timestamp)); + } + + private SimpleDateFormat getDatetimeFormat() { + SimpleDateFormat dateFormat = datetimeFormatHolder.get(); + if (null == dateFormat) { + dateFormat = new SimpleDateFormat(DEFAULT_DATETIME_PATTERN, DEFAULT_LOCALE); + dateFormat.setTimeZone(TIME_ZONE); + + datetimeFormatHolder.set(dateFormat); + } + + return dateFormat; + } +} diff --git a/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/ws/WebSocketAccessLogInitializer.java b/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/ws/WebSocketAccessLogInitializer.java new file mode 100644 index 00000000000..2b3fc799af8 --- /dev/null +++ b/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/ws/WebSocketAccessLogInitializer.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.common.accessLog.ws; + +import org.apache.servicecomb.common.accessLog.AccessLogConfig; +import org.apache.servicecomb.common.accessLog.AccessLogInitializer; +import org.apache.servicecomb.core.event.WebSocketActionEvent; +import org.apache.servicecomb.swagger.invocation.InvocationType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.eventbus.AllowConcurrentEvents; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +public class WebSocketAccessLogInitializer implements AccessLogInitializer { + private static final Logger ACCESS_LOG_LOGGER = LoggerFactory.getLogger("ws.accesslog"); + + private static final Logger REQUEST_LOG_LOGGER = LoggerFactory.getLogger("ws.requestlog"); + + private WebSocketAccessLogGenerator accessLogGenerator; + + private boolean clientLogEnabled; + + private boolean serverLogEnabled; + + @Override + public void init(EventBus eventBus, AccessLogConfig accessLogConfig) { + WebSocketAccessLogConfig config = WebSocketAccessLogConfig.INSTANCE; + clientLogEnabled = config.isClientLogEnabled(); + serverLogEnabled = config.isServerLogEnabled(); + if (clientLogEnabled || serverLogEnabled) { + accessLogGenerator = new WebSocketAccessLogGenerator(); + eventBus.register(this); + } + } + + @Subscribe + @AllowConcurrentEvents + public void onRequestReceived(WebSocketActionEvent actionEvent) { + if (actionEvent == null) { + return; + } + InvocationType invocationType = actionEvent.getInvocationType(); + if (invocationType == null) { + return; + } + + switch (invocationType) { + case CONSUMER: + if (clientLogEnabled) { + REQUEST_LOG_LOGGER.info(accessLogGenerator.generateClientLog(actionEvent)); + } + break; + case PRODUCER: { + if (serverLogEnabled) { + ACCESS_LOG_LOGGER.info(accessLogGenerator.generateServerLog(actionEvent)); + } + break; + } + default: + throw new IllegalStateException("unexpected websocket invocation type: " + invocationType); + } + } +} diff --git a/common/common-access-log/src/main/resources/META-INF/services/org.apache.servicecomb.common.accessLog.AccessLogInitializer b/common/common-access-log/src/main/resources/META-INF/services/org.apache.servicecomb.common.accessLog.AccessLogInitializer index 159849dab99..02d24d5af96 100644 --- a/common/common-access-log/src/main/resources/META-INF/services/org.apache.servicecomb.common.accessLog.AccessLogInitializer +++ b/common/common-access-log/src/main/resources/META-INF/services/org.apache.servicecomb.common.accessLog.AccessLogInitializer @@ -17,3 +17,4 @@ org.apache.servicecomb.common.accessLog.client.ClientDefaultInitializer org.apache.servicecomb.common.accessLog.server.ServerDefaultInitializer +org.apache.servicecomb.common.accessLog.ws.WebSocketAccessLogInitializer diff --git a/common/common-access-log/src/test/java/org/apache/servicecomb/common/accessLog/core/element/impl/DurationMillisecondItemTest.java b/common/common-access-log/src/test/java/org/apache/servicecomb/common/accessLog/core/element/impl/DurationMillisecondItemTest.java index 7ad32b23f87..c97afc41d3c 100644 --- a/common/common-access-log/src/test/java/org/apache/servicecomb/common/accessLog/core/element/impl/DurationMillisecondItemTest.java +++ b/common/common-access-log/src/test/java/org/apache/servicecomb/common/accessLog/core/element/impl/DurationMillisecondItemTest.java @@ -64,6 +64,13 @@ public void testAppendFormattedElement() { ELEMENT.appendServerFormattedItem(accessLogEvent, strBuilder); Assertions.assertEquals("1", strBuilder.toString()); + strBuilder = new StringBuilder(); + ELEMENT.appendClientFormattedItem(finishEvent, strBuilder); + Assertions.assertEquals("0", strBuilder.toString()); + + when(invocationStageTrace.getStartSend()).thenReturn(1000L); + when(invocationStageTrace.getFinish()).thenReturn(1001_000L); + strBuilder = new StringBuilder(); ELEMENT.appendClientFormattedItem(finishEvent, strBuilder); Assertions.assertEquals("1", strBuilder.toString()); diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/AbstractRestInvocation.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/AbstractRestInvocation.java index 7a2af164668..a9431a99074 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/AbstractRestInvocation.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/AbstractRestInvocation.java @@ -48,6 +48,7 @@ import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx; import org.apache.servicecomb.swagger.invocation.Response; import org.apache.servicecomb.swagger.invocation.exception.InvocationException; +import org.apache.servicecomb.swagger.invocation.ws.ServerWebSocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -190,6 +191,8 @@ protected void scheduleInvocation() { synchronized (this.requestEx) { try { if (isInQueueTimeout()) { + // record queue time + invocation.onExecuteStart(); throw new InvocationException(Status.INTERNAL_SERVER_ERROR, "Timeout when processing the request."); } if (requestEx.getAttribute(RestConst.REST_REQUEST) != requestEx) { @@ -277,7 +280,7 @@ protected Response prepareInvoke() throws Throwable { invocation.getInvocationStageTrace().startServerFiltersRequest(); for (HttpServerFilter filter : httpServerFilters) { - if (filter.enabled()) { + if (filter.enabled() && filter.enabledForTransport(invocation.getProviderTransportName())) { Response response = filter.afterReceiveRequest(invocation, requestEx); if (response != null) { return response; @@ -318,7 +321,9 @@ protected void sendResponseQuietly(Response response) { protected void sendResponse(Response response) { RestServerCodecFilter.copyHeadersToHttpResponse(response.getHeaders(), responseEx); - responseEx.setStatus(response.getStatusCode(), response.getReasonPhrase()); + if (!(response.getResult() instanceof ServerWebSocket)) { + responseEx.setStatus(response.getStatusCode(), response.getReasonPhrase()); + } responseEx.setAttribute(RestConst.INVOCATION_HANDLER_RESPONSE, response); responseEx.setAttribute(RestConst.INVOCATION_HANDLER_PROCESSOR, produceProcessor); @@ -344,18 +349,20 @@ protected void onExecuteHttpServerFiltersFinish(Response response, Throwable e) getMicroserviceQualifiedName(), requestEx.getRequestURI(), e); } - try { - responseEx.flushBuffer(); - } catch (Throwable flushException) { - LOGGER.error("Failed to flush rest response, operation:{}, request uri:{}", - getMicroserviceQualifiedName(), requestEx.getRequestURI(), flushException); - } + if (!(response.getResult() instanceof ServerWebSocket)) { + try { + responseEx.flushBuffer(); + } catch (Throwable flushException) { + LOGGER.error("Failed to flush rest response, operation:{}, request uri:{}", + getMicroserviceQualifiedName(), requestEx.getRequestURI(), flushException); + } - try { - requestEx.getAsyncContext().complete(); - } catch (Throwable completeException) { - LOGGER.error("Failed to complete async rest response, operation:{}, request uri:{}", - getMicroserviceQualifiedName(), requestEx.getRequestURI(), completeException); + try { + requestEx.getAsyncContext().complete(); + } catch (Throwable completeException) { + LOGGER.error("Failed to complete async rest response, operation:{}, request uri:{}", + getMicroserviceQualifiedName(), requestEx.getRequestURI(), completeException); + } } // if failed to locate path, then will not create invocation diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/RestConst.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/RestConst.java index 3d0bd954ab0..12ae2e0ecf6 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/RestConst.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/RestConst.java @@ -86,4 +86,6 @@ private RestConst() { public static final String HEADER_CONTEXT_MAPPER = "servicecomb.context.headerContextMapper"; public static final String QUERY_CONTEXT_MAPPER = "servicecomb.context.queryContextMapper"; + + public static final String DECODE_INVOCATION_CONTEXT = "servicecomb.context.decodeInvocationContext"; } diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/RestProducerInvocationCreator.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/RestProducerInvocationCreator.java index f93bc597dbd..73bad7e8d47 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/RestProducerInvocationCreator.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/RestProducerInvocationCreator.java @@ -27,7 +27,6 @@ import javax.annotation.Nonnull; import javax.ws.rs.core.HttpHeaders; -import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.StringUtils; import org.apache.servicecomb.common.rest.codec.produce.ProduceProcessor; import org.apache.servicecomb.common.rest.definition.RestOperationMeta; @@ -45,6 +44,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.annotations.VisibleForTesting; +import com.netflix.config.DynamicPropertyFactory; + import io.vertx.core.json.Json; public abstract class RestProducerInvocationCreator implements InvocationCreator { @@ -90,6 +92,11 @@ protected Invocation createInstance() { } protected void initInvocationContext(Invocation invocation) { + if (!DynamicPropertyFactory.getInstance() + .getBooleanProperty(RestConst.DECODE_INVOCATION_CONTEXT, true).get()) { + return; + } + String strCseContext = requestEx.getHeader(Const.CSE_CONTEXT); if (StringUtils.isEmpty(strCseContext)) { return; diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestCodec.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestCodec.java index bf5d83ddb3e..39fc4de26ec 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestCodec.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestCodec.java @@ -24,15 +24,12 @@ import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.Response.Status; -import org.apache.servicecomb.common.rest.RestConst; import org.apache.servicecomb.common.rest.definition.RestOperationMeta; import org.apache.servicecomb.common.rest.definition.RestParam; import org.apache.servicecomb.swagger.invocation.exception.InvocationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.netflix.config.DynamicPropertyFactory; - public final class RestCodec { private static final Logger LOG = LoggerFactory.getLogger(RestCodec.class); @@ -60,25 +57,17 @@ public static Map restToArgs(HttpServletRequest request, for (RestParam param : paramList) { try { paramValues.put(param.getParamName(), param.getParamProcessor().getValue(request)); - } catch (InvocationException e) { - throw e; } catch (Exception e) { // Avoid information leak of user input, and add option for debug use. String message = String - .format("Parameter is not valid for operation [%s]. Parameter is [%s]. Processor is [%s].", + .format("Parameter is not valid for operation [%s]. Parameter is [%s]. Processor is [%s]. Message is [%s].", restOperation.getOperationMeta().getMicroserviceQualifiedName(), param.getParamName(), - param.getParamProcessor().getProcessorType()); - if (DynamicPropertyFactory.getInstance().getBooleanProperty( - RestConst.PRINT_CODEC_ERROR_MESSGAGE, false).get()) { - LOG.error(message, e); - } else { - LOG.error("{} Add {}=true to print the details.", message, RestConst.PRINT_CODEC_ERROR_MESSGAGE); - } + param.getParamProcessor().getProcessorType(), + e.getMessage()); throw new InvocationException(Status.BAD_REQUEST, message); } } - return paramValues; } } diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodec.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodec.java new file mode 100644 index 00000000000..546605d6bcd --- /dev/null +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodec.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.common.rest.codec.header; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +import org.apache.servicecomb.common.rest.codec.RestClientRequest; +import org.apache.servicecomb.common.rest.codec.RestObjectMapperFactory; +import org.apache.servicecomb.common.rest.codec.param.HeaderProcessorCreator.HeaderProcessor; + +import javax.servlet.http.HttpServletRequest; + +public interface HeaderCodec { + static String encodeValue(Object value) throws UnsupportedEncodingException { + return URLEncoder.encode(value.toString(), StandardCharsets.UTF_8.name()); + } + + // can not be replaced by value.toString() because of date serialize + static String convertToString(Object value) throws Exception { + return RestObjectMapperFactory.getRestObjectMapper().convertToString(value); + } + + String getCodecName(); + + void encode(RestClientRequest clientRequest, String name, Object value) throws Exception; + + Object decode(HeaderProcessor processor, HttpServletRequest request); +} diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecCsv.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecCsv.java new file mode 100644 index 00000000000..c2db1f12f1d --- /dev/null +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecCsv.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.common.rest.codec.header; + +public class HeaderCodecCsv extends HeaderCodecWithDelimiter { + public static final String CODEC_NAME = "csv"; + + public static final String DELIMITER = ","; + + public HeaderCodecCsv() { + super(CODEC_NAME, DELIMITER, DELIMITER); + } +} diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecMulti.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecMulti.java new file mode 100644 index 00000000000..e53e09ca080 --- /dev/null +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecMulti.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.common.rest.codec.header; + +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.Response.Status; + +import org.apache.servicecomb.common.rest.codec.RestClientRequest; +import org.apache.servicecomb.common.rest.codec.RestObjectMapperFactory; +import org.apache.servicecomb.common.rest.codec.param.HeaderProcessorCreator.HeaderProcessor; +import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData; +import org.apache.servicecomb.swagger.invocation.exception.InvocationException; + +public class HeaderCodecMulti implements HeaderCodec { + public static final String NAME = "multi"; + + @Override + public String getCodecName() { + return NAME; + } + + @Override + public void encode(RestClientRequest clientRequest, String name, Object value) throws Exception { + if (null == value) { + // if value is empty, header should not be set to clientRequest to avoid NullPointerException in Netty. + return; + } + if (!(value instanceof Collection)) { + throw new InvocationException(Status.BAD_REQUEST, + new CommonExceptionData("Array type of header should be Collection")); + } + for (Object item : ((Collection) value)) { + clientRequest.getHeaders().add(name, + RestObjectMapperFactory.getConsumerWriterMapper().convertToString(item)); + } + } + + @Override + public Object decode(HeaderProcessor processor, HttpServletRequest request) { + Enumeration headerValues = request.getHeaders(processor.getParameterPath()); + if (headerValues == null) { + //Even if the paramPath does not exist, headerValues won't be null at now + return null; + } + return processor.convertValue(Collections.list(headerValues), processor.getTargetType()); + } +} diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecPipes.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecPipes.java new file mode 100644 index 00000000000..4a96aaebe95 --- /dev/null +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecPipes.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.common.rest.codec.header; + +public class HeaderCodecPipes extends HeaderCodecWithDelimiter { + public static final String CODEC_NAME = "pipes"; + + public static final String JOIN_DELIMITER = "|"; + + public static final String SPLIT_DELIMITER = "\\|"; + + public HeaderCodecPipes() { + super(CODEC_NAME, JOIN_DELIMITER, SPLIT_DELIMITER); + } +} diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecSimple.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecSimple.java new file mode 100644 index 00000000000..3c37e56e2a4 --- /dev/null +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecSimple.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.common.rest.codec.header; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.servicecomb.common.rest.codec.RestClientRequest; +import org.apache.servicecomb.common.rest.codec.RestObjectMapperFactory; +import org.apache.servicecomb.common.rest.codec.param.HeaderProcessorCreator.HeaderProcessor; + +public class HeaderCodecSimple implements HeaderCodec { + public static final String NAME = "simple"; + + @Override + public String getCodecName() { + return NAME; + } + + @Override + public void encode(RestClientRequest clientRequest, String name, Object value) throws Exception { + if (null == value) { + // if value is empty, header should not be set to clientRequest to avoid NullPointerException in Netty. + return; + } + clientRequest.putHeader(name, + RestObjectMapperFactory.getConsumerWriterMapper().convertToString(value)); + } + + @Override + public Object decode(HeaderProcessor processor, HttpServletRequest request) { + Object value = request.getHeader(processor.getParameterPath()); + if (value == null) { + value = processor.checkRequiredAndDefaultValue(); + } + return processor.convertValue(value, processor.getTargetType()); + } +} diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecSsv.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecSsv.java new file mode 100644 index 00000000000..9de25de2ed3 --- /dev/null +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecSsv.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.common.rest.codec.header; + +public class HeaderCodecSsv extends HeaderCodecWithDelimiter { + public static final String CODEC_NAME = "ssv"; + + public static final String DELIMITER = " "; + + public HeaderCodecSsv() { + super(CODEC_NAME, DELIMITER, DELIMITER); + } +} diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecTsv.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecTsv.java new file mode 100644 index 00000000000..51dda805c6c --- /dev/null +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecTsv.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.common.rest.codec.header; + +public class HeaderCodecTsv extends HeaderCodecWithDelimiter { + public static final String CODEC_NAME = "tsv"; + + public static final String DELIMITER = "\t"; + + public HeaderCodecTsv() { + super(CODEC_NAME, DELIMITER, DELIMITER); + } +} diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecWithDelimiter.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecWithDelimiter.java new file mode 100644 index 00000000000..75481c8ce97 --- /dev/null +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecWithDelimiter.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.common.rest.codec.header; + +import java.util.Arrays; +import java.util.Collection; +import java.util.StringJoiner; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.Response.Status; + +import org.apache.servicecomb.common.rest.codec.RestClientRequest; +import org.apache.servicecomb.common.rest.codec.RestObjectMapperFactory; +import org.apache.servicecomb.common.rest.codec.param.HeaderProcessorCreator.HeaderProcessor; +import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData; +import org.apache.servicecomb.swagger.invocation.exception.InvocationException; + +public abstract class HeaderCodecWithDelimiter implements HeaderCodec { + private final String name; + + private final String joinDelimiter; + + private final String splitDelimiter; + + public HeaderCodecWithDelimiter(String name, String joinDelimiter, String splitDelimiter) { + this.name = name; + this.joinDelimiter = joinDelimiter; + this.splitDelimiter = splitDelimiter; + } + + + @Override + public String getCodecName() { + return name; + } + + @Override + public void encode(RestClientRequest clientRequest, String name, Object value) throws Exception { + if (null == value) { + // if value is empty, header should not be set to clientRequest to avoid NullPointerException in Netty. + return; + } + if (!(value instanceof Collection)) { + throw new InvocationException(Status.BAD_REQUEST, + new CommonExceptionData("Array type of header should be Collection")); + } + clientRequest.putHeader(name, join((Collection) value)); + } + + protected String join(Collection values) throws Exception { + StringJoiner joiner = new StringJoiner(joinDelimiter); + for (Object value : values) { + String strValue = RestObjectMapperFactory.getConsumerWriterMapper().convertToString(value); + joiner.add(strValue); + } + + return joiner.toString(); + } + + @Override + public Object decode(HeaderProcessor processor, HttpServletRequest request) { + String headerValues = request.getHeader(processor.getParameterPath()); + if (headerValues == null) { + headerValues = (String) processor.checkRequiredAndDefaultValue(); + } + + return processor.convertValue(Arrays.asList(headerValues.split(splitDelimiter)), processor.getTargetType()); + } +} diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecsUtils.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecsUtils.java new file mode 100644 index 00000000000..cbb968a31ef --- /dev/null +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/header/HeaderCodecsUtils.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.common.rest.codec.header; + +import java.util.HashMap; +import java.util.Map; + +public class HeaderCodecsUtils { + private static final Map CODECS; + + static { + CODECS = new HashMap<>(); + CODECS.put(HeaderCodecSimple.NAME, new HeaderCodecSimple()); + CODECS.put(HeaderCodecMulti.NAME, new HeaderCodecMulti()); + CODECS.put(HeaderCodecCsv.CODEC_NAME, new HeaderCodecCsv()); + CODECS.put(HeaderCodecTsv.CODEC_NAME, new HeaderCodecTsv()); + CODECS.put(HeaderCodecPipes.CODEC_NAME, new HeaderCodecPipes()); + CODECS.put(HeaderCodecSsv.CODEC_NAME, new HeaderCodecSsv()); + } + + private HeaderCodecsUtils() { + } + + public static HeaderCodec find(String collectionFormat) { + return CODECS.get(formatName(collectionFormat)); + } + + private static String formatName(String collectionFormat) { + if (collectionFormat == null) { + return HeaderCodecSimple.NAME; + } + return collectionFormat; + } +} diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/HeaderProcessorCreator.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/HeaderProcessorCreator.java index cb40fdf641b..30846a27cac 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/HeaderProcessorCreator.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/HeaderProcessorCreator.java @@ -18,17 +18,15 @@ package org.apache.servicecomb.common.rest.codec.param; import java.lang.reflect.Type; -import java.util.Collections; -import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.Response.Status; import org.apache.servicecomb.common.rest.codec.RestClientRequest; -import org.apache.servicecomb.common.rest.codec.RestObjectMapperFactory; +import org.apache.servicecomb.common.rest.codec.header.HeaderCodec; +import org.apache.servicecomb.common.rest.codec.header.HeaderCodecMulti; +import org.apache.servicecomb.common.rest.codec.header.HeaderCodecsUtils; import org.apache.servicecomb.swagger.invocation.exception.InvocationException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.type.TypeFactory; @@ -39,8 +37,6 @@ import io.swagger.models.properties.ArrayProperty; public class HeaderProcessorCreator implements ParamValueProcessorCreator { - private static final Logger LOGGER = LoggerFactory.getLogger(HeaderProcessorCreator.class); - public static final String PARAMTYPE = "header"; public static class HeaderProcessor extends AbstractParamProcessor { @@ -48,33 +44,25 @@ public static class HeaderProcessor extends AbstractParamProcessor { private final boolean ignoreRequiredCheck = DynamicPropertyFactory.getInstance() .getBooleanProperty("servicecomb.rest.parameter.header.ignoreRequiredCheck", false).get(); - private final boolean repeatedType; + private final HeaderCodec headerCodec; public HeaderProcessor(HeaderParameter headerParameter, JavaType targetType) { super(headerParameter.getName(), targetType, headerParameter.getDefaultValue(), headerParameter.getRequired()); - this.repeatedType = ArrayProperty.isType(headerParameter.getType()); + if ((ArrayProperty.isType(headerParameter.getType())) && headerParameter.getCollectionFormat() == null) { + // compatible to default settings + this.headerCodec = HeaderCodecsUtils.find(HeaderCodecMulti.NAME); + } else { + this.headerCodec = HeaderCodecsUtils.find(headerParameter.getCollectionFormat()); + } } @Override public Object getValue(HttpServletRequest request) { - if (repeatedType) { - Enumeration headerValues = request.getHeaders(paramPath); - if (headerValues == null) { - //Even if the paramPath does not exist, headerValues won't be null at now - return null; - } - return convertValue(Collections.list(headerValues), targetType); - } - - Object value = request.getHeader(paramPath); - if (value == null) { - value = checkRequiredAndDefaultValue(); - } - return convertValue(value, targetType); + return headerCodec.decode(this, request); } - private Object checkRequiredAndDefaultValue() { + public Object checkRequiredAndDefaultValue() { if (!ignoreRequiredCheck && isRequired()) { throw new InvocationException(Status.BAD_REQUEST, "Parameter is required."); } @@ -83,13 +71,7 @@ private Object checkRequiredAndDefaultValue() { @Override public void setValue(RestClientRequest clientRequest, Object arg) throws Exception { - if (null == arg) { - // null header should not be set to clientRequest to avoid NullPointerException in Netty. - LOGGER.debug("Header arg is null, will not be set into clientRequest. paramPath = [{}]", paramPath); - return; - } - clientRequest.putHeader(paramPath, - RestObjectMapperFactory.getConsumerWriterMapper().convertToString(arg)); + headerCodec.encode(clientRequest, paramPath, arg); } @Override diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/RestClientRequestImpl.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/RestClientRequestImpl.java index 2ca0701b064..24496230191 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/RestClientRequestImpl.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/RestClientRequestImpl.java @@ -32,7 +32,6 @@ import javax.servlet.http.Part; import javax.ws.rs.core.MediaType; -import com.google.common.annotations.VisibleForTesting; import org.apache.servicecomb.common.rest.codec.RestClientRequest; import org.apache.servicecomb.common.rest.codec.RestObjectMapperFactory; import org.apache.servicecomb.foundation.common.utils.PartUtils; @@ -44,6 +43,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; @@ -63,7 +63,7 @@ public class RestClientRequestImpl implements RestClientRequest { protected AsyncResponse asyncResp; @VisibleForTesting - final Multimap uploads = ArrayListMultimap.create(); + protected Multimap uploads; protected HttpClientRequest request; @@ -101,8 +101,11 @@ public Buffer getBodyBuffer() throws Exception { @Override @SuppressWarnings("unchecked") public void attach(String name, Object partOrList) { + if (uploads == null) { + uploads = ArrayListMultimap.create(); + } + if (null == partOrList) { - LOGGER.debug("null file is ignored, file name = [{}]", name); return; } @@ -126,7 +129,7 @@ public void attach(String name, Object partOrList) { public Future end() { writeCookies(); - if (!uploads.isEmpty()) { + if (uploads != null) { return doEndWithUpload(); } diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/query/AbstractQueryCodec.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/query/AbstractQueryCodec.java index 2817f9da830..e34d8996bdc 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/query/AbstractQueryCodec.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/query/AbstractQueryCodec.java @@ -23,6 +23,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.apache.servicecomb.common.rest.codec.RestObjectMapperFactory; import org.apache.servicecomb.common.rest.definition.path.URLPathBuilder.URLPathStringBuilder; public abstract class AbstractQueryCodec implements QueryCodec { @@ -46,6 +47,10 @@ public void encode(URLPathStringBuilder builder, String name, @Nullable Object v } if (value.getClass().isArray()) { + if (!(value instanceof Object[])) { + value = RestObjectMapperFactory.getRestObjectMapper() + .convertValue(value, Object[].class); + } encode(builder, name, Arrays.asList((Object[]) value)); return; } diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilter.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilter.java index 3ae65ff9414..1ad9ab385f7 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilter.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilter.java @@ -19,6 +19,7 @@ import java.util.concurrent.CompletableFuture; +import org.apache.servicecomb.core.Const; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx; import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx; @@ -43,4 +44,10 @@ default CompletableFuture beforeSendRequestAsync(Invocation invocation, Ht * if return a null response, then sdk will call next filter.afterReceiveResponse */ Response afterReceiveResponse(Invocation invocation, HttpServletResponseEx responseEx); + + default boolean enabledForTransport(String transport) { + return Const.RESTFUL.equals(transport) + || Const.HIGHWAY.equals(transport) + || Const.ANY_TRANSPORT.equals(transport); + } } diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilterBeforeSendRequestExecutor.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilterBeforeSendRequestExecutor.java index b536aeb426e..394a6920d3b 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilterBeforeSendRequestExecutor.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilterBeforeSendRequestExecutor.java @@ -48,7 +48,7 @@ public CompletableFuture run() { protected CompletableFuture safeInvoke(HttpClientFilter httpClientFilter) { try { - if (httpClientFilter.enabled()) { + if (httpClientFilter.enabled() && httpClientFilter.enabledForTransport(invocation.getTransportName())) { CompletableFuture future = httpClientFilter.beforeSendRequestAsync(invocation, requestEx); if (future == null) { future = new CompletableFuture<>(); diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpServerFilter.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpServerFilter.java index 922e90725d5..b4f2e7346a8 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpServerFilter.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpServerFilter.java @@ -19,6 +19,7 @@ import java.util.concurrent.CompletableFuture; +import org.apache.servicecomb.core.Const; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.core.definition.OperationMeta; import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx; @@ -48,4 +49,15 @@ default boolean needCacheRequest(OperationMeta operationMeta) { default CompletableFuture beforeSendResponseAsync(Invocation invocation, HttpServletResponseEx responseEx) { return CompletableFuture.completedFuture(null); } + + /** + * check whether this filter should be enabled for the transport. + * Note that the param "transport" may be null. + */ + default boolean enabledForTransport(String transport) { + return transport == null + || Const.RESTFUL.equals(transport) + || Const.HIGHWAY.equals(transport) + || Const.ANY_TRANSPORT.equals(transport); + } } diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpServerFilterBeforeSendResponseExecutor.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpServerFilterBeforeSendResponseExecutor.java index 3c3c23ef2dc..e1a28bf2822 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpServerFilterBeforeSendResponseExecutor.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpServerFilterBeforeSendResponseExecutor.java @@ -48,7 +48,9 @@ public CompletableFuture run() { protected CompletableFuture safeInvoke(HttpServerFilter httpServerFilter) { try { - if (httpServerFilter.enabled()) { + // 404 or other error cases, invocation is null + final String providerTransportName = invocation == null ? null : invocation.getProviderTransportName(); + if (httpServerFilter.enabled() && httpServerFilter.enabledForTransport(providerTransportName)) { CompletableFuture future = httpServerFilter.beforeSendResponseAsync(invocation, responseEx); if (future == null) { future = new CompletableFuture<>(); diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ClientRestArgsFilter.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ClientRestArgsFilter.java index a53283343ed..19652e2db6b 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ClientRestArgsFilter.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ClientRestArgsFilter.java @@ -20,10 +20,11 @@ import java.util.concurrent.CompletableFuture; import org.apache.servicecomb.common.rest.RestConst; +import org.apache.servicecomb.common.rest.codec.RestClientRequest; import org.apache.servicecomb.common.rest.codec.RestCodec; -import org.apache.servicecomb.common.rest.codec.param.RestClientRequestImpl; import org.apache.servicecomb.common.rest.definition.RestOperationMeta; import org.apache.servicecomb.common.rest.filter.HttpClientFilter; +import org.apache.servicecomb.core.Const; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.core.definition.OperationMeta; import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx; @@ -37,10 +38,15 @@ public int getOrder() { return -100; } + @Override + public boolean enabledForTransport(String transport) { + return HttpClientFilter.super.enabledForTransport(transport) || Const.WEBSOCKET.equals(transport); + } + @Override public CompletableFuture beforeSendRequestAsync(Invocation invocation, HttpServletRequestEx requestEx) { CompletableFuture result = new CompletableFuture<>(); - RestClientRequestImpl restClientRequest = (RestClientRequestImpl) invocation.getHandlerContext() + RestClientRequest restClientRequest = (RestClientRequest) invocation.getHandlerContext() .get(RestConst.INVOCATION_HANDLER_REQUESTCLIENT); OperationMeta operationMeta = invocation.getOperationMeta(); RestOperationMeta swaggerRestOperation = operationMeta.getExtData(RestConst.SWAGGER_REST_OPERATION); diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ServerRestArgsFilter.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ServerRestArgsFilter.java index 63ae8eea92a..bef5b48a477 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ServerRestArgsFilter.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ServerRestArgsFilter.java @@ -69,20 +69,20 @@ public CompletableFuture beforeSendResponseAsync(Invocation invocation, Ht Response response = (Response) responseEx.getAttribute(RestConst.INVOCATION_HANDLER_RESPONSE); ProduceProcessor produceProcessor = (ProduceProcessor) responseEx.getAttribute(RestConst.INVOCATION_HANDLER_PROCESSOR); - Object body = response.getResult(); - if (response.isFailed()) { - body = ((InvocationException) body).getErrorData(); - } - - if (null != invocation && isDownloadFileResponseType(invocation, response)) { - return responseEx.sendPart(PartUtils.getSinglePart(null, body)); + boolean failed = response.getResult() instanceof InvocationException; + if (!failed && isDownloadFileResponseType(invocation, response)) { + return responseEx.sendPart(PartUtils.getSinglePart(null, response.getResult())); } responseEx.setContentType(produceProcessor.getName() + "; charset=utf-8"); CompletableFuture future = new CompletableFuture<>(); try (BufferOutputStream output = new BufferOutputStream(Unpooled.compositeBuffer())) { - produceProcessor.encodeResponse(output, body); + if (failed) { + produceProcessor.encodeResponse(output, ((InvocationException) response.getResult()).getErrorData()); + } else { + produceProcessor.encodeResponse(output, response.getResult()); + } responseEx.setBodyBuffer(output.getBuffer()); future.complete(null); diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/RestProducerInvocationCreatorTest.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/RestProducerInvocationCreatorTest.java index 12877bc4ae7..a35ad039723 100644 --- a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/RestProducerInvocationCreatorTest.java +++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/RestProducerInvocationCreatorTest.java @@ -19,9 +19,12 @@ import static javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE; import static javax.ws.rs.core.Response.Status.NOT_FOUND; +import static org.apache.servicecomb.common.rest.RestConst.DECODE_INVOCATION_CONTEXT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowable; +import java.util.ArrayList; + import javax.ws.rs.core.HttpHeaders; import org.apache.servicecomb.common.rest.definition.RestOperationMeta; @@ -43,9 +46,6 @@ import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx; import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData; import org.apache.servicecomb.swagger.invocation.exception.InvocationException; - -import io.vertx.core.json.Json; -import io.vertx.ext.web.RoutingContext; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -53,7 +53,8 @@ import org.mockito.MockedStatic; import org.mockito.Mockito; -import java.util.ArrayList; +import io.vertx.core.json.Json; +import io.vertx.ext.web.RoutingContext; public class RestProducerInvocationCreatorTest { @@ -115,14 +116,15 @@ public void should_failed_when_not_defined_any_schema() { assertThat(throwable.getStatusCode()).isEqualTo(NOT_FOUND.getStatusCode()); assertThat(Json.encode(data)).isIn("{\"code\":\"SCB.00000002\",\"message\":\"Not Found\"}", - "{\"message\":\"Not Found\",\"code\":\"SCB.00000002\"}"); + "{\"message\":\"Not Found\",\"code\":\"SCB.00000002\"}"); } } @Test public void should_failed_when_accept_is_not_support() { try (MockedStatic mockedStatic = Mockito.mockStatic(ServicePathManager.class)) { - mockedStatic.when(() -> ServicePathManager.getServicePathManager(microserviceMeta)).thenReturn(servicePathManager); + mockedStatic.when(() -> ServicePathManager.getServicePathManager(microserviceMeta)) + .thenReturn(servicePathManager); Mockito.when(requestEx.getHeader(HttpHeaders.ACCEPT)).thenReturn("test-type"); Mockito.when(restOperationMeta.ensureFindProduceProcessor(requestEx)).thenReturn(null); Mockito.when(creator.locateOperation(microserviceMeta)).thenReturn(locator); @@ -137,15 +139,17 @@ public void should_failed_when_accept_is_not_support() { CommonExceptionData data = (CommonExceptionData) throwable.getErrorData(); assertThat(throwable.getStatusCode()).isEqualTo(NOT_ACCEPTABLE.getStatusCode()); - assertThat(Json.encode(data)).isIn("{\"code\":\"SCB.00000000\",\"message\":\"Accept test-type is not supported\"}", - "{\"message\":\"Accept test-type is not supported\",\"code\":\"SCB.00000000\"}"); + assertThat(Json.encode(data)).isIn( + "{\"code\":\"SCB.00000000\",\"message\":\"Accept test-type is not supported\"}", + "{\"message\":\"Accept test-type is not supported\",\"code\":\"SCB.00000000\"}"); } } @Test public void should_save_requestEx_in_invocation_context() { try (MockedStatic mockedStatic = Mockito.mockStatic(ServicePathManager.class)) { - mockedStatic.when(() -> ServicePathManager.getServicePathManager(microserviceMeta)).thenReturn(servicePathManager); + mockedStatic.when(() -> ServicePathManager.getServicePathManager(microserviceMeta)) + .thenReturn(servicePathManager); Mockito.when(creator.locateOperation(microserviceMeta)).thenReturn(locator); Mockito.when(locator.getOperation()).thenReturn(restOperationMeta); Mockito.when(restOperationMeta.getOperationMeta()).thenReturn(operationMeta); @@ -165,7 +169,8 @@ public void should_save_requestEx_in_invocation_context() { @Test public void should_save_path_var_map_in_requestEx() { try (MockedStatic mockedStatic = Mockito.mockStatic(ServicePathManager.class)) { - mockedStatic.when(() -> ServicePathManager.getServicePathManager(microserviceMeta)).thenReturn(servicePathManager); + mockedStatic.when(() -> ServicePathManager.getServicePathManager(microserviceMeta)) + .thenReturn(servicePathManager); Mockito.when(creator.locateOperation(microserviceMeta)).thenReturn(locator); Mockito.when(locator.getOperation()).thenReturn(restOperationMeta); Mockito.when(restOperationMeta.getOperationMeta()).thenReturn(operationMeta); @@ -183,8 +188,10 @@ public void should_save_path_var_map_in_requestEx() { @Test public void should_merge_invocation_context_from_request() { + ArchaiusUtils.setProperty(DECODE_INVOCATION_CONTEXT, true); try (MockedStatic mockedStatic = Mockito.mockStatic(ServicePathManager.class)) { - mockedStatic.when(() -> ServicePathManager.getServicePathManager(microserviceMeta)).thenReturn(servicePathManager); + mockedStatic.when(() -> ServicePathManager.getServicePathManager(microserviceMeta)) + .thenReturn(servicePathManager); Mockito.when(creator.locateOperation(microserviceMeta)).thenReturn(locator); Mockito.when(locator.getOperation()).thenReturn(restOperationMeta); Mockito.when(restOperationMeta.getOperationMeta()).thenReturn(operationMeta); @@ -200,4 +207,26 @@ public void should_merge_invocation_context_from_request() { assertThat(invocation.getContext("k")).isEqualTo("v"); } } + + @Test + public void should_not_merge_invocation_context_from_request() { + ArchaiusUtils.setProperty(DECODE_INVOCATION_CONTEXT, false); + try (MockedStatic mockedStatic = Mockito.mockStatic(ServicePathManager.class)) { + mockedStatic.when(() -> ServicePathManager.getServicePathManager(microserviceMeta)) + .thenReturn(servicePathManager); + Mockito.when(creator.locateOperation(microserviceMeta)).thenReturn(locator); + Mockito.when(locator.getOperation()).thenReturn(restOperationMeta); + Mockito.when(restOperationMeta.getOperationMeta()).thenReturn(operationMeta); + Mockito.when(operationMeta.buildBaseProviderRuntimeType()).thenReturn(invocationRuntimeType); + Mockito.when(operationMeta.getSchemaMeta()).thenReturn(schemaMeta); + Mockito.when(schemaMeta.getMicroserviceMeta()).thenReturn(microserviceMeta); + Mockito.when(microserviceMeta.getHandlerChain()).thenReturn(new ArrayList<>()); + Mockito.doNothing().when(creator).initProduceProcessor(); + Mockito.when(requestEx.getHeader(Const.CSE_CONTEXT)).thenReturn("{\"k\":\"v\"}"); + + Invocation invocation = creator.createAsync().join(); + + assertThat(invocation.getContext("k")).isNull(); + } + } } diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/TestAbstractRestInvocation.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/TestAbstractRestInvocation.java index 9404c6e3958..a73790a068a 100644 --- a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/TestAbstractRestInvocation.java +++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/TestAbstractRestInvocation.java @@ -259,6 +259,7 @@ public void invokeFilterHaveResponse() { HttpServerFilter filter = Mockito.mock(HttpServerFilter.class); Response response = Response.ok(""); Mockito.when(filter.enabled()).thenReturn(true); + Mockito.when(filter.enabledForTransport(Mockito.any())).thenReturn(true); Mockito.when(filter.afterReceiveRequest(invocation, requestEx)).thenReturn(response); Holder result = new Holder<>(); @@ -327,6 +328,7 @@ public void invokeFilterException() { HttpServerFilter filter = Mockito.mock(HttpServerFilter.class); Exception error = new RuntimeExceptionWithoutStackTrace(); Mockito.when(filter.enabled()).thenReturn(true); + Mockito.when(filter.enabledForTransport(Mockito.any())).thenReturn(true); Mockito.when(filter.afterReceiveRequest(invocation, requestEx)).thenThrow(error); Holder result = new Holder<>(); @@ -552,7 +554,6 @@ public void should_ignore_content_length_and_transfer_encoding_when_copy_header_ .set(CONTENT_LENGTH, "10") .set(TRANSFER_ENCODING, "encoding"); - Mockito.when(response.getResult()).thenThrow(new RuntimeExceptionWithoutStackTrace("stop")); Mockito.when(response.getHeaders()).thenReturn(headers); MultiMap resultHeaders = MultiMap.caseInsensitiveMultiMap(); @@ -573,7 +574,6 @@ public void testDoSendResponseHeaderNormal() { headers.add("h1", "h1v2"); headers.add("h2", "h2v"); - Mockito.when(response.getResult()).thenThrow(new RuntimeExceptionWithoutStackTrace("stop")); Mockito.when(response.getHeaders()).thenReturn(headers); MultiMap resultHeaders = MultiMap.caseInsensitiveMultiMap(); diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/TestRestCodec.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/TestRestCodec.java index 67d2c914765..20a4e7db74f 100644 --- a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/TestRestCodec.java +++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/TestRestCodec.java @@ -160,6 +160,8 @@ public void testRestToArgsInstanceException() throws Exception { success = true; } catch (InvocationException e) { Assertions.assertEquals(e.getStatusCode(), Status.BAD_REQUEST.getStatusCode()); + Assertions.assertTrue(((CommonExceptionData) e.getErrorData()).getMessage() + .contains("Parameter is not valid for operation")); } Assertions.assertFalse(success); } diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/TestRestObjectMapper.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/TestRestObjectMapper.java index 62c21f71d94..d2f17f5fefe 100644 --- a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/TestRestObjectMapper.java +++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/TestRestObjectMapper.java @@ -21,6 +21,9 @@ import java.io.InputStream; import java.util.Date; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; +import com.google.common.base.Strings; import org.apache.servicecomb.foundation.common.utils.RestObjectMapper; import org.junit.jupiter.api.Assertions; @@ -90,4 +93,18 @@ public void testJsonObjectWork() { Assertions.fail(); } } + + @Test + public void testReadValue() { + String content = "{\"desc\":" + Strings.repeat("9", 1001) + "}"; + try { + RestObjectMapperFactory.getRestObjectMapper().readValue(content, PojoModel.class); + Assertions.fail(); + } catch (StreamConstraintsException e) { + // right place, nothing to do. + } catch (JsonProcessingException ex) { + Assertions.fail(); + } + } + } diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/fix/TestDoSFix.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/fix/TestDoSFix.java index ae8abc8a6ea..db0f04e863b 100644 --- a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/fix/TestDoSFix.java +++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/fix/TestDoSFix.java @@ -35,7 +35,7 @@ public class TestDoSFix { static final ObjectMapper mapper = new RestObjectMapper(); - static final String invalidNum = Strings.repeat("9", 100_0000); + static final String invalidNum = Strings.repeat("9", 1000); static final String invalidStr = "\"" + invalidNum + "\""; diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/param/TestHeaderProcessor.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/param/TestHeaderProcessor.java index c3f07c582a1..4058f045edc 100644 --- a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/param/TestHeaderProcessor.java +++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/param/TestHeaderProcessor.java @@ -65,6 +65,7 @@ private HeaderProcessor createProcessor(String name, Type type, String defaultVa if (javaType.isContainerType()) { headerParameter.type(ArrayProperty.TYPE); + headerParameter.setCollectionFormat("multi"); } return new HeaderProcessor(headerParameter, javaType); } diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/param/TestRestClientRequestImpl.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/param/TestRestClientRequestImpl.java index 87abba9226f..70e3f4bddf7 100644 --- a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/param/TestRestClientRequestImpl.java +++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/codec/param/TestRestClientRequestImpl.java @@ -16,9 +16,9 @@ */ package org.apache.servicecomb.common.rest.codec.param; +import static org.assertj.core.api.Assertions.assertThat; + import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import java.util.UUID; import javax.servlet.http.Part; @@ -31,9 +31,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledForJreRange; -import org.junit.jupiter.api.condition.EnabledOnJre; -import org.junit.jupiter.api.condition.JRE; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -43,7 +40,7 @@ import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpClientRequest; -import static org.assertj.core.api.Assertions.assertThat; +import io.vertx.core.http.impl.headers.HeadersMultiMap; public class TestRestClientRequestImpl { private HttpClientRequest request; @@ -74,13 +71,15 @@ public void testForm() throws Exception { public void testCookie() throws Exception { final MultiMap map = MultiMap.caseInsensitiveMultiMap(); Mockito.doAnswer(invocation -> { - map.add(io.vertx.core.http.HttpHeaders.COOKIE, "sessionid=abcdefghijklmnopqrstuvwxyz; region=china-north; "); - return null; - }).when(request).putHeader(io.vertx.core.http.HttpHeaders.COOKIE, "sessionid=abcdefghijklmnopqrstuvwxyz; region=china-north; "); + map.add(io.vertx.core.http.HttpHeaders.COOKIE, "sessionid=abcdefghijklmnopqrstuvwxyz; region=china-north; "); + return null; + }).when(request) + .putHeader(io.vertx.core.http.HttpHeaders.COOKIE, "sessionid=abcdefghijklmnopqrstuvwxyz; region=china-north; "); Mockito.doAnswer(invocation -> { - map.add(io.vertx.core.http.HttpHeaders.COOKIE, "sessionid=abcdefghijklmnopqrstuvwxyz; region=china-north; "); - return null; - }).when(request).putHeader(io.vertx.core.http.HttpHeaders.COOKIE, "region=china-north; sessionid=abcdefghijklmnopqrstuvwxyz; "); + map.add(io.vertx.core.http.HttpHeaders.COOKIE, "sessionid=abcdefghijklmnopqrstuvwxyz; region=china-north; "); + return null; + }).when(request) + .putHeader(io.vertx.core.http.HttpHeaders.COOKIE, "region=china-north; sessionid=abcdefghijklmnopqrstuvwxyz; "); Mockito.when(request.headers()).thenReturn(map); RestClientRequestImpl restClientRequest = new RestClientRequestImpl(request, null, null); @@ -149,42 +148,26 @@ public void testAttachOnPartIsNull() { } @Test - @EnabledOnJre(JRE.JAVA_8) - public void doEndWithUploadForJre8() { - Map headers = new HashMap<>(); - Mockito.doAnswer(invocation -> { - headers.put(HttpHeaders.CONTENT_TYPE, "multipart/form-data; charset=UTF-8; boundary=boundarynull-null-null-null-null"); - return null; - }).when(request).putHeader(HttpHeaders.CONTENT_TYPE, "multipart/form-data; charset=UTF-8; boundary=boundarynull-null-null-null-null"); - - UUID uuid = new UUID(0, 0); - try (MockedStatic mockedStatic = Mockito.mockStatic(UUID.class)) { - mockedStatic.when(UUID::randomUUID).thenReturn(uuid); - RestClientRequestImpl restClientRequest = new RestClientRequestImpl(request, context, null); - restClientRequest.doEndWithUpload(); - - Assertions.assertEquals("multipart/form-data; charset=UTF-8; boundary=boundarynull-null-null-null-null", - headers.get(HttpHeaders.CONTENT_TYPE)); - } - } - - @Test - @EnabledForJreRange(min = JRE.JAVA_9) - public void doEndWithUploadAfterJre8() { - Map headers = new HashMap<>(); + public void testDoEndWithUpload() { + MultiMap headers = HeadersMultiMap.headers(); Mockito.doAnswer(invocation -> { - headers.put(HttpHeaders.CONTENT_TYPE, "multipart/form-data; charset=UTF-8; boundary=boundary00000000-0000-0000-0000-000000000000"); + headers.add(HttpHeaders.CONTENT_TYPE, + "multipart/form-data; charset=UTF-8; boundary=boundary00000000-0000-0000-0000-000000000000"); return null; - }).when(request).putHeader(HttpHeaders.CONTENT_TYPE, "multipart/form-data; charset=UTF-8; boundary=boundary00000000-0000-0000-0000-000000000000"); + }).when(request).putHeader(HttpHeaders.CONTENT_TYPE, + "multipart/form-data; charset=UTF-8; boundary=boundary00000000-0000-0000-0000-000000000000"); - UUID uuid = new UUID(0, 0); + UUID uuid = Mockito.mock(UUID.class); + Mockito.when(uuid.toString()).thenReturn("00000000-0000-0000-0000-000000000000"); try (MockedStatic mockedStatic = Mockito.mockStatic(UUID.class)) { mockedStatic.when(UUID::randomUUID).thenReturn(uuid); RestClientRequestImpl restClientRequest = new RestClientRequestImpl(request, context, null); + restClientRequest.attach("file", null); restClientRequest.doEndWithUpload(); - Assertions.assertEquals("multipart/form-data; charset=UTF-8; boundary=boundary00000000-0000-0000-0000-000000000000", - headers.get(HttpHeaders.CONTENT_TYPE)); + Assertions.assertEquals( + "multipart/form-data; charset=UTF-8; boundary=boundary00000000-0000-0000-0000-000000000000", + headers.get(HttpHeaders.CONTENT_TYPE)); } } } diff --git a/core/src/main/java/org/apache/servicecomb/core/ConfigurationSpringInitializer.java b/core/src/main/java/org/apache/servicecomb/core/ConfigurationSpringInitializer.java index 790b48af600..791c441f761 100644 --- a/core/src/main/java/org/apache/servicecomb/core/ConfigurationSpringInitializer.java +++ b/core/src/main/java/org/apache/servicecomb/core/ConfigurationSpringInitializer.java @@ -329,7 +329,12 @@ private void getProperties(ConfigurableEnvironment environment, PropertySource enumerablePropertySource = (EnumerablePropertySource) propertySource; for (String propertyName : enumerablePropertySource.getPropertyNames()) { try { - configFromSpringBoot.put(propertyName, environment.getProperty(propertyName, Object.class)); + Object propertyValue = environment.getProperty(propertyName, Object.class); + if (propertyValue == null) { + LOGGER.error("The value of a configuration item is null, please check whether there is any impact, config item key: {}", propertyName); + continue; + } + configFromSpringBoot.put(propertyName, propertyValue); } catch (Exception e) { throw new RuntimeException( "set up spring property source failed.If you still want to start up the application and ignore errors, you can set servicecomb.config.ignoreResolveFailure to true.", diff --git a/core/src/main/java/org/apache/servicecomb/core/Const.java b/core/src/main/java/org/apache/servicecomb/core/Const.java index 03c751def9e..4b860e00008 100644 --- a/core/src/main/java/org/apache/servicecomb/core/Const.java +++ b/core/src/main/java/org/apache/servicecomb/core/Const.java @@ -25,10 +25,14 @@ private Const() { public static final String CSE_CONTEXT = "x-cse-context"; + public static final String TRANSPORT_NAME = "x-transport-name"; + public static final String RESTFUL = "rest"; public static final String HIGHWAY = "highway"; + public static final String WEBSOCKET = "websocket"; + public static final String ANY_TRANSPORT = ""; public static final String VERSION_RULE_LATEST = DefinitionConst.VERSION_RULE_LATEST; diff --git a/core/src/main/java/org/apache/servicecomb/core/Invocation.java b/core/src/main/java/org/apache/servicecomb/core/Invocation.java index b1b48c7f39d..af9679c94f0 100644 --- a/core/src/main/java/org/apache/servicecomb/core/Invocation.java +++ b/core/src/main/java/org/apache/servicecomb/core/Invocation.java @@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicLong; import com.google.common.annotations.VisibleForTesting; + import org.apache.commons.lang3.StringUtils; import org.apache.servicecomb.core.definition.InvocationRuntimeType; import org.apache.servicecomb.core.definition.MicroserviceMeta; @@ -149,6 +150,13 @@ private void init(OperationMeta operationMeta, Map swaggerArgume traceIdLogger = new TraceIdLogger(this); } + public String getTransportName() { + if (endpoint == null || endpoint.getTransport() == null) { + return null; + } + return endpoint.getTransport().getName(); + } + public Transport getTransport() { if (endpoint == null) { throw new IllegalStateException( @@ -288,7 +296,26 @@ public String getOperationName() { return operationMeta.getOperationId(); } + public String getProviderTransportName() { + final Map operationVendorExtensions = operationMeta.getSwaggerOperation().getVendorExtensions(); + if (operationVendorExtensions != null) { + final String operationTransportName = (String) operationVendorExtensions.get(Const.TRANSPORT_NAME); + if (operationTransportName != null) { + return operationTransportName; + } + } + final Map schemaVendorExtensions = schemaMeta.getSwagger().getVendorExtensions(); + if (schemaVendorExtensions == null) { + return null; + } + return (String) schemaVendorExtensions.get(Const.TRANSPORT_NAME); + } + public String getConfigTransportName() { + final String providerTransportName = getProviderTransportName(); + if (providerTransportName != null) { + return providerTransportName; + } return referenceConfig.getTransport(); } diff --git a/core/src/main/java/org/apache/servicecomb/core/SCBEngine.java b/core/src/main/java/org/apache/servicecomb/core/SCBEngine.java index ea2a539ad33..0b0b24076df 100644 --- a/core/src/main/java/org/apache/servicecomb/core/SCBEngine.java +++ b/core/src/main/java/org/apache/servicecomb/core/SCBEngine.java @@ -68,6 +68,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; +import org.springframework.core.env.Environment; import com.google.common.eventbus.AllowConcurrentEvents; import com.google.common.eventbus.EventBus; @@ -129,17 +130,13 @@ public class SCBEngine { private final VendorExtensions vendorExtensions = new VendorExtensions(); - private Thread shutdownHook; - protected SCBEngine() { eventBus = EventManager.getEventBus(); - eventBus.register(this); - - INSTANCE = this; - producerProviderManager = new ProducerProviderManager(this); serviceRegistryListener = new ServiceRegistryListener(this); + + INSTANCE = this; } public ApplicationContext getApplicationContext() { @@ -150,6 +147,14 @@ public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } + public Environment getEnvironment() { + if (this.applicationContext == null) { + // some test cases + return null; + } + return this.applicationContext.getEnvironment(); + } + public VendorExtensions getVendorExtensions() { return vendorExtensions; } @@ -323,6 +328,7 @@ public synchronized SCBEngine run() { try { doRun(); waitStatusUp(); + printServiceInfo(); } catch (TimeoutException e) { LOGGER.warn("{}", e.getMessage()); } catch (Throwable e) { @@ -334,8 +340,6 @@ public synchronized SCBEngine run() { } status = SCBStatus.FAILED; throw new IllegalStateException("ServiceComb init failed.", e); - } finally { - printServiceInfo(); } } @@ -392,9 +396,6 @@ private void doRun() throws Exception { RegistrationManager.INSTANCE.run(); DiscoveryManager.INSTANCE.run(); - - shutdownHook = new Thread(this::destroyForShutdownHook); - Runtime.getRuntime().addShutdownHook(shutdownHook); } private void createProducerMicroserviceMeta() { @@ -406,11 +407,6 @@ private void createProducerMicroserviceMeta() { producerMicroserviceMeta.setMicroserviceVersionsMeta(new MicroserviceVersionsMeta(this, microserviceName)); } - public void destroyForShutdownHook() { - shutdownHook = null; - destroy(); - } - /** * not allow throw any exception * even some step throw exception, must catch it and go on, otherwise shutdown process will be broken. @@ -425,10 +421,6 @@ public synchronized void destroy() { } private void doDestroy() { - if (shutdownHook != null) { - Runtime.getRuntime().removeShutdownHook(shutdownHook); - } - //Step 0: turn down the status of this instance in service center, // so that the consumers can remove this instance record in advance turnDownInstanceStatus(); diff --git a/core/src/main/java/org/apache/servicecomb/core/annotation/Transport.java b/core/src/main/java/org/apache/servicecomb/core/annotation/Transport.java new file mode 100644 index 00000000000..aca52548234 --- /dev/null +++ b/core/src/main/java/org/apache/servicecomb/core/annotation/Transport.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.core.annotation; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Mark Transport protocol of REST method. + */ +@Inherited +@Documented +@Retention(RUNTIME) +@Target({TYPE, METHOD, ANNOTATION_TYPE}) +public @interface Transport { + /** + * Transport name. Valid values: + *
    + *
  • {@link org.apache.servicecomb.core.Const#RESTFUL}
  • + *
  • {@link org.apache.servicecomb.core.Const#HIGHWAY}
  • + *
  • {@link org.apache.servicecomb.core.Const#WEBSOCKET}
  • + *
+ */ + String name(); +} diff --git a/core/src/main/java/org/apache/servicecomb/core/bootstrap/SCBBootstrap.java b/core/src/main/java/org/apache/servicecomb/core/bootstrap/SCBBootstrap.java index 1967e5609b0..8fc096f263f 100644 --- a/core/src/main/java/org/apache/servicecomb/core/bootstrap/SCBBootstrap.java +++ b/core/src/main/java/org/apache/servicecomb/core/bootstrap/SCBBootstrap.java @@ -26,4 +26,9 @@ public static SCBEngine createSCBEngineForTest() { DiscoveryManager.INSTANCE.init(); return new SCBEngineForTest(); } + + public static void resetSCBEngineForTest() { + RegistrationManager.INSTANCE.destroy(); + DiscoveryManager.INSTANCE.destroy(); + } } diff --git a/core/src/main/java/org/apache/servicecomb/core/bootup/ConfigurationProblemsAlarmEvent.java b/core/src/main/java/org/apache/servicecomb/core/bootup/ConfigurationProblemsAlarmEvent.java new file mode 100644 index 00000000000..631cc361235 --- /dev/null +++ b/core/src/main/java/org/apache/servicecomb/core/bootup/ConfigurationProblemsAlarmEvent.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.core.bootup; + +import org.apache.servicecomb.foundation.common.event.AlarmEvent; + +public class ConfigurationProblemsAlarmEvent extends AlarmEvent { + private final String problems; + + public ConfigurationProblemsAlarmEvent(Type type, String problems) { + super(type); + this.problems = problems; + } + + public String getProblems() { + return problems; + } +} diff --git a/core/src/main/java/org/apache/servicecomb/core/bootup/ConfigurationProblemsCollector.java b/core/src/main/java/org/apache/servicecomb/core/bootup/ConfigurationProblemsCollector.java new file mode 100644 index 00000000000..7e8c02e03d9 --- /dev/null +++ b/core/src/main/java/org/apache/servicecomb/core/bootup/ConfigurationProblemsCollector.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.core.bootup; + +import java.util.Arrays; +import java.util.Set; +import java.util.regex.Pattern; + +import org.apache.servicecomb.config.BootStrapProperties; +import org.apache.servicecomb.config.ConfigUtil; +import org.apache.servicecomb.core.SCBEngine; +import org.apache.servicecomb.foundation.common.event.AlarmEvent.Type; +import org.apache.servicecomb.foundation.common.event.EventManager; +import org.springframework.core.env.Environment; + +/** + * Detect deprecated and wrong usages of configurations + * and print warning messages + * and sending ConfigurationProblemsAlarmEvent. + */ +public class ConfigurationProblemsCollector implements BootUpInformationCollector { + private static final String SERVICE_NAME_PATTERN_STRING = "([A-Za-z])|([A-Za-z][A-Za-z0-9_\\-.]*[A-Za-z0-9])"; + + private static final Pattern SERVICE_NAME_PATTERN = Pattern.compile(SERVICE_NAME_PATTERN_STRING); + + @Override + public String collect(SCBEngine engine) { + if (engine.getEnvironment() == null) { + // some test cases + return null; + } + StringBuilder result = new StringBuilder(); + collectCsePrefix(engine.getEnvironment(), result); + collectServiceDefinition(engine.getEnvironment(), result); + collectServiceDefinitionValidation(result); + collectTimeoutConfiguration(engine.getEnvironment(), result); + collectIsolationConfiguration(engine.getEnvironment(), result); + if (result.length() <= 0) { + return null; + } + String warnings = "[WARN]Configurations warnings:\n" + result; + EventManager.post(new ConfigurationProblemsAlarmEvent(Type.OPEN, warnings)); + return warnings; + } + + private void collectServiceDefinitionValidation(StringBuilder result) { + String application = BootStrapProperties.readApplication(); + if (!SERVICE_NAME_PATTERN.matcher(application).matches()) { + result.append("application does not match pattern ").append(SERVICE_NAME_PATTERN_STRING).append("."); + } + String serviceName = BootStrapProperties.readServiceName(); + if (!SERVICE_NAME_PATTERN.matcher(serviceName).matches()) { + result.append("service name does not match pattern ").append(SERVICE_NAME_PATTERN_STRING).append("."); + } + } + + // see https://github.com/apache/servicecomb-java-chassis/issues/4024 + // since 2.8.12 + private void collectIsolationConfiguration(Environment environment, StringBuilder result) { + int percentage = environment.getProperty( + "servicecomb.loadbalance.isolation.errorThresholdPercentage", int.class, -1); + int continuous = environment.getProperty( + "servicecomb.loadbalance.isolation.continuousFailureThreshold", int.class, -1); + if (percentage == -1 && continuous == -1) { + return; + } + Boolean enable = environment.getProperty( + "servicecomb.loadbalance.filter.isolation.enabled", Boolean.class); + if (enable != null) { + return; + } + result.append("Configuration `servicecomb.loadbalance.isolation.*` is deprecated and disabled by default, " + + "use governance instead. " + + "See https://servicecomb.apache.org/references/java-chassis/" + + "zh_CN/references-handlers/governance-best-practise.html. If you want to enable it, add" + + "`servicecomb.loadbalance.filter.isolation.enabled=true` implicitly."); + } + + private void collectTimeoutConfiguration(Environment environment, StringBuilder result) { + int keepAliveTimeoutInSeconds = environment.getProperty( + "servicecomb.rest.client.connection.keepAliveTimeoutInSeconds", int.class, 60); + int idleTimeoutInSeconds = environment.getProperty( + "servicecomb.rest.client.connection.idleTimeoutInSeconds", int.class, 150); + if (keepAliveTimeoutInSeconds >= idleTimeoutInSeconds) { + result.append("Configuration `servicecomb.rest.client.connection.keepAliveTimeoutInSeconds` is longer than " + + "servicecomb.rest.client.connection.idleTimeoutInSeconds."); + result.append("[").append(keepAliveTimeoutInSeconds).append(",").append(idleTimeoutInSeconds).append("]\n"); + } + keepAliveTimeoutInSeconds = environment.getProperty( + "servicecomb.rest.client.http2.connection.keepAliveTimeoutInSeconds", int.class, 60); + idleTimeoutInSeconds = environment.getProperty( + "servicecomb.rest.client.http2.connection.idleTimeoutInSeconds", int.class, 150); + if (keepAliveTimeoutInSeconds >= idleTimeoutInSeconds) { + result.append("Configuration `servicecomb.rest.client.http2.connection.keepAliveTimeoutInSeconds` is longer than " + + "servicecomb.rest.client.http2.connection.idleTimeoutInSeconds."); + result.append("[").append(keepAliveTimeoutInSeconds).append(",").append(idleTimeoutInSeconds).append("]\n"); + } + } + + private void collectServiceDefinition(Environment environment, StringBuilder result) { + if (environment.getProperty("APPLICATION_ID") != null) { + result.append("Configurations `APPLICATION_ID` is deprecated, " + + "use `servicecomb.service.application` instead.\n"); + } + Set names = ConfigUtil.propertiesWithPrefix(environment, "service_description."); + if (!names.isEmpty()) { + result.append("Configurations with prefix `service_description` is deprecated, " + + "use `servicecomb.service` instead. Find keys "); + result.append(Arrays.toString(names.toArray())); + result.append("\n"); + } + } + + private void collectCsePrefix(Environment environment, StringBuilder result) { + Set names = ConfigUtil.propertiesWithPrefix(environment, "cse."); + if (!names.isEmpty()) { + result.append("Configurations with prefix `cse` is deprecated, use `servicecomb` instead. Find keys "); + result.append(Arrays.toString(names.toArray())); + result.append("\n"); + } + } + + @Override + public String collect() { + return null; + } + + @Override + public int getOrder() { + return 1000; + } +} diff --git a/core/src/main/java/org/apache/servicecomb/core/definition/OperationMeta.java b/core/src/main/java/org/apache/servicecomb/core/definition/OperationMeta.java index 4453597bc9c..bdcbd5786ce 100644 --- a/core/src/main/java/org/apache/servicecomb/core/definition/OperationMeta.java +++ b/core/src/main/java/org/apache/servicecomb/core/definition/OperationMeta.java @@ -47,8 +47,6 @@ public class OperationMeta { private final ResponsesMeta responsesMeta = new ResponsesMeta(); - private OperationConfig config; - private final VendorExtensions vendorExtensions = new VendorExtensions(); public OperationMeta init(SchemaMeta schemaMeta, SwaggerOperation swaggerOperation) { @@ -60,7 +58,6 @@ public OperationMeta init(SchemaMeta schemaMeta, SwaggerOperation swaggerOperati this.operationPath = swaggerOperation.getPath(); this.swaggerOperation = swaggerOperation.getOperation(); this.executor = schemaMeta.getMicroserviceMeta().getScbEngine().getExecutorManager().findExecutor(this); - this.config = schemaMeta.getMicroserviceMeta().getMicroserviceVersionsMeta().getOrCreateOperationConfig(this); this.responsesMeta.init(schemaMeta.getSwagger(), swaggerOperation.getOperation()); return this; @@ -75,7 +72,7 @@ public SwaggerProducerOperation getSwaggerProducerOperation() { } public OperationConfig getConfig() { - return config; + return schemaMeta.getMicroserviceMeta().getMicroserviceVersionsMeta().getOrCreateOperationConfig(this); } public String getHttpMethod() { diff --git a/core/src/main/java/org/apache/servicecomb/core/event/WebSocketActionEvent.java b/core/src/main/java/org/apache/servicecomb/core/event/WebSocketActionEvent.java new file mode 100644 index 00000000000..7c88a12d510 --- /dev/null +++ b/core/src/main/java/org/apache/servicecomb/core/event/WebSocketActionEvent.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.core.event; + +import org.apache.servicecomb.core.definition.OperationMeta; +import org.apache.servicecomb.swagger.invocation.InvocationType; +import org.apache.servicecomb.swagger.invocation.ws.WebSocket; +import org.apache.servicecomb.swagger.invocation.ws.WebSocketActionType; +import org.apache.servicecomb.swagger.invocation.ws.WebSocketMessage; + +/** + * A websocket action means any notification including websocket message/frame + * that the underlying framework passed to the user extended "WebSocket handler methods" to handle. + * And the "WebSocket handler methods" means the extensible subscriber methods defined in the + * {@link WebSocket}, like {@link WebSocket#onOpen()} and {@link WebSocket#onMessage(WebSocketMessage)}. + */ +public class WebSocketActionEvent { + /** + * To indicates whether this websocket connection is on consumer side or producer side. + */ + private InvocationType invocationType; + + private OperationMeta operationMeta; + + private String traceId; + + private String connectionId; + + /** + * See {@link WebSocketActionType}. + */ + private WebSocketActionType actionType; + + /** + * The startup time of this WebSocket connection in the UnixTimestamp format. + * We treat the websocket handshaking success time as startup time. + */ + private long connectionStartTimestamp; + + /** + * The timestamp that the action is scheduled to run in executor. + */ + private long scheduleStartTimestamp; + + /** + * The UnixTimestamp when this websocket action is triggered. + */ + private long actionStartTimestamp; + + private long actionEndTimestamp; + + private String handleThreadName; + + /** + * How many bytes of data are passed to the handle methods to handle. + * Note that some kinds of actions carry no data, like {@link WebSocket#onOpen()}, in which case the dataSize is 0. + */ + private long dataSize; + + public InvocationType getInvocationType() { + return invocationType; + } + + public WebSocketActionEvent setInvocationType(InvocationType invocationType) { + this.invocationType = invocationType; + return this; + } + + public OperationMeta getOperationMeta() { + return operationMeta; + } + + public WebSocketActionEvent setOperationMeta(OperationMeta operationMeta) { + this.operationMeta = operationMeta; + return this; + } + + public String getTraceId() { + return traceId; + } + + public WebSocketActionEvent setTraceId(String traceId) { + this.traceId = traceId; + return this; + } + + public String getConnectionId() { + return connectionId; + } + + public WebSocketActionEvent setConnectionId(String connectionId) { + this.connectionId = connectionId; + return this; + } + + public WebSocketActionType getActionType() { + return actionType; + } + + public WebSocketActionEvent setActionType(WebSocketActionType actionType) { + this.actionType = actionType; + return this; + } + + public long getConnectionStartTimestamp() { + return connectionStartTimestamp; + } + + public WebSocketActionEvent setConnectionStartTimestamp(long connectionStartTimestamp) { + this.connectionStartTimestamp = connectionStartTimestamp; + return this; + } + + public long getScheduleStartTimestamp() { + return scheduleStartTimestamp; + } + + public WebSocketActionEvent setScheduleStartTimestamp(long scheduleStartTimestamp) { + this.scheduleStartTimestamp = scheduleStartTimestamp; + return this; + } + + public long getActionStartTimestamp() { + return actionStartTimestamp; + } + + public WebSocketActionEvent setActionStartTimestamp(long actionStartTimestamp) { + this.actionStartTimestamp = actionStartTimestamp; + return this; + } + + public long getActionEndTimestamp() { + return actionEndTimestamp; + } + + public WebSocketActionEvent setActionEndTimestamp(long actionEndTimestamp) { + this.actionEndTimestamp = actionEndTimestamp; + return this; + } + + public String getHandleThreadName() { + return handleThreadName; + } + + public WebSocketActionEvent setHandleThreadName(String handleThreadName) { + this.handleThreadName = handleThreadName; + return this; + } + + public long getDataSize() { + return dataSize; + } + + public WebSocketActionEvent setDataSize(long dataSize) { + this.dataSize = dataSize; + return this; + } +} diff --git a/core/src/main/java/org/apache/servicecomb/core/filter/impl/ParameterValidatorFilter.java b/core/src/main/java/org/apache/servicecomb/core/filter/impl/ParameterValidatorFilter.java index 4b2bcaba2f7..a6a0a22c45a 100644 --- a/core/src/main/java/org/apache/servicecomb/core/filter/impl/ParameterValidatorFilter.java +++ b/core/src/main/java/org/apache/servicecomb/core/filter/impl/ParameterValidatorFilter.java @@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import javax.validation.Configuration; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.validation.Validation; @@ -28,6 +29,7 @@ import javax.validation.executable.ExecutableValidator; import javax.validation.groups.Default; +import org.apache.servicecomb.config.ConfigUtil; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.core.filter.FilterNode; import org.apache.servicecomb.core.filter.ProducerFilter; @@ -40,21 +42,28 @@ import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import com.netflix.config.DynamicPropertyFactory; @Component -public class ParameterValidatorFilter implements ProducerFilter, InitializingBean { +public class ParameterValidatorFilter implements ProducerFilter, ApplicationContextAware, InitializingBean { private static final Logger LOGGER = LoggerFactory.getLogger(ParameterValidatorFilter.class); public static final String NAME = "validator"; private static final String ENABLE_EL = "servicecomb.filters.validation.useResourceBundleMessageInterpolator"; + public static final String HIBERNATE_VALIDATE_PREFIX = "hibernate.validator"; + protected ExecutableValidator validator; + private ApplicationContext applicationContext; + @Nonnull @Override public String getName() { @@ -67,12 +76,23 @@ public void afterPropertiesSet() { .getValidator().forExecutables(); } - protected ValidatorFactory createValidatorFactory() { - return Validation.byProvider(HibernateValidator.class) + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + private ValidatorFactory createValidatorFactory() { + Configuration validatorConfiguration = Validation.byProvider(HibernateValidator.class) .configure() .propertyNodeNameProvider(new JacksonPropertyNodeNameProvider()) - .messageInterpolator(messageInterpolator()) - .buildValidatorFactory(); + .messageInterpolator(messageInterpolator()); + Set keys = ConfigUtil.propertiesWithPrefix(applicationContext.getEnvironment(), HIBERNATE_VALIDATE_PREFIX); + if (!keys.isEmpty()) { + for (String key : keys) { + validatorConfiguration.addProperty(key, applicationContext.getEnvironment().getProperty(key)); + } + } + return validatorConfiguration.buildValidatorFactory(); } protected AbstractMessageInterpolator messageInterpolator() { diff --git a/core/src/main/java/org/apache/servicecomb/core/governance/GovernanceConfiguration.java b/core/src/main/java/org/apache/servicecomb/core/governance/GovernanceConfiguration.java index eff944fff82..33f86e6c71e 100644 --- a/core/src/main/java/org/apache/servicecomb/core/governance/GovernanceConfiguration.java +++ b/core/src/main/java/org/apache/servicecomb/core/governance/GovernanceConfiguration.java @@ -28,6 +28,8 @@ public class GovernanceConfiguration { public static final String RETRY_ON_SAME = "retryOnSame"; + public static final String WITH_DURATION = "waitDuration"; + public static boolean isRetryEnabled(String microservice) { String p = getStringProperty("false", ROOT + microservice + "." + RETRY_ENABLED, @@ -43,6 +45,21 @@ public static int getRetrySameServer(String microservice) { return getRetryServer(microservice, RETRY_ON_SAME); } + public static long getWithDuration(String microservice) { + final long defaultValue = 1; + String duration = getStringProperty("1", ROOT + microservice + "." + WITH_DURATION, + ROOT + WITH_DURATION); + try { + long result = Long.parseLong(duration); + if (result > 0) { + return result; + } + return defaultValue; + } catch (NumberFormatException e) { + return defaultValue; + } + } + private static int getRetryServer(String microservice, String retryType) { final int defaultValue = 0; String p = getStringProperty("0", diff --git a/core/src/main/java/org/apache/servicecomb/core/governance/MatchType.java b/core/src/main/java/org/apache/servicecomb/core/governance/MatchType.java index 66193153112..8f274542ed8 100644 --- a/core/src/main/java/org/apache/servicecomb/core/governance/MatchType.java +++ b/core/src/main/java/org/apache/servicecomb/core/governance/MatchType.java @@ -17,50 +17,89 @@ package org.apache.servicecomb.core.governance; -import java.util.Enumeration; -import java.util.HashMap; import java.util.Map; import org.apache.servicecomb.core.Invocation; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; public final class MatchType { - public static final String REST = "rest"; + private static class GovernanceRequestExtractorImpl implements GovernanceRequestExtractor { + private final Invocation invocation; - public static final String RPC = "rpc"; + private GovernanceRequestExtractorImpl(Invocation invocation) { + this.invocation = invocation; + } - public static GovernanceRequest createGovHttpRequest(Invocation invocation) { - GovernanceRequest request = new GovernanceRequest(); + @Override + public String apiPath() { + if (MatchType.REST.equalsIgnoreCase(invocation.getOperationMeta().getConfig().getGovernanceMatchType())) { + if (invocation.isConsumer()) { + return concatAbsolutePath(invocation.getSchemaMeta().getSwagger().getBasePath(), + invocation.getOperationMeta().getOperationPath()); + } + // not highway + if (invocation.getRequestEx() != null) { + return invocation.getRequestEx().getRequestURI(); + } + } - if (MatchType.REST.equalsIgnoreCase(invocation.getOperationMeta().getConfig().getGovernanceMatchType())) { if (invocation.isConsumer()) { - request.setUri(concatAbsolutePath( - invocation.getSchemaMeta().getSwagger().getBasePath(), invocation.getOperationMeta().getOperationPath())); - request.setMethod(invocation.getOperationMeta().getHttpMethod()); - request.setHeaders(getHeaderMap(invocation, true)); - return request; + return invocation.getOperationMeta().getMicroserviceQualifiedName(); + } + return invocation.getOperationMeta().getSchemaQualifiedName(); + } + + @Override + public String method() { + return invocation.getOperationMeta().getHttpMethod(); + } + + @Override + public String header(String key) { + Map arguments = invocation.getSwaggerArguments(); + if (arguments != null && arguments.get(key) != null) { + return arguments.get(key).toString(); + } + + if (invocation.getContext(key) != null) { + return invocation.getContext(key); } - // not highway if (invocation.getRequestEx() != null) { - request.setUri(invocation.getRequestEx().getRequestURI()); - request.setMethod(invocation.getRequestEx().getMethod()); - request.setHeaders(getHeaderMap(invocation, false)); - return request; + return invocation.getRequestEx().getHeader(key); + } + + return null; + } + + @Override + public String instanceId() { + if (invocation.isConsumer()) { + return invocation.getEndpoint().getMicroserviceInstance().getInstanceId(); } + return null; + } - // maybe highway + @Override + public String serviceName() { + if (invocation.isConsumer()) { + return invocation.getMicroserviceName(); + } + return null; } - if (invocation.isConsumer()) { - request.setUri(invocation.getOperationMeta().getMicroserviceQualifiedName()); - } else { - request.setUri(invocation.getOperationMeta().getSchemaQualifiedName()); + @Override + public Object sourceRequest() { + return invocation; } - request.setMethod(invocation.getOperationMeta().getHttpMethod()); - request.setHeaders(getHeaderMap(invocation, true)); + } + + public static final String REST = "rest"; + + public static final String RPC = "rpc"; - return request; + public static GovernanceRequestExtractor createGovHttpRequest(Invocation invocation) { + return new GovernanceRequestExtractorImpl(invocation); } /** @@ -77,29 +116,4 @@ private static String concatAbsolutePath(String basePath, String operationPath) private static String nonNullify(String path) { return path == null ? "" : path; } - - private static Map getHeaderMap(Invocation invocation, boolean fromContext) { - Map headers = new HashMap<>(); - if (fromContext) { - headers.putAll(invocation.getContext()); - } else { - Enumeration names = invocation.getRequestEx().getHeaderNames(); - while (names.hasMoreElements()) { - String name = names.nextElement(); - if (invocation.getRequestEx().getHeader(name) != null) { - headers.put(name, invocation.getRequestEx().getHeader(name)); - } - } - } - - Map arguments = invocation.getSwaggerArguments(); - if (arguments != null) { - arguments.forEach((k, v) -> { - if (v != null) { - headers.put(k, v.toString()); - } - }); - } - return headers; - } } diff --git a/core/src/main/java/org/apache/servicecomb/core/provider/consumer/InvokerUtils.java b/core/src/main/java/org/apache/servicecomb/core/provider/consumer/InvokerUtils.java index a1df058d0a1..2e4c0603a52 100644 --- a/core/src/main/java/org/apache/servicecomb/core/provider/consumer/InvokerUtils.java +++ b/core/src/main/java/org/apache/servicecomb/core/provider/consumer/InvokerUtils.java @@ -35,6 +35,7 @@ import javax.annotation.Nonnull; import javax.ws.rs.core.Response.Status; +import org.apache.http.HttpStatus; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.core.SCBEngine; import org.apache.servicecomb.core.definition.InvocationRuntimeType; @@ -49,7 +50,7 @@ import org.apache.servicecomb.foundation.common.utils.BeanUtils; import org.apache.servicecomb.governance.handler.RetryHandler; import org.apache.servicecomb.governance.handler.ext.FailurePredictor; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.swagger.invocation.AsyncResponse; import org.apache.servicecomb.swagger.invocation.Response; import org.apache.servicecomb.swagger.invocation.context.ContextUtils; @@ -77,6 +78,8 @@ public final class InvokerUtils { private static volatile ScheduledExecutorService reactiveRetryPool; + private static RetryHandler retryHandler = null; + private static ScheduledExecutorService getOrCreateRetryPool() { if (reactiveRetryPool == null) { synchronized (LOCK) { @@ -190,7 +193,7 @@ public static boolean isInEventLoop() { * This is an internal API, caller make sure already invoked SCBEngine.ensureStatusUp */ public static Response innerSyncInvoke(Invocation invocation) { - GovernanceRequest request = MatchType.createGovHttpRequest(invocation); + GovernanceRequestExtractor request = MatchType.createGovHttpRequest(invocation); return decorateSyncRetry(invocation, request); } @@ -211,6 +214,10 @@ private static Response innerSyncInvokeImpl(Invocation invocation) throws Throwa invocation.getInvocationStageTrace().finishHandlersResponse(); invocation.onFinish(response); + if (response.getStatusCode() == HttpStatus.SC_SWITCHING_PROTOCOLS) { + response.setStatus(Status.OK); // Switch to OK status to make websocket pass the RPC procedure later + } + if (response.isFailed()) { // re-throw exception to make sure retry based on exception // for InvocationException, users can configure status code for retry @@ -245,10 +252,12 @@ private static void updateRetryStatus(Invocation invocation) { new RetryContext(GovernanceConfiguration.getRetrySameServer(invocation.getMicroserviceName()))); } - private static Response decorateSyncRetry(Invocation invocation, GovernanceRequest request) { + private static Response decorateSyncRetry(Invocation invocation, GovernanceRequestExtractor request) { try { // governance implementations. - RetryHandler retryHandler = BeanUtils.getBean(RetryHandler.class); + if (retryHandler == null) { + retryHandler = BeanUtils.getBean(RetryHandler.class); + } Retry retry = retryHandler.getActuator(request); if (retry != null) { CheckedFunction0 supplier = Retry @@ -292,7 +301,7 @@ private static Retry getOrCreateCompatibleRetry(Invocation invocation) { + GovernanceConfiguration.getRetrySameServer(invocation.getMicroserviceName()) + 1) .retryOnResult(InvokerUtils::canRetryForStatusCode) .retryOnException(InvokerUtils::canRetryForException) - .waitDuration(Duration.ofMillis(0)) + .waitDuration(Duration.ofMillis(GovernanceConfiguration.getWithDuration(invocation.getMicroserviceName()))) .build(); RetryRegistry retryRegistry = RetryRegistry.of(retryConfig); return retryRegistry.retry(invocation.getMicroserviceName()); @@ -306,7 +315,7 @@ public static void reactiveInvoke(Invocation invocation, AsyncResponse asyncResp Supplier> next = reactiveInvokeImpl(invocation); DecorateCompletionStage dcs = Decorators.ofCompletionStage(next); - GovernanceRequest request = MatchType.createGovHttpRequest(invocation); + GovernanceRequestExtractor request = MatchType.createGovHttpRequest(invocation); decorateReactiveRetry(invocation, dcs, request); @@ -327,9 +336,11 @@ public static void reactiveInvoke(Invocation invocation, AsyncResponse asyncResp } private static void decorateReactiveRetry(Invocation invocation, DecorateCompletionStage dcs, - GovernanceRequest request) { + GovernanceRequestExtractor request) { // governance implementations. - RetryHandler retryHandler = BeanUtils.getBean(RetryHandler.class); + if (retryHandler == null) { + retryHandler = BeanUtils.getBean(RetryHandler.class); + } Retry retry = retryHandler.getActuator(request); if (retry != null) { dcs.withRetry(retry, getOrCreateRetryPool()); @@ -358,6 +369,10 @@ private static Supplier> reactiveInvokeImpl(Invocation invocation.getInvocationStageTrace().finishHandlersResponse(); invocation.onFinish(ar); try { + if (ar.getStatusCode() == HttpStatus.SC_SWITCHING_PROTOCOLS) { + ar.setStatus(Status.OK); // Switch to OK status to make websocket pass the RPC procedure later + } + if (ar.isFailed()) { // re-throw exception to make sure retry based on exception // for InvocationException, users can configure status code for retry @@ -402,7 +417,7 @@ public static boolean isAsyncMethod(@Nonnull Method method) { public static CompletableFuture invoke(Invocation invocation) { Supplier> next = invokeImpl(invocation); DecorateCompletionStage dcs = Decorators.ofCompletionStage(next); - GovernanceRequest request = MatchType.createGovHttpRequest(invocation); + GovernanceRequestExtractor request = MatchType.createGovHttpRequest(invocation); decorateReactiveRetry(invocation, dcs, request); diff --git a/core/src/main/java/org/apache/servicecomb/core/provider/consumer/SyncResponseExecutor.java b/core/src/main/java/org/apache/servicecomb/core/provider/consumer/SyncResponseExecutor.java index cf65384d3b1..228f21f53a3 100644 --- a/core/src/main/java/org/apache/servicecomb/core/provider/consumer/SyncResponseExecutor.java +++ b/core/src/main/java/org/apache/servicecomb/core/provider/consumer/SyncResponseExecutor.java @@ -98,8 +98,7 @@ private long getWaitTime(Invocation invocation) { // In invocation handlers, may call other microservices, invocation // timeout may be much longer than request timeout. - // But this is quite rare, for simplicity, default two times of request timeout. - // If users need longer timeout, can configure invocation timeout. - return invocation.getOperationMeta().getConfig().getMsRequestTimeout() * 2; + // For simplicity, default 30000 or two times of request timeout. + return Math.max(invocation.getOperationMeta().getConfig().getMsRequestTimeout() * 2, 30000); } } diff --git a/core/src/main/java/org/apache/servicecomb/core/registry/discovery/OperationInstancesDiscoveryFilter.java b/core/src/main/java/org/apache/servicecomb/core/registry/discovery/OperationInstancesDiscoveryFilter.java index a61b3535693..e74f0190ab8 100644 --- a/core/src/main/java/org/apache/servicecomb/core/registry/discovery/OperationInstancesDiscoveryFilter.java +++ b/core/src/main/java/org/apache/servicecomb/core/registry/discovery/OperationInstancesDiscoveryFilter.java @@ -82,7 +82,7 @@ public int getOrder() { @Override public boolean enabled() { return DynamicPropertyFactory.getInstance() - .getBooleanProperty("servicecomb.loadbalance.filter.operation.enabled", true).get(); + .getBooleanProperty("servicecomb.loadbalance.filter.operation.enabled", false).get(); } @Override diff --git a/core/src/main/java/org/apache/servicecomb/core/transport/TransportClassAnnotationProcessor.java b/core/src/main/java/org/apache/servicecomb/core/transport/TransportClassAnnotationProcessor.java new file mode 100644 index 00000000000..b65192a4e42 --- /dev/null +++ b/core/src/main/java/org/apache/servicecomb/core/transport/TransportClassAnnotationProcessor.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.core.transport; + +import java.lang.reflect.Type; + +import org.apache.commons.lang3.StringUtils; +import org.apache.servicecomb.core.Const; +import org.apache.servicecomb.core.annotation.Transport; +import org.apache.servicecomb.swagger.generator.ClassAnnotationProcessor; +import org.apache.servicecomb.swagger.generator.SwaggerGenerator; + +public class TransportClassAnnotationProcessor implements ClassAnnotationProcessor { + @Override + public Type getProcessType() { + return Transport.class; + } + + @Override + public void process(SwaggerGenerator swaggerGenerator, Transport transport) { + if (StringUtils.isEmpty(transport.name())) { + return; + } + + swaggerGenerator.getSwagger() + .setVendorExtension(Const.TRANSPORT_NAME, transport.name()); + } +} diff --git a/core/src/main/java/org/apache/servicecomb/core/transport/TransportMethodAnnotationProcessor.java b/core/src/main/java/org/apache/servicecomb/core/transport/TransportMethodAnnotationProcessor.java new file mode 100644 index 00000000000..7d5340aeee0 --- /dev/null +++ b/core/src/main/java/org/apache/servicecomb/core/transport/TransportMethodAnnotationProcessor.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.core.transport; + +import java.lang.reflect.Type; + +import org.apache.commons.lang3.StringUtils; +import org.apache.servicecomb.core.Const; +import org.apache.servicecomb.core.annotation.Transport; +import org.apache.servicecomb.swagger.generator.MethodAnnotationProcessor; +import org.apache.servicecomb.swagger.generator.OperationGenerator; +import org.apache.servicecomb.swagger.generator.SwaggerGenerator; + +public class TransportMethodAnnotationProcessor implements MethodAnnotationProcessor { + @Override + public Type getProcessType() { + return Transport.class; + } + + @Override + public void process(SwaggerGenerator swaggerGenerator, OperationGenerator operationGenerator, Transport transport) { + if (StringUtils.isEmpty(transport.name())) { + return; + } + + operationGenerator.getOperation() + .setVendorExtension(Const.TRANSPORT_NAME, transport.name()); + } +} diff --git a/core/src/main/resources/META-INF/services/org.apache.servicecomb.core.bootup.BootUpInformationCollector b/core/src/main/resources/META-INF/services/org.apache.servicecomb.core.bootup.BootUpInformationCollector index c33b0de60b1..36ee3dc51e5 100644 --- a/core/src/main/resources/META-INF/services/org.apache.servicecomb.core.bootup.BootUpInformationCollector +++ b/core/src/main/resources/META-INF/services/org.apache.servicecomb.core.bootup.BootUpInformationCollector @@ -16,4 +16,5 @@ # org.apache.servicecomb.core.bootup.ServiceInformationCollector -org.apache.servicecomb.core.bootup.FilterChainCollector \ No newline at end of file +org.apache.servicecomb.core.bootup.FilterChainCollector +org.apache.servicecomb.core.bootup.ConfigurationProblemsCollector diff --git a/core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.ClassAnnotationProcessor b/core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.ClassAnnotationProcessor new file mode 100644 index 00000000000..d8d096f3ae8 --- /dev/null +++ b/core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.ClassAnnotationProcessor @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. +# + +org.apache.servicecomb.core.transport.TransportClassAnnotationProcessor \ No newline at end of file diff --git a/core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.MethodAnnotationProcessor b/core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.MethodAnnotationProcessor new file mode 100644 index 00000000000..a79270a550a --- /dev/null +++ b/core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.MethodAnnotationProcessor @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. +# + +org.apache.servicecomb.core.transport.TransportMethodAnnotationProcessor \ No newline at end of file diff --git a/core/src/test/java/org/apache/servicecomb/core/filter/impl/ParameterValidatorFilterTest.java b/core/src/test/java/org/apache/servicecomb/core/filter/impl/ParameterValidatorFilterTest.java index 7942a355f67..f08b89176bd 100644 --- a/core/src/test/java/org/apache/servicecomb/core/filter/impl/ParameterValidatorFilterTest.java +++ b/core/src/test/java/org/apache/servicecomb/core/filter/impl/ParameterValidatorFilterTest.java @@ -44,6 +44,10 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.context.ApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MutablePropertySources; import com.fasterxml.jackson.annotation.JsonProperty; @@ -96,6 +100,12 @@ public void op(@NotNull(message = "not null") String query, @Valid Model model) @BeforeClass public static void beforeClass() throws Exception { + ApplicationContext applicationContext = Mockito.mock(ApplicationContext.class); + ConfigurableEnvironment environment = Mockito.mock(ConfigurableEnvironment.class); + MutablePropertySources sources = new MutablePropertySources(); + Mockito.when(applicationContext.getEnvironment()).thenReturn(environment); + Mockito.when(environment.getPropertySources()).thenReturn(sources); + filter.setApplicationContext(applicationContext); filter.afterPropertiesSet(); } diff --git a/core/src/test/java/org/apache/servicecomb/core/invocation/endpoint/EndpointTest.java b/core/src/test/java/org/apache/servicecomb/core/invocation/endpoint/EndpointTest.java index d6203589c18..b6e1f8bd5e4 100644 --- a/core/src/test/java/org/apache/servicecomb/core/invocation/endpoint/EndpointTest.java +++ b/core/src/test/java/org/apache/servicecomb/core/invocation/endpoint/EndpointTest.java @@ -44,9 +44,6 @@ public interface TestSchema { @Test void should_ignore_endpoint_when_generate_swagger() { SwaggerGenerator generator = SwaggerGenerator.create(TestSchema.class); - generator.getSwaggerGeneratorFeature() - .setExtJavaInterfaceInVendor(false) - .setExtJavaClassInVendor(false); Swagger swagger = generator.generate(); assertThat(SwaggerUtils.swaggerToString(swagger)) @@ -55,6 +52,7 @@ void should_ignore_endpoint_when_generate_swagger() { + "info:\n" + " version: \"1.0.0\"\n" + " title: \"swagger definition for org.apache.servicecomb.core.invocation.endpoint.EndpointTest$TestSchema\"\n" + + " x-java-interface: \"org.apache.servicecomb.core.invocation.endpoint.EndpointTest$TestSchema\"\n" + "basePath: \"/TestSchema\"\n" + "consumes:\n" + "- \"application/json\"\n" diff --git a/core/src/test/java/org/apache/servicecomb/core/transport/TestAbstractTransport.java b/core/src/test/java/org/apache/servicecomb/core/transport/TestAbstractTransport.java index b0fd7ba0c4b..c43ea62b110 100644 --- a/core/src/test/java/org/apache/servicecomb/core/transport/TestAbstractTransport.java +++ b/core/src/test/java/org/apache/servicecomb/core/transport/TestAbstractTransport.java @@ -18,7 +18,6 @@ package org.apache.servicecomb.core.transport; import java.io.UnsupportedEncodingException; -import java.lang.reflect.Method; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Collections; @@ -26,27 +25,12 @@ import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.foundation.common.net.IpPort; import org.apache.servicecomb.foundation.vertx.VertxUtils; -import org.apache.servicecomb.registry.RegistrationManager; import org.apache.servicecomb.swagger.invocation.AsyncResponse; import org.junit.AfterClass; import org.junit.Test; import org.junit.jupiter.api.Assertions; -import org.springframework.util.ReflectionUtils; - -import com.netflix.config.DynamicProperty; - -import mockit.Expectations; -import mockit.Mocked; public class TestAbstractTransport { - private final Method updatePropertyMethod = - ReflectionUtils.findMethod(DynamicProperty.class, "updateProperty", String.class, Object.class); - - private void updateProperty(String key, Object value) { - updatePropertyMethod.setAccessible(true); - ReflectionUtils.invokeMethod(updatePropertyMethod, null, key, value); - } - static class MyAbstractTransport extends AbstractTransport { @Override @@ -71,12 +55,6 @@ public static void classTeardown() { @Test public void testSetListenAddressWithoutSchemaChineseSpaceNewSC() throws UnsupportedEncodingException { - new Expectations() { - { - RegistrationManager.getPublishAddress("my", "127.0.0.1:9090"); - } - }; - MyAbstractTransport transport = new MyAbstractTransport(); transport.setListenAddressWithoutSchema("127.0.0.1:9090", Collections.singletonMap("country", "中 国")); Assertions.assertEquals("my://127.0.0.1:9090?country=" + URLEncoder.encode("中 国", StandardCharsets.UTF_8.name()), @@ -113,7 +91,7 @@ public void testMyAbstractTransport() { } @Test(expected = IllegalArgumentException.class) - public void testMyAbstractTransportException(@Mocked TransportManager manager) { + public void testMyAbstractTransportException() { MyAbstractTransport transport = new MyAbstractTransport(); transport.setListenAddressWithoutSchema(":127.0.0.1:9090"); diff --git a/core/src/test/java/org/apache/servicecomb/core/transport/TestTransportManager.java b/core/src/test/java/org/apache/servicecomb/core/transport/TestTransportManager.java index 208224515c9..89b591beecb 100644 --- a/core/src/test/java/org/apache/servicecomb/core/transport/TestTransportManager.java +++ b/core/src/test/java/org/apache/servicecomb/core/transport/TestTransportManager.java @@ -25,30 +25,20 @@ import org.apache.servicecomb.core.SCBEngine; import org.apache.servicecomb.core.Transport; import org.apache.servicecomb.foundation.common.exceptions.ServiceCombException; -import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; -import org.junit.Test; - -import mockit.Expectations; -import mockit.Injectable; -import mockit.Mocked; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; public class TestTransportManager { @Test - public void testTransportManagerInitFail(@Mocked SCBEngine scbEngine, @Injectable Transport transport) + public void testTransportManagerInitFail() throws Exception { - new Expectations() { - { - transport.getName(); - result = "test"; - transport.init(); - result = false; - transport.canInit(); - result = true; - } - }; + SCBEngine scbEngine = Mockito.mock(SCBEngine.class); + Transport transport = Mockito.mock(Transport.class); + Mockito.when(transport.getName()).thenReturn("test"); + Mockito.when(transport.init()).thenReturn(false); + Mockito.when(transport.canInit()).thenReturn(true); List transports = Arrays.asList(transport); - TransportManager manager = new TransportManager(); manager.addTransportsBeforeInit(transports); @@ -57,20 +47,14 @@ public void testTransportManagerInitFail(@Mocked SCBEngine scbEngine, @Injectabl } @Test - public void testTransportManagerInitSucc(@Mocked SCBEngine scbEngine, @Injectable Transport transport, - @Injectable Endpoint endpoint, @Injectable MicroserviceInstance instance) throws Exception { - new Expectations() { - { - transport.getName(); - result = "test"; - transport.canInit(); - result = true; - transport.init(); - result = true; - transport.getPublishEndpoint(); - result = endpoint; - } - }; + public void testTransportManagerInitSuccess() throws Exception { + SCBEngine scbEngine = Mockito.mock(SCBEngine.class); + Transport transport = Mockito.mock(Transport.class); + Endpoint endpoint = Mockito.mock(Endpoint.class); + Mockito.when(transport.getName()).thenReturn("test"); + Mockito.when(transport.init()).thenReturn(true); + Mockito.when(transport.canInit()).thenReturn(true); + Mockito.when(transport.getPublishEndpoint()).thenReturn(endpoint); List transports = Arrays.asList(transport); TransportManager manager = new TransportManager(); @@ -81,42 +65,31 @@ public void testTransportManagerInitSucc(@Mocked SCBEngine scbEngine, @Injectabl } @Test - public void testGroupByName(@Mocked Transport t1, @Mocked Transport t2_1, @Mocked Transport t2_2) { - new Expectations() { - { - t1.getName(); - result = "t1"; - - t2_1.getName(); - result = "t2"; - t2_2.getName(); - result = "t2"; - } - }; - + public void testGroupByName() { + Transport t1 = Mockito.mock(Transport.class); + Mockito.when(t1.getName()).thenReturn("t1"); + Transport t21 = Mockito.mock(Transport.class); + Mockito.when(t21.getName()).thenReturn("t2"); + Transport t22 = Mockito.mock(Transport.class); + Mockito.when(t22.getName()).thenReturn("t2"); TransportManager manager = new TransportManager(); - manager.addTransportsBeforeInit(Arrays.asList(t1, t2_1, t2_2)); + manager.addTransportsBeforeInit(Arrays.asList(t1, t21, t22)); Map> groups = manager.groupByName(); Assertions.assertEquals(2, groups.size()); Assertions.assertEquals(1, groups.get("t1").size()); Assertions.assertEquals(t1, groups.get("t1").get(0)); Assertions.assertEquals(2, groups.get("t2").size()); - Assertions.assertEquals(t2_1, groups.get("t2").get(0)); - Assertions.assertEquals(t2_2, groups.get("t2").get(1)); + Assertions.assertEquals(t21, groups.get("t2").get(0)); + Assertions.assertEquals(t22, groups.get("t2").get(1)); } @Test - public void testCheckTransportGroupInvalid(@Mocked Transport t1, @Mocked Transport t2) { - new Expectations() { - { - t1.getOrder(); - result = 1; - - t2.getOrder(); - result = 1; - } - }; + public void testCheckTransportGroupInvalid() { + Transport t1 = Mockito.mock(Transport.class); + Mockito.when(t1.getOrder()).thenReturn(1); + Transport t2 = Mockito.mock(Transport.class); + Mockito.when(t2.getOrder()).thenReturn(1); TransportManager manager = new TransportManager(); List group = Arrays.asList(t1, t2); @@ -125,23 +98,16 @@ public void testCheckTransportGroupInvalid(@Mocked Transport t1, @Mocked Transpo manager.checkTransportGroup(group); Assertions.fail("must throw exception"); } catch (ServiceCombException e) { - Assertions.assertEquals( - "org.apache.servicecomb.core.$Impl_Transport and org.apache.servicecomb.core.$Impl_Transport have the same order 1", - e.getMessage()); + Assertions.assertTrue(e.getMessage().contains("have the same order")); } } @Test - public void testCheckTransportGroupValid(@Mocked Transport t1, @Mocked Transport t2) { - new Expectations() { - { - t1.getOrder(); - result = 1; - - t2.getOrder(); - result = 2; - } - }; + public void testCheckTransportGroupValid() { + Transport t1 = Mockito.mock(Transport.class); + Mockito.when(t1.getOrder()).thenReturn(1); + Transport t2 = Mockito.mock(Transport.class); + Mockito.when(t2.getOrder()).thenReturn(2); TransportManager manager = new TransportManager(); List group = Arrays.asList(t1, t2); @@ -154,18 +120,12 @@ public void testCheckTransportGroupValid(@Mocked Transport t1, @Mocked Transport } @Test - public void testChooseOneTransportFirst(@Mocked Transport t1, @Mocked Transport t2) { - new Expectations() { - { - t1.getOrder(); - result = 1; - t1.canInit(); - result = true; - - t2.getOrder(); - result = 2; - } - }; + public void testChooseOneTransportFirst() { + Transport t1 = Mockito.mock(Transport.class); + Mockito.when(t1.getOrder()).thenReturn(1); + Mockito.when(t1.canInit()).thenReturn(true); + Transport t2 = Mockito.mock(Transport.class); + Mockito.when(t2.getOrder()).thenReturn(2); TransportManager manager = new TransportManager(); List group = Arrays.asList(t1, t2); @@ -174,21 +134,13 @@ public void testChooseOneTransportFirst(@Mocked Transport t1, @Mocked Transport } @Test - public void testChooseOneTransportSecond(@Mocked Transport t1, @Mocked Transport t2) { - new Expectations() { - { - t1.getOrder(); - result = Integer.MAX_VALUE; - t1.canInit(); - result = true; - - t2.getOrder(); - result = -1000; - t2.canInit(); - result = false; - } - }; - + public void testChooseOneTransportSecond() { + Transport t1 = Mockito.mock(Transport.class); + Mockito.when(t1.getOrder()).thenReturn(Integer.MAX_VALUE); + Mockito.when(t1.canInit()).thenReturn(true); + Transport t2 = Mockito.mock(Transport.class); + Mockito.when(t2.getOrder()).thenReturn(-1000); + Mockito.when(t2.canInit()).thenReturn(false); TransportManager manager = new TransportManager(); List group = Arrays.asList(t1, t2); @@ -196,22 +148,14 @@ public void testChooseOneTransportSecond(@Mocked Transport t1, @Mocked Transport } @Test - public void testChooseOneTransportNone(@Mocked Transport t1, @Mocked Transport t2) { - new Expectations() { - { - t1.getName(); - result = "t"; - t1.getOrder(); - result = 1; - t1.canInit(); - result = false; - - t2.getOrder(); - result = 2; - t2.canInit(); - result = false; - } - }; + public void testChooseOneTransportNone() { + Transport t1 = Mockito.mock(Transport.class); + Mockito.when(t1.getName()).thenReturn("t"); + Mockito.when(t1.getOrder()).thenReturn(1); + Mockito.when(t1.canInit()).thenReturn(false); + Transport t2 = Mockito.mock(Transport.class); + Mockito.when(t2.getOrder()).thenReturn(2); + Mockito.when(t2.canInit()).thenReturn(false); TransportManager manager = new TransportManager(); List group = Arrays.asList(t1, t2); diff --git a/core/src/test/resources/test/test/schema.yaml b/core/src/test/resources/test/test/schema.yaml index 1fc4b15f949..d0ec83c9ed6 100644 --- a/core/src/test/resources/test/test/schema.yaml +++ b/core/src/test/resources/test/test/schema.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/coverage-reports/pom.xml b/coverage-reports/pom.xml index 0e328f47e60..939661403d3 100644 --- a/coverage-reports/pom.xml +++ b/coverage-reports/pom.xml @@ -560,129 +560,6 @@ ${project.version} test - - - - org.apache.servicecomb.tests - dynamic-config-tests - ${project.version} - test - - - org.apache.servicecomb.tests - it-common - ${project.version} - test - - - org.apache.servicecomb.tests - it-consumer - ${project.version} - test - - - org.apache.servicecomb.tests - it-edge - ${project.version} - test - - - org.apache.servicecomb.tests - it-producer - ${project.version} - test - - - org.apache.servicecomb.tests - it-producer-deploy-springboot2-servlet - ${project.version} - test - - - org.apache.servicecomb.tests - it-producer-deploy-springboot2-standalone - ${project.version} - test - - - org.apache.servicecomb.tests - jaxrs-tests - ${project.version} - test - - - org.apache.servicecomb.tests - pojo-test - ${project.version} - test - - - org.apache.servicecomb.tests - spring-jaxrs-tests - ${project.version} - test - - - org.apache.servicecomb.tests - spring-pojo-connection-limit-test - ${project.version} - test - - - org.apache.servicecomb.tests - spring-pojo-tests - ${project.version} - test - - - - org.apache.servicecomb.tests - springmvc-tests-common - ${project.version} - test - - - org.apache.servicecomb.tests - springmvc-tests-connection-limit - ${project.version} - test - - - org.apache.servicecomb.tests - springmvc-tests-general - ${project.version} - test - - - org.apache.servicecomb.tests - springmvc-tests-general-with-springboot - ${project.version} - test - - - org.apache.servicecomb.tests - springmvc-tests-simplified-mapping - ${project.version} - test - - - org.apache.servicecomb.tests - springmvc-tests-simplified-mapping-with-springboot - ${project.version} - test - - - org.apache.servicecomb.tests - test-common - ${project.version} - test - - - org.apache.servicecomb.tests - tracing-tests - ${project.version} - test - diff --git a/demo/demo-crossapp/crossapp-client/pom.xml b/demo/demo-crossapp/crossapp-client/pom.xml index 70468e4c10a..73d7375cd5c 100644 --- a/demo/demo-crossapp/crossapp-client/pom.xml +++ b/demo/demo-crossapp/crossapp-client/pom.xml @@ -46,14 +46,6 @@ org.apache.servicecomb.demo.crossapp.CrossappClient - - - - org.apache.maven.plugins - maven-dependency-plugin - - - diff --git a/demo/demo-crossapp/crossapp-client/src/main/java/org/apache/servicecomb/demo/crossapp/CrossappClient.java b/demo/demo-crossapp/crossapp-client/src/main/java/org/apache/servicecomb/demo/crossapp/CrossappClient.java index f76ca083c96..785105707a7 100644 --- a/demo/demo-crossapp/crossapp-client/src/main/java/org/apache/servicecomb/demo/crossapp/CrossappClient.java +++ b/demo/demo-crossapp/crossapp-client/src/main/java/org/apache/servicecomb/demo/crossapp/CrossappClient.java @@ -32,6 +32,8 @@ import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; @Component @@ -64,25 +66,81 @@ public static void run() { result = helloWorld.sayHello(); TestMgr.check("hello world", result); - testCorsHandler(); + testCorsHandlerOptions(); + testCorsHandlerGet(); } - private static void testCorsHandler() { - RestTemplate springRestTemplate = new RestTemplate(); + private static void testCorsHandlerOptions() { + // first domain + RestOperations springRestTemplate = new RestTemplate(); MultiValueMap requestHeaders = new LinkedMultiValueMap<>(); - requestHeaders.put("Origin", Collections.singletonList("http://localhost:8080")); + requestHeaders.put("Origin", Collections.singletonList("http://test.domain:8080")); requestHeaders.put("Access-Control-Request-Method", Collections.singletonList("PUT")); - HttpEntity requestEntity = new HttpEntity<>(requestHeaders); ResponseEntity responseEntity = springRestTemplate .exchange("http://127.0.0.1:8080/helloworld/hello", HttpMethod.OPTIONS, requestEntity, String.class); - - TestMgr.check("204", responseEntity.getStatusCodeValue()); + TestMgr.check("204", responseEntity.getStatusCode().value()); TreeSet sortedSet = new TreeSet<>(responseEntity.getHeaders().get("Access-Control-Allow-Methods")); TestMgr.check("[DELETE,POST,GET,PUT]", sortedSet); sortedSet = new TreeSet<>(responseEntity.getHeaders().get("Access-Control-Allow-Headers")); TestMgr.check("[abc,def]", sortedSet); - TestMgr.check("*", responseEntity.getHeaders().getFirst("Access-Control-Allow-Origin")); + TestMgr.check("http://test.domain:8080", + responseEntity.getHeaders().getFirst("Access-Control-Allow-Origin")); + + // second domain + requestHeaders = new LinkedMultiValueMap<>(); + requestHeaders.put("Origin", Collections.singletonList("http://test.domain:9090")); + requestHeaders.put("Access-Control-Request-Method", Collections.singletonList("PUT")); + requestEntity = new HttpEntity<>(requestHeaders); + responseEntity = springRestTemplate + .exchange("http://127.0.0.1:8080/helloworld/hello", HttpMethod.OPTIONS, requestEntity, + String.class); + TestMgr.check("204", responseEntity.getStatusCode().value()); + sortedSet = new TreeSet<>(responseEntity.getHeaders().get("Access-Control-Allow-Methods")); + TestMgr.check("[DELETE,POST,GET,PUT]", sortedSet); + sortedSet = new TreeSet<>(responseEntity.getHeaders().get("Access-Control-Allow-Headers")); + TestMgr.check("[abc,def]", sortedSet); + TestMgr.check("http://test.domain:9090", + responseEntity.getHeaders().getFirst("Access-Control-Allow-Origin")); + } + + private static void testCorsHandlerGet() { + // allowed origin + RestOperations springRestTemplate = new RestTemplate(); + MultiValueMap requestHeaders = new LinkedMultiValueMap<>(); + requestHeaders.put("Origin", Collections.singletonList("http://test.domain:8080")); + HttpEntity requestEntity = new HttpEntity<>(requestHeaders); + ResponseEntity responseEntity = springRestTemplate + .exchange("http://127.0.0.1:8080/helloworld/hello", HttpMethod.GET, requestEntity, + String.class); + + TestMgr.check("200", responseEntity.getStatusCode().value()); + TestMgr.check("\"hello world\"", responseEntity.getBody()); + + // allowed origin + requestHeaders = new LinkedMultiValueMap<>(); + requestHeaders.put("Origin", Collections.singletonList("http://test.domain:9090")); + requestEntity = new HttpEntity<>(requestHeaders); + responseEntity = springRestTemplate + .exchange("http://127.0.0.1:8080/helloworld/hello", HttpMethod.GET, requestEntity, + String.class); + + TestMgr.check("200", responseEntity.getStatusCode().value()); + TestMgr.check("\"hello world\"", responseEntity.getBody()); + + // not allowed origin + try { + requestHeaders = new LinkedMultiValueMap<>(); + requestHeaders.put("Origin", Collections.singletonList("http://test.domain:7070")); + requestEntity = new HttpEntity<>(requestHeaders); + springRestTemplate + .exchange("http://127.0.0.1:8080/helloworld/hello", HttpMethod.GET, requestEntity, + String.class); + TestMgr.fail("must throw"); + } catch (HttpServerErrorException e) { + TestMgr.check(500, e.getStatusCode().value()); + TestMgr.check(true, e.getMessage().contains("500 CORS Rejected")); + } } } diff --git a/demo/demo-crossapp/crossapp-server/src/main/resources/microservice.yaml b/demo/demo-crossapp/crossapp-server/src/main/resources/microservice.yaml index 3f263b861bd..36fd8c07b8e 100644 --- a/demo/demo-crossapp/crossapp-server/src/main/resources/microservice.yaml +++ b/demo/demo-crossapp/crossapp-server/src/main/resources/microservice.yaml @@ -33,7 +33,7 @@ servicecomb: address: 0.0.0.0:8080 cors: enabled: true - origin: "*" + origin: "http://test.domain:8080,http://test.domain:9090" allowedHeader: abc,def allowedMethod: GET,PUT,POST,DELETE exposedHeader: abc,def diff --git a/demo/demo-cse-v1/consumer/src/main/resources/application.yml b/demo/demo-cse-v1/consumer/src/main/resources/application.yml index acc9b81f715..2b50e1a670c 100644 --- a/demo/demo-cse-v1/consumer/src/main/resources/application.yml +++ b/demo/demo-cse-v1/consumer/src/main/resources/application.yml @@ -59,6 +59,71 @@ servicecomb: - weight: 100 tags: version: 0.0.1 + - precedence: 3 + match: + headers: + canary: + exact: fallback + route: + - weight: 100 + tags: + version: 0.0.3 + fallback: + - weight: 20 + tags: + version: 0.0.1 + - weight: 80 + tags: + version: 0.0.2 + - precedence: 4 + emptyProtection: false + match: + headers: + canary: + exact: emptyProtectionClose100 + route: + - weight: 100 + tags: + version: 0.0.3 + - precedence: 5 + emptyProtection: false + match: + headers: + canary: + exact: emptyProtectionCloseLess100 + route: + - weight: 50 + tags: + version: 0.0.3 + - precedence: 6 + emptyProtection: false + match: + headers: + canary: + exact: emptyProtectionCloseFallback + route: + - weight: 100 + tags: + version: 0.0.3 + fallback: + - weight: 100 + tags: + version: 0.0.1 + - precedence: 7 + emptyProtection: false + match: + headers: + canary: + exact: emptyProtectionClose100-2 + route: + - weight: 50 + tags: + version: 0.0.1 + - weight: 50 + tags: + version: 0.0.3 + + router: type: router header: canary diff --git a/demo/demo-cse-v1/test-client/src/main/java/org/apache/servicecomb/samples/HelloWorldIT.java b/demo/demo-cse-v1/test-client/src/main/java/org/apache/servicecomb/samples/HelloWorldIT.java index 124df7712ab..e0f4d3d6610 100644 --- a/demo/demo-cse-v1/test-client/src/main/java/org/apache/servicecomb/samples/HelloWorldIT.java +++ b/demo/demo-cse-v1/test-client/src/main/java/org/apache/servicecomb/samples/HelloWorldIT.java @@ -33,8 +33,14 @@ public class HelloWorldIT implements CategorizedTestCase { @Override public void testRestTransport() throws Exception { + testHelloWorldFallback(); + testHelloWorldNoHeader(); testHelloWorld(); testHelloWorldCanary(); + testHelloWorldEmptyProtectionCloseWeight100(); + testHelloWorldeEptyProtectionCloseWeightLess100(); + testHelloWorldEmptyProtectionCloseFallback(); + testHelloWorldEmptyProtectionCloseWeight100Two(); } private void testHelloWorld() { @@ -71,4 +77,127 @@ private void testHelloWorldCanary() { double ratio = oldCount / (float) (oldCount + newCount); TestMgr.check(ratio > 0.1 && ratio < 0.3, true); } + + private void testHelloWorldFallback() { + int oldCount = 0; + int newCount = 0; + + for (int i = 0; i < 20; i++) { + MultiValueMap headers = new HttpHeaders(); + headers.add("canary", "fallback"); + HttpEntity entity = new HttpEntity<>(headers); + String result = template + .exchange(Config.GATEWAY_URL + "/sayHelloCanary?name=World", HttpMethod.GET, entity, String.class).getBody(); + if (result.equals("\"Hello Canary World\"")) { + oldCount++; + } else if (result.equals("\"Hello Canary in canary World\"")) { + newCount++; + } else { + TestMgr.fail("not expected result testHelloWorldCanary"); + return; + } + } + + double ratio = oldCount / (float) (oldCount + newCount); + TestMgr.check(ratio > 0.1 && ratio < 0.3, true); + } + + private void testHelloWorldNoHeader() { + int oldCount = 0; + int newCount = 0; + + for (int i = 0; i < 20; i++) { + String result = template + .getForObject(Config.GATEWAY_URL + "/sayHelloCanary?name=World", String.class); + if (result.equals("\"Hello Canary World\"")) { + oldCount++; + } else if (result.equals("\"Hello Canary in canary World\"")) { + newCount++; + } else { + TestMgr.fail("not expected result testHelloWorldCanary"); + return; + } + } + + double ratio = oldCount / (float) (oldCount + newCount); + TestMgr.check(ratio == 0.5, true); + } + + private void testHelloWorldEmptyProtectionCloseWeight100() { + int failCount = 0; + + for (int i = 0; i < 20; i++) { + MultiValueMap headers = new HttpHeaders(); + headers.add("canary", "emptyProtectionClose100"); + HttpEntity entity = new HttpEntity<>(headers); + try { + template.exchange(Config.GATEWAY_URL + "/sayHelloCanary?name=World", HttpMethod.GET, + entity, String.class); + } catch (Exception e) { + failCount++; + } + } + + TestMgr.check(failCount == 20, true); + } + + private void testHelloWorldeEptyProtectionCloseWeightLess100() { + int failCount = 0; + int succCount = 0; + + for (int i = 0; i < 20; i++) { + MultiValueMap headers = new HttpHeaders(); + headers.add("canary", "emptyProtectionCloseLess100"); + HttpEntity entity = new HttpEntity<>(headers); + try { + template.exchange(Config.GATEWAY_URL + "/sayHelloCanary?name=World", HttpMethod.GET, + entity, String.class); + succCount++; + } catch (Exception e) { + failCount++; + } + } + + TestMgr.check(succCount == 20, true); + } + + private void testHelloWorldEmptyProtectionCloseFallback() { + int failCount = 0; + int succCount = 0; + + for (int i = 0; i < 20; i++) { + MultiValueMap headers = new HttpHeaders(); + headers.add("canary", "emptyProtectionCloseFallback"); + HttpEntity entity = new HttpEntity<>(headers); + try { + template.exchange(Config.GATEWAY_URL + "/sayHelloCanary?name=World", HttpMethod.GET, + entity, String.class); + succCount++; + } catch (Exception e) { + failCount++; + } + } + + TestMgr.check(succCount == 20, true); + } + + private void testHelloWorldEmptyProtectionCloseWeight100Two() { + int failCount = 0; + int succCount = 0; + + for (int i = 0; i < 20; i++) { + MultiValueMap headers = new HttpHeaders(); + headers.add("canary", "emptyProtectionClose100-2"); + HttpEntity entity = new HttpEntity<>(headers); + try { + template.exchange(Config.GATEWAY_URL + "/sayHelloCanary?name=World", HttpMethod.GET, + entity, String.class); + succCount++; + } catch (Exception e) { + failCount++; + } + } + + TestMgr.check(failCount == succCount, true); + } } diff --git a/demo/demo-cse-v2/README.md b/demo/demo-cse-v2/README.md index 7d6b20b4791..001b59da912 100644 --- a/demo/demo-cse-v2/README.md +++ b/demo/demo-cse-v2/README.md @@ -59,6 +59,7 @@ cse: test: priority: v4 ``` + * 应用级配置:consumerApp.yaml,应用选择demo-java-chassis-cse-v2。类型为 yaml。 ```yaml cse: @@ -66,6 +67,7 @@ cse: test: priority1: v1 ``` + * 服务级配置:consumerSer.yaml,微服务性选择consumer。类型为 yaml。 ```yaml cse: @@ -73,13 +75,6 @@ cse: test: priority1: v2 ``` -* 版本级配置:consumerIns.yaml,labels: app=demo-java-chassis-cse-v2,environment=,service=consumer,version = 0.0.1。类型为 yaml。 -```yaml -cse: - v2: - test: - priority1: v3 -``` * 应用级配置: cse.v2.test.bar: bar 。 类型为 text。 diff --git a/demo/demo-cse-v2/consumer/src/main/resources/application.yml b/demo/demo-cse-v2/consumer/src/main/resources/application.yml index 72a6dafadb5..38113f6c913 100644 --- a/demo/demo-cse-v2/consumer/src/main/resources/application.yml +++ b/demo/demo-cse-v2/consumer/src/main/resources/application.yml @@ -25,11 +25,11 @@ servicecomb: name: consumer version: 0.0.1 registry: - address: ${CSE_V2_SC} + address: ${PAAS_CSE_SC_ENDPOINT:http://127.0.0.1:30100} instance: watch: false kie: - serverUri: ${CSE_V2_CC} + serverUri: ${PAAS_CSE_CC_ENDPOINT:http://127.0.0.1:30110} customLabel: public customLabelValue: default diff --git a/demo/demo-cse-v2/pom.xml b/demo/demo-cse-v2/pom.xml index 0222496bc09..cec5a5057e1 100644 --- a/demo/demo-cse-v2/pom.xml +++ b/demo/demo-cse-v2/pom.xml @@ -41,6 +41,10 @@ org.apache.servicecomb servicestage-environment + + org.apache.logging.log4j + log4j-slf4j-impl + diff --git a/demo/demo-cse-v2/provider/src/main/resources/application.yml b/demo/demo-cse-v2/provider/src/main/resources/application.yml index 991bf6d7de0..a3a6f874f72 100644 --- a/demo/demo-cse-v2/provider/src/main/resources/application.yml +++ b/demo/demo-cse-v2/provider/src/main/resources/application.yml @@ -28,11 +28,11 @@ servicecomb: name: provider version: 0.0.1 registry: - address: ${CSE_V2_SC} + address: ${PAAS_CSE_SC_ENDPOINT:http://127.0.0.1:30100} instance: watch: false kie: - serverUri: ${CSE_V2_CC} + serverUri: ${PAAS_CSE_CC_ENDPOINT:http://127.0.0.1:30110} customLabel: public rest: diff --git a/demo/demo-cse-v2/test-client/src/main/java/org/apache/servicecomb/samples/ConsumerConfigIT.java b/demo/demo-cse-v2/test-client/src/main/java/org/apache/servicecomb/samples/ConsumerConfigIT.java index 8d783d626e2..f25980d47a4 100644 --- a/demo/demo-cse-v2/test-client/src/main/java/org/apache/servicecomb/samples/ConsumerConfigIT.java +++ b/demo/demo-cse-v2/test-client/src/main/java/org/apache/servicecomb/samples/ConsumerConfigIT.java @@ -38,13 +38,13 @@ private void testConfig() { result = template.getForObject(Config.GATEWAY_URL + "/config?key=cse.v2.test.bar", String.class); TestMgr.check("\"bar\"", result); result = template.getForObject(Config.GATEWAY_URL + "/config?key=cse.v2.test.priority", String.class); - TestMgr.check("\"v3\"", result); + TestMgr.check("\"v1\"", result); result = template.getForObject(Config.GATEWAY_URL + "/config?key=cse.v2.test.common", String.class); TestMgr.check("\"common\"", result); result = template.getForObject(Config.GATEWAY_URL + "/config?key=cse.v2.test.extra", String.class); TestMgr.check("\"common\"", result); result = template.getForObject(Config.GATEWAY_URL + "/config?key=cse.v2.test.priority1", String.class); - TestMgr.check("\"v3\"", result); + TestMgr.check("\"v2\"", result); } private void testFooBar() { @@ -53,7 +53,7 @@ private void testFooBar() { result = template.getForObject(Config.GATEWAY_URL + "/foo", String.class); TestMgr.check("\"foo\"", result); result = template.getForObject(Config.GATEWAY_URL + "/priority", String.class); - TestMgr.check("\"v3\"", result); + TestMgr.check("\"v1\"", result); result = template.getForObject(Config.GATEWAY_URL + "/common", String.class); TestMgr.check("\"common\"", result); result = template.getForObject(Config.GATEWAY_URL + "/extra", String.class); diff --git a/demo/demo-edge/consumer/src/main/resources/microservice.yaml b/demo/demo-edge/consumer/src/main/resources/microservice.yaml index 660dfbf2849..53c1e23475a 100644 --- a/demo/demo-edge/consumer/src/main/resources/microservice.yaml +++ b/demo/demo-edge/consumer/src/main/resources/microservice.yaml @@ -25,6 +25,10 @@ servicecomb: address: http://127.0.0.1:30100 rest: client.http2.useAlpnEnabled: false + loadbalance: + filter: + operation: + enabled: true handler: chain: Consumer: diff --git a/demo/demo-edge/edge-service/src/main/resources/microservice.yaml b/demo/demo-edge/edge-service/src/main/resources/microservice.yaml index 4b2e12b4a7c..cb336471948 100644 --- a/demo/demo-edge/edge-service/src/main/resources/microservice.yaml +++ b/demo/demo-edge/edge-service/src/main/resources/microservice.yaml @@ -36,6 +36,10 @@ servicecomb: client: verticle-count: 1 http2.useAlpnEnabled: false + loadbalance: + filter: + operation: + enabled: true handler: chain: Consumer: diff --git a/demo/demo-filter/filter-client/src/main/java/org/apache/servicecomb/demo/filter/FilterClient.java b/demo/demo-filter/filter-client/src/main/java/org/apache/servicecomb/demo/filter/FilterClient.java index 7c2368ff243..f2bcc3d5b80 100644 --- a/demo/demo-filter/filter-client/src/main/java/org/apache/servicecomb/demo/filter/FilterClient.java +++ b/demo/demo-filter/filter-client/src/main/java/org/apache/servicecomb/demo/filter/FilterClient.java @@ -43,6 +43,6 @@ public static void main(String[] args) throws Exception { } public static void run() throws Exception { - CategorizedTestCaseRunner.runCategorizedTestCase("filterServer"); + CategorizedTestCaseRunner.runCategorizedTestCase("com.servicecomb.filterServer"); } } diff --git a/demo/demo-filter/filter-client/src/main/java/org/apache/servicecomb/demo/filter/retry/TestRetrySchema.java b/demo/demo-filter/filter-client/src/main/java/org/apache/servicecomb/demo/filter/retry/TestRetrySchema.java index 0ee6a7fc34e..93b990aa0bc 100644 --- a/demo/demo-filter/filter-client/src/main/java/org/apache/servicecomb/demo/filter/retry/TestRetrySchema.java +++ b/demo/demo-filter/filter-client/src/main/java/org/apache/servicecomb/demo/filter/retry/TestRetrySchema.java @@ -33,12 +33,12 @@ interface RetrySchemaInf { CompletableFuture successWhenRetryAsync(); } - @RpcReference(microserviceName = "filterServer", schemaId = "RetrySchema") + @RpcReference(microserviceName = "com.servicecomb.filterServer", schemaId = "RetrySchema") private RetrySchemaInf retrySchemaInf; RestTemplate restTemplate = RestTemplateBuilder.create(); - private static final String SERVER = "servicecomb://filterServer"; + private static final String SERVER = "servicecomb://com.servicecomb.filterServer"; @Override public void testAllTransport() throws Exception { diff --git a/demo/demo-filter/filter-client/src/main/resources/microservice.yaml b/demo/demo-filter/filter-client/src/main/resources/microservice.yaml index a5b83259d3a..c697bb7fedd 100644 --- a/demo/demo-filter/filter-client/src/main/resources/microservice.yaml +++ b/demo/demo-filter/filter-client/src/main/resources/microservice.yaml @@ -18,7 +18,8 @@ servicecomb: service: application: filtertest - name: filterClient + # test service name with dot + name: com.servicecomb.filterClient version: 0.0.1 registry: diff --git a/demo/demo-filter/filter-server/src/main/resources/microservice.yaml b/demo/demo-filter/filter-server/src/main/resources/microservice.yaml index b33c96230b3..a6d1c641372 100644 --- a/demo/demo-filter/filter-server/src/main/resources/microservice.yaml +++ b/demo/demo-filter/filter-server/src/main/resources/microservice.yaml @@ -18,7 +18,8 @@ servicecomb: service: application: filtertest - name: filterServer + # test service name with dot + name: com.servicecomb.filterServer version: 0.0.1 registry: diff --git a/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/MultiErrorCodeServiceClient.java b/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/MultiErrorCodeServiceClient.java index ad1d21cc04e..4b2d0d2c872 100644 --- a/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/MultiErrorCodeServiceClient.java +++ b/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/MultiErrorCodeServiceClient.java @@ -102,9 +102,8 @@ private static void testErrorCodeWrongType() { .postForEntity(serverDirectURL + "/MultiErrorCodeService/errorCode", entity, MultiResponse200.class); } catch (HttpClientErrorException e) { TestMgr.check(e.getRawStatusCode(), 400); - TestMgr.check(e.getMessage(), - "400 Bad Request: \"{\"message\":\"Parameter is not valid for operation [jaxrs.MultiErrorCodeService.errorCode]." + - " Parameter is [request]. Processor is [body].\"}\""); + TestMgr.check(e.getMessage().contains("Parameter is not valid for operation " + + "[jaxrs.MultiErrorCodeService.errorCode]. Parameter is [request]. Processor is [body]."), true); } entity = new HttpEntity<>(null, headers); diff --git a/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/TestFormRequestSchema.java b/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/TestFormRequestSchema.java index f0e9938c120..776bd277f80 100644 --- a/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/TestFormRequestSchema.java +++ b/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/TestFormRequestSchema.java @@ -44,6 +44,7 @@ public void testRestTransport() throws Exception { testFormRequestFail(); // testFormRequestFail会关闭连接,防止下个测试用例失败,睡眠2s Thread.sleep(2000); + testFormRequestBufferSize(); testFormRequestSuccess(); } @@ -86,4 +87,28 @@ private void testFormRequestFail() throws Exception { TestMgr.check(e.getMessage().contains("Internal Server Error"), true); } } + + private void testFormRequestBufferSize() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + MultiValueMap formData = new LinkedMultiValueMap<>(); + StringBuilder stringBuffer = new StringBuilder(); + for (int i = 0; i < 1020; i++) { + stringBuffer.append("a"); + } + formData.add("F0123456789001234567890012345678900123456789001234567890" + + "0123456789001234567890012345678900123456789001234567890", String.valueOf(stringBuffer) + ); + HttpEntity> requestEntity = new HttpEntity<>(formData, headers); + try { + // we can not test a situation for form exceed max buffer size, because the netty buffer is very + // big and the trunc can always be decoded and cached buffer size is always 0. + ResponseEntity responseEntity = + restTemplate.postForEntity("cse://jaxrs/form/formLongName", requestEntity, String.class); + TestMgr.check(responseEntity.getBody(), "formRequest success : 1020"); + } catch (Throwable e) { + LOGGER.error("testFormRequestBufferSize-->", e); + TestMgr.failed("", e); + } + } } diff --git a/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/TestQueryParamWithListSchema.java b/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/TestQueryParamWithListSchema.java index 40c9b8d0ebf..bc061d33af1 100644 --- a/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/TestQueryParamWithListSchema.java +++ b/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/TestQueryParamWithListSchema.java @@ -66,12 +66,19 @@ private void testCSVRest() { } private void testSSV() { - TestMgr.check("2:[1, 2]", + TestMgr.check("1:[1%202]", restTemplate.getForObject("cse://jaxrs/queryList/queryListSSV?queryList=1%202", String.class)); - TestMgr.check("2:[, ]", + TestMgr.check("1:[%20]", restTemplate.getForObject("cse://jaxrs/queryList/queryListSSV?queryList=%20", String.class)); TestMgr.check("1:[]", restTemplate.getForObject("cse://jaxrs/queryList/queryListSSV?queryList=", String.class)); + + TestMgr.check("2:[1, 2]", + restTemplate.getForObject("cse://jaxrs/queryList/queryListSSV?queryList={1}", String.class, "1 2")); + TestMgr.check("2:[, ]", + restTemplate.getForObject("cse://jaxrs/queryList/queryListSSV?queryList={1}", String.class, " ")); + TestMgr.check("1:[]", + restTemplate.getForObject("cse://jaxrs/queryList/queryListSSV?queryList=", String.class)); } private void testTSVHighway() { diff --git a/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/validation/ValidationServiceClient.java b/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/validation/ValidationServiceClient.java index 803a1dfe450..415731840b3 100644 --- a/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/validation/ValidationServiceClient.java +++ b/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/validation/ValidationServiceClient.java @@ -26,6 +26,7 @@ import org.apache.servicecomb.demo.validator.Teacher; import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; import org.apache.servicecomb.provider.springmvc.reference.RestTemplateBuilder; +import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData; import org.apache.servicecomb.swagger.invocation.exception.InvocationException; import org.springframework.web.client.RestTemplate; @@ -89,7 +90,7 @@ private static void testValidation() { } catch (InvocationException e) { TestMgr.check(400, e.getStatus().getStatusCode()); TestMgr.check(Status.BAD_REQUEST, e.getReasonPhrase()); - TestMgr.check(e.getErrorData().toString().contains("Parameter is required."), true); + TestMgr.check(((CommonExceptionData) e.getErrorData()).getMessage().contains("Parameter is not valid"), true); } Teacher teacher = new Teacher(); diff --git a/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/FormRequestSchema.java b/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/FormRequestSchema.java index 7ceaf74be04..2a62049f1e8 100644 --- a/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/FormRequestSchema.java +++ b/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/FormRequestSchema.java @@ -38,4 +38,11 @@ public String formRequestSuccess(@FormParam("formData") String formData) throws return "formRequest success : " + formData.length(); } + @Path("/formLongName") + @POST + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public String formLongName(@FormParam("F0123456789001234567890012345678900123456789001234567890" + + "0123456789001234567890012345678900123456789001234567890") String formData) throws Exception { + return "formRequest success : " + formData.length(); + } } diff --git a/demo/demo-jaxrs/jaxrs-server/src/main/resources/microservice.yaml b/demo/demo-jaxrs/jaxrs-server/src/main/resources/microservice.yaml index f4f585ecc05..c4b4f4afe56 100644 --- a/demo/demo-jaxrs/jaxrs-server/src/main/resources/microservice.yaml +++ b/demo/demo-jaxrs/jaxrs-server/src/main/resources/microservice.yaml @@ -25,7 +25,8 @@ servicecomb: rest: address: 0.0.0.0:8080 server: - maxFormAttributeSize: 1024 + maxFormAttributeSize: 1024 # for testing, and bigger than netty buffer allocator + maxFormBufferedBytes: 100 highway: address: 0.0.0.0:7070 handler: diff --git a/demo/demo-local-registry/demo-local-registry-client/src/main/java/org/apache/servicecomb/demo/localRegistryClient/Application.java b/demo/demo-local-registry/demo-local-registry-client/src/main/java/org/apache/servicecomb/demo/localRegistryClient/Application.java index c6ed407b0b0..5c937b674a8 100644 --- a/demo/demo-local-registry/demo-local-registry-client/src/main/java/org/apache/servicecomb/demo/localRegistryClient/Application.java +++ b/demo/demo-local-registry/demo-local-registry-client/src/main/java/org/apache/servicecomb/demo/localRegistryClient/Application.java @@ -45,6 +45,9 @@ private static void registerSchema() { RegistrationManager.INSTANCE.getSwaggerLoader().registerSwagger("demo-local-registry", "demo-local-registry-server", "CodeFirstEndpoint", CodeFirstService.class); + RegistrationManager.INSTANCE.getSwaggerLoader().registerSwagger("demo-local-registry-cross", + "demo-local-registry-server-cross", + "CodeFirstEndpoint", CodeFirstService.class); RegistrationManager.INSTANCE.getSwaggerLoader().registerSwagger("demo-local-registry", "demo-local-registry-server-bean", "CodeFirstEndpoint", CodeFirstService.class); diff --git a/demo/demo-local-registry/demo-local-registry-client/src/main/java/org/apache/servicecomb/demo/localRegistryClient/LocalRegistryServerTest.java b/demo/demo-local-registry/demo-local-registry-client/src/main/java/org/apache/servicecomb/demo/localRegistryClient/LocalRegistryServerTest.java index c50508cc0b7..f914b53ad06 100644 --- a/demo/demo-local-registry/demo-local-registry-client/src/main/java/org/apache/servicecomb/demo/localRegistryClient/LocalRegistryServerTest.java +++ b/demo/demo-local-registry/demo-local-registry-client/src/main/java/org/apache/servicecomb/demo/localRegistryClient/LocalRegistryServerTest.java @@ -33,6 +33,10 @@ public class LocalRegistryServerTest implements CategorizedTestCase { @RpcReference(microserviceName = "demo-local-registry-server", schemaId = "CodeFirstEndpoint") private CodeFirstService codeFirstService; + @RpcReference(microserviceName = "demo-local-registry-cross:demo-local-registry-server-cross", + schemaId = "CodeFirstEndpoint") + private CodeFirstService codeFirstServiceCross; + @RpcReference(microserviceName = "demo-local-registry-server-bean", schemaId = "CodeFirstEndpoint") private CodeFirstService codeFirstServiceBean; @@ -62,6 +66,7 @@ private void testGetAllMicroservice() { private void testCodeFirstGetName() { TestMgr.check("2", codeFirstService.getName("2")); + TestMgr.check("2", codeFirstServiceCross.getName("2")); TestMgr.check("2", codeFirstServiceBean.getName("2")); TestMgr.check("2", codeFirstServiceBean2.getName("2")); } @@ -71,6 +76,10 @@ private void testServerGetName() { TestMgr.check("2", template .getForObject("cse://demo-local-registry-server/register/url/prefix/getName?name=2", String.class)); + TestMgr.check("2", template + .getForObject( + "cse://demo-local-registry-cross:demo-local-registry-server-cross/register/url/prefix/getName?name=2", + String.class)); TestMgr.check("2", template .getForObject("cse://demo-local-registry-server-bean/register/url/prefix/getName?name=2", String.class)); diff --git a/demo/demo-local-registry/demo-local-registry-client/src/main/resources/applications/demo-local-registry-cross/demo-local-registry-server-cross/ServerEndpoint.yaml b/demo/demo-local-registry/demo-local-registry-client/src/main/resources/applications/demo-local-registry-cross/demo-local-registry-server-cross/ServerEndpoint.yaml new file mode 100644 index 00000000000..0f7fdc77889 --- /dev/null +++ b/demo/demo-local-registry/demo-local-registry-client/src/main/resources/applications/demo-local-registry-cross/demo-local-registry-server-cross/ServerEndpoint.yaml @@ -0,0 +1,42 @@ +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You 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 +## +## http://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. +## --------------------------------------------------------------------------- + +swagger: "2.0" +info: + version: "1.0.0" + title: "swagger definition for org.apache.servicecomb.demo.registry.ServerEndpoint" +basePath: "/register/url/prefix" +schemes: +- "http" +consumes: +- "application/json" +produces: +- "application/json" +paths: + /getName: + get: + operationId: "getName" + parameters: + - name: "name" + in: "query" + required: true + type: "string" + responses: + "200": + description: "response of 200" + schema: + type: "string" \ No newline at end of file diff --git a/demo/demo-local-registry/demo-local-registry-client/src/main/resources/microservices/demo-local-registry-server-bean/ServerEndpoint.yaml b/demo/demo-local-registry/demo-local-registry-client/src/main/resources/microservices/demo-local-registry-server-bean/ServerEndpoint.yaml index 78c9d1bca63..0f7fdc77889 100644 --- a/demo/demo-local-registry/demo-local-registry-client/src/main/resources/microservices/demo-local-registry-server-bean/ServerEndpoint.yaml +++ b/demo/demo-local-registry/demo-local-registry-client/src/main/resources/microservices/demo-local-registry-server-bean/ServerEndpoint.yaml @@ -19,7 +19,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.demo.registry.ServerEndpoint" - x-java-interface: "gen.swagger.ServerEndpointIntf" basePath: "/register/url/prefix" schemes: - "http" diff --git a/demo/demo-local-registry/demo-local-registry-client/src/main/resources/microservices/demo-local-registry-server/ServerEndpoint.yaml b/demo/demo-local-registry/demo-local-registry-client/src/main/resources/microservices/demo-local-registry-server/ServerEndpoint.yaml index 78c9d1bca63..0f7fdc77889 100644 --- a/demo/demo-local-registry/demo-local-registry-client/src/main/resources/microservices/demo-local-registry-server/ServerEndpoint.yaml +++ b/demo/demo-local-registry/demo-local-registry-client/src/main/resources/microservices/demo-local-registry-server/ServerEndpoint.yaml @@ -19,7 +19,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.demo.registry.ServerEndpoint" - x-java-interface: "gen.swagger.ServerEndpointIntf" basePath: "/register/url/prefix" schemes: - "http" diff --git a/demo/demo-local-registry/demo-local-registry-client/src/main/resources/registry.yaml b/demo/demo-local-registry/demo-local-registry-client/src/main/resources/registry.yaml index 2ad3c23269f..f0e611e56cb 100644 --- a/demo/demo-local-registry/demo-local-registry-client/src/main/resources/registry.yaml +++ b/demo/demo-local-registry/demo-local-registry-client/src/main/resources/registry.yaml @@ -16,9 +16,19 @@ ## --------------------------------------------------------------------------- demo-local-registry-server: - - id: "001" + - id: "service-001" version: "1.0" appid: demo-local-registry + schemaIds: + - ServerEndpoint + - CodeFirstEndpoint + instances: + - endpoints: + - rest://localhost:8080 +demo-local-registry-server-cross: + - id: "service-002" + version: "1.0" + appid: demo-local-registry-cross schemaIds: - ServerEndpoint - CodeFirstEndpoint diff --git a/demo/demo-multi-registries/demo-multi-registries-client/src/main/resources/microservices/thirdParty-service-center/ServiceCenterEndpoint.yaml b/demo/demo-multi-registries/demo-multi-registries-client/src/main/resources/microservices/thirdParty-service-center/ServiceCenterEndpoint.yaml index 8ba87fadd56..7526a896046 100644 --- a/demo/demo-multi-registries/demo-multi-registries-client/src/main/resources/microservices/thirdParty-service-center/ServiceCenterEndpoint.yaml +++ b/demo/demo-multi-registries/demo-multi-registries-client/src/main/resources/microservices/thirdParty-service-center/ServiceCenterEndpoint.yaml @@ -19,7 +19,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.demo.registry.ServiceCenterEndpoint" - x-java-interface: "gen.swagger.ServiceCenterEndpointIntf" basePath: "/v4/default/registry" schemes: - "http" diff --git a/demo/demo-multi-service-center/demo-multi-service-center-client/src/main/java/org/apache/servicecomb/demo/multiServiceCenterClient/RegistryClientTest.java b/demo/demo-multi-service-center/demo-multi-service-center-client/src/main/java/org/apache/servicecomb/demo/multiServiceCenterClient/RegistryClientTest.java index 85f041034cf..bfcfc4bbf9a 100644 --- a/demo/demo-multi-service-center/demo-multi-service-center-client/src/main/java/org/apache/servicecomb/demo/multiServiceCenterClient/RegistryClientTest.java +++ b/demo/demo-multi-service-center/demo-multi-service-center-client/src/main/java/org/apache/servicecomb/demo/multiServiceCenterClient/RegistryClientTest.java @@ -42,6 +42,8 @@ import org.apache.servicecomb.service.center.client.model.MicroserviceInstance; import org.apache.servicecomb.service.center.client.model.SchemaInfo; import org.apache.servicecomb.service.center.client.model.ServiceCenterConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import com.google.common.base.Charsets; @@ -58,14 +60,21 @@ public class RegistryClientTest implements CategorizedTestCase { // auto test only tests 'hasRegistered=false', can run this client many times to test 'hasRegistered=true' private boolean hasRegistered = true; + private final Environment environment; + + @Autowired + public RegistryClientTest(Environment environment) { + this.environment = environment; + } + @Override public void testRestTransport() throws Exception { - ServiceCenterAddressManager addressManager = new ServiceCenterAddressManager("default", Arrays.asList("http://127.0.0.1:30100"), - new EventBus()); + ServiceCenterAddressManager addressManager = new ServiceCenterAddressManager("default", + Arrays.asList("http://127.0.0.1:30100"), "", "", new EventBus()); SSLProperties sslProperties = new SSLProperties(); sslProperties.setEnabled(false); ServiceCenterClient serviceCenterClient = new ServiceCenterClient(addressManager, sslProperties, - new DefaultRequestAuthHeaderProvider(), "default", null); + new DefaultRequestAuthHeaderProvider(), "default", null, environment); EventBus eventBus = new SimpleEventBus(); ServiceCenterConfiguration serviceCenterConfiguration = new ServiceCenterConfiguration(); ServiceCenterRegistration serviceCenterRegistration = new ServiceCenterRegistration(serviceCenterClient, diff --git a/demo/demo-pojo/pojo-client/src/main/java/org/apache/servicecomb/demo/pojo/client/ClientInterfaceForServerTest.java b/demo/demo-pojo/pojo-client/src/main/java/org/apache/servicecomb/demo/pojo/client/ClientInterfaceForServerTest.java new file mode 100644 index 00000000000..34399d081d1 --- /dev/null +++ b/demo/demo-pojo/pojo-client/src/main/java/org/apache/servicecomb/demo/pojo/client/ClientInterfaceForServerTest.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.pojo.client; + +import org.apache.servicecomb.demo.server.TestRequest; +import org.apache.servicecomb.demo.server.User; + +public interface ClientInterfaceForServerTest { + User splitParam(int nameNotIndex, User user); + + User wrapParam(TestRequest nameNotRequest); +} diff --git a/demo/demo-pojo/pojo-client/src/main/java/org/apache/servicecomb/demo/pojo/client/TestClientInterfaceForServerTest.java b/demo/demo-pojo/pojo-client/src/main/java/org/apache/servicecomb/demo/pojo/client/TestClientInterfaceForServerTest.java new file mode 100644 index 00000000000..bf0bcd6b61f --- /dev/null +++ b/demo/demo-pojo/pojo-client/src/main/java/org/apache/servicecomb/demo/pojo/client/TestClientInterfaceForServerTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.pojo.client; + +import org.apache.servicecomb.demo.CategorizedTestCase; +import org.apache.servicecomb.demo.TestMgr; +import org.apache.servicecomb.demo.server.TestRequest; +import org.apache.servicecomb.demo.server.User; +import org.apache.servicecomb.provider.pojo.RpcReference; +import org.springframework.stereotype.Component; + +@Component +public class TestClientInterfaceForServerTest implements CategorizedTestCase { + @RpcReference(microserviceName = "pojo", schemaId = "server") + private ClientInterfaceForServerTest pojo; + + @Override + public void testAllTransport() throws Exception { + User user = new User(); + user.setName("name"); + user.setIndex(100); + User result = pojo.splitParam(100, user); + TestMgr.check("name, users count:0", result.getName()); + TestMgr.check(100, result.getIndex()); + + TestRequest request = new TestRequest(); + request.setIndex(200); + request.setUser(user); + result = pojo.wrapParam(request); + TestMgr.check("name, users count:0", result.getName()); + TestMgr.check(200, result.getIndex()); + } +} diff --git a/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/api/IHeaderParamWithListSchema.java b/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/api/IHeaderParamWithListSchema.java new file mode 100644 index 00000000000..0f0b5b5a967 --- /dev/null +++ b/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/api/IHeaderParamWithListSchema.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.api; + +import java.util.List; + +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.Path; + +import io.swagger.annotations.ApiParam; + +@Path("/headerList") +public interface IHeaderParamWithListSchema { + @Path("headerListDefault") + @GET + String headerListDefault( + @HeaderParam("headerList") List headerList); + + @Path("headerListCSV") + @GET + String headerListCSV( + @ApiParam(collectionFormat = "csv") @HeaderParam("headerList") + List headerList); + + @Path("headerListMULTI") + @GET + String headerListMULTI( + @ApiParam(collectionFormat = "multi") @HeaderParam("headerList") + List headerList); + + @Path("headerListSSV") + @GET + String headerListSSV( + @ApiParam(collectionFormat = "ssv") @HeaderParam("headerList") + List headerList); + + @Path("headerListPIPES") + @GET + String headerListPIPES( + @ApiParam(collectionFormat = "pipes") @HeaderParam("headerList") + List headerList); + + @Path("headerListTSV") + @GET + String headerListTSV( + @ApiParam(collectionFormat = "tsv") @HeaderParam("headerList") + List headerList); +} diff --git a/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/controller/PersonAlias.java b/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/controller/PersonAlias.java new file mode 100644 index 00000000000..a1766eb29c2 --- /dev/null +++ b/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/controller/PersonAlias.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.controller; + +public class PersonAlias { + private String name; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + @Override + public String toString() { + return name; + } +} diff --git a/demo/demo-schema/src/main/resources/microservices/springmvc/controller.yaml b/demo/demo-schema/src/main/resources/microservices/springmvc/controller.yaml index be2feb0bfca..d279bfec793 100644 --- a/demo/demo-schema/src/main/resources/microservices/springmvc/controller.yaml +++ b/demo/demo-schema/src/main/resources/microservices/springmvc/controller.yaml @@ -127,7 +127,14 @@ paths: description: check the handler is effective schema: type: string - + /testResponseModel: + get: + operationId: testResponseModel + responses: + "200": + description: testResponseModel + schema: + $ref: '#/definitions/Person' definitions: Person: type: object diff --git a/demo/demo-spring-boot-transport/demo-spring-boot-springmvc-client/src/main/java/org/apache/servicecomb/springboot/springmvc/client/UploadDownloadSchemaTest.java b/demo/demo-spring-boot-transport/demo-spring-boot-springmvc-client/src/main/java/org/apache/servicecomb/springboot/springmvc/client/UploadDownloadSchemaTest.java new file mode 100644 index 00000000000..60f61f1c55b --- /dev/null +++ b/demo/demo-spring-boot-transport/demo-spring-boot-springmvc-client/src/main/java/org/apache/servicecomb/springboot/springmvc/client/UploadDownloadSchemaTest.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.springboot.springmvc.client; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.io.FileUtils; +import org.apache.servicecomb.demo.CategorizedTestCase; +import org.apache.servicecomb.demo.TestMgr; +import org.apache.servicecomb.provider.springmvc.reference.RestTemplateBuilder; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestOperations; + +@Component +public class UploadDownloadSchemaTest implements CategorizedTestCase { + RestOperations restOperations = RestTemplateBuilder.create(); + + @Override + public void testRestTransport() throws Exception { + testEmptyFileUploadWork(); + testNonEmptyFileUploadWorkd(); + } + + private void testNonEmptyFileUploadWorkd() throws Exception { + String file2Content = " bonjour"; + File someFile = File.createTempFile("upload2", ".txt"); + FileUtils.writeStringToFile(someFile, file2Content, StandardCharsets.UTF_8, false); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + Map params = new HashMap<>(); + params.put("name", "test"); + params.put("file", someFile); + HttpEntity> entity = new HttpEntity<>(params, headers); + String url = "servicecomb://springmvc/up/down/upload"; + String result = restOperations.postForObject(url, entity, String.class); + TestMgr.check("test; bonjour", result); + } + + private void testEmptyFileUploadWork() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + Map params = new HashMap<>(); + params.put("name", "test"); + HttpEntity> entity = new HttpEntity<>(params, headers); + String url = "servicecomb://springmvc/up/down/upload"; + String result = restOperations.postForObject(url, entity, String.class); + TestMgr.check("test;", result); + } +} diff --git a/demo/demo-spring-boot-transport/demo-spring-boot-springmvc-server/src/main/java/org/apache/servicecomb/springboot/springmvc/server/UploadDownloadSchema.java b/demo/demo-spring-boot-transport/demo-spring-boot-springmvc-server/src/main/java/org/apache/servicecomb/springboot/springmvc/server/UploadDownloadSchema.java new file mode 100644 index 00000000000..4c4cd0df5e9 --- /dev/null +++ b/demo/demo-spring-boot-transport/demo-spring-boot-springmvc-server/src/main/java/org/apache/servicecomb/springboot/springmvc/server/UploadDownloadSchema.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.springboot.springmvc.server; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import javax.ws.rs.core.MediaType; + +import org.apache.commons.io.IOUtils; +import org.apache.servicecomb.provider.rest.common.RestSchema; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; + + +@RestSchema(schemaId = "UploadDownloadSchema") +@RequestMapping(path = "/up/down") +public class UploadDownloadSchema { + @PostMapping(path = "/upload", consumes = MediaType.MULTIPART_FORM_DATA) + public String fileUpload(@RequestPart MultipartFile file, @RequestPart(value = "name") String name) throws Exception { + StringBuilder result = new StringBuilder(); + result.append(name).append(";"); + if (file == null || file.isEmpty()) { + return result.toString(); + } + try (InputStream is = file.getInputStream()) { + result.append(IOUtils.toString(is, StandardCharsets.UTF_8)); + } + return result.toString(); + } +} diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/SpringmvcClient.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/SpringmvcClient.java index 200f0047cc4..ba487e52c8a 100644 --- a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/SpringmvcClient.java +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/SpringmvcClient.java @@ -75,6 +75,7 @@ public static void main(String[] args) throws Exception { LOGGER.error("-------------- test failed -------------"); } TestMgr.summary(); + LOGGER.info("-------------- last time updated checks(maybe more/less): 1222 -------------"); } private static void changeTransport(String microserviceName, String transport) { @@ -238,6 +239,14 @@ private static void testControllerRest(RestTemplate template, String microservic template.getForObject(prefix + "/controller/sayhi?name=world", String.class)); + TestMgr.check("hi world boot [world boot]", + template.getForObject(prefix + "/controller/sayhi?name=world boot", + String.class)); + + TestMgr.check("hi world boot [world boot]", + template.getForObject(prefix + "/controller/sayhi?name=world+boot", + String.class)); + TestMgr.check("hi world1 [world1]", template.getForObject(prefix + "/controller/sayhi?name={name}", String.class, diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/ICompatible1xTestSchema.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/ICompatible1xTestSchema.java new file mode 100644 index 00000000000..dc4cb52002f --- /dev/null +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/ICompatible1xTestSchema.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.client; + +import org.apache.servicecomb.swagger.invocation.context.InvocationContext; + +import io.swagger.annotations.ApiOperation; + +public interface ICompatible1xTestSchema { + String parameterName(int c, int d); + + @ApiOperation(nickname = "parameterName", value = "parameterName") + String parameterNamePartMatchLeft(int a, int d); + + @ApiOperation(nickname = "parameterName", value = "parameterName") + String parameterNamePartMatchRight(int c, int b); + + String parameterName(InvocationContext context, int c, int d); + + String parameterNameServerContext(int c, int d); + + String beanParameter(String notName, int notAge); +} diff --git a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/cpu/AbstractCpuUsage.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/ResponseOKData.java similarity index 51% rename from metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/cpu/AbstractCpuUsage.java rename to demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/ResponseOKData.java index 375a64875b0..df2bf9c5f42 100644 --- a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/cpu/AbstractCpuUsage.java +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/ResponseOKData.java @@ -14,52 +14,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.servicecomb.metrics.core.meter.os.cpu; +package org.apache.servicecomb.demo.springmvc.client; -import com.netflix.spectator.api.Id; +public class ResponseOKData { + private String errorCode; -public abstract class AbstractCpuUsage { + private String errorMessage; - protected Id id; + public ResponseOKData() { - protected double usage; - - protected int cpuCount = Runtime.getRuntime().availableProcessors(); - - static class Period { - double last; - - double period; - - void update(double current) { - period = current - last; - last = current; - } } - public AbstractCpuUsage(Id id) { - this.id = id; + public ResponseOKData(String errorCode, String errorMessage) { + this.errorCode = errorCode; + this.errorMessage = errorMessage; } - public Id getId() { - return id; + public String getErrorCode() { + return errorCode; } - public double getUsage() { - return usage; + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; } - public void setUsage(double usage) { - this.usage = usage; + public String getErrorMessage() { + return errorMessage; } - protected void updateUsage(double periodBusy, double periodTotal, boolean irixMode) { - usage = periodTotal == 0 ? 0 : periodBusy / periodTotal; - if (usage > 1) { - usage = 1; - } - if (irixMode) { - usage *= cpuCount; - } + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; } } diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestApiImplicitParamsSchema.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestApiImplicitParamsSchema.java new file mode 100644 index 00000000000..b4457b0ed3d --- /dev/null +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestApiImplicitParamsSchema.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.client; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.apache.servicecomb.demo.CategorizedTestCase; +import org.apache.servicecomb.demo.TestMgr; +import org.apache.servicecomb.provider.springmvc.reference.RestTemplateBuilder; +import org.apache.servicecomb.swagger.invocation.exception.InvocationException; +import org.springframework.http.HttpMethod; +import org.springframework.http.RequestEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestOperations; + +@Component +public class TestApiImplicitParamsSchema implements CategorizedTestCase { + private final RestOperations restOperations = RestTemplateBuilder.create(); + + @Override + public void testRestTransport() throws Exception { + testImplicitAndExplicitParam(); + testIntegerTypeValidation(); + } + + private void testIntegerTypeValidation() throws URISyntaxException { + MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.add("x-test-a", "a"); + headers.add("x-test-b", "30"); + headers.add("x-test-c", "x"); // invalid integer for object-mapper. + RequestEntity entity = new RequestEntity<>(headers, HttpMethod.GET, + new URI("servicecomb://springmvc/implicit/testIntegerTypeValidation")); + try { + String result = restOperations.exchange(entity, String.class).getBody(); + TestMgr.check(result, "do not have integer type check"); + } catch (InvocationException e) { + TestMgr.check(e.getStatusCode(), 400); + TestMgr.check(e.getMessage().contains("x-test-c"), true); + } + } + + private void testImplicitAndExplicitParam() throws URISyntaxException { + // test all parameters case + MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.add("x-test-a", "a"); + headers.add("x-test-b", "30"); + RequestEntity entity = new RequestEntity<>(headers, HttpMethod.GET, + new URI("servicecomb://springmvc/implicit/testImplicitAndExplicitParam?a=1&b=2")); + String result = restOperations.exchange(entity, String.class).getBody(); + TestMgr.check("a,30,3", result); + + // test default value + headers = new LinkedMultiValueMap<>(); + headers.add("x-test-b", "10"); + entity = new RequestEntity<>(headers, HttpMethod.GET, + new URI("servicecomb://springmvc/implicit/testImplicitAndExplicitParam?a=1&b=2")); + result = restOperations.exchange(entity, String.class).getBody(); + TestMgr.check("test,10,3", result); + + // test default required check + try { + result = restOperations.getForObject("servicecomb://springmvc/implicit/testImplicitAndExplicitParam?a=1&b=2", + String.class); + TestMgr.check(result, "do not have required check"); + } catch (InvocationException e) { + TestMgr.check(e.getStatusCode(), 400); + TestMgr.check(e.getMessage().contains("x-test-b"), true); + } + } +} diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestCompatible1xTestSchema.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestCompatible1xTestSchema.java new file mode 100644 index 00000000000..a5f62438826 --- /dev/null +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestCompatible1xTestSchema.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.client; + +import org.apache.servicecomb.demo.CategorizedTestCase; +import org.apache.servicecomb.demo.TestMgr; +import org.apache.servicecomb.provider.pojo.RpcReference; +import org.apache.servicecomb.swagger.invocation.context.InvocationContext; +import org.springframework.stereotype.Component; + +@Component +public class TestCompatible1xTestSchema implements CategorizedTestCase { + @RpcReference(microserviceName = "springmvc", schemaId = "Compatible1xTestSchema") + private ICompatible1xTestSchema schema; + + @Override + public void testAllTransport() throws Exception { + testParameterName(); + testParameterNameClientContext(); + testParameterNameServerContext(); + testBeanParameter(); + testParameterNamePartMatchLeft(); + testParameterNamePartMatchRight(); + } + + private void testParameterNamePartMatchRight() { + String result = schema.parameterNamePartMatchRight(3, 4); + TestMgr.check("springmvcClient38", result); + } + + private void testParameterNamePartMatchLeft() { + String result = schema.parameterNamePartMatchLeft(3, 4); + TestMgr.check("springmvcClient38", result); + } + + private void testBeanParameter() { + String result = schema.beanParameter("name", 4); + TestMgr.check("name4", result); + } + + private void testParameterNameServerContext() { + String result = schema.parameterNameServerContext(3, 4); + TestMgr.check("springmvcClient38", result); + } + + private void testParameterNameClientContext() { + String result = schema.parameterName(new InvocationContext(), 3, 4); + TestMgr.check("springmvcClient38", result); + } + + private void testParameterName() { + String result = schema.parameterName(3, 4); + TestMgr.check("springmvcClient38", result); + } +} diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestControllerImpl.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestControllerImpl.java index 37fd873f870..9b4880740e5 100644 --- a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestControllerImpl.java +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestControllerImpl.java @@ -18,6 +18,8 @@ import org.apache.servicecomb.demo.CategorizedTestCase; import org.apache.servicecomb.demo.TestMgr; +import org.apache.servicecomb.demo.controller.Person; +import org.apache.servicecomb.demo.controller.PersonAlias; import org.apache.servicecomb.provider.springmvc.reference.RestTemplateBuilder; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @@ -32,6 +34,16 @@ public class TestControllerImpl implements CategorizedTestCase { public void testRestTransport() throws Exception { testQueryParamSpecial(); + testResponseModel(); + } + + private void testResponseModel() { + Person person = restTemplate.getForObject(SERVER + "/springmvc/controller/testResponseModel", Person.class); + TestMgr.check("jack", person.getName()); + + PersonAlias personAlias = restTemplate.getForObject(SERVER + "/springmvc/controller/testResponseModel", + PersonAlias.class); + TestMgr.check("jack", personAlias.getName()); } private void testQueryParamSpecial() { diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDataTypesAnnotationsSchema.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDataTypesAnnotationsSchema.java new file mode 100644 index 00000000000..6234b0a99f6 --- /dev/null +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDataTypesAnnotationsSchema.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.client; + +import org.apache.servicecomb.demo.CategorizedTestCase; +import org.apache.servicecomb.demo.TestMgr; +import org.apache.servicecomb.provider.pojo.RpcReference; +import org.springframework.stereotype.Component; + +@Component +public class TestDataTypesAnnotationsSchema implements CategorizedTestCase { + public interface DataTypesAnnotationsItf { + int[] testIntArrayQuery(int[] param); + + Integer[] testIntegerArrayQuery(Integer[] param); + } + + @RpcReference(schemaId = "DataTypesAnnotationsSchema", microserviceName = "springmvc") + private DataTypesAnnotationsItf client; + + @Override + public void testAllTransport() throws Exception { + testIntArrayQuery(); + testIntegerArrayQuery(); + } + + private void testIntArrayQuery() { + int[] request = new int[] {5, 11, 4}; + int[] result = client.testIntArrayQuery(request); + TestMgr.check(request.length, result.length); + TestMgr.check(request[1], result[1]); + } + + private void testIntegerArrayQuery() { + Integer[] request = new Integer[] {5, 11, 4}; + Integer[] result = client.testIntegerArrayQuery(request); + TestMgr.check(request.length, result.length); + TestMgr.check(request[1], result[1]); + } +} diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDownloadSchema.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDownloadSchema.java index 37a24116a44..84afdad9f41 100644 --- a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDownloadSchema.java +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDownloadSchema.java @@ -17,20 +17,83 @@ package org.apache.servicecomb.demo.springmvc.client; +import java.net.URI; +import java.util.Collections; + import org.apache.servicecomb.demo.CategorizedTestCase; import org.apache.servicecomb.demo.TestMgr; import org.apache.servicecomb.foundation.vertx.http.ReadStreamPart; import org.apache.servicecomb.provider.springmvc.reference.RestTemplateBuilder; +import org.apache.servicecomb.registry.DiscoveryManager; +import org.apache.servicecomb.registry.api.registry.MicroserviceInstances; +import org.apache.servicecomb.registry.definition.DefinitionConst; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; +import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; +import com.netflix.config.DynamicPropertyFactory; + @Component public class TestDownloadSchema implements CategorizedTestCase { @Override public void testRestTransport() throws Exception { testDownloadFileAndDeleted(); + testDownloadFileAndDeletedCN(); testDownloadFileNotDeleted(); testDownloadFileWithNull(); + testSetContentTypeByResponseEntity(); + testResponseOKException(); + } + + private void testResponseOKException() { + String prefix; + String app; + if (DynamicPropertyFactory.getInstance() + .getBooleanProperty("servicecomb.test.vert.transport", true).get()) { + prefix = "/api/download/"; + app = "springmvctest"; + } else { + prefix = "/download/"; + app = "springmvcboottest"; + } + + MicroserviceInstances instances = + DiscoveryManager.INSTANCE. + findServiceInstances(app, "springmvc", DefinitionConst.VERSION_RULE_ALL); + String endpoint = instances.getInstancesResponse().getInstances().get(0).getEndpoints().stream() + .filter(item -> item.startsWith("rest")).findFirst().get(); + URI endpointItem = URI.create(endpoint); + RestTemplate template = new RestTemplate(); + + // This is for compatible usage. For best practise, any status code + // should have only one type of response. + ResponseEntity resultFail = template.getForEntity( + "http://" + endpointItem.getHost() + ":" + endpointItem.getPort() + + prefix + "testResponseOKExceptionBean?exception=true", ResponseOKData.class); + TestMgr.check(200, resultFail.getStatusCode().value()); + TestMgr.check("code-005", resultFail.getBody().getErrorCode()); + TestMgr.check("error-005", resultFail.getBody().getErrorMessage()); + ResponseEntity resultOK = template.getForEntity( + "http://" + endpointItem.getHost() + ":" + endpointItem.getPort() + + prefix + "testResponseOKExceptionBean?exception=false", boolean.class); + TestMgr.check(true, resultOK.getBody()); + + resultFail = template.getForEntity( + "http://" + endpointItem.getHost() + ":" + endpointItem.getPort() + + prefix + "testResponseOKExceptionDownload?exception=true&content=ddd&contentType=plain/text", + ResponseOKData.class); + TestMgr.check(200, resultFail.getStatusCode().value()); + TestMgr.check("code-005", resultFail.getBody().getErrorCode()); + TestMgr.check("error-005", resultFail.getBody().getErrorMessage()); + + ResponseEntity resultPartOK = template.getForEntity( + "http://" + endpointItem.getHost() + ":" + endpointItem.getPort() + + prefix + "testResponseOKExceptionDownload?exception=false&content=ddd&contentType=plain/text", + String.class); + TestMgr.check(200, resultPartOK.getStatusCode().value()); + TestMgr.check("ddd", resultPartOK.getBody()); } private void testDownloadFileAndDeleted() throws Exception { @@ -45,6 +108,19 @@ private void testDownloadFileAndDeleted() throws Exception { TestMgr.check(exists, false); } + private void testDownloadFileAndDeletedCN() throws Exception { + RestOperations restTemplate = RestTemplateBuilder.create(); + ReadStreamPart readStreamPart = restTemplate + .getForObject("servicecomb://springmvc/download/deleteAfterFinished?content={1}&fileName={2}", + ReadStreamPart.class, "hello", "中文"); + String hello = readStreamPart.saveAsString().get(); + TestMgr.check(hello, "hello"); + + boolean exists = restTemplate + .getForObject("servicecomb://springmvc/download/assertLastFileDeleted", boolean.class); + TestMgr.check(exists, false); + } + private void testDownloadFileWithNull() throws Exception { RestTemplate restTemplate = RestTemplateBuilder.create(); ReadStreamPart readStreamPart = restTemplate @@ -69,4 +145,15 @@ private void testDownloadFileNotDeleted() throws Exception { .getForObject("servicecomb://springmvc/download/assertLastFileDeleted", boolean.class); TestMgr.check(exists, true); } + + private void testSetContentTypeByResponseEntity() throws Exception { + RestTemplate restTemplate = RestTemplateBuilder.create(); + ResponseEntity responseEntity = restTemplate + .getForEntity( + "servicecomb://springmvc/download/setContentTypeByResponseEntity?content=hello&contentType=customType", + ReadStreamPart.class); + String hello = responseEntity.getBody().saveAsString().get(); + TestMgr.check(responseEntity.getHeaders().get(HttpHeaders.CONTENT_TYPE), Collections.singletonList("customType")); + TestMgr.check(hello, "hello"); + } } diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestFactoryBean.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestFactoryBean.java new file mode 100644 index 00000000000..78295f47a8a --- /dev/null +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestFactoryBean.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.client; + +import org.apache.servicecomb.demo.CategorizedTestCase; +import org.apache.servicecomb.demo.TestMgr; +import org.apache.servicecomb.demo.springmvc.client.factory.ServiceBean; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +@Component +public class TestFactoryBean implements CategorizedTestCase, ApplicationContextAware { + ApplicationContext applicationContext; + + @Override + public void testRestTransport() throws Exception { + ServiceBean serviceBean = (ServiceBean) this.applicationContext.getBean("ServiceFactoryBean"); + TestMgr.check("a-3", serviceBean.getName()); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } +} diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestMaxHttpUrlLength.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestMaxHttpUrlLength.java index 56b64c734ba..2cbc09a7d23 100644 --- a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestMaxHttpUrlLength.java +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestMaxHttpUrlLength.java @@ -22,7 +22,7 @@ import org.apache.servicecomb.provider.springmvc.reference.RestTemplateBuilder; import org.apache.servicecomb.swagger.invocation.exception.InvocationException; import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.RestOperations; import com.google.common.base.Strings; import com.netflix.config.DynamicPropertyFactory; @@ -37,14 +37,15 @@ public void testRestTransport() throws Exception { } private void testUrlNotLongerThan4096() { - RestTemplate restTemplate = RestTemplateBuilder.create(); - - String q = Strings.repeat("q", 4096 - "GET /api/springmvc/controller/sayhi?name=".length() - " HTTP/1.1\r".length()); + RestOperations restTemplate = RestTemplateBuilder.create(); + // \r doesn't count for url length Since netty 4.1.88.Final See https://github.com/netty/netty/pull/12321 + String q = Strings.repeat("q", + 4096 + 1 - "GET /api/springmvc/controller/sayhi?name=".length() - " HTTP/1.1\r".length()); TestMgr.check("hi " + q + " [" + q + "]", restTemplate.getForObject("cse://springmvc/springmvc/controller/sayhi?name=" + q, String.class)); - q = Strings.repeat("q", 4096 + 1 - "GET /api/springmvc/controller/sayhi?name=".length() - " HTTP/1.1\r".length()); + q = Strings.repeat("q", 4096 + 2 - "GET /api/springmvc/controller/sayhi?name=".length() - " HTTP/1.1\r".length()); try { restTemplate.getForObject("cse://springmvc/springmvc/controller/sayhi?name=" + q, String.class); diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestUploadSchema.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestUploadSchema.java index 8acc9bdb02e..f74f629d47f 100644 --- a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestUploadSchema.java +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestUploadSchema.java @@ -41,6 +41,7 @@ import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; @Component @@ -59,6 +60,7 @@ public void testRestTransport() throws Exception { testUploadMultiBigFiles(); testFileUploadMultiRpc(); testUploadFileAndAttribute(); + testUploadFileAndAttributeCN(); testUploadFileRequestPartAttribute(); } @@ -126,6 +128,23 @@ private void testUploadFileAndAttribute() throws Exception { TestMgr.check("hi test", result); } + private void testUploadFileAndAttributeCN() throws Exception { + RestOperations template = RestTemplateBuilder.create(); + Map map = new HashMap<>(); + String message = "hi"; + File file = File.createTempFile("中文名称", ".txt"); + FileUtils.writeStringToFile(file, "test", StandardCharsets.UTF_8, false); + + map.put("file", new FileSystemResource(file)); + map.put("attribute", message); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(org.springframework.http.MediaType.MULTIPART_FORM_DATA); + String result = template.postForObject("servicecomb://springmvc/upload/uploadFileAndAttribute", + new HttpEntity<>(map, headers), String.class); + TestMgr.check("hi test", result); + } + + private void testUploadFileRequestPartAttribute() throws Exception { RestTemplate template = RestTemplateBuilder.create(); Map map = new HashMap<>(); diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/factory/ServiceBean.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/factory/ServiceBean.java new file mode 100644 index 00000000000..1a25838d083 --- /dev/null +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/factory/ServiceBean.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.client.factory; + +public class ServiceBean { + private final String name; + + public ServiceBean(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/factory/ServiceFactoryBean.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/factory/ServiceFactoryBean.java new file mode 100644 index 00000000000..9bee6805c48 --- /dev/null +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/factory/ServiceFactoryBean.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.client.factory; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component("ServiceFactoryBean") +public class ServiceFactoryBean implements FactoryBean { + + ServiceWithReference serviceWithReference; + + @Autowired + public ServiceFactoryBean(ServiceWithReference serviceWithReference) { + this.serviceWithReference = serviceWithReference; + } + + @Override + public ServiceBean getObject() throws Exception { + return new ServiceBean(serviceWithReference.test("a")); + } + + @Override + public Class getObjectType() { + return ServiceBean.class; + } + + @Override + public boolean isSingleton() { + return true; + } +} diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/factory/ServiceWithReference.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/factory/ServiceWithReference.java new file mode 100644 index 00000000000..f371bf35148 --- /dev/null +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/factory/ServiceWithReference.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.client.factory; + +public interface ServiceWithReference { + String test(String name); +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/ServiceCombCanaryDistributer.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/factory/ServiceWithReferenceImpl.java similarity index 58% rename from handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/ServiceCombCanaryDistributer.java rename to demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/factory/ServiceWithReferenceImpl.java index 1bde97588ad..a5f8d7c6bb1 100644 --- a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/ServiceCombCanaryDistributer.java +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/factory/ServiceWithReferenceImpl.java @@ -14,22 +14,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.servicecomb.router.custom; -import org.apache.servicecomb.loadbalance.ServiceCombServer; -import org.apache.servicecomb.registry.api.registry.Microservice; -import org.apache.servicecomb.router.distribute.AbstractRouterDistributor; +package org.apache.servicecomb.demo.springmvc.client.factory; + +import org.apache.servicecomb.demo.springmvc.client.SchemeInterfaceSpringmvc; +import org.apache.servicecomb.provider.pojo.RpcReference; import org.springframework.stereotype.Component; @Component -public class ServiceCombCanaryDistributer extends - AbstractRouterDistributor { +public class ServiceWithReferenceImpl implements ServiceWithReference { + @RpcReference(schemaId = "SchemeInterfaceSpringmvc", microserviceName = "springmvc") + private SchemeInterfaceSpringmvc springmvc; - public ServiceCombCanaryDistributer() { - init(server -> MicroserviceCache.getInstance() - .getService(server.getInstance().getServiceId()), - Microservice::getVersion, - Microservice::getServiceName, - Microservice::getProperties); + @Override + public String test(String name) { + return name + "-" + springmvc.add(1, 2); } } diff --git a/demo/demo-springmvc/springmvc-client/src/main/resources/SpringMVCSchema.yaml b/demo/demo-springmvc/springmvc-client/src/main/resources/SpringMVCSchema.yaml index 34031a987ec..a4cc4b75fd8 100644 --- a/demo/demo-springmvc/springmvc-client/src/main/resources/SpringMVCSchema.yaml +++ b/demo/demo-springmvc/springmvc-client/src/main/resources/SpringMVCSchema.yaml @@ -25,7 +25,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.demo.springmvc.client.SpringMVCSchema" - x-java-interface: "gen.swagger.SpringMVCSchemaIntf" basePath: "/springMvcSchema" schemes: - "http" diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/SpringmvcServer.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/SpringmvcServer.java index 38841a98258..768930e7107 100644 --- a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/SpringmvcServer.java +++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/SpringmvcServer.java @@ -17,6 +17,8 @@ package org.apache.servicecomb.demo.springmvc; +import org.apache.servicecomb.demo.CategorizedTestCaseRunner; +import org.apache.servicecomb.demo.TestMgr; import org.apache.servicecomb.foundation.common.utils.BeanUtils; import org.apache.servicecomb.foundation.common.utils.Log4jUtils; @@ -24,5 +26,20 @@ public class SpringmvcServer { public static void main(String[] args) throws Exception { Log4jUtils.init(); BeanUtils.init(); + + runTests(); + + TestMgr.summary(); + if (!TestMgr.isSuccess()) { + System.exit(1); + } + } + + private static void runTests() { + try { + CategorizedTestCaseRunner.runCategorizedTestCase("springmvc"); + } catch (Exception e) { + TestMgr.failed("runCategorizedTestCase failed", e); + } } } diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ApiImplicitParamsSchema.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ApiImplicitParamsSchema.java new file mode 100644 index 00000000000..82aef935848 --- /dev/null +++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ApiImplicitParamsSchema.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.server; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.servicecomb.provider.rest.common.RestSchema; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; + +@RestSchema(schemaId = "ApiImplicitParamsSchema") +@RequestMapping(path = "/implicit", produces = MediaType.APPLICATION_JSON_VALUE) +@ApiImplicitParams({ + @ApiImplicitParam(name = "x-test-a", paramType = "header", dataType = "string", defaultValue = "test"), + @ApiImplicitParam(name = "x-test-b", paramType = "header", dataType = "integer", defaultValue = "20", required = true) +}) +public class ApiImplicitParamsSchema { + @GetMapping(path = "/testImplicitAndExplicitParam") + public String testImplicitAndExplicitParam(HttpServletRequest request, + @RequestParam("a") int a, @RequestParam("b") int b) { + return request.getHeader("x-test-a") + "," + request.getHeader("x-test-b") + "," + + (a + b); + } + + @GetMapping(path = "/testIntegerTypeValidation") + @ApiImplicitParams({ + @ApiImplicitParam(name = "x-test-c", paramType = "header", dataType = "integer") + }) + public String testIntegerTypeValidation(HttpServletRequest request) { + return request.getHeader("x-test-a") + "," + request.getHeader("x-test-b") + "," + + request.getHeader("x-test-c"); + } +} diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/Compatible1xTestSchema.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/Compatible1xTestSchema.java new file mode 100644 index 00000000000..79d6f6b5dee --- /dev/null +++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/Compatible1xTestSchema.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.server; + +import javax.ws.rs.core.MediaType; + +import org.apache.servicecomb.core.Const; +import org.apache.servicecomb.provider.rest.common.RestSchema; +import org.apache.servicecomb.swagger.invocation.context.ContextUtils; +import org.apache.servicecomb.swagger.invocation.context.InvocationContext; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@RestSchema(schemaId = "Compatible1xTestSchema") +@RequestMapping(path = "/compatible", produces = MediaType.APPLICATION_JSON) +public class Compatible1xTestSchema { + @GetMapping(path = "/parameterName") + public String parameterName(@RequestParam(name = "a", defaultValue = "10") int a, + @RequestParam(name = "b", defaultValue = "10") int b) { + return ContextUtils.getInvocationContext().getContext(Const.SRC_MICROSERVICE) + a + b * 2; + } + + @GetMapping(path = "/parameterNameServerContext") + public String parameterNameServerContext(InvocationContext context, + @RequestParam(name = "a", defaultValue = "10") int a, + @RequestParam(name = "b", defaultValue = "10") int b) { + return context.getContext(Const.SRC_MICROSERVICE) + a + b * 2; + } + + @GetMapping(path = "/beanParameter") + public String beanParameter(CompatibleQueryBean bean) { + return bean.getName() + bean.getAge(); + } +} diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/CompatibleQueryBean.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/CompatibleQueryBean.java new file mode 100644 index 00000000000..65135037278 --- /dev/null +++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/CompatibleQueryBean.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.server; + +public class CompatibleQueryBean { + private String name; + + private int age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } +} diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ConfigurationProblemsCollectorTest.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ConfigurationProblemsCollectorTest.java new file mode 100644 index 00000000000..71a309990ee --- /dev/null +++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ConfigurationProblemsCollectorTest.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.server; + +import org.apache.servicecomb.core.bootup.ConfigurationProblemsAlarmEvent; +import org.apache.servicecomb.demo.CategorizedTestCase; +import org.apache.servicecomb.demo.TestMgr; +import org.apache.servicecomb.foundation.common.event.EventManager; +import org.springframework.stereotype.Component; + +import com.google.common.eventbus.Subscribe; + +@Component +public class ConfigurationProblemsCollectorTest implements CategorizedTestCase { + private ConfigurationProblemsAlarmEvent event; + + public ConfigurationProblemsCollectorTest() { + EventManager.register(this); + } + + @Subscribe + public void onConfigurationProblemsAlarmEvent(ConfigurationProblemsAlarmEvent event) { + this.event = event; + } + + @Override + public void testRestTransport() throws Exception { + TestMgr.check(event != null, true); + TestMgr.check(event.getProblems(), "[WARN]Configurations warnings:\n" + + "Configurations with prefix `cse` is deprecated, use `servicecomb` instead. Find keys [cse.test.duplicate1]\n" + + "Configurations `APPLICATION_ID` is deprecated, use `servicecomb.service.application` instead.\n" + + "Configurations with prefix `service_description` is deprecated, " + + "use `servicecomb.service` instead. Find keys [service_description.name, service_description.paths, " + + "service_description.version]\n" + + "Configuration `servicecomb.loadbalance.isolation.*` is deprecated and disabled by default, " + + "use governance instead. " + + "See https://servicecomb.apache.org/references/java-chassis/" + + "zh_CN/references-handlers/governance-best-practise.html. If you want to enable it, add" + + "`servicecomb.loadbalance.filter.isolation.enabled=true` implicitly."); + } +} diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ControllerImpl.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ControllerImpl.java index f3a65fb18cc..ff1c6514e39 100644 --- a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ControllerImpl.java +++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ControllerImpl.java @@ -83,4 +83,11 @@ public String sayHei(@RequestHeader("name") String name) { public String sayHello1(@RequestParam("name") String name) { return "Hello " + name + "," + ContextUtils.getInvocationContext().getContext("k"); } + + @RequestMapping(path = "/testResponseModel", method = RequestMethod.GET) + public Person testResponseModel() { + Person person = new Person(); + person.setName("jack"); + return person; + } } diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DataTypesAnnotationsSchema.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DataTypesAnnotationsSchema.java new file mode 100644 index 00000000000..21077009ccf --- /dev/null +++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DataTypesAnnotationsSchema.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.server; + +import org.apache.servicecomb.provider.rest.common.RestSchema; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@RestSchema(schemaId = "DataTypesAnnotationsSchema") +@RequestMapping(path = "/dataTypes") +public class DataTypesAnnotationsSchema { + @GetMapping(path = "/testIntArrayQuery") + public int[] testIntArrayQuery(@RequestParam("param") int[] param) { + return param; + } + + @GetMapping(path = "/testIntegerArrayQuery") + public Integer[] testIntegerArrayQuery(@RequestParam("param") Integer[] param) { + return param; + } +} diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DownloadSchema.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DownloadSchema.java index c75775f24eb..bcabd9ec419 100644 --- a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DownloadSchema.java +++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DownloadSchema.java @@ -57,8 +57,15 @@ private File createTempFile(String name, String content) throws IOException { } @GetMapping(path = "/deleteAfterFinished") - public ResponseEntity deleteAfterFinished(@RequestParam("content") String content) throws IOException { - File file = createTempFile(content); + public ResponseEntity deleteAfterFinished(@RequestParam("content") String content, + @RequestParam(value = "fileName", required = false) String fileName) throws IOException { + File file; + + if (StringUtils.isNotEmpty(fileName)) { + file = createTempFile(fileName, content); + } else { + file = createTempFile(content); + } return ResponseEntity .ok() @@ -92,8 +99,46 @@ public ResponseEntity notDeleteAfterFinished(@RequestParam("content") Stri .body(new FilePart(null, file)); } + @GetMapping(path = "/setContentTypeByResponseEntity") + public ResponseEntity setContentTypeByResponseEntity(@RequestParam("content") String content, + @RequestParam("contentType") String contentType) throws IOException { + File file = createTempFile(content); + + return ResponseEntity + .ok() + .header(HttpHeaders.CONTENT_TYPE, contentType) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=tempFileEntity.txt") + .body(new FilePart(null, file)); + } + @GetMapping(path = "/assertLastFileDeleted") public boolean assertLastFileDeleted() { return lastFile.exists(); } + + @GetMapping(path = "/testResponseOKExceptionDownload") + public ResponseEntity testResponseOKExceptionDownload( + @RequestParam("exception") boolean exception, + @RequestParam("content") String content, + @RequestParam("contentType") String contentType) throws IOException { + if (exception) { + throw new ResponseOKException(); + } + + File file = createTempFile(content); + + return ResponseEntity + .ok() + .header(HttpHeaders.CONTENT_TYPE, contentType) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=tempFileEntity.txt") + .body(new FilePart(null, file)); + } + + @GetMapping(path = "/testResponseOKExceptionBean") + public boolean testResponseOKExceptionBean(@RequestParam("exception") boolean exception) { + if (exception) { + throw new ResponseOKException(); + } + return true; + } } diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ProducerTestsAfterBootup.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ProducerTestsAfterBootup.java index ebbe1dd8ccc..3f4faa706fd 100644 --- a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ProducerTestsAfterBootup.java +++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ProducerTestsAfterBootup.java @@ -26,9 +26,9 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; -import com.netflix.config.DynamicPropertyFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectWriter; +import com.netflix.config.DynamicPropertyFactory; import io.swagger.models.Swagger; import io.swagger.util.Yaml; @@ -47,7 +47,6 @@ public class ProducerTestsAfterBootup implements BootListener { + "info:\n" + " version: \"1.0.0\"\n" + " title: \"swagger definition for org.apache.servicecomb.demo.springmvc.server.CodeFirstSpringmvcForSchema\"\n" - + " x-java-interface: \"gen.swagger.CodeFirstSpringmvcForSchemaIntf\"\n" + "basePath: \"/forScheam\"\n" + "consumes:\n" + "- \"application/json\"\n" @@ -91,9 +90,9 @@ public void testSchemaNotChange(SCBEngine scbEngine) { public void testRegisteredBasePath() { if (DynamicPropertyFactory.getInstance().getBooleanProperty("servicecomb.test.vert.transport", true).get()) { - TestMgr.check(21, RegistrationManager.INSTANCE.getMicroservice().getPaths().size()); + TestMgr.check(RegistrationManager.INSTANCE.getMicroservice().getPaths().size() >= 23, true); } else { - TestMgr.check(22, RegistrationManager.INSTANCE.getMicroservice().getPaths().size()); + TestMgr.check(RegistrationManager.INSTANCE.getMicroservice().getPaths().size() >= 25, true); } } diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKData.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKData.java new file mode 100644 index 00000000000..aeadbbeda90 --- /dev/null +++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKData.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.server; + +public class ResponseOKData { + private String errorCode; + + private String errorMessage; + + public ResponseOKData(String errorCode, String errorMessage) { + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } + + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKException.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKException.java new file mode 100644 index 00000000000..6dcafb2db99 --- /dev/null +++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKException.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.server; + +public class ResponseOKException extends RuntimeException { +} diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKExceptionConverter.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKExceptionConverter.java new file mode 100644 index 00000000000..da363efe616 --- /dev/null +++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKExceptionConverter.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.springmvc.server; + +import javax.ws.rs.core.Response.Status; + +import org.apache.servicecomb.swagger.invocation.Response; +import org.apache.servicecomb.swagger.invocation.SwaggerInvocation; +import org.apache.servicecomb.swagger.invocation.exception.ExceptionToProducerResponseConverter; +import org.apache.servicecomb.swagger.invocation.exception.InvocationException; + +public class ResponseOKExceptionConverter implements + ExceptionToProducerResponseConverter { + + @Override + public Class getExceptionClass() { + return ResponseOKException.class; + } + + @Override + public Response convert(SwaggerInvocation swaggerInvocation, ResponseOKException e) { + // This is for compatible usage. For best practise, any status code + // should have only one type of response. + return Response.createFail(new InvocationException(Status.OK, + new ResponseOKData("code-005", "error-005"))); + } +} diff --git a/demo/demo-springmvc/springmvc-server/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.invocation.exception.ExceptionToProducerResponseConverter b/demo/demo-springmvc/springmvc-server/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.invocation.exception.ExceptionToProducerResponseConverter new file mode 100644 index 00000000000..1fab23dc1f4 --- /dev/null +++ b/demo/demo-springmvc/springmvc-server/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.invocation.exception.ExceptionToProducerResponseConverter @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. +# + +org.apache.servicecomb.demo.springmvc.server.ResponseOKExceptionConverter \ No newline at end of file diff --git a/demo/demo-springmvc/springmvc-server/src/main/resources/microservice.yaml b/demo/demo-springmvc/springmvc-server/src/main/resources/microservice.yaml index 5c76a8abdb0..5707a6f8766 100644 --- a/demo/demo-springmvc/springmvc-server/src/main/resources/microservice.yaml +++ b/demo/demo-springmvc/springmvc-server/src/main/resources/microservice.yaml @@ -86,6 +86,11 @@ servicecomb: region: my-Region availableZone: my-Zone codec.printErrorMessage: true + + # For old testing + loadbalance: + isolation: + errorThresholdPercentage: 20 #########SSL options # open jdk 8 now TLSv1.3 not available # ssl.protocols: TLSv1.3 diff --git a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/src/main/java/org/apache/servicecomb/demo/zeroconfig/client/ConsumerHeaderParamWithListSchema.java b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/src/main/java/org/apache/servicecomb/demo/zeroconfig/client/ConsumerHeaderParamWithListSchema.java new file mode 100644 index 00000000000..4b759246e8f --- /dev/null +++ b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/src/main/java/org/apache/servicecomb/demo/zeroconfig/client/ConsumerHeaderParamWithListSchema.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.zeroconfig.client; + +import java.util.List; + +import org.apache.servicecomb.demo.api.IHeaderParamWithListSchema; +import org.apache.servicecomb.provider.pojo.RpcReference; +import org.apache.servicecomb.provider.rest.common.RestSchema; + +@RestSchema(schemaId = "ConsumerHeaderParamWithListSchema", schemaInterface = IHeaderParamWithListSchema.class) +public class ConsumerHeaderParamWithListSchema implements IHeaderParamWithListSchema { + @RpcReference(microserviceName = "demo-zeroconfig-schemadiscovery-registry-server", + schemaId = "HeaderParamWithListSchema") + private IHeaderParamWithListSchema provider; + + @Override + public String headerListDefault(List headerList) { + return provider.headerListDefault(headerList); + } + + @Override + public String headerListCSV(List headerList) { + return provider.headerListCSV(headerList); + } + + @Override + public String headerListMULTI(List headerList) { + return provider.headerListMULTI(headerList); + } + + @Override + public String headerListSSV(List headerList) { + return provider.headerListSSV(headerList); + } + + @Override + public String headerListPIPES(List headerList) { + return provider.headerListPIPES(headerList); + } + + @Override + public String headerListTSV(List headerList) { + return provider.headerListTSV(headerList); + } +} diff --git a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-edge/src/main/resources/application.yml b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-edge/src/main/resources/application.yml index 685fd5937bc..8f327cbef32 100644 --- a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-edge/src/main/resources/application.yml +++ b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-edge/src/main/resources/application.yml @@ -45,6 +45,16 @@ servicecomb: path: "/register/url/prefix/.*" microserviceName: demo-zeroconfig-schemadiscovery-registry-client versionRule: 0+ + client-2: + prefixSegmentCount: 0 + path: "/headerList/.*" + microserviceName: demo-zeroconfig-schemadiscovery-registry-client + versionRule: 0+ + + references: + transport: + demo-zeroconfig-schemadiscovery-registry-client.ClientServerEndpoint.contextMapper: rest + context: headerContextMapper: | gatewayHeader: context-gateway-header diff --git a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/src/main/java/org/apache/servicecomb/demo/zeroconfig/server/HeaderParamWithListSchema.java b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/src/main/java/org/apache/servicecomb/demo/zeroconfig/server/HeaderParamWithListSchema.java new file mode 100644 index 00000000000..f51852fb8f4 --- /dev/null +++ b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/src/main/java/org/apache/servicecomb/demo/zeroconfig/server/HeaderParamWithListSchema.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.zeroconfig.server; + +import java.util.List; + +import org.apache.servicecomb.demo.api.IHeaderParamWithListSchema; +import org.apache.servicecomb.provider.rest.common.RestSchema; + +@RestSchema(schemaId = "HeaderParamWithListSchema", schemaInterface = IHeaderParamWithListSchema.class) +public class HeaderParamWithListSchema implements IHeaderParamWithListSchema { + @Override + public String headerListDefault(List headerList) { + return headerList == null ? "null" : headerList.size() + ":" + headerList; + } + + @Override + public String headerListCSV(List headerList) { + return headerList == null ? "null" : headerList.size() + ":" + headerList; + } + + @Override + public String headerListMULTI(List headerList) { + return headerList == null ? "null" : headerList.size() + ":" + headerList; + } + + @Override + public String headerListSSV(List headerList) { + return headerList == null ? "null" : headerList.size() + ":" + headerList; + } + + @Override + public String headerListPIPES(List headerList) { + return headerList == null ? "null" : headerList.size() + ":" + headerList; + } + + @Override + public String headerListTSV(List headerList) { + return headerList == null ? "null" : headerList.size() + ":" + headerList; + } +} diff --git a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/java/org/apache/servicecomb/demo/zeroconfig/tests/HeaderParamWithListSchemaIT.java b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/java/org/apache/servicecomb/demo/zeroconfig/tests/HeaderParamWithListSchemaIT.java new file mode 100644 index 00000000000..37bab8df039 --- /dev/null +++ b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/java/org/apache/servicecomb/demo/zeroconfig/tests/HeaderParamWithListSchemaIT.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.demo.zeroconfig.tests; + +import org.apache.servicecomb.demo.CategorizedTestCase; +import org.apache.servicecomb.demo.TestMgr; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; + +@Component +public class HeaderParamWithListSchemaIT implements CategorizedTestCase { + private static final String GATEWAY_URL = "http://localhost:8888"; + + RestOperations template = new RestTemplate(); + + @Override + public void testRestTransport() throws Exception { + testHeaderListDefault(); + testHeaderListMulti(); + testHeaderListCSV(); + testHeaderListSSV(); + testHeaderListTSV(); + testHeaderListPipes(); + } + + // default to csv, this is different from Open API 3.0 which default to none. + private void testHeaderListDefault() { + MultiValueMap headers = new HttpHeaders(); + headers.add("headerList", "a,b,c"); + HttpEntity entity = new HttpEntity<>(headers); + String result = template + .exchange(GATEWAY_URL + "/headerList/headerListDefault", HttpMethod.GET, entity, String.class).getBody(); + TestMgr.check("\"3:[a, b, c]\"", result); + } + + private void testHeaderListPipes() { + MultiValueMap headers = new HttpHeaders(); + headers.add("headerList", "a|b|c"); + HttpEntity entity = new HttpEntity<>(headers); + String result = template + .exchange(GATEWAY_URL + "/headerList/headerListPIPES", HttpMethod.GET, entity, String.class).getBody(); + TestMgr.check("\"3:[a, b, c]\"", result); + } + + private void testHeaderListTSV() { + MultiValueMap headers = new HttpHeaders(); + headers.add("headerList", "a\tb\tc"); + HttpEntity entity = new HttpEntity<>(headers); + String result = template + .exchange(GATEWAY_URL + "/headerList/headerListTSV", HttpMethod.GET, entity, String.class).getBody(); + TestMgr.check("\"3:[a, b, c]\"", result); + } + + private void testHeaderListSSV() { + MultiValueMap headers = new HttpHeaders(); + headers.add("headerList", "a b c"); + HttpEntity entity = new HttpEntity<>(headers); + String result = template + .exchange(GATEWAY_URL + "/headerList/headerListSSV", HttpMethod.GET, entity, String.class).getBody(); + TestMgr.check("\"3:[a, b, c]\"", result); + } + + private void testHeaderListCSV() { + MultiValueMap headers = new HttpHeaders(); + headers.add("headerList", "a,b,c"); + HttpEntity entity = new HttpEntity<>(headers); + String result = template + .exchange(GATEWAY_URL + "/headerList/headerListCSV", HttpMethod.GET, entity, String.class).getBody(); + TestMgr.check("\"3:[a, b, c]\"", result); + + headers.add("headerList", "a, b, c"); + entity = new HttpEntity<>(headers); + result = template + .exchange(GATEWAY_URL + "/headerList/headerListCSV", HttpMethod.GET, entity, String.class).getBody(); + TestMgr.check("\"3:[a, b, c]\"", result); + } + + private void testHeaderListMulti() { + MultiValueMap headers = new HttpHeaders(); + headers.add("headerList", "a"); + headers.add("headerList", "b"); + headers.add("headerList", "c"); + HttpEntity entity = new HttpEntity<>(headers); + String result = template + .exchange(GATEWAY_URL + "/headerList/headerListMULTI", HttpMethod.GET, entity, String.class).getBody(); + TestMgr.check("\"3:[a, b, c]\"", result); + } +} diff --git a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/java/org/apache/servicecomb/demo/zeroconfig/tests/ServerTest.java b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/java/org/apache/servicecomb/demo/zeroconfig/tests/ServerTest.java index aaf8233df3e..363496a05ae 100644 --- a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/java/org/apache/servicecomb/demo/zeroconfig/tests/ServerTest.java +++ b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/java/org/apache/servicecomb/demo/zeroconfig/tests/ServerTest.java @@ -59,8 +59,11 @@ private void testContextMapper() throws URISyntaxException { RequestEntity requestEntity = new RequestEntity<>(headers, HttpMethod.GET, new URI("cse://demo-zeroconfig-schemadiscovery-registry-edge/register/url/prefix/contextMapper?clientQuery=v3&" + "gatewayQuery=v4")); + // test two times to check different transport(only use rest) ResponseEntity response = template.exchange(requestEntity, String.class); TestMgr.check(response.getBody(), "success"); + response = template.exchange(requestEntity, String.class); + TestMgr.check(response.getBody(), "success"); } @SuppressWarnings("unchecked") diff --git a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/resources/microservices/demo-zeroconfig-schemadiscovery-registry-edge/ClientServerEndpoint.yaml b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/resources/microservices/demo-zeroconfig-schemadiscovery-registry-edge/ClientServerEndpoint.yaml index 7cf4edd1d68..6c00bfa295e 100644 --- a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/resources/microservices/demo-zeroconfig-schemadiscovery-registry-edge/ClientServerEndpoint.yaml +++ b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/resources/microservices/demo-zeroconfig-schemadiscovery-registry-edge/ClientServerEndpoint.yaml @@ -19,7 +19,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.demo.zeroconfig.client.ClientServerEndpoint" - x-java-interface: "gen.swagger.ClientServerEndpointIntf" basePath: "/register/url/prefix" schemes: - "http" diff --git a/demo/docker-build-config/pom.xml b/demo/docker-build-config/pom.xml index 4faa3efdd33..625e01894ab 100644 --- a/demo/docker-build-config/pom.xml +++ b/demo/docker-build-config/pom.xml @@ -42,7 +42,7 @@ ${project.artifactId}:${project.version} ${project.artifactId} - openjdk:8-jre-alpine + openjdk:8u342-jre 7070 8080 diff --git a/dependencies/default/pom.xml b/dependencies/default/pom.xml index 3d02025de64..a4f3b63ef09 100644 --- a/dependencies/default/pom.xml +++ b/dependencies/default/pom.xml @@ -32,7 +32,7 @@ 1.2.2 - 0.7.7 + 0.7.8 2.5.3 1.9.19 3.23.1 @@ -59,7 +59,7 @@ 6.2.5.Final 4.5.14 1.5.18 - 2.14.1 + 2.15.0 1.5.3 1.3.2 1 @@ -78,7 +78,7 @@ 5.14.0 2.2.0 0.3.0 - 4.1.86.Final + 4.1.130.Final 4.10.0 0.16.0 3.21.12 @@ -87,20 +87,21 @@ 1.7.0 2.7.18 1.3.8 - 0.5.1 + 0.4.20 1.0.0 0.13.2 4.0.4 1.7.36 - 1.33 + 2.2 1.4.2 - 5.3.24 - 2.7.7 + 5.3.36 + 2.7.18 1.6.9 1.3.3 - 4.3.7 + 4.5.23 2.24.0 2.16.3 + 9.0.108 ${basedir}/../.. @@ -774,6 +775,22 @@ ${java-websocket.version} + + org.apache.tomcat.embed + tomcat-embed-core + ${tomcat.version} + + + org.apache.tomcat.embed + tomcat-embed-el + ${tomcat.version} + + + org.apache.tomcat.embed + tomcat-embed-websocket + ${tomcat.version} + + org.apache.servicecomb java-chassis-bom diff --git a/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/ConfigCenterConfig.java b/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/ConfigCenterConfig.java index 837f6e0c720..daf3935aee4 100644 --- a/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/ConfigCenterConfig.java +++ b/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/ConfigCenterConfig.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.List; +import org.apache.commons.configuration.Configuration; import org.apache.servicecomb.config.collect.ConfigCenterDefaultDeploymentProvider; import org.apache.servicecomb.deployment.Deployment; import org.apache.servicecomb.foundation.vertx.VertxConst; @@ -51,6 +52,16 @@ public final class ConfigCenterConfig { private static final int DEFAULT_FIRST_REFRESH_INTERVAL = 0; + private static final String CLIENT_CONNECT_TIMEOUT = "servicecomb.config.client.timeout.connect"; + + private static final String CLIENT_REQUEST_TIMEOUT = "servicecomb.config.client.timeout.request"; + + private static final String CLIENT_SOCKET_TIMEOUT = "servicecomb.config.client.timeout.socket"; + + private static final String REGION = "servicecomb.datacenter.region"; + + private static final String AVAILABLE_ZONE = "servicecomb.datacenter.availableZone"; + private ConfigCenterConfig() { } @@ -135,4 +146,24 @@ public boolean getAutoDiscoveryEnabled() { public String getEnvironment() { return BootStrapProperties.readServiceEnvironment(finalConfig); } + + public int getConnectTimeout(Configuration localConfiguration) { + return localConfiguration.getInt(CLIENT_CONNECT_TIMEOUT, 5000); + } + + public int getConnectionRequestTimeout(Configuration configuration) { + return configuration.getInt(CLIENT_REQUEST_TIMEOUT, 5000); + } + + public int getSocketTimeout(Configuration configuration) { + return configuration.getInt(CLIENT_SOCKET_TIMEOUT, 5000); + } + + public String getRegion() { + return finalConfig.getString(REGION, ""); + } + + public String getAvailableZone() { + return finalConfig.getString(AVAILABLE_ZONE, ""); + } } diff --git a/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/ConfigCenterConfigurationSourceImpl.java b/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/ConfigCenterConfigurationSourceImpl.java index 74834aaef6d..37e315212d2 100644 --- a/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/ConfigCenterConfigurationSourceImpl.java +++ b/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/ConfigCenterConfigurationSourceImpl.java @@ -88,29 +88,37 @@ public boolean isValidSource(Configuration localConfiguration) { public void init(Configuration localConfiguration) { configConverter = new ConfigConverter(ConfigCenterConfig.INSTANCE.getFileSources()); - ConfigCenterAddressManager kieAddressManager = configKieAddressManager(); + ConfigCenterAddressManager configCenterAddressManager = configCenterAddressManager(); - HttpTransport httpTransport = createHttpTransport(kieAddressManager, - HttpTransportFactory.defaultRequestConfig().build(), - localConfiguration); - ConfigCenterClient configCenterClient = new ConfigCenterClient(kieAddressManager, httpTransport); + HttpTransport httpTransport = createHttpTransport(configCenterAddressManager, + buildRequestConfig(localConfiguration), localConfiguration); + ConfigCenterClient configCenterClient = new ConfigCenterClient(configCenterAddressManager, httpTransport); EventManager.register(this); ConfigCenterConfiguration configCenterConfiguration = createConfigCenterConfiguration(); - QueryConfigurationsRequest queryConfigurationsRequest = firstPull(configCenterClient); + QueryConfigurationsRequest queryConfigurationsRequest = firstPull(configCenterClient, configCenterAddressManager); configCenterManager = new ConfigCenterManager(configCenterClient, EventManager.getEventBus(), - configConverter, configCenterConfiguration); + configConverter, configCenterConfiguration, configCenterAddressManager); configCenterManager.setQueryConfigurationsRequest(queryConfigurationsRequest); configCenterManager.startConfigCenterManager(); } - private QueryConfigurationsRequest firstPull(ConfigCenterClient configCenterClient) { + private RequestConfig buildRequestConfig(Configuration configuration) { + RequestConfig.Builder builder = HttpTransportFactory.defaultRequestConfig(); + builder.setConnectTimeout(ConfigCenterConfig.INSTANCE.getConnectTimeout(configuration)); + builder.setConnectionRequestTimeout(ConfigCenterConfig.INSTANCE.getConnectionRequestTimeout(configuration)); + builder.setSocketTimeout(ConfigCenterConfig.INSTANCE.getSocketTimeout(configuration)); + return builder.build(); + } + + private QueryConfigurationsRequest firstPull(ConfigCenterClient configCenterClient, + ConfigCenterAddressManager configCenterAddressManager) { QueryConfigurationsRequest queryConfigurationsRequest = createQueryConfigurationsRequest(); try { QueryConfigurationsResponse response = configCenterClient - .queryConfigurations(queryConfigurationsRequest); + .queryConfigurations(queryConfigurationsRequest, configCenterAddressManager.address()); if (response.isChanged()) { configConverter.updateData(response.getConfigurations()); updateConfiguration(WatchedUpdateResult.createIncremental(configConverter.getCurrentData(), null, null)); @@ -146,8 +154,8 @@ private ConfigCenterConfiguration createConfigCenterConfiguration(){ return new ConfigCenterConfiguration().setRefreshIntervalInMillis(ConfigCenterConfig.INSTANCE.getRefreshInterval()); } - private HttpTransport createHttpTransport(ConfigCenterAddressManager kieAddressManager, RequestConfig requestConfig, - Configuration localConfiguration) { + private HttpTransport createHttpTransport(ConfigCenterAddressManager configCenterAddressManager, + RequestConfig requestConfig, Configuration localConfiguration) { List authHeaderProviders = SPIServiceUtils.getOrLoadSortedService(AuthHeaderProvider.class); if (ConfigCenterConfig.INSTANCE.isProxyEnable()) { @@ -165,30 +173,41 @@ private HttpTransport createHttpTransport(ConfigCenterAddressManager kieAddressM return HttpTransportFactory .createHttpTransport( TransportUtils - .createSSLProperties(kieAddressManager.sslEnabled(), localConfiguration, ConfigCenterConfig.SSL_TAG), + .createSSLProperties(configCenterAddressManager.sslEnabled(), localConfiguration, + ConfigCenterConfig.SSL_TAG), getRequestAuthHeaderProvider(authHeaderProviders), httpClientBuilder); } return HttpTransportFactory .createHttpTransport( TransportUtils - .createSSLProperties(kieAddressManager.sslEnabled(), localConfiguration, ConfigCenterConfig.SSL_TAG), + .createSSLProperties(configCenterAddressManager.sslEnabled(), localConfiguration, + ConfigCenterConfig.SSL_TAG), getRequestAuthHeaderProvider(authHeaderProviders), requestConfig); } private static RequestAuthHeaderProvider getRequestAuthHeaderProvider(List authHeaderProviders) { return signRequest -> { + String host = signRequest != null && signRequest.getEndpoint() != null ? signRequest.getEndpoint().getHost() : ""; Map headers = new HashMap<>(); - authHeaderProviders.forEach(provider -> headers.putAll(provider.authHeaders())); + authHeaderProviders.forEach(provider -> headers.putAll(provider.authHeaders(host))); return headers; }; } - private ConfigCenterAddressManager configKieAddressManager() { + private ConfigCenterAddressManager configCenterAddressManager() { return new ConfigCenterAddressManager(ConfigCenterConfig.INSTANCE.getDomainName(), Deployment .getSystemBootStrapInfo(ConfigCenterDefaultDeploymentProvider.SYSTEM_KEY_CONFIG_CENTER).getAccessURL(), - EventManager.getEventBus()); + getRegion(), getAvailableZone(), EventManager.getEventBus()); + } + + private String getRegion() { + return ConfigCenterConfig.INSTANCE.getRegion(); + } + + private String getAvailableZone() { + return ConfigCenterConfig.INSTANCE.getAvailableZone(); } private void updateConfiguration(WatchedUpdateResult result) { diff --git a/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/ConfigCenterConfigurationSourceImplTest.java b/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/ConfigCenterConfigurationSourceImplTest.java index 76f0cb49f42..f5fa3d40488 100644 --- a/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/ConfigCenterConfigurationSourceImplTest.java +++ b/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/ConfigCenterConfigurationSourceImplTest.java @@ -18,6 +18,7 @@ package org.apache.servicecomb.config; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -30,13 +31,16 @@ import org.junit.jupiter.api.Test; class ConfigCenterConfigurationSourceImplTest { - @Test - void configAddressManagerTest() { + void configAddressManagerTest() throws IllegalAccessException, NoSuchFieldException { List addresses = new ArrayList<>(); addresses.add("http://127.0.0.1:30103"); addresses.add("http://127.0.0.2:30103"); - ConfigCenterAddressManager addressManager = new ConfigCenterAddressManager("test", addresses, EventManager.getEventBus()); + ConfigCenterAddressManager addressManager = new ConfigCenterAddressManager("test", addresses, + "", "", EventManager.getEventBus()); + Field addressManagerField = addressManager.getClass().getSuperclass().getDeclaredField("index"); + addressManagerField.setAccessible(true); + addressManagerField.set(addressManager, 0); Assertions.assertNotNull(addressManager); String address = addressManager.address(); @@ -44,7 +48,10 @@ void configAddressManagerTest() { address = addressManager.address(); Assertions.assertEquals("http://127.0.0.1:30103/v3/test", address); - addressManager = new ConfigCenterAddressManager(null, addresses, EventManager.getEventBus()); + addressManager = new ConfigCenterAddressManager(null, addresses, "", "", EventManager.getEventBus()); + addressManagerField = addressManager.getClass().getSuperclass().getDeclaredField("index"); + addressManagerField.setAccessible(true); + addressManagerField.set(addressManager, 0); address = addressManager.address(); Assertions.assertEquals("http://127.0.0.2:30103/v3/default", address); } @@ -59,7 +66,8 @@ void onRefreshEndpointEventTest() { zoneAndRegion.put("sameZone", addressAZ); zoneAndRegion.put("sameRegion", new ArrayList<>()); RefreshEndpointEvent event = new RefreshEndpointEvent(zoneAndRegion, "CseConfigCenter"); - ConfigCenterAddressManager addressManager = new ConfigCenterAddressManager("test", addresses, EventManager.getEventBus()); + ConfigCenterAddressManager addressManager = new ConfigCenterAddressManager("test", addresses, + "", "", EventManager.getEventBus()); addressManager.onRefreshEndpointEvent(event); List availableAZ = addressManager.getAvailableZone(); diff --git a/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/center/client/ConfigCenterAddressManagerTest.java b/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/center/client/ConfigCenterAddressManagerTest.java index 86b3e305db7..4592b867f78 100644 --- a/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/center/client/ConfigCenterAddressManagerTest.java +++ b/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/center/client/ConfigCenterAddressManagerTest.java @@ -17,6 +17,7 @@ package org.apache.servicecomb.config.center.client; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -36,11 +37,17 @@ class ConfigCenterAddressManagerTest { private static ConfigCenterAddressManager addressManager2; @Test - public void addressManagerTest() { + public void addressManagerTest() throws NoSuchFieldException, IllegalAccessException { addresses.add("http://127.0.0.1:30103"); addresses.add("https://127.0.0.2:30103"); - addressManager1 = new ConfigCenterAddressManager("project", addresses, new EventBus()); - addressManager2 = new ConfigCenterAddressManager(null, addresses, new EventBus()); + addressManager1 = new ConfigCenterAddressManager("project", addresses, "", "", new EventBus()); + addressManager2 = new ConfigCenterAddressManager(null, addresses, "", "", new EventBus()); + Field addressManagerField = addressManager1.getClass().getSuperclass().getDeclaredField("index"); + addressManagerField.setAccessible(true); + addressManagerField.set(addressManager1, 0); + addressManagerField = addressManager2.getClass().getSuperclass().getDeclaredField("index"); + addressManagerField.setAccessible(true); + addressManagerField.set(addressManager2, 0); Assertions.assertNotNull(addressManager1); Assertions.assertNotNull(addressManager2); @@ -63,7 +70,7 @@ public void onRefreshEndpointEvent() { Map> zoneAndRegion = new HashMap<>(); zoneAndRegion.put("sameZone", addressAZ); zoneAndRegion.put("sameRegion", addressRG); - addressManager1 = new ConfigCenterAddressManager("project", addresses, new EventBus()); + addressManager1 = new ConfigCenterAddressManager("project", addresses, "", "", new EventBus()); RefreshEndpointEvent event = new RefreshEndpointEvent(zoneAndRegion, "CseConfigCenter"); addressManager1.refreshEndpoint(event, "CseConfigCenter"); diff --git a/dynamic-config/config-kie/src/main/java/org/apache/servicecomb/config/kie/KieConfig.java b/dynamic-config/config-kie/src/main/java/org/apache/servicecomb/config/kie/KieConfig.java index 872165f51eb..e556c778e8e 100644 --- a/dynamic-config/config-kie/src/main/java/org/apache/servicecomb/config/kie/KieConfig.java +++ b/dynamic-config/config-kie/src/main/java/org/apache/servicecomb/config/kie/KieConfig.java @@ -75,6 +75,10 @@ public class KieConfig { private static final String CUSTOM_LABEL_VALUE_DEFAULT = ""; + private static final String REGION = "servicecomb.datacenter.region"; + + private static final String AVAILABLE_ZONE = "servicecomb.datacenter.availableZone"; + private KieConfig() { } @@ -185,4 +189,12 @@ public String getProxyUsername() { public String getProxyPasswd() { return finalConfig.getString(VertxConst.PROXY_PASSWD, null); } + + public String getRegion() { + return finalConfig.getString(REGION, ""); + } + + public String getAvailableZone() { + return finalConfig.getString(AVAILABLE_ZONE, ""); + } } diff --git a/dynamic-config/config-kie/src/main/java/org/apache/servicecomb/config/kie/KieConfigurationSourceImpl.java b/dynamic-config/config-kie/src/main/java/org/apache/servicecomb/config/kie/KieConfigurationSourceImpl.java index fbf700e8284..683a81b8394 100644 --- a/dynamic-config/config-kie/src/main/java/org/apache/servicecomb/config/kie/KieConfigurationSourceImpl.java +++ b/dynamic-config/config-kie/src/main/java/org/apache/servicecomb/config/kie/KieConfigurationSourceImpl.java @@ -31,12 +31,14 @@ import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.config.RequestConfig.Builder; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.servicecomb.config.common.ConfigConverter; import org.apache.servicecomb.config.common.ConfigurationChangedEvent; import org.apache.servicecomb.config.kie.client.KieClient; import org.apache.servicecomb.config.kie.client.KieConfigManager; +import org.apache.servicecomb.config.kie.client.model.ConfigConstants; import org.apache.servicecomb.config.kie.client.model.KieAddressManager; import org.apache.servicecomb.config.kie.client.model.KieConfiguration; import org.apache.servicecomb.config.spi.ConfigCenterConfigurationSource; @@ -85,7 +87,7 @@ public void init(Configuration localConfiguration) { configConverter = new ConfigConverter(KieConfig.INSTANCE.getFileSources()); KieAddressManager kieAddressManager = configKieAddressManager(); - RequestConfig.Builder requestBuilder = HttpTransportFactory.defaultRequestConfig(); + RequestConfig.Builder requestBuilder = buildRequestConfigBuilder(localConfiguration); if (KieConfig.INSTANCE.enableLongPolling() && KieConfig.INSTANCE.getPollingWaitTime() >= 0) { requestBuilder.setConnectionRequestTimeout(KieConfig.INSTANCE.getPollingWaitTime() * 2 * 1000); @@ -96,12 +98,21 @@ public void init(Configuration localConfiguration) { KieConfiguration kieConfiguration = createKieConfiguration(); KieClient kieClient = new KieClient(kieAddressManager, httpTransport, kieConfiguration); EventManager.register(this); - kieConfigManager = new KieConfigManager(kieClient, EventManager.getEventBus(), kieConfiguration, configConverter); + kieConfigManager = new KieConfigManager(kieClient, EventManager.getEventBus(), kieConfiguration, configConverter, + kieAddressManager); kieConfigManager.firstPull(); kieConfigManager.startConfigKieManager(); updateConfiguration(WatchedUpdateResult.createIncremental(configConverter.getCurrentData(), null, null)); } + private Builder buildRequestConfigBuilder(Configuration configuration) { + RequestConfig.Builder builder = HttpTransportFactory.defaultRequestConfig(); + builder.setConnectTimeout(configuration.getInt(ConfigConstants.CLIENT_CONNECT_TIMEOUT, 5000)); + builder.setConnectionRequestTimeout(configuration.getInt(ConfigConstants.CLIENT_REQUEST_TIMEOUT, 5000)); + builder.setSocketTimeout(configuration.getInt(ConfigConstants.CLIENT_SOCKET_TIMEOUT, 5000)); + return builder; + } + @Subscribe public void onConfigurationChangedEvent(ConfigurationChangedEvent event) { updateConfiguration( @@ -158,15 +169,19 @@ private HttpTransport createHttpTransport(KieAddressManager kieAddressManager, R private static RequestAuthHeaderProvider getRequestAuthHeaderProvider(List authHeaderProviders) { return signRequest -> { + String host = signRequest != null && signRequest.getEndpoint() != null ? signRequest.getEndpoint().getHost() : ""; Map headers = new HashMap<>(); - authHeaderProviders.forEach(provider -> headers.putAll(provider.authHeaders())); + authHeaderProviders.forEach(provider -> headers.putAll(provider.authHeaders(host))); return headers; }; } private KieAddressManager configKieAddressManager() { + String region = KieConfig.INSTANCE.getRegion(); + String availableZone = KieConfig.INSTANCE.getAvailableZone(); return new KieAddressManager( - Arrays.asList(KieConfig.INSTANCE.getServerUri().split(",")), EventManager.getEventBus()); + Arrays.asList(KieConfig.INSTANCE.getServerUri().split(",")), region, availableZone, + EventManager.getEventBus()); } private void updateConfiguration(WatchedUpdateResult result) { diff --git a/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/EdgeAddHeaderClientFilter.java b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/EdgeAddHeaderClientFilter.java index 49258fab9c6..3c6387c4b12 100644 --- a/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/EdgeAddHeaderClientFilter.java +++ b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/EdgeAddHeaderClientFilter.java @@ -24,6 +24,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.servicecomb.common.rest.filter.HttpClientFilter; +import org.apache.servicecomb.core.Const; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx; import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx; @@ -77,6 +78,11 @@ public boolean enabled() { return enabled; } + @Override + public boolean enabledForTransport(String transport) { + return HttpClientFilter.super.enabledForTransport(transport) || Const.WEBSOCKET.equals(transport); + } + @Override public CompletableFuture beforeSendRequestAsync(Invocation invocation, HttpServletRequestEx requestEx) { addHeaders(invocation, requestEx::addHeader); diff --git a/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/EdgeInvocationCreator.java b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/EdgeInvocationCreator.java index 17d67a647f0..c848ff74a1b 100644 --- a/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/EdgeInvocationCreator.java +++ b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/EdgeInvocationCreator.java @@ -73,11 +73,6 @@ protected OperationLocator locateOperation(ServicePathManager servicePathManager return servicePathManager.consumerLocateOperation(path, requestEx.getMethod()); } - @Override - protected void initInvocationContext(Invocation invocation) { - // do not read InvocationContext from HTTP header, for security reason - } - @Override protected Invocation createInstance() { ReferenceConfig referenceConfig = microserviceReferenceConfig diff --git a/edge/edge-core/src/main/resources/microservice.yaml b/edge/edge-core/src/main/resources/microservice.yaml new file mode 100644 index 00000000000..a456c2868b2 --- /dev/null +++ b/edge/edge-core/src/main/resources/microservice.yaml @@ -0,0 +1,23 @@ +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You 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 +## +## http://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. +## --------------------------------------------------------------------------- + +servicecomb-config-order: -500 + +servicecomb: + context: + # do not decode invocation context for edge service by default + decodeInvocationContext: false diff --git a/foundations/foundation-common/pom.xml b/foundations/foundation-common/pom.xml index d06c9f495ae..9dd012c3ef3 100644 --- a/foundations/foundation-common/pom.xml +++ b/foundations/foundation-common/pom.xml @@ -27,6 +27,10 @@ Java Chassis::Foundations::Common + + com.netflix.archaius + archaius-core + com.sun.activation jakarta.activation diff --git a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/http/HttpUtils.java b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/http/HttpUtils.java index 4793be59b97..63f4a1222f0 100644 --- a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/http/HttpUtils.java +++ b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/http/HttpUtils.java @@ -20,9 +20,9 @@ import java.net.URI; import java.net.URISyntaxException; +import org.apache.commons.lang3.StringUtils; import com.google.common.net.UrlEscapers; -import org.apache.commons.lang3.StringUtils; public final class HttpUtils { private HttpUtils() { @@ -43,9 +43,8 @@ public static String parseParamFromHeaderValue(String headerValue, String paramN if (idx == -1) { continue; } - - if (paramName.equalsIgnoreCase(value.substring(0, idx))) { - return value.substring(idx + 1); + if (paramName.equalsIgnoreCase(value.substring(0, idx).trim())) { + return value.substring(idx + 1).replaceAll("\"", "").trim(); } } return null; @@ -143,4 +142,41 @@ public static String getCharsetFromContentType(String contentType) { } return encoding.trim(); } + + public static String splitPathFromUri(String uri) { + if (uri == null || uri.isEmpty()) { + return uri; + } + int pathStart; + final int i = uri.indexOf("://"); + if (i < 0) { // uri=/path?x=y or uri=path?x=y + pathStart = 0; + } else { // uri=http://localhost:8080/path?x=y or uri=http://localhost:8080 + pathStart = uri.indexOf("/", i + 3); + } + + if (pathStart < 0) { + // for uri=http://localhost:8080 case, return "/" to keep compatible with Vert.x io.vertx.core.http.HttpClientRequest.path. + // (i.e. keep compatible with io.vertx.core.http.impl.HttpUtils.parsePath) + return "/"; + } + + final int queryStart = uri.indexOf('?', pathStart); + if (queryStart < 0) { + return uri.substring(pathStart); + } else { + return uri.substring(pathStart, queryStart); + } + } + + public static String splitQueryFromUri(String uri) { + if (uri == null || uri.isEmpty()) { + return null; + } + final int queryStart = uri.indexOf('?'); + if (queryStart < 0) { + return null; + } + return uri.substring(queryStart + 1); + } } diff --git a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/net/URIEndpointObject.java b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/net/URIEndpointObject.java index 03b0ffc9647..6ab5a1f5afb 100644 --- a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/net/URIEndpointObject.java +++ b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/net/URIEndpointObject.java @@ -35,16 +35,23 @@ public class URIEndpointObject extends IpPort { private static final String PROTOCOL_KEY = "protocol"; + private static final String WEBSOCKET_ENABLED_KEY = "websocketEnabled"; + private static final String HTTP2 = "http2"; private final boolean sslEnabled; private boolean http2Enabled; + private boolean websocketEnabled; + private final Map> querys; + private final String scheme; + public URIEndpointObject(String endpoint) { URI uri = URI.create(endpoint); + scheme = uri.getScheme(); setHostOrIp(uri.getHost()); if (uri.getPort() < 0) { // do not use default port @@ -53,6 +60,7 @@ public URIEndpointObject(String endpoint) { setPort(uri.getPort()); querys = splitQuery(uri); sslEnabled = Boolean.parseBoolean(getFirst(SSL_ENABLED_KEY)); + websocketEnabled = Boolean.parseBoolean(getFirst(WEBSOCKET_ENABLED_KEY)); String httpVersion = getFirst(PROTOCOL_KEY); if (HTTP2.equals(httpVersion)) { http2Enabled = true; @@ -73,10 +81,18 @@ public boolean isSslEnabled() { return sslEnabled; } + public boolean isWebsocketEnabled() { + return websocketEnabled; + } + public boolean isHttp2Enabled() { return http2Enabled; } + public String getScheme() { + return this.scheme; + } + public List getQuery(String key) { return querys.get(key); } diff --git a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/part/FilePart.java b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/part/FilePart.java index d44c40b92d5..1bd9ddfd952 100644 --- a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/part/FilePart.java +++ b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/part/FilePart.java @@ -18,9 +18,9 @@ package org.apache.servicecomb.foundation.common.part; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; import org.apache.commons.io.FileUtils; @@ -41,7 +41,7 @@ public FilePart(String name, File file) { @Override public InputStream getInputStream() throws IOException { - return new FileInputStream(file); + return Files.newInputStream(file.toPath()); } @Override diff --git a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/BeanUtils.java b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/BeanUtils.java index 4f0b9177097..85193e979ea 100644 --- a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/BeanUtils.java +++ b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/BeanUtils.java @@ -36,12 +36,9 @@ public final class BeanUtils { private static final Logger LOGGER = LoggerFactory.getLogger(BeanUtils.class); - public static final String DEFAULT_BEAN_CORE_RESOURCE = "classpath*:META-INF/spring/scb-core-bean.xml"; - public static final String DEFAULT_BEAN_NORMAL_RESOURCE = "classpath*:META-INF/spring/*.bean.xml"; - public static final String[] DEFAULT_BEAN_RESOURCE = new String[] {DEFAULT_BEAN_CORE_RESOURCE - , DEFAULT_BEAN_NORMAL_RESOURCE}; + public static final String[] DEFAULT_BEAN_RESOURCE = new String[] {DEFAULT_BEAN_NORMAL_RESOURCE}; public static final String SCB_SCAN_PACKAGE = "scb-scan-package"; @@ -65,6 +62,14 @@ public static void init(String... configLocations) { context = new ClassPathXmlApplicationContext(locationSet.toArray(new String[0])); } + public static void initWithoutDefault(String... configLocations) { + prepareServiceCombScanPackage(); + + Set locationSet = new LinkedHashSet<>(); + addBeanLocation(locationSet, configLocations); + context = new ClassPathXmlApplicationContext(locationSet.toArray(new String[0])); + } + public static void addBeanLocation(Set locationSet, String... location) { Arrays.stream(location).forEach(loc -> addBeanLocation(locationSet, loc)); } diff --git a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/RSAKeyPairEntry.java b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/KeyPairEntry.java similarity index 91% rename from foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/RSAKeyPairEntry.java rename to foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/KeyPairEntry.java index 0865860eece..be31df199a4 100644 --- a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/RSAKeyPairEntry.java +++ b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/KeyPairEntry.java @@ -19,7 +19,7 @@ import java.security.PrivateKey; import java.security.PublicKey; -public final class RSAKeyPairEntry { +public final class KeyPairEntry { private final PrivateKey privateKey; @@ -27,7 +27,7 @@ public final class RSAKeyPairEntry { private final String publicKeyEncoded; - public RSAKeyPairEntry(PrivateKey privateKey, PublicKey publicKey, String publicKeyEncoded) { + public KeyPairEntry(PrivateKey privateKey, PublicKey publicKey, String publicKeyEncoded) { this.privateKey = privateKey; this.publicKey = publicKey; this.publicKeyEncoded = publicKeyEncoded; diff --git a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/RSAUtils.java b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/KeyPairUtils.java similarity index 78% rename from foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/RSAUtils.java rename to foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/KeyPairUtils.java index 22742700bf6..70b0700672c 100644 --- a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/RSAUtils.java +++ b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/KeyPairUtils.java @@ -34,15 +34,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class RSAUtils { +import com.netflix.config.DynamicPropertyFactory; - private static final Logger LOGGER = LoggerFactory.getLogger(RSAUtils.class); +public class KeyPairUtils { - private static final String RSA_ALG = "RSA"; + private static final Logger LOGGER = LoggerFactory.getLogger(KeyPairUtils.class); - private static final String SIGN_ALG = "SHA256withRSA"; + private static final String KEY_GENERATOR_ALGORITHM = DynamicPropertyFactory.getInstance() + .getStringProperty("servicecomb.publicKey.accessControl.keyGeneratorAlgorithm", "RSA").get();; - private static final int KEY_SIZE = 2048; + private static final String SIGN_ALG = DynamicPropertyFactory.getInstance() + .getStringProperty("servicecomb.publicKey.accessControl.signAlgorithm", "SHA256withRSA").get(); + + private static final int KEY_SIZE = DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.publicKey.accessControl.keySize", 2048).get(); private static final Base64.Encoder encoder = Base64.getEncoder(); @@ -53,20 +58,20 @@ public class RSAUtils { static { try { - kf = KeyFactory.getInstance(RSA_ALG); + kf = KeyFactory.getInstance(KEY_GENERATOR_ALGORITHM); } catch (NoSuchAlgorithmException e) { LOGGER.error("init keyfactory error"); } } - public static RSAKeyPairEntry generateRSAKeyPair() { + public static KeyPairEntry generateRSAKeyPair() { try { - KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance(RSA_ALG); + KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance(KEY_GENERATOR_ALGORITHM); keyGenerator.initialize(KEY_SIZE, new SecureRandom()); KeyPair keyPair = keyGenerator.generateKeyPair(); PublicKey pubKey = keyPair.getPublic(); PrivateKey privKey = keyPair.getPrivate(); - return new RSAKeyPairEntry(privKey, pubKey, encoder.encodeToString(pubKey.getEncoded())); + return new KeyPairEntry(privKey, pubKey, encoder.encodeToString(pubKey.getEncoded())); } catch (NoSuchAlgorithmException e) { LOGGER.error("generate rsa keypair faild"); throw new IllegalStateException("perhaps error occurred on jre"); @@ -100,7 +105,7 @@ public static String sign(String content, PrivateKey privateKey) public static boolean verify(String publicKey, String sign, String content) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { if (null == kf) { - throw new NoSuchAlgorithmException(RSA_ALG + " KeyFactory not available"); + throw new NoSuchAlgorithmException(KEY_GENERATOR_ALGORITHM + " KeyFactory not available"); } byte[] bytes = decoder.decode(publicKey); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes); diff --git a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/token/RSAKeypair4Auth.java b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/token/Keypair4Auth.java similarity index 92% rename from foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/token/RSAKeypair4Auth.java rename to foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/token/Keypair4Auth.java index b95cbf32a3e..a3172419465 100644 --- a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/token/RSAKeypair4Auth.java +++ b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/token/Keypair4Auth.java @@ -24,9 +24,9 @@ * 进程级别公私钥对 * */ -public class RSAKeypair4Auth { +public class Keypair4Auth { - private RSAKeypair4Auth() { + private Keypair4Auth() { } private PrivateKey privateKey; @@ -65,5 +65,5 @@ public void setPublicKeyEncoded(String publicKeyEncoded) { this.publicKeyEncoded = publicKeyEncoded; } - public static RSAKeypair4Auth INSTANCE = new RSAKeypair4Auth(); + public static Keypair4Auth INSTANCE = new Keypair4Auth(); } diff --git a/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/http/TestHttpUtils.java b/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/http/TestHttpUtils.java index 0c56a978537..a4201429620 100644 --- a/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/http/TestHttpUtils.java +++ b/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/http/TestHttpUtils.java @@ -27,32 +27,24 @@ public class TestHttpUtils { @Test public void parseParamFromHeaderValue_normal() { - Assertions.assertEquals("v", HttpUtils.parseParamFromHeaderValue("xx;k=v", "k")); - } - - @Test - public void parseParamFromHeaderValue_normal_ignoreCase() { - Assertions.assertEquals("v", HttpUtils.parseParamFromHeaderValue("xx;K=v", "k")); + Assertions.assertEquals("a", HttpUtils.parseParamFromHeaderValue("key1=a;key2=b", "key1")); + Assertions.assertEquals("b", HttpUtils.parseParamFromHeaderValue("key1=a;key2= b", "key2")); + Assertions.assertEquals("b", HttpUtils.parseParamFromHeaderValue("key1=a; key2=b", "key2")); + Assertions.assertEquals("a", HttpUtils.parseParamFromHeaderValue("key1=\"a\";key2=\"b\"", "key1")); + Assertions.assertEquals("b", HttpUtils.parseParamFromHeaderValue("key1=\"a\";key2=\" b\"", "key2")); + Assertions.assertEquals("b", HttpUtils.parseParamFromHeaderValue("key1=\"a\"; key2=\"b\"", "key2")); + Assertions.assertEquals("b c.txt", HttpUtils.parseParamFromHeaderValue("key1=\"a\"; key2=\"b c.txt\"", "key2")); } @Test public void parseParamFromHeaderValue_null() { - Assertions.assertNull(HttpUtils.parseParamFromHeaderValue(null, "k")); - } - - @Test - public void parseParamFromHeaderValue_noKv() { - Assertions.assertNull(HttpUtils.parseParamFromHeaderValue("xx", "k")); + Assertions.assertNull(HttpUtils.parseParamFromHeaderValue(null, "key")); + Assertions.assertNull(HttpUtils.parseParamFromHeaderValue("key1=a; key2=b", "key")); } @Test - public void parseParamFromHeaderValue_noV() { - Assertions.assertEquals("", HttpUtils.parseParamFromHeaderValue("xx;k=", "k")); - } - - @Test - public void parseParamFromHeaderValue_keyNotFound() { - Assertions.assertNull(HttpUtils.parseParamFromHeaderValue("xx;k=", "kk")); + public void parseParamFromHeaderValue_emptyStr() { + Assertions.assertEquals("", HttpUtils.parseParamFromHeaderValue("key1=a; key2=", "key2")); } @Test @@ -75,7 +67,7 @@ public void uriEncode_chineseAndSpace() { @Test public void uriEncode_failed() { IllegalArgumentException illegalArgumentException = Assertions.assertThrows(IllegalArgumentException.class, - () -> HttpUtils.uriEncodePath(":")); + () -> HttpUtils.uriEncodePath(":")); Assertions.assertEquals("uriEncode failed, path=\":\".", illegalArgumentException.getMessage()); Assertions.assertTrue(illegalArgumentException.getCause() instanceof URISyntaxException); } @@ -114,7 +106,7 @@ public void pathParamEncode_SafeChar() { @Test public void uriDecode_failed() { IllegalArgumentException illegalArgumentException = Assertions.assertThrows(IllegalArgumentException.class, - () -> HttpUtils.uriDecodePath(":")); + () -> HttpUtils.uriDecodePath(":")); Assertions.assertEquals("uriDecode failed, path=\":\".", illegalArgumentException.getMessage()); Assertions.assertTrue(illegalArgumentException.getCause() instanceof URISyntaxException); } @@ -184,4 +176,76 @@ public void getCharsetFromContentType_quotationMarks_needTrim() { Assertions.assertEquals("utf-8", character); } + + @Test + public void splitPath() { + Assertions.assertNull(HttpUtils.splitPathFromUri(null)); + Assertions.assertEquals("", HttpUtils.splitPathFromUri("")); + Assertions.assertEquals(" ", HttpUtils.splitPathFromUri(" ")); + Assertions.assertEquals("\t", HttpUtils.splitPathFromUri("\t")); + Assertions.assertEquals("/", HttpUtils.splitPathFromUri("/")); + Assertions.assertEquals("//", HttpUtils.splitPathFromUri("//")); + Assertions.assertEquals("/abc", HttpUtils.splitPathFromUri("/abc")); + Assertions.assertEquals("//abc", HttpUtils.splitPathFromUri("//abc")); + Assertions.assertEquals("abc", HttpUtils.splitPathFromUri("abc")); + Assertions.assertEquals("a/bc", HttpUtils.splitPathFromUri("a/bc")); + Assertions.assertEquals("/a/bc", HttpUtils.splitPathFromUri("/a/bc")); + Assertions.assertEquals("", HttpUtils.splitPathFromUri("?abc=def")); + + Assertions.assertEquals("/", HttpUtils.splitPathFromUri("http://localhost")); + Assertions.assertEquals("/", HttpUtils.splitPathFromUri("http://localhost ")); + Assertions.assertEquals("/", HttpUtils.splitPathFromUri("http://localhost\t")); + Assertions.assertEquals("/", HttpUtils.splitPathFromUri("http://localhost/")); + Assertions.assertEquals("//", HttpUtils.splitPathFromUri("http://localhost//")); + Assertions.assertEquals("/abc", HttpUtils.splitPathFromUri("http://localhost/abc")); + Assertions.assertEquals("/a/bc", HttpUtils.splitPathFromUri("http://localhost/a/bc")); + Assertions.assertEquals("/abc", HttpUtils.splitPathFromUri("http://localhost/abc?")); + Assertions.assertEquals("/abc", HttpUtils.splitPathFromUri("http://localhost/abc?k=v")); + Assertions.assertEquals("/a/bc", HttpUtils.splitPathFromUri("http://localhost/a/bc?k=v")); + + Assertions.assertEquals("/", HttpUtils.splitPathFromUri("rest://localhost:80")); + Assertions.assertEquals("/", HttpUtils.splitPathFromUri("rest://localhost:80 ")); + Assertions.assertEquals("/", HttpUtils.splitPathFromUri("rest://localhost:80\t")); + Assertions.assertEquals("/", HttpUtils.splitPathFromUri("rest://localhost:80/")); + Assertions.assertEquals("//", HttpUtils.splitPathFromUri("rest://localhost:80//")); + Assertions.assertEquals("/abc", HttpUtils.splitPathFromUri("rest://localhost:80/abc")); + Assertions.assertEquals("/a/bc", HttpUtils.splitPathFromUri("rest://localhost:80/a/bc")); + Assertions.assertEquals("/abc", HttpUtils.splitPathFromUri("rest://localhost:80/abc?")); + Assertions.assertEquals("/a/bc", HttpUtils.splitPathFromUri("rest://localhost:80/a/bc?")); + Assertions.assertEquals("/abc", HttpUtils.splitPathFromUri("rest://localhost:80/abc?k=v&m=n")); + } + + @Test + public void splitQuery() { + Assertions.assertNull(HttpUtils.splitQueryFromUri(null)); + Assertions.assertNull(HttpUtils.splitQueryFromUri("")); + Assertions.assertNull(HttpUtils.splitQueryFromUri(" ")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("\t")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("/")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("//")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("/abc")); + Assertions.assertEquals("", HttpUtils.splitQueryFromUri("/abc?")); + Assertions.assertEquals("k=v", HttpUtils.splitQueryFromUri("/abc?k=v")); + Assertions.assertEquals("k=v&m=n", HttpUtils.splitQueryFromUri("/abc?k=v&m=n")); + + Assertions.assertNull(HttpUtils.splitQueryFromUri("http://localhost")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("http://localhost ")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("http://localhost\t")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("http://localhost/")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("http://localhost//")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("http://localhost/abc")); + Assertions.assertEquals("", HttpUtils.splitQueryFromUri("http://localhost/abc?")); + Assertions.assertEquals("k=v", HttpUtils.splitQueryFromUri("http://localhost/abc?k=v")); + Assertions.assertEquals("k=v&m=n", HttpUtils.splitQueryFromUri("http://localhost/abc?k=v&m=n")); + + Assertions.assertNull(HttpUtils.splitQueryFromUri("http://localhost:80")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("http://localhost:80 ")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("http://localhost:80\t")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("http://localhost:80/")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("http://localhost:80//")); + Assertions.assertNull(HttpUtils.splitQueryFromUri("http://localhost:80/abc")); + Assertions.assertEquals("", HttpUtils.splitQueryFromUri("http://localhost:80/abc?")); + Assertions.assertEquals("k=v", HttpUtils.splitQueryFromUri("http://localhost:80/abc?k=v")); + Assertions.assertEquals("k=v&m=n", HttpUtils.splitQueryFromUri("http://localhost:80/abc?k=v&m=n")); + } } diff --git a/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/utils/TestRSAUtil.java b/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/utils/TestRSAUtil.java index b259c5edbbc..ab6458cffc2 100644 --- a/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/utils/TestRSAUtil.java +++ b/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/utils/TestRSAUtil.java @@ -29,14 +29,14 @@ public class TestRSAUtil { @Test public void testSignVerify() throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, SignatureException { - RSAKeyPairEntry rsaKeyPairEntry = RSAUtils.generateRSAKeyPair(); + KeyPairEntry keyPairEntry = KeyPairUtils.generateRSAKeyPair(); - Assertions.assertNotNull(rsaKeyPairEntry.getPublicKeyEncoded()); - Assertions.assertNotNull(rsaKeyPairEntry.getPrivateKey()); - Assertions.assertNotNull(rsaKeyPairEntry.getPublicKey()); + Assertions.assertNotNull(keyPairEntry.getPublicKeyEncoded()); + Assertions.assertNotNull(keyPairEntry.getPrivateKey()); + Assertions.assertNotNull(keyPairEntry.getPublicKey()); String testContent = "instance-id@201711201930@randomstr"; - String signstr = RSAUtils.sign(testContent, rsaKeyPairEntry.getPrivateKey()); - Assertions.assertTrue(RSAUtils.verify(rsaKeyPairEntry.getPublicKeyEncoded(), signstr, testContent)); + String signstr = KeyPairUtils.sign(testContent, keyPairEntry.getPrivateKey()); + Assertions.assertTrue(KeyPairUtils.verify(keyPairEntry.getPublicKeyEncoded(), signstr, testContent)); } @Test @@ -48,6 +48,6 @@ public void testSignVerify2() "e8a04b54cf2711e7b701286ed488fc20@c8636e5acf1f11e7b701286ed488fc20@1511315597475@9t0tp8ce80SUM5ts6iRGjFJMvCdQ7uvhpyh0RM7smKm3p4wYOrojr4oT1Pnwx7xwgcgEFbQdwPJxIMfivpQ1rHGqiLp67cjACvJ3Ke39pmeAVhybsLADfid6oSjscFaJ"; String pubKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxKl5TNUTec7fL2degQcCk6vKf3c0wsfNK5V6elKzjWxm0MwbRj/UeR20VSnicBmVIOWrBS9LiERPPvjmmWUOSS2vxwr5XfhBhZ07gCAUNxBOTzgMo5nE45DhhZu5Jzt5qSV6o10Kq7+fCCBlDZ1UoWxZceHkUt5AxcrhEDulFjQIDAQAB"; - Assertions.assertTrue(RSAUtils.verify(pubKey, sign, content)); + Assertions.assertTrue(KeyPairUtils.verify(pubKey, sign, content)); } } diff --git a/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/ConfigUtil.java b/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/ConfigUtil.java index d0e7730ffc4..10d9bc7b3ea 100644 --- a/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/ConfigUtil.java +++ b/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/ConfigUtil.java @@ -22,18 +22,19 @@ import static org.apache.servicecomb.foundation.common.base.ServiceCombConstants.CONFIG_SERVICECOMB_PREFIX; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.stream.Collectors; import javax.annotation.Nonnull; -import com.google.common.annotations.VisibleForTesting; import org.apache.commons.configuration.AbstractConfiguration; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.EnvironmentConfiguration; @@ -52,7 +53,12 @@ import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; +import com.google.common.annotations.VisibleForTesting; import com.netflix.config.ConcurrentCompositeConfiguration; import com.netflix.config.ConcurrentMapConfiguration; import com.netflix.config.ConfigurationManager; @@ -342,4 +348,18 @@ public static CopyOnWriteArraySet getCallbacks(DynamicProperty propert throw new IllegalStateException(e); } } + + public static Set propertiesWithPrefix(Environment environment, String prefix) { + Set result = new HashSet<>(); + for (PropertySource propertySource : ((ConfigurableEnvironment) environment).getPropertySources()) { + if (propertySource instanceof EnumerablePropertySource) { + for (String key : ((EnumerablePropertySource) propertySource).getPropertyNames()) { + if (key.startsWith(prefix)) { + result.add(key); + } + } + } + } + return result; + } } diff --git a/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/inject/InjectBeanPostProcessor.java b/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/inject/InjectBeanPostProcessor.java index a5202504dbd..1e8cd763c59 100644 --- a/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/inject/InjectBeanPostProcessor.java +++ b/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/inject/InjectBeanPostProcessor.java @@ -22,13 +22,17 @@ import org.apache.servicecomb.config.priority.PriorityPropertyManager; import org.apache.servicecomb.foundation.common.utils.BeanUtils; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @Component public class InjectBeanPostProcessor implements BeanPostProcessor { private final PriorityPropertyManager priorityPropertyManager; + @Autowired + @Lazy public InjectBeanPostProcessor(PriorityPropertyManager priorityPropertyManager) { this.priorityPropertyManager = priorityPropertyManager; } diff --git a/foundations/foundation-config/src/test/java/org/apache/servicecomb/config/TestYAMLUtil.java b/foundations/foundation-config/src/test/java/org/apache/servicecomb/config/TestYAMLUtil.java index 6694ace8644..4abafe8765e 100644 --- a/foundations/foundation-config/src/test/java/org/apache/servicecomb/config/TestYAMLUtil.java +++ b/foundations/foundation-config/src/test/java/org/apache/servicecomb/config/TestYAMLUtil.java @@ -51,19 +51,6 @@ public void setName(String name) { public void testSafeParser() { Person person = YAMLUtil.parserObject("name: hello", Person.class); Assertions.assertEquals("hello", person.getName()); - - person = YAMLUtil.parserObject("!!org.apache.servicecomb.config.TestYAMLUtil$Person\n" - + "name: hello", Person.class); - Assertions.assertEquals("hello", person.getName()); - - person = YAMLUtil.parserObject("!!org.apache.servicecomb.config.TestYAMLUtil$UnsafePerson\n" - + "name: hello", Person.class); - Assertions.assertEquals("hello", person.getName()); - - // using Object.class is not safe, do not used in product code. - Object object = YAMLUtil.parserObject("!!org.apache.servicecomb.config.TestYAMLUtil$UnsafePerson\n" - + "name: hello", Object.class); - Assertions.assertEquals("hello", ((UnsafePerson) object).getName()); } @Test diff --git a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/DiscoveryManager.java b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/DiscoveryManager.java index 1afc569df0c..df8713c74c1 100644 --- a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/DiscoveryManager.java +++ b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/DiscoveryManager.java @@ -73,9 +73,10 @@ public MicroserviceInstances findServiceInstances(String appId, String serviceNa // default values not suitable for aggregate, reset. result.setNeedRefresh(false); result.setMicroserviceNotExist(true); + result.setRevision(revision); discoveryList .forEach(discovery -> { - MicroserviceInstances instances = discovery.findServiceInstances(appId, serviceName, versionRule, revision); + MicroserviceInstances instances = discovery.findServiceInstances(appId, serviceName, versionRule); result.mergeMicroserviceInstances(instances); }); diff --git a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/RegistrationManager.java b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/RegistrationManager.java index be59114a5e7..f3c61b0e11c 100644 --- a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/RegistrationManager.java +++ b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/RegistrationManager.java @@ -28,7 +28,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import com.google.common.annotations.VisibleForTesting; import org.apache.http.client.utils.URIBuilder; import org.apache.servicecomb.foundation.common.event.EnableExceptionPropagation; import org.apache.servicecomb.foundation.common.event.EventManager; @@ -51,6 +50,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.annotations.VisibleForTesting; import com.google.common.eventbus.Subscribe; import com.netflix.config.DynamicPropertyFactory; @@ -64,10 +64,10 @@ public class RegistrationManager { private static final String PUBLISH_PORT = "servicecomb.{transport_name}.publishPort"; - private static final SwaggerLoader swaggerLoader = new SwaggerLoader(); - public static RegistrationManager INSTANCE = new RegistrationManager(); + private final SwaggerLoader swaggerLoader = new SwaggerLoader(); + private final List registrationList = new ArrayList<>(); private Registration primary; @@ -85,6 +85,11 @@ public static void setINSTANCE(RegistrationManager INSTANCE) { RegistrationManager.INSTANCE = INSTANCE; } + @VisibleForTesting + public static void renewInstance() { + RegistrationManager.INSTANCE = new RegistrationManager(); + } + public MicroserviceInstance getMicroserviceInstance() { return primary.getMicroserviceInstance(); } @@ -308,7 +313,6 @@ private static IpPort genPublishIpPort(String schema, IpPort ipPort) { .getHostAddress(); } - return new IpPort(publicAddressSetting, publishPort); } diff --git a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/Discovery.java b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/Discovery.java index 593fa43969f..6205bf98f1e 100644 --- a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/Discovery.java +++ b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/Discovery.java @@ -73,6 +73,10 @@ default MicroserviceInstances findServiceInstances(String appId, String serviceN return findServiceInstances(appId, serviceName, versionRule, null); } + /** + * @Depraction revision is not used for Discovery implementations. + */ + @Deprecated default MicroserviceInstances findServiceInstances(String appId, String serviceName, String versionRule, String revision) { return null; diff --git a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/event/MicroserviceInstanceChangedEvent.java b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/event/MicroserviceInstanceChangedEvent.java index c850be99e62..42d86940fa9 100644 --- a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/event/MicroserviceInstanceChangedEvent.java +++ b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/event/MicroserviceInstanceChangedEvent.java @@ -23,6 +23,9 @@ /** * Created by on 2016/12/25. + * + * This event is sent by service-center watcher, and indicates the status(CREATE/UPDATE/DELETE, etc.) + * of one instance change. */ public class MicroserviceInstanceChangedEvent { private WatchAction action; diff --git a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/registry/MicroserviceInstance.java b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/registry/MicroserviceInstance.java index fdab707d6db..f7d41bf3a45 100644 --- a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/registry/MicroserviceInstance.java +++ b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/registry/MicroserviceInstance.java @@ -23,6 +23,7 @@ import java.util.Map; import org.apache.commons.configuration.Configuration; +import org.apache.commons.lang3.StringUtils; import org.apache.servicecomb.config.BootStrapProperties; import org.apache.servicecomb.registry.config.InstancePropertiesLoader; import org.apache.servicecomb.registry.definition.DefinitionConst; @@ -196,14 +197,19 @@ public static MicroserviceInstance createFromDefinition(Configuration configurat private static void loadDataCenterInfo(MicroserviceInstance microserviceInstance) { String dataCenterName = DynamicPropertyFactory.getInstance() - .getStringProperty("servicecomb.datacenter.name", "default") + .getStringProperty("servicecomb.datacenter.name", null) .get(); + if (StringUtils.isEmpty(dataCenterName)) { + return; + } + String region = DynamicPropertyFactory.getInstance(). + getStringProperty("servicecomb.datacenter.region", null).get(); + String availableZone = DynamicPropertyFactory.getInstance(). + getStringProperty("servicecomb.datacenter.availableZone", null).get(); DataCenterInfo dataCenterInfo = new DataCenterInfo(); dataCenterInfo.setName(dataCenterName); - dataCenterInfo.setRegion(DynamicPropertyFactory.getInstance(). - getStringProperty("servicecomb.datacenter.region", "default").get()); - dataCenterInfo.setAvailableZone(DynamicPropertyFactory.getInstance(). - getStringProperty("servicecomb.datacenter.availableZone", "default").get()); + dataCenterInfo.setRegion(region); + dataCenterInfo.setAvailableZone(availableZone); microserviceInstance.setDataCenterInfo(dataCenterInfo); } } diff --git a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/registry/MicroserviceInstances.java b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/registry/MicroserviceInstances.java index 1a9e0fdff61..c6df4921602 100644 --- a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/registry/MicroserviceInstances.java +++ b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/api/registry/MicroserviceInstances.java @@ -17,6 +17,15 @@ package org.apache.servicecomb.registry.api.registry; +import java.nio.charset.StandardCharsets; + +import javax.crypto.Mac; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.digest.HmacAlgorithms; +import org.apache.commons.codec.digest.HmacUtils; +import org.apache.commons.lang3.StringUtils; + public class MicroserviceInstances { private boolean microserviceNotExist; @@ -65,17 +74,22 @@ public MicroserviceInstances setInstancesResponse(FindInstancesResponse instance public void mergeMicroserviceInstances(MicroserviceInstances other) { mergeNeedRefresh(other.needRefresh); mergeMicroserviceNotExist(other.microserviceNotExist); - mergeRevision(other.revision); + mergeRevision(other); mergeInstanceResponse(other.getInstancesResponse()); } - private void mergeRevision(String revision) { - if (revision == null) { - return; + private void mergeRevision(MicroserviceInstances other) { + if (!other.isMicroserviceNotExist() && other.needRefresh) { + Mac mac = HmacUtils.getInitializedMac(HmacAlgorithms.HMAC_SHA_1, stringToBytes(this.revision)); + this.revision = Base64.encodeBase64String(mac.doFinal(stringToBytes(other.revision))); } - if (this.revision == null || this.revision.compareTo(revision) < 0) { - this.revision = revision; + } + + private byte[] stringToBytes(String input) { + if (StringUtils.isEmpty(input)) { + input = "@"; } + return input.getBytes(StandardCharsets.UTF_8); } private void mergeMicroserviceNotExist(boolean microserviceNotExist) { diff --git a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/consumer/MicroserviceManager.java b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/consumer/MicroserviceManager.java index e873a94dbcc..862ea506c3a 100644 --- a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/consumer/MicroserviceManager.java +++ b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/consumer/MicroserviceManager.java @@ -50,7 +50,7 @@ public Map getVersionsByName() { } /** - * update instance information triggered by first timeout pull + * update instance information triggered by first time pull */ public MicroserviceVersions getOrCreateMicroserviceVersions(String microserviceName) { // do not use ConcurrentHashMap computeIfAbsent for versionsByName @@ -126,7 +126,7 @@ public void pullInstances() { } /** - * update instance information triggered by event + * Update instance information triggered by event, called when instance list changed. */ public void onMicroserviceInstanceChanged(MicroserviceInstanceChangedEvent changedEvent) { synchronized (lock) { diff --git a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/consumer/MicroserviceVersions.java b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/consumer/MicroserviceVersions.java index da5f99cb564..c84925a4783 100644 --- a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/consumer/MicroserviceVersions.java +++ b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/consumer/MicroserviceVersions.java @@ -42,6 +42,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.annotations.VisibleForTesting; + public class MicroserviceVersions { private static final Logger LOGGER = LoggerFactory.getLogger(MicroserviceVersions.class); @@ -120,13 +122,18 @@ public String getShortName() { return shortName; } + @VisibleForTesting public Map getVersions() { - return versions; + synchronized (lock) { + return versions; + } } @SuppressWarnings("unchecked") public T getVersion(String serviceId) { - return (T) versions.get(serviceId); + synchronized (lock) { + return (T) versions.get(serviceId); + } } public String getRevision() { @@ -141,6 +148,10 @@ public List getPulledInstances() { return pulledInstances; } + public List getInstances() { + return new ArrayList(instances); + } + public long getLastPullTime() { return lastPullTime; } @@ -181,7 +192,7 @@ public void pullInstances() { protected MicroserviceInstances findServiceInstances() { return DiscoveryManager.INSTANCE.findServiceInstances(appId, - microserviceName, + shortName, DefinitionConst.VERSION_RULE_ALL, revision); } @@ -226,15 +237,21 @@ private void setInstances(List pulledInstances, String rev synchronized (lock) { MergedInstances mergedInstances = mergeInstances(pulledInstances, instances); instances = mergedInstances.instanceIdMap.values(); - // clear cache - versions.forEach((key, value) -> value.setInstances(new ArrayList<>())); + + // update instances for (Entry> entry : mergedInstances.microserviceIdMap.entrySet()) { - // always update microservice versions, because we allow microservice info override, like schema info - MicroserviceVersion newVersion = createMicroserviceVersion(entry.getKey(), entry.getValue()); - newVersion.setInstances(entry.getValue()); - versions.put(entry.getKey(), newVersion); + versions.computeIfAbsent(entry.getKey(), + microserviceId -> createMicroserviceVersion(microserviceId, entry.getValue())) + .setInstances(entry.getValue()); } + // set instances to empty for no instance versions + versions.forEach((key, value) -> { + if (!mergedInstances.microserviceIdMap.containsKey(key)) { + value.setInstances(new ArrayList<>()); + } + }); + for (MicroserviceVersionRule microserviceVersionRule : versionRules.values()) { microserviceVersionRule.update(versions, instances); } @@ -318,10 +335,11 @@ protected boolean isEventAccept(MicroserviceInstanceChangedEvent changedEvent) { } public void destroy() { - for (MicroserviceVersion microserviceVersion : versions.values()) { - microserviceVersion.destroy(); + synchronized (lock) { + for (MicroserviceVersion microserviceVersion : versions.values()) { + microserviceVersion.destroy(); + } } - appManager.getEventBus().post(new DestroyMicroserviceEvent(this)); } } diff --git a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/discovery/AbstractDiscoveryFilter.java b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/discovery/AbstractDiscoveryFilter.java index 36e88770ee6..d77a9227fd2 100644 --- a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/discovery/AbstractDiscoveryFilter.java +++ b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/discovery/AbstractDiscoveryFilter.java @@ -18,6 +18,7 @@ package org.apache.servicecomb.registry.discovery; import java.util.HashMap; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,9 +40,12 @@ public DiscoveryTreeNode discovery(DiscoveryContext context, DiscoveryTreeNode p String childName = findChildName(context, parent); DiscoveryTreeNode node = parent.child(childName); if (node == null) { - LOGGER.warn("discovery filter {} return null.", this.getClass().getName()); + LOGGER.warn("discovery filter {}/{} return null.", this.getClass().getName(), childName); return new DiscoveryTreeNode().subName(parent, "empty").data(new HashMap<>()); } + if (node.data() == null || ((Map) node.data()).isEmpty()) { + LOGGER.info("discovery filter {}/{} return empty data.", this.getClass().getName(), childName); + } return node; } diff --git a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/discovery/AbstractEndpointDiscoveryFilter.java b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/discovery/AbstractEndpointDiscoveryFilter.java index 1aa2d61558b..9ea13139241 100644 --- a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/discovery/AbstractEndpointDiscoveryFilter.java +++ b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/discovery/AbstractEndpointDiscoveryFilter.java @@ -17,11 +17,11 @@ package org.apache.servicecomb.registry.discovery; -import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Map; +import org.apache.servicecomb.foundation.common.net.URIEndpointObject; import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +31,8 @@ public abstract class AbstractEndpointDiscoveryFilter implements DiscoveryFilter private static final String ALL_TRANSPORT = ""; + private static final String WEBSOCKET_TRANSPORT = "websocket"; + @Override public boolean isGroupingFilter() { return true; @@ -50,8 +52,11 @@ protected DiscoveryTreeNode createDiscoveryTreeNode(String expectTransportName, for (MicroserviceInstance instance : ((Map) parent.data()).values()) { for (String endpoint : instance.getEndpoints()) { try { - URI uri = URI.create(endpoint); - String transportName = uri.getScheme(); + final URIEndpointObject endpointObject = new URIEndpointObject(endpoint); + String transportName = endpointObject.getScheme(); + if (endpointObject.isWebsocketEnabled() && WEBSOCKET_TRANSPORT.equals(expectTransportName)) { + transportName = WEBSOCKET_TRANSPORT; + } if (!isTransportNameMatch(transportName, expectTransportName)) { continue; } @@ -74,7 +79,10 @@ protected DiscoveryTreeNode createDiscoveryTreeNode(String expectTransportName, } protected boolean isTransportNameMatch(String transportName, String expectTransportName) { - return ALL_TRANSPORT.equals(expectTransportName) || transportName.equals(expectTransportName); + if (ALL_TRANSPORT.equals(expectTransportName)) { + return true; + } + return transportName.equals(expectTransportName); } protected abstract String findTransportName(DiscoveryContext context, DiscoveryTreeNode parent); diff --git a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/swagger/SwaggerLoader.java b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/swagger/SwaggerLoader.java index 8d08d24e286..6c49ae44208 100644 --- a/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/swagger/SwaggerLoader.java +++ b/foundations/foundation-registry/src/main/java/org/apache/servicecomb/registry/swagger/SwaggerLoader.java @@ -52,6 +52,9 @@ public class SwaggerLoader { // third key : schemaId private final Map>> apps = new ConcurrentHashMapEx<>(); + // first key: appId + microservice short name + service id + private final Map remoteSwagger = new ConcurrentHashMapEx<>(); + public SwaggerLoader() { } @@ -117,7 +120,7 @@ public void registerSwagger(String appId, String shortName, String schemaId, Swa apps.computeIfAbsent(appId, k -> new ConcurrentHashMapEx<>()) .computeIfAbsent(shortName, k -> new ConcurrentHashMapEx<>()) .put(schemaId, swagger); - LOGGER.info("register swagger appId={}, name={}, schemaId={}.", appId, shortName, schemaId); + LOGGER.debug("register swagger appId={}, name={}, schemaId={}.", appId, shortName, schemaId); } public void unregisterSwagger(String appId, String shortName, String schemaId) { @@ -136,7 +139,7 @@ public Swagger loadSwagger(Microservice microservice, Collection instances, String schemaId) { - String schemaContent = DiscoveryManager.INSTANCE.getSchema(microservice.getServiceId(), instances, schemaId); - if (schemaContent != null) { - LOGGER.info( - "load schema from service center, appId={}, microserviceName={}, version={}, serviceId={}, schemaId={}.", - microservice.getAppId(), - microservice.getServiceName(), - microservice.getVersion(), - microservice.getServiceId(), - schemaId); - LOGGER.debug(schemaContent); - return SwaggerUtils.parseAndValidateSwagger(schemaContent); + String key = microservice.getServiceId() + "." + schemaId; + Swagger result = remoteSwagger.computeIfAbsent(key, k -> { + String schemaContent = DiscoveryManager.INSTANCE.getSchema(microservice.getServiceId(), instances, schemaId); + if (schemaContent != null) { + LOGGER.info( + "load schema from service center, appId={}, microserviceName={}, version={}, serviceId={}, schemaId={}.", + microservice.getAppId(), + microservice.getServiceName(), + microservice.getVersion(), + microservice.getServiceId(), + schemaId); + LOGGER.debug(schemaContent); + return SwaggerUtils.parseAndValidateSwagger(schemaContent); + } + return null; + }); + + if (result != null) { + return result; } LOGGER.warn("no schema in local, and can not get schema from service center, " diff --git a/foundations/foundation-registry/src/test/java/org/apache/servicecomb/registry/discovery/TestDiscoveryTree.java b/foundations/foundation-registry/src/test/java/org/apache/servicecomb/registry/discovery/TestDiscoveryTree.java index 4aed1423832..4868654cae2 100644 --- a/foundations/foundation-registry/src/test/java/org/apache/servicecomb/registry/discovery/TestDiscoveryTree.java +++ b/foundations/foundation-registry/src/test/java/org/apache/servicecomb/registry/discovery/TestDiscoveryTree.java @@ -18,7 +18,12 @@ package org.apache.servicecomb.registry.discovery; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.servicecomb.config.ConfigUtil; import org.apache.servicecomb.foundation.common.cache.VersionedCache; @@ -26,6 +31,8 @@ import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils; import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; import org.apache.servicecomb.registry.DiscoveryManager; +import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; +import org.apache.servicecomb.registry.api.registry.MicroserviceInstanceStatus; import org.apache.servicecomb.registry.cache.InstanceCacheManager; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; @@ -42,6 +49,7 @@ public class TestDiscoveryTree { public void before() { ConfigUtil.installDynamicConfig(); } + @AfterEach public void tearDown() { ArchaiusUtils.resetConfig(); @@ -176,7 +184,6 @@ public void easyDiscovery() { Mockito.when(DiscoveryManager.INSTANCE.getInstanceCacheManager()).thenReturn(instanceCacheManager); Mockito.when(instanceCacheManager.getOrCreateVersionedCache(null, null, null)).thenReturn(parent); - result = discoveryTree.discovery(context, null, null, null); Assertions.assertEquals(parent.name(), result.name()); Assertions.assertEquals(parent.cacheVersion(), result.cacheVersion()); @@ -203,7 +210,7 @@ public DiscoveryTreeNode discovery(DiscoveryContext context, DiscoveryTreeNode p discoveryTree.addFilter(filter); ServiceCombException exception = Assertions.assertThrows(ServiceCombException.class, - () -> result = discoveryTree.discovery(context, null, null, null)); + () -> result = discoveryTree.discovery(context, null, null, null)); Assertions.assertEquals(filter.getClass().getName() + " discovery return null.", exception.getMessage()); } @@ -278,4 +285,51 @@ public void getOrCreateRoot_tempRoot() { Assertions.assertEquals(inputCache.cacheVersion(), root.cacheVersion()); Assertions.assertNotSame(discoveryTree.getRoot(), root); } + + @Test + @SuppressWarnings("unchecked") + public void test_one_service_concurrent_correct() throws Exception { + DiscoveryTree discoveryTree = new DiscoveryTree(); + DiscoveryContext discoveryContext = new DiscoveryContext(); + discoveryTree.addFilter(new InstanceStatusDiscoveryFilter()); + + Map service1 = new HashMap<>(); + MicroserviceInstance instance1 = Mockito.mock(MicroserviceInstance.class); + MicroserviceInstance instance2 = Mockito.mock(MicroserviceInstance.class); + Mockito.when(instance1.getInstanceId()).thenReturn("instance1"); + Mockito.when(instance1.getStatus()).thenReturn(MicroserviceInstanceStatus.UP); + Mockito.when(instance2.getInstanceId()).thenReturn("instance2"); + Mockito.when(instance2.getStatus()).thenReturn(MicroserviceInstanceStatus.UP); + service1.put(instance1.getInstanceId(), instance1); + service1.put(instance2.getInstanceId(), instance2); + + InstanceCacheManager instanceCacheManager = Mockito.mock(InstanceCacheManager.class); + DiscoveryManager.INSTANCE = Mockito.spy(DiscoveryManager.INSTANCE); + Mockito.when(DiscoveryManager.INSTANCE.getInstanceCacheManager()).thenReturn(instanceCacheManager); + + VersionedCache expects0 = new VersionedCache().autoCacheVersion().name("0+").data(service1); + VersionedCache[] expects999 = new VersionedCache[999]; + for (int i = 0; i < 999; i++) { + expects999[i] = new VersionedCache().name("0+").data(service1).cacheVersion(i + 1); + } + Mockito.when(instanceCacheManager.getOrCreateVersionedCache("app", "service1", + "0+")).thenReturn(expects0, expects999); + + CountDownLatch countDownLatch = new CountDownLatch(1000); + AtomicInteger success = new AtomicInteger(0); + for (int i = 0; i < 10; i++) { + new Thread(() -> { + for (int j = 0; j < 100; j++) { + DiscoveryTreeNode result = discoveryTree.discovery(discoveryContext, "app", "service1", "0+"); + if (((Map) result.data()).size() == 2) { + success.getAndIncrement(); + } + countDownLatch.countDown(); + } + }).start(); + } + + countDownLatch.await(3000, TimeUnit.MILLISECONDS); + Assertions.assertEquals(1000, success.get()); + } } diff --git a/foundations/foundation-spi/src/main/java/org/apache/servicecomb/foundation/auth/AuthHeaderProvider.java b/foundations/foundation-spi/src/main/java/org/apache/servicecomb/foundation/auth/AuthHeaderProvider.java index 313c19ee5ba..0e036e21a58 100644 --- a/foundations/foundation-spi/src/main/java/org/apache/servicecomb/foundation/auth/AuthHeaderProvider.java +++ b/foundations/foundation-spi/src/main/java/org/apache/servicecomb/foundation/auth/AuthHeaderProvider.java @@ -21,11 +21,21 @@ import java.util.Map; public interface AuthHeaderProvider { - default Map authHeaders() { + /** + * Obtain RBAC authentication request header, host is the key of cache + * + * @param host engine address ip + * @return auth headers + */ + default Map authHeaders(String host) { return new HashMap<>(0); } default Map getSignAuthHeaders(SignRequest request) { - return authHeaders(); + String host = ""; + if (request != null && request.getEndpoint() != null) { + host = request.getEndpoint().getHost(); + } + return authHeaders(host); } } diff --git a/foundations/foundation-ssl/src/main/java/org/apache/servicecomb/foundation/ssl/SSLManager.java b/foundations/foundation-ssl/src/main/java/org/apache/servicecomb/foundation/ssl/SSLManager.java index aeb54ffab7f..a0dbafbe035 100644 --- a/foundations/foundation-ssl/src/main/java/org/apache/servicecomb/foundation/ssl/SSLManager.java +++ b/foundations/foundation-ssl/src/main/java/org/apache/servicecomb/foundation/ssl/SSLManager.java @@ -184,7 +184,7 @@ public static SSLSocket createSSLSocket(SSLOption option, SSLCustom custom) { (SSLSocket) factory.createSocket(); socket.setEnabledProtocols(option.getProtocols().split(",")); String[] supported = socket.getSupportedCipherSuites(); - String[] enabled = option.getCiphers().split(","); + String[] enabled = option.getCiphers().split("\\s*,\\s*"); socket.setEnabledCipherSuites(getEnabledCiphers(supported, enabled)); return socket; } catch (UnknownHostException e) { diff --git a/foundations/foundation-ssl/src/test/resources/client.ssl.properties b/foundations/foundation-ssl/src/test/resources/client.ssl.properties index 4d25cd495bb..828e1de6b8a 100644 --- a/foundations/foundation-ssl/src/test/resources/client.ssl.properties +++ b/foundations/foundation-ssl/src/test/resources/client.ssl.properties @@ -17,7 +17,8 @@ #########SSL options ssl.protocols=TLSv1.3,TLSv1.2,TLSv1.1,TLSv1,SSLv2Hello -ssl.ciphers=TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA +## test with extra blank +ssl.ciphers=TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA ssl.authPeer=true ssl.checkCN.host=false ssl.checkCN.white=true diff --git a/foundations/foundation-test-scaffolding/src/main/java/io/vertx/core/impl/SyncContext.java b/foundations/foundation-test-scaffolding/src/main/java/io/vertx/core/impl/SyncContext.java index eb25a08f56f..5295ab158c5 100644 --- a/foundations/foundation-test-scaffolding/src/main/java/io/vertx/core/impl/SyncContext.java +++ b/foundations/foundation-test-scaffolding/src/main/java/io/vertx/core/impl/SyncContext.java @@ -16,20 +16,39 @@ */ package io.vertx.core.impl; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +import io.netty.channel.EventLoop; +import io.vertx.codegen.annotations.Nullable; import io.vertx.core.AsyncResult; +import io.vertx.core.Context; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.Promise; +import io.vertx.core.ThreadingModel; +import io.vertx.core.json.JsonObject; +import io.vertx.core.spi.tracing.VertxTracer; + +/** + * This class is created to make vertx unit test easier + */ +@SuppressWarnings({"rawtypes"}) +public class SyncContext extends ContextBase implements ContextInternal { + private static final long serialVersionUID = -6209656149925076980L; -public class SyncContext extends EventLoopContext { protected VertxInternal owner; + protected Executor executor = Executors.newSingleThreadExecutor(); + public SyncContext() { - this(null); + this(0); } - public SyncContext(VertxInternal vertx) { - super(vertx, null, null, null, null, null, null); + public SyncContext(int localsLength) { + super(localsLength); } @Override @@ -37,15 +56,81 @@ public VertxInternal owner() { return owner; } - public void setOwner(VertxInternal owner) { - this.owner = owner; + @Override + public Context exceptionHandler(@Nullable Handler handler) { + return null; + } + + @Override + public @Nullable Handler exceptionHandler() { + return null; + } + + @Override + public boolean inThread() { + return false; + } + + @Override + public void emit(T t, Handler handler) { + + } + + @Override + public void execute(Runnable runnable) { + + } + + @Override + public void execute(T t, Handler handler) { + + } + + @Override + public Future close() { + return Future.succeededFuture(); + } + + @Override + public void reportException(Throwable throwable) { + + } + + @Override + public ConcurrentMap contextData() { + return null; + } + + @Override + public ClassLoader classLoader() { + return null; + } + + @Override + public WorkerPool workerPool() { + return null; + } + + @Override + public VertxTracer tracer() { + return null; + } + + @Override + public ContextInternal duplicate() { + return null; } @Override - protected void runOnContext(ContextInternal ctx, Handler action) { - action.handle(null); + public CloseFuture closeFuture() { + return null; } + public void setOwner(VertxInternal owner) { + this.owner = owner; + } + + public static void syncExecuteBlocking(Handler> blockingCodeHandler, Handler> asyncResultHandler) { Promise res = Promise.promise(); @@ -70,7 +155,6 @@ private static Future syncExecuteBlocking(Handler> blockingCod return res.future(); } - res.complete(); return res.future(); } @@ -80,8 +164,81 @@ public Future executeBlockingInternal(Handler> action) { } @Override + public Future executeBlockingInternal(Callable callable) { + return null; + } + + @Override + public Future executeBlockingInternal(Handler> handler, boolean b) { + return null; + } + + @Override + public Future executeBlockingInternal(Callable callable, boolean b) { + return null; + } + + @Override + public Deployment getDeployment() { + return null; + } + + @Override + public Executor executor() { + return executor; + } + + @Override + public EventLoop nettyEventLoop() { + return null; + } + + @Override + @Deprecated + public Future executeBlocking(Handler> handler, TaskQueue taskQueue) { + return null; + } + + @Override + public Future executeBlocking(Callable callable, TaskQueue taskQueue) { + return null; + } + + @Override + @Deprecated public void executeBlocking(Handler> blockingCodeHandler, boolean ordered, Handler> asyncResultHandler) { syncExecuteBlocking(blockingCodeHandler, asyncResultHandler); } + + @Override + public Future<@Nullable T> executeBlocking(Callable callable, boolean b) { + return null; + } + + @Override + @Deprecated + public Future<@Nullable T> executeBlocking(Handler> handler, boolean b) { + return null; + } + + @Override + public @Nullable JsonObject config() { + return null; + } + + @Override + public boolean isEventLoopContext() { + return false; + } + + @Override + public boolean isWorkerContext() { + return false; + } + + @Override + public ThreadingModel threadingModel() { + return null; + } } diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/VertxTLSBuilder.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/VertxTLSBuilder.java index fae651c4512..8a2012e3b86 100644 --- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/VertxTLSBuilder.java +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/VertxTLSBuilder.java @@ -32,6 +32,7 @@ import io.vertx.core.http.ClientAuth; import io.vertx.core.http.HttpClientOptions; +import io.vertx.core.http.WebSocketClientOptions; import io.vertx.core.net.ClientOptionsBase; import io.vertx.core.net.JksOptions; import io.vertx.core.net.NetServerOptions; @@ -86,6 +87,18 @@ public static void buildHttpClientOptions(String sslKey, HttpClientOptions httpC buildHttpClientOptions(sslOption, sslCustom, httpClientOptions); } + public static void buildWebSocketClientOptions(String sslKey, WebSocketClientOptions webSocketClientOptions) { + SSLOptionFactory factory = SSLOptionFactory.createSSLOptionFactory(sslKey, null); + SSLOption sslOption; + if (factory == null) { + sslOption = SSLOption.buildFromYaml(sslKey); + } else { + sslOption = factory.createSSLOption(); + } + SSLCustom sslCustom = SSLCustom.createSSLCustom(sslOption.getSslCustomClass()); + buildWebSocketClientOptions(sslOption, sslCustom, webSocketClientOptions); + } + public static HttpClientOptions buildHttpClientOptions(SSLOption sslOption, SSLCustom sslCustom, HttpClientOptions httpClientOptions) { buildClientOptionsBase(sslOption, sslCustom, httpClientOptions); @@ -93,6 +106,13 @@ public static HttpClientOptions buildHttpClientOptions(SSLOption sslOption, SSLC return httpClientOptions; } + public static WebSocketClientOptions buildWebSocketClientOptions(SSLOption sslOption, SSLCustom sslCustom, + WebSocketClientOptions webSocketClientOptions) { + buildClientOptionsBase(sslOption, sslCustom, webSocketClientOptions); + webSocketClientOptions.setVerifyHost(sslOption.isCheckCNHost()); + return webSocketClientOptions; + } + public static ClientOptionsBase buildClientOptionsBase(SSLOption sslOption, SSLCustom sslCustom, ClientOptionsBase clientOptionsBase) { buildTCPSSLOptions(sslOption, sslCustom, clientOptionsBase); @@ -110,7 +130,7 @@ private static TCPSSLOptions buildTCPSSLOptions(SSLOption sslOption, SSLCustom s tcpClientOptions.setSsl(true); if (sslOption.getEngine().equalsIgnoreCase("openssl")) { - tcpClientOptions.setOpenSslEngineOptions(new OpenSSLEngineOptions()); + tcpClientOptions.setSslEngineOptions(new OpenSSLEngineOptions()); } String fullKeyStore = sslCustom.getFullPath(sslOption.getKeyStore()); if (isFileExists(fullKeyStore)) { @@ -118,12 +138,12 @@ private static TCPSSLOptions buildTCPSSLOptions(SSLOption sslOption, SSLCustom s PfxOptions keyPfxOptions = new PfxOptions(); keyPfxOptions.setPath(fullKeyStore); keyPfxOptions.setPassword(new String(sslCustom.decode(sslOption.getKeyStoreValue().toCharArray()))); - tcpClientOptions.setPfxKeyCertOptions(keyPfxOptions); + tcpClientOptions.setKeyCertOptions(keyPfxOptions); } else if (STORE_JKS.equalsIgnoreCase(sslOption.getKeyStoreType())) { JksOptions keyJksOptions = new JksOptions(); keyJksOptions.setPath(fullKeyStore); keyJksOptions.setPassword(new String(sslCustom.decode(sslOption.getKeyStoreValue().toCharArray()))); - tcpClientOptions.setKeyStoreOptions(keyJksOptions); + tcpClientOptions.setKeyCertOptions(keyJksOptions); } else { throw new IllegalArgumentException("invalid key store type."); } @@ -137,13 +157,13 @@ private static TCPSSLOptions buildTCPSSLOptions(SSLOption sslOption, SSLCustom s trustPfxOptions.setPath(fullTrustStore); trustPfxOptions .setPassword(new String(sslCustom.decode(sslOption.getTrustStoreValue().toCharArray()))); - tcpClientOptions.setPfxTrustOptions(trustPfxOptions); + tcpClientOptions.setTrustOptions(trustPfxOptions); } else if (STORE_JKS.equalsIgnoreCase(sslOption.getTrustStoreType())) { JksOptions trustJksOptions = new JksOptions(); trustJksOptions.setPath(fullTrustStore); trustJksOptions .setPassword(new String(sslCustom.decode(sslOption.getTrustStoreValue().toCharArray()))); - tcpClientOptions.setTrustStoreOptions(trustJksOptions); + tcpClientOptions.setTrustOptions(trustJksOptions); } else { throw new IllegalArgumentException("invalid trust store type."); } @@ -173,10 +193,14 @@ private static boolean isFileExists(String name) { return true; } - ClassLoader classLoader = - Thread.currentThread().getContextClassLoader() == null ? VertxTLSBuilder.class.getClassLoader() - : Thread.currentThread().getContextClassLoader(); - URL resource = classLoader.getResource(name); - return resource != null; + try { + ClassLoader classLoader = + Thread.currentThread().getContextClassLoader() == null ? VertxTLSBuilder.class.getClassLoader() + : Thread.currentThread().getContextClassLoader(); + URL resource = classLoader.getResource(name); + return resource != null; + } catch (Exception e) { + return false; + } } } diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/VertxUtils.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/VertxUtils.java index a3f7efa420b..b9db0621afd 100644 --- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/VertxUtils.java +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/VertxUtils.java @@ -20,14 +20,13 @@ import java.io.IOException; import java.io.InputStream; import java.lang.management.ManagementFactory; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import io.vertx.core.file.impl.FileResolverImpl; import org.apache.commons.io.IOUtils; -import org.apache.servicecomb.foundation.common.Holder; import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx; import org.apache.servicecomb.foundation.vertx.client.ClientPoolManager; import org.apache.servicecomb.foundation.vertx.client.ClientVerticle; @@ -44,6 +43,7 @@ import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.impl.FileResolverImpl; import io.vertx.core.impl.VertxBuilder; import io.vertx.core.impl.VertxThread; import io.vertx.core.spi.VertxThreadFactory; @@ -85,16 +85,16 @@ public static DeploymentOptions createClientDeployOptions( } // deploy Verticle and wait for its success. do not call this method in event-loop thread - public static boolean blockDeploy(Vertx vertx, + public static Map blockDeploy(Vertx vertx, Class cls, DeploymentOptions options) throws InterruptedException { - Holder result = new Holder<>(); + Map result = new HashMap<>(); CountDownLatch latch = new CountDownLatch(1); vertx.deployVerticle(cls.getName(), options, ar -> { - result.value = ar.succeeded(); - + result.put("code", ar.succeeded()); if (ar.failed()) { + result.put("message", ar.cause().getMessage()); LOGGER.error("deploy vertx failed, cause ", ar.cause()); } @@ -103,7 +103,7 @@ public static boolean blockDeploy(Vertx vertx, latch.await(); - return result.value; + return result; } public static Vertx getOrCreateVertxByName(String name, VertxOptions vertxOptions) { @@ -192,7 +192,7 @@ public static CompletableFuture closeVertxByName(String name) { public static void blockCloseVertxByName(String name) { CompletableFuture future = closeVertxByName(name); try { - future.get(); + future.get(30, TimeUnit.SECONDS); } catch (Throwable e) { LOGGER.error("Failed to wait close vertx {}.", name, e); } @@ -211,8 +211,8 @@ public static void blockCloseVertx(Vertx vertx) { }); try { - latch.await(); - } catch (InterruptedException e) { + latch.await(30, TimeUnit.SECONDS); + } catch (Throwable e) { LOGGER.info("Failed to wait close vertx {}.", vertx); } } diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/http/HttpClientOptionsSPI.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/http/HttpClientOptionsSPI.java index 164e4f4dc4c..0e684e7c306 100644 --- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/http/HttpClientOptionsSPI.java +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/http/HttpClientOptionsSPI.java @@ -24,6 +24,7 @@ import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpVersion; +import io.vertx.core.net.ClientOptionsBase; import io.vertx.core.net.ProxyOptions; /** @@ -40,7 +41,7 @@ public interface HttpClientOptionsSPI { boolean enabled(); /* config tag is used for group configurations, like ssl, address resolver, etc. set config tag to distinguish - * other clients configuration or read the common configuration. */ + * other clients configuration or read the common configuration. */ String getConfigTag(); /* for config modules, the configuration is not ready, need set up config reader */ @@ -79,12 +80,15 @@ public interface HttpClientOptionsSPI { int getKeepAliveTimeout(); + boolean enableLogActivity(); + /***************** http 2 settings ****************************/ int getHttp2MultiplexingLimit(); int getHttp2MaxPoolSize(); boolean isUseAlpn(); + /***************** proxy settings ***************************/ boolean isProxyEnable(); @@ -99,18 +103,10 @@ public interface HttpClientOptionsSPI { /***************** ssl settings ***************************/ boolean isSsl(); - static HttpClientOptions createHttpClientOptions(HttpClientOptionsSPI spi) { - HttpClientOptions httpClientOptions = new HttpClientOptions(); - - httpClientOptions.setProtocolVersion(spi.getHttpVersion()); + static void buildClientOptionsBase(HttpClientOptionsSPI spi, ClientOptionsBase httpClientOptions) { httpClientOptions.setConnectTimeout(spi.getConnectTimeoutInMillis()); httpClientOptions.setIdleTimeout(spi.getIdleTimeoutInSeconds()); - httpClientOptions.setTryUseCompression(spi.isTryUseCompression()); - httpClientOptions.setMaxWaitQueueSize(spi.getMaxWaitQueueSize()); - httpClientOptions.setMaxPoolSize(spi.getMaxPoolSize()); - httpClientOptions.setKeepAlive(spi.isKeepAlive()); - httpClientOptions.setMaxHeaderSize(spi.getMaxHeaderSize()); - httpClientOptions.setKeepAliveTimeout(spi.getKeepAliveTimeout()); + httpClientOptions.setLogActivity(spi.enableLogActivity()); if (spi.isProxyEnable()) { ProxyOptions proxy = new ProxyOptions(); @@ -123,10 +119,28 @@ static HttpClientOptions createHttpClientOptions(HttpClientOptionsSPI spi) { } if (spi.getHttpVersion() == HttpVersion.HTTP_2) { - httpClientOptions.setHttp2ClearTextUpgrade(false); httpClientOptions.setUseAlpn(spi.isUseAlpn()); + } + } + + static HttpClientOptions createHttpClientOptions(HttpClientOptionsSPI spi) { + HttpClientOptions httpClientOptions = new HttpClientOptions(); + buildClientOptionsBase(spi, httpClientOptions); + + httpClientOptions.setProtocolVersion(spi.getHttpVersion()); + httpClientOptions.setTryUseCompression(spi.isTryUseCompression()); + httpClientOptions.setMaxWaitQueueSize(spi.getMaxWaitQueueSize()); + httpClientOptions.setMaxPoolSize(spi.getMaxPoolSize()); + httpClientOptions.setKeepAlive(spi.isKeepAlive()); + httpClientOptions.setMaxHeaderSize(spi.getMaxHeaderSize()); + + if (spi.getHttpVersion() == HttpVersion.HTTP_2) { + httpClientOptions.setHttp2ClearTextUpgrade(false); httpClientOptions.setHttp2MultiplexingLimit(spi.getHttp2MultiplexingLimit()); httpClientOptions.setHttp2MaxPoolSize(spi.getHttp2MaxPoolSize()); + httpClientOptions.setHttp2KeepAliveTimeout(spi.getKeepAliveTimeout()); + } else { + httpClientOptions.setKeepAliveTimeout(spi.getKeepAliveTimeout()); } if (spi.isSsl()) { diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/http/HttpClientPoolFactory.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/http/HttpClientPoolFactory.java index c8752949ddc..64445db189e 100644 --- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/http/HttpClientPoolFactory.java +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/http/HttpClientPoolFactory.java @@ -46,7 +46,7 @@ public HttpClientWithContext createClientPool(Context context) { connection.localAddress(), connection.remoteAddress()) ); connection.exceptionHandler(e -> - LOGGER.info("http connection exception, local:{}, remote:{}.", + LOGGER.error("http connection exception, local:{}, remote:{}.", connection.localAddress(), connection.remoteAddress(), e) ); }); diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/http/HttpClients.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/http/HttpClients.java index 06085446caa..4449a3d0261 100644 --- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/http/HttpClients.java +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/http/HttpClients.java @@ -27,6 +27,9 @@ import org.apache.servicecomb.foundation.vertx.VertxUtils; import org.apache.servicecomb.foundation.vertx.client.ClientPoolManager; import org.apache.servicecomb.foundation.vertx.client.ClientVerticle; +import org.apache.servicecomb.foundation.vertx.client.ws.WebSocketClientOptionsSPI; +import org.apache.servicecomb.foundation.vertx.client.ws.WebSocketClientPoolFactory; +import org.apache.servicecomb.foundation.vertx.client.ws.WebSocketClientWithContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +47,8 @@ public class HttpClients { private static final Map> httpClients = new HashMap<>(); + private static final Map> wsClients = new HashMap<>(); + /* load at boot up, call this method once and only once. */ public static void load() { List clientOptionsList = SPIServiceUtils.getOrLoadSortedService(HttpClientOptionsSPI.class); @@ -56,6 +61,18 @@ public static void load() { httpClients.put(option.clientName(), createClientPoolManager(option)); } }); + + final List websocketClientOptionsList = + SPIServiceUtils.getOrLoadSortedService(WebSocketClientOptionsSPI.class); + websocketClientOptionsList.forEach(option -> { + if (!option.enabled()) { + return; + } + if (wsClients.containsKey(option.clientName())) { + LOGGER.warn("websocket client pool {} initialized again.", option.clientName()); + } + wsClients.put(option.clientName(), createWebSocketClientPoolManager(option)); + }); } /* destroy at shutdown. */ @@ -63,6 +80,11 @@ public static void destroy() { httpClients.clear(); List clientOptionsList = SPIServiceUtils.getOrLoadSortedService(HttpClientOptionsSPI.class); clientOptionsList.forEach(option -> VertxUtils.blockCloseVertxByName(option.clientName())); + + wsClients.clear(); + final List websocketClientOptionsList = + SPIServiceUtils.getOrLoadSortedService(WebSocketClientOptionsSPI.class); + websocketClientOptionsList.forEach(option -> VertxUtils.blockCloseVertxByName(option.clientName())); } private static ClientPoolManager createClientPoolManager(HttpClientOptionsSPI option) { @@ -83,6 +105,27 @@ private static ClientPoolManager createClientPoolManager( } } + private static ClientPoolManager createWebSocketClientPoolManager( + WebSocketClientOptionsSPI option) { + Vertx vertx = getOrCreateVertx(option); + final ClientPoolManager clientPoolManager = new ClientPoolManager<>( + vertx, + new WebSocketClientPoolFactory(option, + WebSocketClientOptionsSPI.createWebSocketClientOptions(option))); + + final DeploymentOptions deployOptions = VertxUtils.createClientDeployOptions(clientPoolManager, + option.getInstanceCount()) + .setWorker(option.isWorker()) + .setWorkerPoolName(option.getWorkerPoolName()) + .setWorkerPoolSize(option.getWorkerPoolSize()); + try { + VertxUtils.blockDeploy(vertx, ClientVerticle.class, deployOptions); + return clientPoolManager; + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + private static Vertx getOrCreateVertx(HttpClientOptionsSPI option) { if (option.useSharedVertx()) { return SharedVertxFactory.getSharedVertx(); @@ -132,4 +175,13 @@ public static HttpClientWithContext getClient(String clientName, boolean sync, C } return poolManager.findClientPool(sync, targetContext); } + + public static WebSocketClientWithContext getWebSocketClient(String clientName, boolean sync, Context targetContext) { + final ClientPoolManager poolManager = wsClients.get(clientName); + if (poolManager == null) { + LOGGER.error("websocket client name [{}] not exists, should only happen in tests.", clientName); + return null; + } + return poolManager.findClientPool(sync, targetContext); + } } diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/ws/WebSocketClientOptionsSPI.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/ws/WebSocketClientOptionsSPI.java new file mode 100644 index 00000000000..46b1d7bc1fd --- /dev/null +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/ws/WebSocketClientOptionsSPI.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.foundation.vertx.client.ws; + +import org.apache.servicecomb.foundation.vertx.VertxTLSBuilder; +import org.apache.servicecomb.foundation.vertx.client.http.HttpClientOptionsSPI; + +import io.vertx.core.http.HttpVersion; +import io.vertx.core.http.WebSocketClientOptions; + +/** + * WebSocket client options must be set by implementations. + */ +public abstract class WebSocketClientOptionsSPI implements HttpClientOptionsSPI { + public static WebSocketClientOptions createWebSocketClientOptions(WebSocketClientOptionsSPI spi) { + WebSocketClientOptions webSocketClientOptions = new WebSocketClientOptions(); + HttpClientOptionsSPI.buildClientOptionsBase(spi, webSocketClientOptions); + + webSocketClientOptions.setMaxFrameSize(spi.getMaxFrameSize()); + webSocketClientOptions.setMaxMessageSize(spi.getMaxMessageSize()); + webSocketClientOptions.setMaxConnections(spi.getMaxConnections()); + webSocketClientOptions.setTryUsePerFrameCompression(spi.getTryUsePerFrameCompression()); + webSocketClientOptions.setTryUsePerMessageCompression(spi.getTryUsePerMessageCompression()); + webSocketClientOptions.setCompressionLevel(spi.getCompressionLevel()); + webSocketClientOptions.setClosingTimeout(spi.getClosingTimeoutInSeconds()); + webSocketClientOptions.setUseAlpn(spi.isUseAlpn()); + + if (spi.isSsl()) { + VertxTLSBuilder.buildWebSocketClientOptions(spi.getConfigTag(), webSocketClientOptions); + } + return webSocketClientOptions; + } + + public abstract int getMaxFrameSize(); + + public abstract int getMaxMessageSize(); + + /** + * Set the max number of WebSockets per endpoint. + * @see WebSocketClientOptions#setMaxConnections(int) + */ + public abstract int getMaxConnections(); + + public abstract boolean getTryUsePerFrameCompression(); + + public abstract boolean getTryUsePerMessageCompression(); + + public abstract int getCompressionLevel(); + + public abstract int getClosingTimeoutInSeconds(); + + @Override + public final HttpVersion getHttpVersion() { + // does not actually work, just avoid error in + // org.apache.servicecomb.foundation.vertx.client.http.HttpClientOptionsSPI.buildClientOptionsBase + return HttpVersion.HTTP_1_1; + } + + @Override + public final boolean isTryUseCompression() { + throw unsupportedException(); + } + + @Override + public final int getMaxWaitQueueSize() { + throw unsupportedException(); + } + + @Override + public final int getMaxPoolSize() { + throw unsupportedException(); + } + + @Override + public final boolean isKeepAlive() { + throw unsupportedException(); + } + + @Override + public final int getMaxHeaderSize() { + throw unsupportedException(); + } + + @Override + public final int getHttp2MultiplexingLimit() { + throw unsupportedException(); + } + + @Override + public final int getHttp2MaxPoolSize() { + throw unsupportedException(); + } + + @Override + public final int getKeepAliveTimeout() { + throw unsupportedException(); + } + + protected static UnsupportedOperationException unsupportedException() { + return new UnsupportedOperationException("WebSocket Not Support this option."); + } +} diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/ws/WebSocketClientPoolFactory.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/ws/WebSocketClientPoolFactory.java new file mode 100644 index 00000000000..c2cc0f80b18 --- /dev/null +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/ws/WebSocketClientPoolFactory.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.foundation.vertx.client.ws; + +import org.apache.servicecomb.foundation.vertx.client.ClientPoolFactory; + +import io.vertx.core.Context; +import io.vertx.core.http.WebSocketClient; +import io.vertx.core.http.WebSocketClientOptions; + +/** + * Factory for {@link WebSocketClientWithContext} + */ +public class WebSocketClientPoolFactory implements ClientPoolFactory { + private final WebSocketClientOptions webSocketClientOptions; + + private final WebSocketClientOptionsSPI option; + + public WebSocketClientPoolFactory(WebSocketClientOptionsSPI option, WebSocketClientOptions webSocketClientOptions) { + this.option = option; + this.webSocketClientOptions = webSocketClientOptions; + } + + @Override + public WebSocketClientWithContext createClientPool(Context context) { + final WebSocketClient webSocketClient = context.owner().createWebSocketClient(webSocketClientOptions); + return new WebSocketClientWithContext(option, webSocketClient, context); + } +} diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/ws/WebSocketClientWithContext.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/ws/WebSocketClientWithContext.java new file mode 100644 index 00000000000..34581d68d3e --- /dev/null +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/client/ws/WebSocketClientWithContext.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.foundation.vertx.client.ws; + +import io.vertx.core.Context; +import io.vertx.core.http.WebSocketClient; + +public class WebSocketClientWithContext { + + public interface RunHandler { + void run(WebSocketClient webSocketClient); + } + + private final WebSocketClientOptionsSPI option; + + private final WebSocketClient webSocketClient; + + private final Context context; + + public WebSocketClientWithContext(WebSocketClientOptionsSPI option, WebSocketClient webSocketClient, + Context context) { + this.option = option; + this.webSocketClient = webSocketClient; + this.context = context; + } + + public WebSocketClient getWebSocketClient() { + return webSocketClient; + } + + public WebSocketClientOptionsSPI getOption() { + return option; + } + + public void runOnContext(RunHandler handler) { + context.runOnContext(v -> handler.run(webSocketClient)); + } + + public Context context() { + return context; + } +} diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/DownloadUtils.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/DownloadUtils.java index f37712e34e1..e83e65e6471 100644 --- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/DownloadUtils.java +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/DownloadUtils.java @@ -44,7 +44,11 @@ public static void prepareDownloadHeader(HttpServletResponseEx responseEx, Part return; } if (responseEx.getHeader(HttpHeaders.CONTENT_TYPE.toString()) == null) { - responseEx.setHeader(HttpHeaders.CONTENT_TYPE.toString(), part.getContentType()); + if (responseEx.getContentType() != null) { + responseEx.setHeader(HttpHeaders.CONTENT_TYPE.toString(), responseEx.getContentType()); + } else { + responseEx.setHeader(HttpHeaders.CONTENT_TYPE.toString(), part.getContentType()); + } } if (responseEx.getHeader(javax.ws.rs.core.HttpHeaders.CONTENT_DISPOSITION) == null) { diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/FileUploadPart.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/FileUploadPart.java index a444495f675..ba7acb2efc2 100644 --- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/FileUploadPart.java +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/FileUploadPart.java @@ -18,9 +18,9 @@ package org.apache.servicecomb.foundation.vertx.http; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; import org.apache.commons.io.FileUtils; import org.apache.servicecomb.foundation.common.part.AbstractPart; @@ -36,7 +36,8 @@ public FileUploadPart(FileUpload fileUpload) { @Override public InputStream getInputStream() throws IOException { - return new FileInputStream(fileUpload.uploadedFileName()); + final InputStream inputStream = Files.newInputStream(new File(fileUpload.uploadedFileName()).toPath()); + return new InputStreamWrapper(inputStream); } @Override diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/FileUploadStreamRecorder.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/FileUploadStreamRecorder.java new file mode 100644 index 00000000000..289914f986b --- /dev/null +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/FileUploadStreamRecorder.java @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.foundation.vertx.http; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.apache.servicecomb.foundation.common.event.EventManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.eventbus.EventBus; +import com.netflix.config.DynamicPropertyFactory; + +public class FileUploadStreamRecorder { + private static final Logger LOGGER = LoggerFactory.getLogger(FileUploadStreamRecorder.class); + + private static final FileUploadStreamRecorder RECORDER = new FileUploadStreamRecorder(); + + public static final String STREAM_RECORDER_MAX_SIZE = "servicecomb.uploads.file.streamRecorder.maxSize"; + + public static final String STREAM_STACKTRACE_ENABLED + = "servicecomb.uploads.file.streamRecorder.stackTraceEnabled"; + + public static final String STREAM_CHECK_INTERVAL = "servicecomb.uploads.file.streamRecorder.checkInterval"; + + public static final String STREAM_MAX_OPEN_TIME = "servicecomb.uploads.file.streamRecorder.streamMaxOpenTime"; + + public static final int DEFAULT_STREAM_RECORDER_MAX_SIZE = 5000; + + public static final long DEFAULT_STREAM_CHECK_INTERVAL = 30000L; + + public static final long DEFAULT_STREAM_MAX_OPEN_TIME = 90000L; + + private final Map streamWrapperRecorder = new ConcurrentHashMap<>(); + + private final EventBus eventBus; + + private final ScheduledExecutorService streamCheckExecutor; + + private final Object lock = new Object(); + + private FileUploadStreamRecorder() { + eventBus = EventManager.getEventBus(); + streamCheckExecutor = Executors.newScheduledThreadPool(1, + (t) -> new Thread(t, "upload-file-stream-check")); + startCheckRecordFileStream(); + } + + private void startCheckRecordFileStream() { + streamCheckExecutor.scheduleWithFixedDelay(this::checkRecordFileStream, DEFAULT_STREAM_CHECK_INTERVAL, + getStreamCheckInterval(), TimeUnit.MILLISECONDS); + } + + public static FileUploadStreamRecorder getInstance() { + return RECORDER; + } + + public void recordOpenStream(final InputStreamWrapper wrapper) { + checkAndRemoveOldestStream(); + streamWrapperRecorder.put(wrapper, new StreamOperateEvent(wrapper)); + } + + private void checkAndRemoveOldestStream() { + int maxSize = getStreamRecorderMaxSize(); + if (streamWrapperRecorder.size() < maxSize) { + return; + } + synchronized (lock) { + StreamOperateEvent oldestEvent = getOldestOperateEvent(streamWrapperRecorder.values()); + LOGGER.warn("reached record maxSize [{}] of file stream, delete oldest stream, operate time [{}], stackTrace: ", + maxSize, oldestEvent.getOpenStreamTimestamp(), oldestEvent.getInvokeStackTrace()); + oldestEvent.setEventType(EventType.OVER_SIZE); + eventBus.post(oldestEvent); + closeStreamWrapper(oldestEvent.getInputStreamWrapper()); + } + } + + private StreamOperateEvent getOldestOperateEvent(Collection values) { + StreamOperateEvent oldestEvent = null; + for (StreamOperateEvent event : values) { + if (oldestEvent == null) { + oldestEvent = event; + continue; + } + if (oldestEvent.getOpenStreamTimestamp() > event.getOpenStreamTimestamp()) { + oldestEvent = event; + } + } + return oldestEvent; + } + + public void clearRecorder(InputStreamWrapper inputStreamWrapper) { + streamWrapperRecorder.remove(inputStreamWrapper); + } + + private void checkRecordFileStream() { + try { + if (streamWrapperRecorder.isEmpty()) { + return; + } + List overdueStreamEvents = new ArrayList<>(); + long currentMillis = System.currentTimeMillis(); + for (StreamOperateEvent event : streamWrapperRecorder.values()) { + long streamOperateTime = event.getOpenStreamTimestamp(); + if (currentMillis - streamOperateTime >= getStreamMaxOpenTime()) { + overdueStreamEvents.add(event); + } + } + for (StreamOperateEvent overdueEvent : overdueStreamEvents) { + overdueEvent.setEventType(EventType.TIMEOUT); + eventBus.post(overdueEvent); + closeStreamWrapper(overdueEvent.getInputStreamWrapper()); + LOGGER.warn("closed timeout stream, operate time [{}], operate stackTrace: ", + overdueEvent.getOpenStreamTimestamp(), overdueEvent.getInvokeStackTrace()); + } + } catch (Exception e) { + LOGGER.error("checkRecordFileStream failed, next interval will try again.", e); + } + } + + private void closeStreamWrapper(InputStreamWrapper wrapper) { + try { + wrapper.close(); + } catch (IOException e) { + LOGGER.error("closed input stream failed!", e); + } + } + + private int getStreamRecorderMaxSize() { + return DynamicPropertyFactory.getInstance() + .getIntProperty(STREAM_RECORDER_MAX_SIZE, DEFAULT_STREAM_RECORDER_MAX_SIZE).get(); + } + + private static boolean getStreamStackTraceEnabled() { + return DynamicPropertyFactory.getInstance().getBooleanProperty(STREAM_STACKTRACE_ENABLED, false).get(); + } + + private long getStreamCheckInterval() { + return DynamicPropertyFactory.getInstance() + .getLongProperty(STREAM_CHECK_INTERVAL, DEFAULT_STREAM_CHECK_INTERVAL).get(); + } + + private long getStreamMaxOpenTime() { + return DynamicPropertyFactory.getInstance() + .getLongProperty(STREAM_MAX_OPEN_TIME, DEFAULT_STREAM_MAX_OPEN_TIME).get(); + } + + public static class StreamOperateEvent { + private final InputStreamWrapper inputStreamWrapper; + + private final long openStreamTimestamp; + + private Exception invokeStackTrace; + + private EventType eventType; + + public StreamOperateEvent(InputStreamWrapper inputStreamWrapper) { + this.inputStreamWrapper = inputStreamWrapper; + if (getStreamStackTraceEnabled()) { + this.invokeStackTrace = new Exception(); + } + this.openStreamTimestamp = System.currentTimeMillis(); + } + + public InputStreamWrapper getInputStreamWrapper() { + return inputStreamWrapper; + } + + public Exception getInvokeStackTrace() { + return invokeStackTrace; + } + + public long getOpenStreamTimestamp() { + return openStreamTimestamp; + } + + public EventType getEventType() { + return eventType; + } + + public void setEventType(EventType eventType) { + this.eventType = eventType; + } + } + + public enum EventType { + OVER_SIZE, + TIMEOUT + } +} diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/InputStreamWrapper.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/InputStreamWrapper.java new file mode 100644 index 00000000000..3bf5889bcfb --- /dev/null +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/InputStreamWrapper.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.foundation.vertx.http; + +import java.io.IOException; +import java.io.InputStream; + +public class InputStreamWrapper extends InputStream { + private final InputStream inputStream; + + public InputStreamWrapper(InputStream inputStream) { + this.inputStream = inputStream; + FileUploadStreamRecorder.getInstance().recordOpenStream(this); + } + + public InputStream getInputStream() { + return inputStream; + } + + @Override + public int read() throws IOException { + return inputStream.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return inputStream.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return inputStream.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return inputStream.skip(n); + } + + @Override + public int available() throws IOException { + return inputStream.available(); + } + + @Override + public boolean markSupported() { + return inputStream.markSupported(); + } + + @Override + public void mark(int readlimit) { + inputStream.mark(readlimit); + } + + @Override + public void close() throws IOException { + FileUploadStreamRecorder.getInstance().clearRecorder(this); + inputStream.close(); + } + + @Override + public void reset() throws IOException { + inputStream.reset(); + } +} diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerRequestToHttpServletRequest.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerRequestToHttpServletRequest.java index 435b660bc62..5029769a38e 100644 --- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerRequestToHttpServletRequest.java +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerRequestToHttpServletRequest.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import javax.servlet.AsyncContext; @@ -33,7 +34,6 @@ import javax.servlet.http.Part; import javax.ws.rs.core.HttpHeaders; -import io.vertx.ext.web.impl.RoutingContextInternal; import org.apache.servicecomb.foundation.common.http.HttpUtils; import org.apache.servicecomb.foundation.vertx.stream.BufferInputStream; import org.slf4j.Logger; @@ -42,9 +42,11 @@ import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.ServerWebSocket; import io.vertx.core.net.SocketAddress; import io.vertx.ext.web.FileUpload; import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.impl.RoutingContextInternal; // wrap vertx http request to Servlet http request public class VertxServerRequestToHttpServletRequest extends AbstractHttpServletRequest { @@ -302,4 +304,30 @@ public String getCharacterEncoding() { return characterEncoding; } + + /** + * Async switch protocol to WebSocket. Notice that the returned ServerWebSocket has been paused. + */ + public CompletableFuture toWebSocket() { + if (vertxRequest.isEnded()) { + throw new IllegalStateException("an ended VertxRequest cannot switch to WebSocket protocol!"); + } + final CompletableFuture future = vertxRequest.toWebSocket() + .toCompletionStage() + .toCompletableFuture() + .whenComplete((ws, t) -> { + if (ws != null) { + ws.pause(); + return; + } + + if (t == null) { + LOGGER.error("failed to switch to WebSocket!"); + } else { + LOGGER.error("failed to switch to WebSocket!", t); + } + }); + vertxRequest.resume(); + return future; + } } diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerResponseToHttpServletResponse.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerResponseToHttpServletResponse.java index 668ce0022b5..97b07405b39 100644 --- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerResponseToHttpServletResponse.java +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerResponseToHttpServletResponse.java @@ -117,6 +117,9 @@ public void flushBuffer() { } public void internalFlushBuffer() { + if (serverResponse.closed()) { + return; + } if (bodyBuffer == null) { serverResponse.end(); return; diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/server/TcpServer.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/server/TcpServer.java index 46608d1212c..972d145ec77 100644 --- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/server/TcpServer.java +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/server/TcpServer.java @@ -20,7 +20,6 @@ import java.net.InetSocketAddress; import org.apache.servicecomb.foundation.common.net.URIEndpointObject; -import org.apache.servicecomb.foundation.common.utils.ExceptionUtils; import org.apache.servicecomb.foundation.ssl.SSLCustom; import org.apache.servicecomb.foundation.ssl.SSLOption; import org.apache.servicecomb.foundation.ssl.SSLOptionFactory; @@ -78,7 +77,7 @@ public void init(Vertx vertx, String sslKey, AsyncResultCallback LOGGER.error("Unexpected error in server.{}", ExceptionUtils.getExceptionMessageWithoutTrace(e))); + netServer.exceptionHandler(e -> LOGGER.error("Unexpected error in server.", e)); InetSocketAddress socketAddress = endpointObject.getSocketAddress(); netServer.listen(socketAddress.getPort(), socketAddress.getHostString(), ar -> { if (ar.succeeded()) { diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/stream/BufferInputStream.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/stream/BufferInputStream.java index 58938cf9823..06510c0cb6e 100644 --- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/stream/BufferInputStream.java +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/stream/BufferInputStream.java @@ -45,6 +45,10 @@ public byte readByte() { @Override public int read() { + int avail = available(); + if (avail <= 0) { + return -1; + } return byteBuf.readUnsignedByte(); } @@ -83,12 +87,16 @@ public int read(byte[] b) { @Override public int read(byte[] b, int off, int len) { int avail = available(); - if (len > avail) { - len = avail; + if (avail <= 0) { + return -1; } if (len == 0) { - return -1; + return 0; + } + + if (len > avail) { + len = avail; } byteBuf.readBytes(b, off, len); diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/ws/VertxClientWebSocketRequestToHttpServletRequest.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/ws/VertxClientWebSocketRequestToHttpServletRequest.java new file mode 100644 index 00000000000..c0693061f25 --- /dev/null +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/ws/VertxClientWebSocketRequestToHttpServletRequest.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.foundation.vertx.ws; + +import java.util.Collections; +import java.util.Enumeration; + +import org.apache.http.HttpHeaders; +import org.apache.servicecomb.foundation.common.http.HttpUtils; +import org.apache.servicecomb.foundation.vertx.http.AbstractHttpServletRequest; + +import io.vertx.core.http.WebSocketConnectOptions; + +public class VertxClientWebSocketRequestToHttpServletRequest extends AbstractHttpServletRequest { + private final WebSocketConnectOptions clientRequest; + + private String characterEncoding; + + public VertxClientWebSocketRequestToHttpServletRequest(WebSocketConnectOptions clientRequest) { + this.clientRequest = clientRequest; + } + + @Override + public String getRequestURI() { + return HttpUtils.splitPathFromUri(clientRequest.getURI()); + } + + @Override + public String getQueryString() { + return HttpUtils.splitQueryFromUri(clientRequest.getURI()); + } + + @Override + public String getHeader(String name) { + return clientRequest.getHeaders().get(name); + } + + @Override + public Enumeration getHeaders(String name) { + return Collections.enumeration(clientRequest.getHeaders().getAll(name)); + } + + @Override + public Enumeration getHeaderNames() { + return Collections.enumeration(clientRequest.getHeaders().names()); + } + + @Override + public void setHeader(String name, String value) { + clientRequest.getHeaders().set(name, value); + } + + @Override + public void addHeader(String name, String value) { + clientRequest.getHeaders().add(name, value); + } + + @Override + public String getContextPath() { + return ""; + } + + @Override + public String getMethod() { + return clientRequest.getMethod().name(); + } + + @Override + public String getContentType() { + return clientRequest.getHeaders().get(HttpHeaders.CONTENT_TYPE); + } + + @Override + public String getCharacterEncoding() { + if (characterEncoding == null) { + characterEncoding = HttpUtils.getCharsetFromContentType(getContentType()); + } + + return characterEncoding; + } +} diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/ws/VertxClientWebSocketResponseToHttpServletResponse.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/ws/VertxClientWebSocketResponseToHttpServletResponse.java new file mode 100644 index 00000000000..c12f2e0ef9d --- /dev/null +++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/ws/VertxClientWebSocketResponseToHttpServletResponse.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.foundation.vertx.ws; + +import java.util.Collection; +import java.util.Collections; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response.StatusType; + +import org.apache.servicecomb.foundation.common.http.HttpStatus; +import org.apache.servicecomb.foundation.vertx.http.AbstractHttpServletResponse; + +import io.vertx.core.MultiMap; +import io.vertx.core.http.WebSocket; + +public class VertxClientWebSocketResponseToHttpServletResponse extends AbstractHttpServletResponse { + private final WebSocket vertxClientWebSocket; + + private final HttpStatus httpStatus; + + public VertxClientWebSocketResponseToHttpServletResponse(WebSocket vertxClientWebSocket) { + this.vertxClientWebSocket = vertxClientWebSocket; + // if run this function, the underlying WebSocket handshake must be completed. + httpStatus = new HttpStatus(101, "Switching Protocols"); + } + + @Override + public int getStatus() { + return httpStatus.getStatusCode(); + } + + @Override + public StatusType getStatusType() { + return httpStatus; + } + + @Override + public String getContentType() { + return getHeader(HttpHeaders.CONTENT_TYPE); + } + + @Override + public String getHeader(String name) { + final MultiMap headers = vertxClientWebSocket.headers(); + if (headers == null) { + return null; + } + return headers.get(name); + } + + @Override + public Collection getHeaders(String name) { + final MultiMap headers = vertxClientWebSocket.headers(); + if (headers == null) { + return Collections.emptyList(); + } + return headers.getAll(name); + } + + @Override + public Collection getHeaderNames() { + final MultiMap headers = vertxClientWebSocket.headers(); + if (headers == null) { + return Collections.emptyList(); + } + return headers.names(); + } + + public WebSocket getVertxClientWebSocket() { + return vertxClientWebSocket; + } +} diff --git a/foundations/foundation-vertx/src/test/java/io/vertx/ext/web/impl/TestHttpServerRequestUtils.java b/foundations/foundation-vertx/src/test/java/io/vertx/ext/web/impl/TestHttpServerRequestUtils.java index ec8255fdcc9..7297a866b2f 100644 --- a/foundations/foundation-vertx/src/test/java/io/vertx/ext/web/impl/TestHttpServerRequestUtils.java +++ b/foundations/foundation-vertx/src/test/java/io/vertx/ext/web/impl/TestHttpServerRequestUtils.java @@ -17,15 +17,16 @@ package io.vertx.ext.web.impl; -import io.vertx.ext.web.RequestBody; import org.apache.servicecomb.foundation.vertx.http.VertxServerRequestToHttpServletRequest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import io.vertx.core.http.impl.HttpServerRequestInternal; +import io.vertx.core.net.HostAndPort; import io.vertx.ext.web.AllowForwardHeaders; +import io.vertx.ext.web.RequestBody; import io.vertx.ext.web.RoutingContext; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; // HttpServerRequestWrapper is a package visible class, so put this test in package io.vertx.ext.web.impl public class TestHttpServerRequestUtils { @@ -33,9 +34,10 @@ public class TestHttpServerRequestUtils { public void testVertxServerRequestToHttpServletRequest() { RoutingContext context = Mockito.mock(RoutingContext.class); HttpServerRequestInternal request = Mockito.mock(HttpServerRequestInternal.class); - HttpServerRequestWrapper wrapper = new HttpServerRequestWrapper(request, AllowForwardHeaders.NONE); + HttpServerRequestWrapper wrapper = new HttpServerRequestWrapper(request, AllowForwardHeaders.NONE, null); Mockito.when(request.scheme()).thenReturn("http"); Mockito.when(context.request()).thenReturn(wrapper); + Mockito.when(request.authority()).thenReturn(HostAndPort.create("localhost", 8080)); RequestBody requestBody = Mockito.mock(RequestBody.class); Mockito.when(context.body()).thenReturn(requestBody); diff --git a/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/stream/TestBufferInputStream.java b/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/stream/TestBufferInputStream.java index 070fe207d6d..1d219ce1a08 100644 --- a/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/stream/TestBufferInputStream.java +++ b/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/stream/TestBufferInputStream.java @@ -49,7 +49,9 @@ public void tearDown() throws Exception { @Test public void testRead() { - Assertions.assertEquals(0, instance.read()); + ByteBuf buffer = Unpooled.buffer(); + BufferInputStream bIn = new BufferInputStream(buffer); + Assertions.assertEquals(-1, bIn.read()); } @Test diff --git a/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/stream/TestPumpFromPart.java b/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/stream/TestPumpFromPart.java index dd391c4f91d..c4c14b50b00 100644 --- a/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/stream/TestPumpFromPart.java +++ b/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/stream/TestPumpFromPart.java @@ -25,23 +25,28 @@ import javax.servlet.http.Part; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.servicecomb.foundation.common.part.InputStreamPart; import org.apache.servicecomb.foundation.vertx.stream.InputStreamToReadStream.ReadResult; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; +import org.junit.Before; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import io.vertx.core.Context; import io.vertx.core.Promise; +import io.vertx.core.Vertx; import io.vertx.core.impl.SyncContext; +import io.vertx.core.impl.VertxInternal; import mockit.Expectations; import mockit.Mock; import mockit.MockUp; -import org.junit.jupiter.api.Assertions; public class TestPumpFromPart { - String src = RandomStringUtils.random(100); + static Vertx vertx = Vertx.vertx(); + + String src = RandomStringUtils.random(100, true, true); boolean inputStreamClosed; @@ -61,7 +66,12 @@ public void close() throws IOException { IOException error = new IOException(); - Context context = new SyncContext(); + SyncContext context = new SyncContext(); + + @Before + public void setup() throws IOException { + context.setOwner((VertxInternal) vertx); + } private void run(Context context, boolean closeOutput) throws Throwable { inputStream.reset(); diff --git a/governance/src/main/java/org/apache/servicecomb/governance/MatchersManager.java b/governance/src/main/java/org/apache/servicecomb/governance/MatchersManager.java index 1c84a54c997..00caaafdbff 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/MatchersManager.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/MatchersManager.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.AbstractPolicy; import org.apache.servicecomb.governance.service.MatchersService; @@ -31,13 +31,13 @@ public MatchersManager(MatchersService matchersService) { this.matchersService = matchersService; } - public T match(GovernanceRequest request, Map policies) { + public T match(GovernanceRequestExtractor requestExtractor, Map policies) { List sortPolicies = new ArrayList<>(policies.size()); sortPolicies.addAll(policies.values()); sortPolicies.sort(T::compareTo); for (T policy : sortPolicies) { - if (matchersService.checkMatch(request, policy.getName())) { + if (matchersService.checkMatch(requestExtractor, policy.getName())) { return policy; } } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/handler/AbstractGovernanceHandler.java b/governance/src/main/java/org/apache/servicecomb/governance/handler/AbstractGovernanceHandler.java index 631fc87fe1b..d28392e413c 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/handler/AbstractGovernanceHandler.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/handler/AbstractGovernanceHandler.java @@ -20,7 +20,7 @@ import org.apache.servicecomb.governance.MatchersManager; import org.apache.servicecomb.governance.event.GovernanceConfigurationChangedEvent; import org.apache.servicecomb.governance.event.GovernanceEventManager; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.AbstractPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,13 +56,13 @@ public void setMeterRegistry(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } - public PROCESSOR getActuator(GovernanceRequest governanceRequest) { - POLICY policy = matchPolicy(governanceRequest); + public PROCESSOR getActuator(GovernanceRequestExtractor requestExtractor) { + POLICY policy = matchPolicy(requestExtractor); if (policy == null) { return null; } - String key = createKey(governanceRequest, policy); + String key = createKey(requestExtractor, policy); if (key == null) { return null; } @@ -72,7 +72,7 @@ public PROCESSOR getActuator(GovernanceRequest governanceRequest) { synchronized (lock) { processor = processors.get(key); if (processor == null) { - processor = createProcessor(key, governanceRequest, policy); + processor = createProcessor(key, requestExtractor, policy); processors.put(key, processor); } } @@ -81,11 +81,11 @@ public PROCESSOR getActuator(GovernanceRequest governanceRequest) { return processor.get(); } - protected abstract String createKey(GovernanceRequest governanceRequest, POLICY policy); + protected abstract String createKey(GovernanceRequestExtractor requestExtractor, POLICY policy); - protected abstract POLICY matchPolicy(GovernanceRequest governanceRequest); + protected abstract POLICY matchPolicy(GovernanceRequestExtractor requestExtractor); - protected abstract Disposable createProcessor(String key, GovernanceRequest governanceRequest, + protected abstract Disposable createProcessor(String key, GovernanceRequestExtractor requestExtractor, POLICY policy); protected void onConfigurationChanged(String key) { diff --git a/governance/src/main/java/org/apache/servicecomb/governance/handler/BulkheadHandler.java b/governance/src/main/java/org/apache/servicecomb/governance/handler/BulkheadHandler.java index 3fa0e21db0b..12d9ddc4eb2 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/handler/BulkheadHandler.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/handler/BulkheadHandler.java @@ -19,7 +19,7 @@ import java.time.Duration; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.BulkheadPolicy; import org.apache.servicecomb.governance.properties.BulkheadProperties; import org.slf4j.Logger; @@ -41,17 +41,17 @@ public BulkheadHandler(BulkheadProperties bulkheadProperties) { } @Override - protected String createKey(GovernanceRequest governanceRequest, BulkheadPolicy policy) { + protected String createKey(GovernanceRequestExtractor requestExtractor, BulkheadPolicy policy) { return bulkheadProperties.getConfigKey() + "." + policy.getName(); } @Override - public BulkheadPolicy matchPolicy(GovernanceRequest governanceRequest) { - return matchersManager.match(governanceRequest, bulkheadProperties.getParsedEntity()); + public BulkheadPolicy matchPolicy(GovernanceRequestExtractor requestExtractor) { + return matchersManager.match(requestExtractor, bulkheadProperties.getParsedEntity()); } @Override - public Disposable createProcessor(String key, GovernanceRequest governanceRequest, BulkheadPolicy policy) { + public Disposable createProcessor(String key, GovernanceRequestExtractor requestExtractor, BulkheadPolicy policy) { return getBulkhead(key, policy); } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/handler/CircuitBreakerHandler.java b/governance/src/main/java/org/apache/servicecomb/governance/handler/CircuitBreakerHandler.java index 0f75e7ea7cc..fdeb10a5a59 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/handler/CircuitBreakerHandler.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/handler/CircuitBreakerHandler.java @@ -19,7 +19,7 @@ import java.time.Duration; import org.apache.servicecomb.governance.handler.ext.AbstractCircuitBreakerExtension; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.CircuitBreakerPolicy; import org.apache.servicecomb.governance.properties.CircuitBreakerProperties; import org.slf4j.Logger; @@ -45,17 +45,17 @@ public CircuitBreakerHandler(CircuitBreakerProperties circuitBreakerProperties, } @Override - protected String createKey(GovernanceRequest governanceRequest, CircuitBreakerPolicy policy) { + protected String createKey(GovernanceRequestExtractor requestExtractor, CircuitBreakerPolicy policy) { return this.circuitBreakerProperties.getConfigKey() + "." + policy.getName(); } @Override - public CircuitBreakerPolicy matchPolicy(GovernanceRequest governanceRequest) { - return matchersManager.match(governanceRequest, circuitBreakerProperties.getParsedEntity()); + public CircuitBreakerPolicy matchPolicy(GovernanceRequestExtractor requestExtractor) { + return matchersManager.match(requestExtractor, circuitBreakerProperties.getParsedEntity()); } @Override - public Disposable createProcessor(String key, GovernanceRequest governanceRequest, + public Disposable createProcessor(String key, GovernanceRequestExtractor requestExtractor, CircuitBreakerPolicy policy) { return getCircuitBreaker(key, policy); } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/handler/FaultInjectionHandler.java b/governance/src/main/java/org/apache/servicecomb/governance/handler/FaultInjectionHandler.java index ecdd7662e70..e44b1685fe5 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/handler/FaultInjectionHandler.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/handler/FaultInjectionHandler.java @@ -17,11 +17,11 @@ package org.apache.servicecomb.governance.handler; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.FaultInjectionPolicy; -import org.apache.servicecomb.governance.properties.FaultInjectionProperties; import org.apache.servicecomb.governance.processor.injection.Fault; import org.apache.servicecomb.governance.processor.injection.FaultInjectionUtil; +import org.apache.servicecomb.governance.properties.FaultInjectionProperties; public class FaultInjectionHandler extends AbstractGovernanceHandler { @@ -32,17 +32,17 @@ public FaultInjectionHandler(FaultInjectionProperties faultInjectionProperties) } @Override - protected String createKey(GovernanceRequest governanceRequest, FaultInjectionPolicy policy) { + protected String createKey(GovernanceRequestExtractor requestExtractor, FaultInjectionPolicy policy) { return FaultInjectionProperties.MATCH_FAULT_INJECTION_KEY + "." + policy.getName(); } @Override - public FaultInjectionPolicy matchPolicy(GovernanceRequest governanceRequest) { - return matchersManager.match(governanceRequest, faultInjectionProperties.getParsedEntity()); + public FaultInjectionPolicy matchPolicy(GovernanceRequestExtractor requestExtractor) { + return matchersManager.match(requestExtractor, faultInjectionProperties.getParsedEntity()); } @Override - protected DisposableHolder createProcessor(String key, GovernanceRequest governanceRequest, + protected DisposableHolder createProcessor(String key, GovernanceRequestExtractor requestExtractor, FaultInjectionPolicy policy) { return new DisposableHolder<>(key, FaultInjectionUtil.getFault(key, policy)); } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/handler/GovernanceCacheHandler.java b/governance/src/main/java/org/apache/servicecomb/governance/handler/GovernanceCacheHandler.java index 88d96f93739..7e7b6e6dd72 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/handler/GovernanceCacheHandler.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/handler/GovernanceCacheHandler.java @@ -16,15 +16,15 @@ */ package org.apache.servicecomb.governance.handler; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import java.time.Duration; + +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.GovernanceCachePolicy; import org.apache.servicecomb.governance.properties.GovernanceCacheProperties; import org.apache.servicecomb.governance.service.GovernanceCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.Duration; - import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @@ -39,17 +39,17 @@ public GovernanceCacheHandler(GovernanceCacheProperties cacheProperties) { } @Override - public GovernanceCachePolicy matchPolicy(GovernanceRequest governanceRequest) { - return matchersManager.match(governanceRequest, cacheProperties.getParsedEntity()); + public GovernanceCachePolicy matchPolicy(GovernanceRequestExtractor requestExtractor) { + return matchersManager.match(requestExtractor, cacheProperties.getParsedEntity()); } @Override - protected String createKey(GovernanceRequest governanceRequest, GovernanceCachePolicy policy) { + protected String createKey(GovernanceRequestExtractor requestExtractor, GovernanceCachePolicy policy) { return cacheProperties.getConfigKey() + "." + policy.getName(); } @Override - protected Disposable> createProcessor(String key, GovernanceRequest governanceRequest, + protected Disposable> createProcessor(String key, GovernanceRequestExtractor requestExtractor, GovernanceCachePolicy policy) { return getGovernanceCache(key, policy); } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/handler/IdentifierRateLimitingHandler.java b/governance/src/main/java/org/apache/servicecomb/governance/handler/IdentifierRateLimitingHandler.java index e733d1d178f..26751c64dfa 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/handler/IdentifierRateLimitingHandler.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/handler/IdentifierRateLimitingHandler.java @@ -20,7 +20,7 @@ import java.time.Duration; import org.apache.commons.lang3.StringUtils; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.IdentifierRateLimitingPolicy; import org.apache.servicecomb.governance.properties.IdentifierRateLimitProperties; import org.slf4j.Logger; @@ -43,15 +43,15 @@ public IdentifierRateLimitingHandler(IdentifierRateLimitProperties rateLimitProp } @Override - protected String createKey(GovernanceRequest governanceRequest, IdentifierRateLimitingPolicy policy) { + protected String createKey(GovernanceRequestExtractor requestExtractor, IdentifierRateLimitingPolicy policy) { if (StringUtils.isEmpty(policy.getIdentifier()) || - StringUtils.isEmpty(governanceRequest.getHeader(policy.getIdentifier()))) { + StringUtils.isEmpty(requestExtractor.header(policy.getIdentifier()))) { LOGGER.info("identifier rate limiting is not properly configured, identifier is empty."); return null; } return this.rateLimitProperties.getConfigKey() + "." + policy.getName() - + "." + governanceRequest.getHeader(policy.getIdentifier()); + + "." + requestExtractor.header(policy.getIdentifier()); } @Override @@ -70,12 +70,12 @@ protected void onConfigurationChanged(String key) { } @Override - public IdentifierRateLimitingPolicy matchPolicy(GovernanceRequest governanceRequest) { - return matchersManager.match(governanceRequest, rateLimitProperties.getParsedEntity()); + public IdentifierRateLimitingPolicy matchPolicy(GovernanceRequestExtractor requestExtractor) { + return matchersManager.match(requestExtractor, rateLimitProperties.getParsedEntity()); } @Override - public Disposable createProcessor(String key, GovernanceRequest governanceRequest, + public Disposable createProcessor(String key, GovernanceRequestExtractor requestExtractor, IdentifierRateLimitingPolicy policy) { return getRateLimiter(key, policy); } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/handler/InstanceBulkheadHandler.java b/governance/src/main/java/org/apache/servicecomb/governance/handler/InstanceBulkheadHandler.java index 31e40743490..e90f2472915 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/handler/InstanceBulkheadHandler.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/handler/InstanceBulkheadHandler.java @@ -20,7 +20,7 @@ import java.time.Duration; import org.apache.commons.lang3.StringUtils; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.BulkheadPolicy; import org.apache.servicecomb.governance.properties.InstanceBulkheadProperties; import org.slf4j.Logger; @@ -42,11 +42,11 @@ public InstanceBulkheadHandler(InstanceBulkheadProperties bulkheadProperties) { } @Override - protected String createKey(GovernanceRequest governanceRequest, BulkheadPolicy policy) { + protected String createKey(GovernanceRequestExtractor requestExtractor, BulkheadPolicy policy) { return this.bulkheadProperties.getConfigKey() + "." + policy.getName() - + "." + governanceRequest.getServiceName() - + "." + governanceRequest.getInstanceId(); + + "." + requestExtractor.serviceName() + + "." + requestExtractor.instanceId(); } @Override @@ -65,17 +65,17 @@ protected void onConfigurationChanged(String key) { } @Override - public BulkheadPolicy matchPolicy(GovernanceRequest governanceRequest) { - if (StringUtils.isEmpty(governanceRequest.getServiceName()) || StringUtils.isEmpty( - governanceRequest.getInstanceId())) { + public BulkheadPolicy matchPolicy(GovernanceRequestExtractor requestExtractor) { + if (StringUtils.isEmpty(requestExtractor.serviceName()) || StringUtils.isEmpty( + requestExtractor.instanceId())) { LOGGER.info("Instance bulkhead is not properly configured, service id or instance id is empty."); return null; } - return matchersManager.match(governanceRequest, bulkheadProperties.getParsedEntity()); + return matchersManager.match(requestExtractor, bulkheadProperties.getParsedEntity()); } @Override - public Disposable createProcessor(String key, GovernanceRequest governanceRequest, BulkheadPolicy policy) { + public Disposable createProcessor(String key, GovernanceRequestExtractor requestExtractor, BulkheadPolicy policy) { return getBulkhead(key, policy); } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/handler/InstanceIsolationHandler.java b/governance/src/main/java/org/apache/servicecomb/governance/handler/InstanceIsolationHandler.java index d9d1943737f..6713a3307d8 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/handler/InstanceIsolationHandler.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/handler/InstanceIsolationHandler.java @@ -21,7 +21,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.servicecomb.governance.handler.ext.AbstractInstanceIsolationExtension; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.CircuitBreakerPolicy; import org.apache.servicecomb.governance.properties.InstanceIsolationProperties; import org.slf4j.Logger; @@ -53,11 +53,11 @@ public InstanceIsolationHandler(InstanceIsolationProperties instanceIsolationPro } @Override - protected String createKey(GovernanceRequest governanceRequest, CircuitBreakerPolicy policy) { + protected String createKey(GovernanceRequestExtractor requestExtractor, CircuitBreakerPolicy policy) { return this.instanceIsolationProperties.getConfigKey() + "." + policy.getName() - + "." + governanceRequest.getServiceName() - + "." + governanceRequest.getInstanceId(); + + "." + requestExtractor.serviceName() + + "." + requestExtractor.instanceId(); } @Override @@ -76,17 +76,17 @@ protected void onConfigurationChanged(String key) { } @Override - public CircuitBreakerPolicy matchPolicy(GovernanceRequest governanceRequest) { - if (StringUtils.isEmpty(governanceRequest.getServiceName()) || StringUtils.isEmpty( - governanceRequest.getInstanceId())) { + public CircuitBreakerPolicy matchPolicy(GovernanceRequestExtractor requestExtractor) { + if (StringUtils.isEmpty(requestExtractor.serviceName()) || StringUtils.isEmpty( + requestExtractor.instanceId())) { LOGGER.info("Isolation is not properly configured, service id or instance id is empty."); return null; } - return matchersManager.match(governanceRequest, instanceIsolationProperties.getParsedEntity()); + return matchersManager.match(requestExtractor, instanceIsolationProperties.getParsedEntity()); } @Override - public Disposable createProcessor(String key, GovernanceRequest governanceRequest, + public Disposable createProcessor(String key, GovernanceRequestExtractor requestExtractor, CircuitBreakerPolicy policy) { return getCircuitBreaker(key, policy); } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/handler/LoadBalanceHandler.java b/governance/src/main/java/org/apache/servicecomb/governance/handler/LoadBalanceHandler.java index d63c8c766fb..ab549c759f2 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/handler/LoadBalanceHandler.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/handler/LoadBalanceHandler.java @@ -17,7 +17,7 @@ package org.apache.servicecomb.governance.handler; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.LoadBalancerPolicy; import org.apache.servicecomb.governance.processor.loadbanlance.LoadBalance; import org.apache.servicecomb.governance.properties.LoadBalanceProperties; @@ -31,17 +31,17 @@ public LoadBalanceHandler(LoadBalanceProperties loadBalanceProperties) { } @Override - protected String createKey(GovernanceRequest governanceRequest, LoadBalancerPolicy policy) { + protected String createKey(GovernanceRequestExtractor requestExtractor, LoadBalancerPolicy policy) { return this.loadBalanceProperties.getConfigKey() + "." + policy.getName(); } @Override - public LoadBalancerPolicy matchPolicy(GovernanceRequest governanceRequest) { - return matchersManager.match(governanceRequest, loadBalanceProperties.getParsedEntity()); + public LoadBalancerPolicy matchPolicy(GovernanceRequestExtractor requestExtractor) { + return matchersManager.match(requestExtractor, loadBalanceProperties.getParsedEntity()); } @Override - protected DisposableHolder createProcessor(String key, GovernanceRequest governanceRequest, + protected DisposableHolder createProcessor(String key, GovernanceRequestExtractor requestExtractor, LoadBalancerPolicy policy) { return new DisposableHolder<>(key, LoadBalance.getLoadBalance(key, policy)); } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/handler/MapperHandler.java b/governance/src/main/java/org/apache/servicecomb/governance/handler/MapperHandler.java index 8ca0da4aa66..53726a2b0e4 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/handler/MapperHandler.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/handler/MapperHandler.java @@ -16,7 +16,7 @@ */ package org.apache.servicecomb.governance.handler; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.MapperPolicy; import org.apache.servicecomb.governance.processor.mapping.Mapper; import org.apache.servicecomb.governance.properties.MapperProperties; @@ -33,17 +33,17 @@ public MapperHandler(MapperProperties mapperProperties) { } @Override - protected String createKey(GovernanceRequest governanceRequest, MapperPolicy policy) { + protected String createKey(GovernanceRequestExtractor requestExtractor, MapperPolicy policy) { return mapperProperties.getConfigKey() + "." + policy.getName(); } @Override - public MapperPolicy matchPolicy(GovernanceRequest governanceRequest) { - return matchersManager.match(governanceRequest, mapperProperties.getParsedEntity()); + public MapperPolicy matchPolicy(GovernanceRequestExtractor requestExtractor) { + return matchersManager.match(requestExtractor, mapperProperties.getParsedEntity()); } @Override - public Disposable createProcessor(String key, GovernanceRequest governanceRequest, + public Disposable createProcessor(String key, GovernanceRequestExtractor requestExtractor, MapperPolicy policy) { return getMapper(key, policy); } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/handler/RateLimitingHandler.java b/governance/src/main/java/org/apache/servicecomb/governance/handler/RateLimitingHandler.java index 7570137ad71..e70ae60f78e 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/handler/RateLimitingHandler.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/handler/RateLimitingHandler.java @@ -18,7 +18,7 @@ import java.time.Duration; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.RateLimitingPolicy; import org.apache.servicecomb.governance.properties.RateLimitProperties; import org.slf4j.Logger; @@ -40,17 +40,17 @@ public RateLimitingHandler(RateLimitProperties rateLimitProperties) { } @Override - protected String createKey(GovernanceRequest governanceRequest, RateLimitingPolicy policy) { + protected String createKey(GovernanceRequestExtractor requestExtractor, RateLimitingPolicy policy) { return this.rateLimitProperties.getConfigKey() + "." + policy.getName(); } @Override - public RateLimitingPolicy matchPolicy(GovernanceRequest governanceRequest) { - return matchersManager.match(governanceRequest, rateLimitProperties.getParsedEntity()); + public RateLimitingPolicy matchPolicy(GovernanceRequestExtractor requestExtractor) { + return matchersManager.match(requestExtractor, rateLimitProperties.getParsedEntity()); } @Override - public Disposable createProcessor(String key, GovernanceRequest governanceRequest, + public Disposable createProcessor(String key, GovernanceRequestExtractor requestExtractor, RateLimitingPolicy policy) { return getRateLimiter(key, policy); } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/handler/RetryHandler.java b/governance/src/main/java/org/apache/servicecomb/governance/handler/RetryHandler.java index 18abbdbda89..8b4c819b05d 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/handler/RetryHandler.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/handler/RetryHandler.java @@ -19,7 +19,7 @@ import java.time.Duration; import org.apache.servicecomb.governance.handler.ext.AbstractRetryExtension; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.RetryPolicy; import org.apache.servicecomb.governance.properties.RetryProperties; import org.apache.servicecomb.governance.utils.GovernanceUtils; @@ -47,17 +47,18 @@ public RetryHandler(RetryProperties retryProperties, AbstractRetryExtension retr } @Override - protected String createKey(GovernanceRequest governanceRequest, RetryPolicy policy) { + protected String createKey(GovernanceRequestExtractor requestExtractor, RetryPolicy policy) { return this.retryProperties.getConfigKey() + "." + policy.getName(); } @Override - public RetryPolicy matchPolicy(GovernanceRequest governanceRequest) { - return matchersManager.match(governanceRequest, retryProperties.getParsedEntity()); + public RetryPolicy matchPolicy(GovernanceRequestExtractor requestExtractor) { + return matchersManager.match(requestExtractor, retryProperties.getParsedEntity()); } @Override - public Disposable createProcessor(String key, GovernanceRequest governanceRequest, RetryPolicy policy) { + public Disposable createProcessor(String key, GovernanceRequestExtractor requestExtractor, + RetryPolicy policy) { return getRetry(key, policy); } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/handler/TimeLimiterHandler.java b/governance/src/main/java/org/apache/servicecomb/governance/handler/TimeLimiterHandler.java index 357951bee68..d9f03034d07 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/handler/TimeLimiterHandler.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/handler/TimeLimiterHandler.java @@ -17,19 +17,19 @@ package org.apache.servicecomb.governance.handler; -import io.github.resilience4j.micrometer.tagged.TaggedTimeLimiterMetrics; -import io.github.resilience4j.micrometer.tagged.TimeLimiterMetricNames; -import io.github.resilience4j.timelimiter.TimeLimiter; -import io.github.resilience4j.timelimiter.TimeLimiterConfig; -import io.github.resilience4j.timelimiter.TimeLimiterRegistry; +import java.time.Duration; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.policy.TimeLimiterPolicy; import org.apache.servicecomb.governance.properties.TimeLimiterProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.Duration; +import io.github.resilience4j.micrometer.tagged.TaggedTimeLimiterMetrics; +import io.github.resilience4j.micrometer.tagged.TimeLimiterMetricNames; +import io.github.resilience4j.timelimiter.TimeLimiter; +import io.github.resilience4j.timelimiter.TimeLimiterConfig; +import io.github.resilience4j.timelimiter.TimeLimiterRegistry; public class TimeLimiterHandler extends AbstractGovernanceHandler { @@ -42,17 +42,17 @@ public TimeLimiterHandler(TimeLimiterProperties timeLimiterProperties) { } @Override - protected String createKey(GovernanceRequest governanceRequest, TimeLimiterPolicy policy) { + protected String createKey(GovernanceRequestExtractor requestExtractor, TimeLimiterPolicy policy) { return timeLimiterProperties.getConfigKey() + "." + policy.getName(); } @Override - public TimeLimiterPolicy matchPolicy(GovernanceRequest governanceRequest) { - return matchersManager.match(governanceRequest, timeLimiterProperties.getParsedEntity()); + public TimeLimiterPolicy matchPolicy(GovernanceRequestExtractor requestExtractor) { + return matchersManager.match(requestExtractor, timeLimiterProperties.getParsedEntity()); } @Override - public Disposable createProcessor(String key, GovernanceRequest governanceRequest, + public Disposable createProcessor(String key, GovernanceRequestExtractor requestExtractor, TimeLimiterPolicy policy) { return getTimeLimiter(key, policy); } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/marker/GovernanceRequest.java b/governance/src/main/java/org/apache/servicecomb/governance/marker/GovernanceRequest.java index e4888dda0ea..2a454e5d11a 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/marker/GovernanceRequest.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/marker/GovernanceRequest.java @@ -20,52 +20,53 @@ import org.springframework.util.LinkedCaseInsensitiveMap; -public class GovernanceRequest { +public class GovernanceRequest implements GovernanceRequestExtractor { /** - headers with this request, maybe null. - For provider: headers indicates the request headers to me. - For consumer: headers indicates the request headers to the target. + * headers with this request, maybe null. + * For provider: headers indicates the request headers to me. + * For consumer: headers indicates the request headers to the target. */ private Map headers; /** - uri with this request, maybe null. - For provider: uri indicates the request uri to me. - For consumer: uri indicates the request uri to the target. + * api path with this request, maybe null. For REST, e.g. /foo/bar; For RPC, e.g. MySchema.sayHello + * For provider: uri indicates the request uri to me. + * For consumer: uri indicates the request uri to the target. */ - private String uri; + private String apiPath; /** - method with this request, maybe null. - For provider: method indicates the request method to me. - For consumer: method indicates the request method to the target. + * method with this request, maybe null. + * For provider: method indicates the request method to me. + * For consumer: method indicates the request method to the target. */ private String method; /** - instance id with this request, maybe null. - For provider: instanceId indicates who calls me. - For consumer: instanceId indicates the target instance. + * instance id with this request, maybe null. + * For provider: instanceId indicates who calls me. + * For consumer: instanceId indicates the target instance. */ private String instanceId; /** - microservice id (microservice name or application name + microservice name) with this request, maybe null. - For provider: serviceName indicates who calls me. - For consumer: serviceName indicates the target service. + * microservice id (microservice name or application name + microservice name) with this request, maybe null. + * For provider: serviceName indicates who calls me. + * For consumer: serviceName indicates the target service. */ private String serviceName; /** - sourceRequest the source request for creating this governanceRequest - For provider: uri indicates the request to me. - For consumer: uri indicates the request to the target. - the type of sourceRequest could be ClientRequest, ServerWebExchange, HttpRequest, HttpServletRequest and so on, - it is desided by user. user will use this request to extract the information he need + * sourceRequest the source request for creating this governanceRequest + * For provider: uri indicates the request to me. + * For consumer: uri indicates the request to the target. + * the type of sourceRequest could be ClientRequest, ServerWebExchange, HttpRequest, HttpServletRequest and so on, + * User will use this request to extract the information he need */ private Object sourceRequest; - public String getHeader(String key) { + @Override + public String header(String key) { return headers.get(key); } @@ -79,15 +80,17 @@ public void setHeaders(Map headers) { this.headers = temp; } - public String getUri() { - return uri; + @Override + public String apiPath() { + return apiPath; } - public void setUri(String uri) { - this.uri = uri; + public void setApiPath(String apiPath) { + this.apiPath = apiPath; } - public String getMethod() { + @Override + public String method() { return method; } @@ -95,8 +98,8 @@ public void setMethod(String method) { this.method = method; } - - public String getInstanceId() { + @Override + public String instanceId() { return instanceId; } @@ -104,7 +107,8 @@ public void setInstanceId(String instanceId) { this.instanceId = instanceId; } - public String getServiceName() { + @Override + public String serviceName() { return serviceName; } @@ -112,7 +116,8 @@ public void setServiceName(String serviceName) { this.serviceName = serviceName; } - public Object getSourceRequest() { + @Override + public Object sourceRequest() { return sourceRequest; } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/marker/GovernanceRequestExtractor.java b/governance/src/main/java/org/apache/servicecomb/governance/marker/GovernanceRequestExtractor.java new file mode 100644 index 00000000000..873f78665ed --- /dev/null +++ b/governance/src/main/java/org/apache/servicecomb/governance/marker/GovernanceRequestExtractor.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.governance.marker; + +public interface GovernanceRequestExtractor { + String apiPath(); + + String method(); + + String header(String key); + + String instanceId(); + + String serviceName(); + + Object sourceRequest(); +} diff --git a/governance/src/main/java/org/apache/servicecomb/governance/marker/RequestProcessor.java b/governance/src/main/java/org/apache/servicecomb/governance/marker/RequestProcessor.java index 762361f0b04..a17d3f72b40 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/marker/RequestProcessor.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/marker/RequestProcessor.java @@ -34,12 +34,14 @@ /** * Request Processor checks if a request matches a configuration. */ -public class RequestProcessor implements ApplicationContextAware{ +public class RequestProcessor implements ApplicationContextAware { private static final Logger LOGGER = LoggerFactory.getLogger(RequestProcessor.class); public static final String errorMessageForNotImplements = " didn't implement interface org.apache.servicecomb.governance.utils.CustomMatch"; + public static final String errorMessageForAbstractClass = " should be a instantiable class rather than abstract class or other else"; + public static final String infoMessageForCreatingClass = "is not in spring container, create one and register it to spring container"; private static final String OPERATOR_SUFFIX = "Operator"; @@ -52,7 +54,7 @@ public RequestProcessor(Map operatorMap) { this.operatorMap = operatorMap; } - public boolean match(GovernanceRequest request, Matcher matcher) { + public boolean match(GovernanceRequestExtractor request, Matcher matcher) { if (!methodMatch(request, matcher)) { return false; } @@ -68,38 +70,38 @@ public boolean match(GovernanceRequest request, Matcher matcher) { return customMatch(request, matcher); } - private boolean serviceNameMatch(GovernanceRequest request, Matcher matcher) { + private boolean serviceNameMatch(GovernanceRequestExtractor request, Matcher matcher) { if (matcher.getServiceName() == null) { return true; } - return matcher.getServiceName().equals(request.getServiceName()); + return matcher.getServiceName().equals(request.serviceName()); } - private boolean headersMatch(GovernanceRequest request, Matcher matcher) { + private boolean headersMatch(GovernanceRequestExtractor request, Matcher matcher) { if (matcher.getHeaders() == null) { return true; } for (Entry entry : matcher.getHeaders().entrySet()) { - if (request.getHeader(entry.getKey()) == null || - !operatorMatch(request.getHeader(entry.getKey()), entry.getValue())) { + if (request.header(entry.getKey()) == null || + !operatorMatch(request.header(entry.getKey()), entry.getValue())) { return false; } } return true; } - private boolean apiPathMatch(GovernanceRequest request, Matcher matcher) { + private boolean apiPathMatch(GovernanceRequestExtractor request, Matcher matcher) { if (matcher.getApiPath() == null) { return true; } - return operatorMatch(request.getUri(), matcher.getApiPath()); + return operatorMatch(request.apiPath(), matcher.getApiPath()); } - private boolean methodMatch(GovernanceRequest request, Matcher matcher) { + private boolean methodMatch(GovernanceRequestExtractor request, Matcher matcher) { if (matcher.getMethod() == null) { return true; } - return matcher.getMethod().contains(request.getMethod()); + return matcher.getMethod().contains(request.method()); } private boolean operatorMatch(String str, RawOperator rawOperator) { @@ -120,7 +122,7 @@ private boolean operatorMatch(String str, RawOperator rawOperator) { return true; } - private boolean customMatch(GovernanceRequest request, Matcher matcher) { + private boolean customMatch(GovernanceRequestExtractor request, Matcher matcher) { if (matcher.getCustomMatcher() == null) { return true; } @@ -172,8 +174,8 @@ public CustomMatch generateHandler(String customMatcherHandler) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) applicationContext; registry.registerBeanDefinition(customMatcherHandler, builder.getBeanDefinition()); try { - extractObject = (CustomMatch) applicationContext.getBean(customMatcherHandler); - return extractObject; + extractObject = (CustomMatch) applicationContext.getBean(customMatcherHandler); + return extractObject; } catch (BeansException e) { LOGGER.error(e.getMessage(), e); throw new RuntimeException(customMatcherHandler + errorMessageForAbstractClass, e); diff --git a/governance/src/main/java/org/apache/servicecomb/governance/marker/TrafficMarker.java b/governance/src/main/java/org/apache/servicecomb/governance/marker/TrafficMarker.java index 330e8605f84..73dcd526c9a 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/marker/TrafficMarker.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/marker/TrafficMarker.java @@ -39,7 +39,7 @@ public void setMatches(List matches) { this.matches = matches; } - public boolean checkMatch(GovernanceRequest governanceRequest, RequestProcessor requestProcessor) { - return this.matches.stream().anyMatch(match -> requestProcessor.match(governanceRequest, match)); + public boolean checkMatch(GovernanceRequestExtractor extractor, RequestProcessor requestProcessor) { + return this.matches.stream().anyMatch(match -> requestProcessor.match(extractor, match)); } } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/service/MatchersService.java b/governance/src/main/java/org/apache/servicecomb/governance/service/MatchersService.java index 43d2756558d..1b0f0b42f9a 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/service/MatchersService.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/service/MatchersService.java @@ -16,8 +16,8 @@ */ package org.apache.servicecomb.governance.service; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; public interface MatchersService { - boolean checkMatch(GovernanceRequest governanceRequest, String key); + boolean checkMatch(GovernanceRequestExtractor governanceRequest, String key); } diff --git a/governance/src/main/java/org/apache/servicecomb/governance/service/MatchersServiceImpl.java b/governance/src/main/java/org/apache/servicecomb/governance/service/MatchersServiceImpl.java index b4d09af2490..cc851f16892 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/service/MatchersServiceImpl.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/service/MatchersServiceImpl.java @@ -19,7 +19,7 @@ import java.util.Map; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.marker.RequestProcessor; import org.apache.servicecomb.governance.marker.TrafficMarker; import org.apache.servicecomb.governance.properties.MatchProperties; @@ -35,7 +35,7 @@ public MatchersServiceImpl(RequestProcessor requestProcessor, MatchProperties ma } @Override - public boolean checkMatch(GovernanceRequest governanceRequest, String key) { + public boolean checkMatch(GovernanceRequestExtractor governanceRequest, String key) { Map parsedEntity = matchProperties.getParsedEntity(); TrafficMarker trafficMarker = parsedEntity.get(key); diff --git a/governance/src/main/java/org/apache/servicecomb/governance/utils/CustomMatch.java b/governance/src/main/java/org/apache/servicecomb/governance/utils/CustomMatch.java index faf9461d39b..d2627e82af8 100644 --- a/governance/src/main/java/org/apache/servicecomb/governance/utils/CustomMatch.java +++ b/governance/src/main/java/org/apache/servicecomb/governance/utils/CustomMatch.java @@ -17,8 +17,8 @@ package org.apache.servicecomb.governance.utils; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; public interface CustomMatch { - boolean matchRequest(GovernanceRequest request, String parameters); + boolean matchRequest(GovernanceRequestExtractor requestExtractor, String parameters); } diff --git a/governance/src/main/java/org/apache/servicecomb/router/RouterFilter.java b/governance/src/main/java/org/apache/servicecomb/router/RouterFilter.java index 7e48b9c002b..549123b1432 100644 --- a/governance/src/main/java/org/apache/servicecomb/router/RouterFilter.java +++ b/governance/src/main/java/org/apache/servicecomb/router/RouterFilter.java @@ -45,8 +45,8 @@ public RouterFilter(RouterRuleMatcher routerRuleMatcher, RouterRuleCache routerR this.routerRuleCache = routerRuleCache; } - public List getFilteredListOfServers(List list, - String targetServiceName, Map headers, RouterDistributor distributer) { + public List getFilteredListOfServers(List list, + String targetServiceName, Map headers, RouterDistributor distributor) { if (CollectionUtils.isEmpty(list)) { return list; } @@ -69,7 +69,7 @@ public List getFilteredListOfServers(List list, LOGGER.debug("route management match rule success: {}", invokeRule); // 3.distribute select endpoint - List resultList = distributer.distribute(targetServiceName, list, invokeRule); + List resultList = distributor.distribute(targetServiceName, list, invokeRule); LOGGER.debug("route management distribute rule success: {}", resultList); diff --git a/governance/src/main/java/org/apache/servicecomb/router/cache/RouterRuleCache.java b/governance/src/main/java/org/apache/servicecomb/router/cache/RouterRuleCache.java index 8ff480cead5..05ffeb6a376 100644 --- a/governance/src/main/java/org/apache/servicecomb/router/cache/RouterRuleCache.java +++ b/governance/src/main/java/org/apache/servicecomb/router/cache/RouterRuleCache.java @@ -49,6 +49,8 @@ public class RouterRuleCache { private static final String ROUTE_RULE = "servicecomb.routeRule.%s"; + public static final String GLOBAL_ROUTE_RULE_KEY = "servicecomb.globalRouteRule"; + private final Environment environment; private final ConcurrentHashMap serviceInfoCacheMap = new ConcurrentHashMap<>(); @@ -78,7 +80,7 @@ public boolean doInit(String targetServiceName) { if (serviceInfoCacheMap.containsKey(targetServiceName)) { return true; } - return addAllRule(targetServiceName, environment.getProperty(String.format(ROUTE_RULE, targetServiceName), "")); + return addAllRule(targetServiceName); } } return true; @@ -90,10 +92,17 @@ public void onConfigurationChangedEvent(GovernanceConfigurationChangedEvent even if (key.startsWith(ROUTE_RULE_PREFIX)) { serviceInfoCacheMap.remove(key.substring(ROUTE_RULE_PREFIX.length())); } + if (key.equals(GLOBAL_ROUTE_RULE_KEY)) { + serviceInfoCacheMap.clear(); + } } } - private boolean addAllRule(String targetServiceName, String ruleStr) { + private boolean addAllRule(String targetServiceName) { + String ruleStr = environment.getProperty(String.format(ROUTE_RULE, targetServiceName), ""); + if (StringUtils.isEmpty(ruleStr)) { + ruleStr = environment.getProperty(GLOBAL_ROUTE_RULE_KEY, ""); + } if (StringUtils.isEmpty(ruleStr)) { return false; } @@ -114,6 +123,8 @@ private boolean addAllRule(String targetServiceName, String ruleStr) { } ServiceInfoCache serviceInfoCache = new ServiceInfoCache(policyRuleItemList); serviceInfoCacheMap.put(targetServiceName, serviceInfoCache); + LOGGER.info("Route management serialization service {} rules success, content: {}", targetServiceName, + serviceInfoCache.getAllrule()); return true; } @@ -121,7 +132,8 @@ private boolean addAllRule(String targetServiceName, String ruleStr) { * if a server don't have rule , avoid registered too many callback , it may cause memory leak */ private boolean isServerContainRule(String targetServiceName) { - return !StringUtils.isEmpty(environment.getProperty(String.format(ROUTE_RULE, targetServiceName), "")); + return !StringUtils.isEmpty(environment.getProperty(String.format(ROUTE_RULE, targetServiceName), "")) || + !StringUtils.isEmpty(environment.getProperty(GLOBAL_ROUTE_RULE_KEY, "")); } public ConcurrentHashMap getServiceInfoCacheMap() { diff --git a/governance/src/main/java/org/apache/servicecomb/router/distribute/AbstractRouterDistributor.java b/governance/src/main/java/org/apache/servicecomb/router/distribute/AbstractRouterDistributor.java index d3f8f7640f5..c34ad909ae8 100644 --- a/governance/src/main/java/org/apache/servicecomb/router/distribute/AbstractRouterDistributor.java +++ b/governance/src/main/java/org/apache/servicecomb/router/distribute/AbstractRouterDistributor.java @@ -17,34 +17,31 @@ package org.apache.servicecomb.router.distribute; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; -import java.util.stream.Collectors; import org.apache.servicecomb.router.cache.RouterRuleCache; import org.apache.servicecomb.router.model.PolicyRuleItem; import org.apache.servicecomb.router.model.RouteItem; import org.apache.servicecomb.router.model.TagItem; -import org.apache.servicecomb.router.util.VersionCompareUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; -public abstract class AbstractRouterDistributor implements - RouterDistributor { +public abstract class AbstractRouterDistributor implements + RouterDistributor { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRouterDistributor.class); - private Function getIns; + private Function getVersion; - private Function getVersion; + private Function getServerName; - private Function getServerName; - - private Function> getProperties; + private Function> getProperties; private RouterRuleCache routerRuleCache; @@ -56,35 +53,61 @@ public void setRouterRuleCache(RouterRuleCache routerRuleCache) { protected AbstractRouterDistributor() { } + /** + * distribute logic: + * 1、First according to the set route rules to choose target instances, if have just return. + * 2、if route rules not match instance, check if fallback rules are set, if set and match instances then return. + * 3、if route and fallback routes all have not match instance, then if route rules weight count less 100, return + * unset instances, otherwise return all instances. + * @param targetServiceName + * @param list + * @param invokeRule + * @return + */ @Override - public List distribute(String targetServiceName, List list, PolicyRuleItem invokeRule) { - //init LatestVersion - initLatestVersion(targetServiceName, list); + public List distribute(String targetServiceName, List list, PolicyRuleItem invokeRule) { + invokeRule.check(); - invokeRule.check( - routerRuleCache.getServiceInfoCacheMap().get(targetServiceName).getLatestVersionTag()); + // unSetTags instance list + List unSetTagInstances = new ArrayList<>(); - // get tag list - Map> versionServerMap = getDistributList(targetServiceName, list, invokeRule); + // record fallback router targItem instance + Map> fallbackVersionServerMap = new HashMap<>(); - if (CollectionUtils.isEmpty(versionServerMap)) { - LOGGER.debug("route management can not match any rule and route the latest version"); - return getLatestVersionList(list, targetServiceName); - } + // get tag instance map, fallbackVersionServerMap, unSetTagInstances + Map> versionServerMap = getDistributList(targetServiceName, list, invokeRule, + unSetTagInstances, fallbackVersionServerMap); + // weight calculation to obtain the next tags instance TagItem targetTag = getFiltedServerTagItem(invokeRule, targetServiceName); - if (versionServerMap.containsKey(targetTag)) { + if (targetTag != null && versionServerMap.containsKey(targetTag)) { return versionServerMap.get(targetTag); } - return getLatestVersionList(list, targetServiceName); + + if (!fallbackVersionServerMap.isEmpty()) { + // weight calculation to obtain the next fallback tags instance + TagItem fallbackTargetTag = getFallbackFiltedServerTagItem(invokeRule, targetServiceName); + if (fallbackTargetTag != null && fallbackVersionServerMap.containsKey(fallbackTargetTag)) { + return fallbackVersionServerMap.get(fallbackTargetTag); + } + } + + // has weightLess situation and unSetTagInstances has values + if (invokeRule.isWeightLess() && !unSetTagInstances.isEmpty()) { + return unSetTagInstances; + } + if (invokeRule.isEmptyProtection()) { + return list; + } + + // weight set 100 but not matched any instance, then return empty when emptyProtection close + return Collections.emptyList(); } @Override - public void init(Function getIns, - Function getVersion, - Function getServerName, - Function> getProperties) { - this.getIns = getIns; + public void init(Function getVersion, + Function getServerName, + Function> getProperties) { this.getVersion = getVersion; this.getServerName = getServerName; this.getProperties = getProperties; @@ -95,76 +118,63 @@ public TagItem getFiltedServerTagItem(PolicyRuleItem rule, String targetServiceN .getNextInvokeVersion(rule); } + public TagItem getFallbackFiltedServerTagItem(PolicyRuleItem rule, String targetServiceName) { + return routerRuleCache.getServiceInfoCacheMap().get(targetServiceName) + .getFallbackNextInvokeVersion(rule); + } + /** - * 1.filter targetService + * 1.filter set route rules targetService, build fallback targetService map and unSetTagInstances list. * 2.establish map is a more complicate way than direct traversal, because of multiple matches. * * the method getProperties() contains other field that we don't need. */ - private Map> getDistributList(String serviceName, - List list, - PolicyRuleItem invokeRule) { - String latestV = routerRuleCache.getServiceInfoCacheMap().get(serviceName).getLatestVersionTag() - .getVersion(); - Map> versionServerMap = new HashMap<>(); - for (T server : list) { + private Map> getDistributList(String serviceName, List list, + PolicyRuleItem invokeRule, List unSetTagInstances, Map> fallbackVersionMap) { + Map> versionServerMap = new HashMap<>(); + for (INSTANCE instance : list) { //get server - E ms = getIns.apply(server); - if (getServerName.apply(ms).equals(serviceName)) { - //most matching - TagItem tagItem = new TagItem(getVersion.apply(ms), getProperties.apply(ms)); - TagItem targetTag = null; - int maxMatch = 0; - for (RouteItem entry : invokeRule.getRoute()) { - if (entry.getTagitem() == null){ - continue; - } - int nowMatch = entry.getTagitem().matchNum(tagItem); - if (nowMatch > maxMatch) { - maxMatch = nowMatch; - targetTag = entry.getTagitem(); - } - } - if (invokeRule.isWeightLess() && getVersion.apply(ms).equals(latestV)) { - TagItem latestVTag = invokeRule.getRoute().get(invokeRule.getRoute().size() - 1) - .getTagitem(); - if (!versionServerMap.containsKey(latestVTag)) { - versionServerMap.put(latestVTag, new ArrayList<>()); - } - versionServerMap.get(latestVTag).add(server); - } + if (getServerName.apply(instance).equals(serviceName)) { + TagItem tagItem = new TagItem(getVersion.apply(instance), getProperties.apply(instance)); + // route most matching TagItem + TagItem targetTag = buildTargetTag(invokeRule.getRoute(), tagItem); if (targetTag != null) { if (!versionServerMap.containsKey(targetTag)) { versionServerMap.put(targetTag, new ArrayList<>()); } - versionServerMap.get(targetTag).add(server); + versionServerMap.get(targetTag).add(instance); + } else { + // not matched, placed in the unset tag instances collection + unSetTagInstances.add(instance); + } + // ensure the tags can build when set for both route and fallback at the same time + if (!CollectionUtils.isEmpty(invokeRule.getFallback())) { + // fallback most matching TagItem + TagItem targetTagFallback = buildTargetTag(invokeRule.getFallback(), tagItem); + if (!fallbackVersionMap.containsKey(targetTagFallback)) { + fallbackVersionMap.put(targetTagFallback, new ArrayList<>()); + } + fallbackVersionMap.get(targetTagFallback).add(instance); } } } return versionServerMap; } - - public void initLatestVersion(String serviceName, List list) { - String latestVersion = null; - for (T server : list) { - E ms = getIns.apply(server); - if (getServerName.apply(ms).equals(serviceName)) { - if (latestVersion == null || VersionCompareUtil - .compareVersion(latestVersion, getVersion.apply(ms)) == -1) { - latestVersion = getVersion.apply(ms); - } + private TagItem buildTargetTag(List route, TagItem tagItem) { + int maxMatch = 0; + TagItem targetTag = null; + // obtain the rule with the most parameter matches + for (RouteItem entry : route) { + if (entry.getTagitem() == null){ + continue; + } + int nowMatch = entry.getTagitem().matchNum(tagItem); + if (nowMatch > maxMatch) { + maxMatch = nowMatch; + targetTag = entry.getTagitem(); } } - TagItem tagitem = new TagItem(latestVersion); - routerRuleCache.getServiceInfoCacheMap().get(serviceName).setLatestVersionTag(tagitem); - } - - public List getLatestVersionList(List list, String targetServiceName) { - String latestV = routerRuleCache.getServiceInfoCacheMap().get(targetServiceName) - .getLatestVersionTag().getVersion(); - return list.stream().filter(server -> - getVersion.apply(getIns.apply(server)).equals(latestV) - ).collect(Collectors.toList()); + return targetTag; } } diff --git a/governance/src/main/java/org/apache/servicecomb/router/distribute/RouterDistributor.java b/governance/src/main/java/org/apache/servicecomb/router/distribute/RouterDistributor.java index fe5cf37b14f..fa9af8fd16f 100644 --- a/governance/src/main/java/org/apache/servicecomb/router/distribute/RouterDistributor.java +++ b/governance/src/main/java/org/apache/servicecomb/router/distribute/RouterDistributor.java @@ -19,17 +19,18 @@ import java.util.List; import java.util.Map; import java.util.function.Function; + import org.apache.servicecomb.router.model.PolicyRuleItem; /** * @Author GuoYl123 * @Date 2019/10/17 **/ -public interface RouterDistributor { +public interface RouterDistributor { - void init(Function getIns, Function getVersion, - Function getServerName, - Function> getProperties); + void init(Function getVersion, + Function getServerName, + Function> getProperties); - List distribute(String targetServiceName, List list, PolicyRuleItem invokeRule); + List distribute(String targetServiceName, List list, PolicyRuleItem invokeRule); } diff --git a/governance/src/main/java/org/apache/servicecomb/router/model/PolicyRuleItem.java b/governance/src/main/java/org/apache/servicecomb/router/model/PolicyRuleItem.java index edf950505e5..2f1f1421a1b 100644 --- a/governance/src/main/java/org/apache/servicecomb/router/model/PolicyRuleItem.java +++ b/governance/src/main/java/org/apache/servicecomb/router/model/PolicyRuleItem.java @@ -42,37 +42,36 @@ public class PolicyRuleItem implements Comparable { private boolean weightLess = false; + private List fallback; + + private Integer fallbackTotal; + + private boolean emptyProtection = true; + public PolicyRuleItem() { } /** * if weight is less than 100, fill with minimum version * - * @param latestVersionTag */ - public void check(TagItem latestVersionTag) { + public void check() { if (CollectionUtils.isEmpty(route)) { throw new RouterIllegalParamException("canary rule list can not be null"); } - if (route.size() == 1) { - route.get(0).setWeight(100); - return; - } int sum = 0; for (RouteItem item : route) { if (item.getWeight() == null) { throw new RouterIllegalParamException("canary rule weight can not be null"); } + sum += item.getWeight(); } if (sum > 100) { LOGGER.warn("canary rule weight sum is more than 100"); } else if (sum < 100) { - if (latestVersionTag == null) { - LOGGER.warn("canary has some error when set default latestVersion"); - } weightLess = true; - route.add(new RouteItem(100 - sum, latestVersionTag)); + route.add(new RouteItem(100 - sum, null)); } } @@ -121,6 +120,30 @@ public void setWeightLess(boolean weightLess) { this.weightLess = weightLess; } + public List getFallback() { + return fallback; + } + + public void setFallback(List fallback) { + this.fallback = fallback; + } + + public Integer getFallbackTotal() { + return fallbackTotal; + } + + public void setFallbackTotal(Integer fallbackTotal) { + this.fallbackTotal = fallbackTotal; + } + + public boolean isEmptyProtection() { + return emptyProtection; + } + + public void setEmptyProtection(boolean emptyProtection) { + this.emptyProtection = emptyProtection; + } + @Override public String toString() { return "PolicyRuleItem{" + @@ -129,6 +152,9 @@ public String toString() { ", route=" + route + ", total=" + total + ", weightLess=" + weightLess + + ", fallback=" + fallback + + ", fallbackTotal=" + fallbackTotal + + ", emptyProtection=" + emptyProtection + '}'; } } diff --git a/governance/src/main/java/org/apache/servicecomb/router/model/ServiceInfoCache.java b/governance/src/main/java/org/apache/servicecomb/router/model/ServiceInfoCache.java index 15cc08c949b..67640320636 100644 --- a/governance/src/main/java/org/apache/servicecomb/router/model/ServiceInfoCache.java +++ b/governance/src/main/java/org/apache/servicecomb/router/model/ServiceInfoCache.java @@ -19,6 +19,8 @@ import java.util.List; import java.util.stream.Collectors; +import org.springframework.util.CollectionUtils; + /** * @Author GuoYl123 * @Date 2019/10/17 @@ -34,9 +36,12 @@ public class ServiceInfoCache { public ServiceInfoCache(List policyRuleItemList) { this.allrule = policyRuleItemList.stream().sorted().collect(Collectors.toList()); - this.getAllrule().forEach(rule -> - rule.getRoute().forEach(RouteItem::initTagItem) - ); + this.getAllrule().forEach(rule -> { + rule.getRoute().forEach(RouteItem::initTagItem); + if (!CollectionUtils.isEmpty(rule.getFallback())) { + rule.getFallback().forEach(RouteItem::initTagItem); + } + }); } public TagItem getNextInvokeVersion(PolicyRuleItem policyRuleItem) { @@ -44,7 +49,19 @@ public TagItem getNextInvokeVersion(PolicyRuleItem policyRuleItem) { if (policyRuleItem.getTotal() == null) { policyRuleItem.setTotal(rule.stream().mapToInt(RouteItem::getWeight).sum()); } - rule.stream().forEach(RouteItem::addCurrentWeight); + return calculateWeight(rule, policyRuleItem.getTotal()); + } + + public TagItem getFallbackNextInvokeVersion(PolicyRuleItem policyRuleItem) { + List rule = policyRuleItem.getFallback(); + if (policyRuleItem.getFallbackTotal() == null) { + policyRuleItem.setFallbackTotal(rule.stream().mapToInt(RouteItem::getWeight).sum()); + } + return calculateWeight(rule, policyRuleItem.getFallbackTotal()); + } + + private TagItem calculateWeight(List rule, int total) { + rule.forEach(RouteItem::addCurrentWeight); int maxIndex = 0, maxWeight = -1; for (int i = 0; i < rule.size(); i++) { if (maxWeight < rule.get(i).getCurrentWeight()) { @@ -52,7 +69,7 @@ public TagItem getNextInvokeVersion(PolicyRuleItem policyRuleItem) { maxWeight = rule.get(i).getCurrentWeight(); } } - rule.get(maxIndex).reduceCurrentWeight(policyRuleItem.getTotal()); + rule.get(maxIndex).reduceCurrentWeight(total); return rule.get(maxIndex).getTagitem(); } diff --git a/governance/src/test/java/org/apache/servicecomb/governance/BulkheadHandlerTest.java b/governance/src/test/java/org/apache/servicecomb/governance/BulkheadHandlerTest.java index d08541b5163..a5d429cc456 100644 --- a/governance/src/test/java/org/apache/servicecomb/governance/BulkheadHandlerTest.java +++ b/governance/src/test/java/org/apache/servicecomb/governance/BulkheadHandlerTest.java @@ -39,7 +39,7 @@ public void setInstanceIsolationHandler(BulkheadHandler bulkheadHandler) { @Test public void testMatchPriorityPolicy() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/bulkhead"); + request.setApiPath("/bulkhead"); BulkheadPolicy policy = bulkheadHandler.matchPolicy(request); Assertions.assertEquals("demo-bulkhead-priority", policy.getName()); } diff --git a/governance/src/test/java/org/apache/servicecomb/governance/FaultInjectionTest.java b/governance/src/test/java/org/apache/servicecomb/governance/FaultInjectionTest.java index 47eadd80d01..ce343ff2665 100644 --- a/governance/src/test/java/org/apache/servicecomb/governance/FaultInjectionTest.java +++ b/governance/src/test/java/org/apache/servicecomb/governance/FaultInjectionTest.java @@ -57,7 +57,7 @@ public void test_delay_fault_injection_service_name_work() throws Throwable { FaultInjectionDecorators.ofCheckedSupplier(() -> "test"); GovernanceRequest request = new GovernanceRequest(); - request.setUri("/faultInjectDelay"); + request.setApiPath("/faultInjectDelay"); request.setServiceName("srcService"); Fault fault = faultInjectionHandler.getActuator(request); @@ -99,7 +99,7 @@ public void test_abort_fault_injection_service_name_work() throws Throwable { FaultInjectionDecorators.ofCheckedSupplier(() -> "test"); GovernanceRequest request = new GovernanceRequest(); - request.setUri("/faultInjectAbort"); + request.setApiPath("/faultInjectAbort"); request.setServiceName("srcService"); Fault fault = faultInjectionHandler.getActuator(request); @@ -137,7 +137,7 @@ public void test_fallback_returnNull_work() throws Throwable { FaultInjectionDecorators.ofCheckedSupplier(() -> "test"); GovernanceRequest request = new GovernanceRequest(); - request.setUri("/returnNull"); + request.setApiPath("/returnNull"); request.setServiceName("returnNull"); Fault fault = faultInjectionHandler.getActuator(request); @@ -151,7 +151,7 @@ public void test_fallback_ThrowException_work() throws Throwable { FaultInjectionDecorators.ofCheckedSupplier(() -> "test"); GovernanceRequest request = new GovernanceRequest(); - request.setUri("/throwException"); + request.setApiPath("/throwException"); request.setServiceName("ThrowException"); Fault fault = faultInjectionHandler.getActuator(request); @@ -173,7 +173,7 @@ public void test_fallback_forceClosed_work() throws Throwable { FaultInjectionDecorators.ofCheckedSupplier(() -> "test"); GovernanceRequest request = new GovernanceRequest(); - request.setUri("/forceClosed"); + request.setApiPath("/forceClosed"); request.setServiceName("forceClosed"); Fault fault = faultInjectionHandler.getActuator(request); @@ -187,7 +187,7 @@ public void test_fallback_ThrowException_work_handler2() throws Throwable { FaultInjectionDecorators.ofCheckedSupplier(() -> "test"); GovernanceRequest request = new GovernanceRequest(); - request.setUri("/throwException"); + request.setApiPath("/throwException"); request.setServiceName("ThrowException"); Fault fault = faultInjectionHandler2.getActuator(request); diff --git a/governance/src/test/java/org/apache/servicecomb/governance/FlowControlTest.java b/governance/src/test/java/org/apache/servicecomb/governance/FlowControlTest.java index 52d067a6829..c649e21cf3e 100644 --- a/governance/src/test/java/org/apache/servicecomb/governance/FlowControlTest.java +++ b/governance/src/test/java/org/apache/servicecomb/governance/FlowControlTest.java @@ -52,7 +52,7 @@ public void test_rate_limiting_work() throws Throwable { DecorateCheckedSupplier ds = Decorators.ofCheckedSupplier(() -> "test"); GovernanceRequest request = new GovernanceRequest(); - request.setUri("/hello"); + request.setApiPath("/hello"); RateLimiter rateLimiter = rateLimitingHandler.getActuator(request); ds.withRateLimiter(rateLimiter); @@ -90,7 +90,7 @@ public void test_rate_limiting_service_name_work() throws Throwable { DecorateCheckedSupplier ds = Decorators.ofCheckedSupplier(() -> "test"); GovernanceRequest request = new GovernanceRequest(); - request.setUri("/helloServiceName"); + request.setApiPath("/helloServiceName"); request.setServiceName("srcService"); RateLimiter rateLimiter = rateLimitingHandler.getActuator(request); diff --git a/governance/src/test/java/org/apache/servicecomb/governance/GovernanceCacheHandlerTest.java b/governance/src/test/java/org/apache/servicecomb/governance/GovernanceCacheHandlerTest.java index 3d1ee28cd8e..38ad5e1990f 100644 --- a/governance/src/test/java/org/apache/servicecomb/governance/GovernanceCacheHandlerTest.java +++ b/governance/src/test/java/org/apache/servicecomb/governance/GovernanceCacheHandlerTest.java @@ -40,7 +40,7 @@ public void setInstanceIsolationHandler(@Autowired GovernanceCacheHandler governanceCache = governanceCacheHandler.getActuator(request); diff --git a/governance/src/test/java/org/apache/servicecomb/governance/IdentifierRateLimitingHandlerTest.java b/governance/src/test/java/org/apache/servicecomb/governance/IdentifierRateLimitingHandlerTest.java index 9917b70078c..288aa7bbb83 100644 --- a/governance/src/test/java/org/apache/servicecomb/governance/IdentifierRateLimitingHandlerTest.java +++ b/governance/src/test/java/org/apache/servicecomb/governance/IdentifierRateLimitingHandlerTest.java @@ -54,7 +54,7 @@ public void test_rate_limiting_work() throws Throwable { DecorateCheckedSupplier ds = Decorators.ofCheckedSupplier(() -> "test"); GovernanceRequest request = new GovernanceRequest(); - request.setUri("/hello"); + request.setApiPath("/hello"); Map headers = new HashMap<>(); headers.put("test", "1234"); request.setHeaders(headers); @@ -95,7 +95,7 @@ public void test_rate_limiting_service_name_work() throws Throwable { DecorateCheckedSupplier ds = Decorators.ofCheckedSupplier(() -> "test"); GovernanceRequest request = new GovernanceRequest(); - request.setUri("/helloServiceName"); + request.setApiPath("/helloServiceName"); request.setServiceName("srcService"); Map headers = new HashMap<>(); headers.put("test", "1234"); diff --git a/governance/src/test/java/org/apache/servicecomb/governance/InstanceBulkheadHandlerTest.java b/governance/src/test/java/org/apache/servicecomb/governance/InstanceBulkheadHandlerTest.java index 2377aabc3ad..f815db3e289 100644 --- a/governance/src/test/java/org/apache/servicecomb/governance/InstanceBulkheadHandlerTest.java +++ b/governance/src/test/java/org/apache/servicecomb/governance/InstanceBulkheadHandlerTest.java @@ -55,7 +55,7 @@ public void test_instance_bulkhead_work() throws Throwable { GovernanceRequest requestInstance1 = new GovernanceRequest(); requestInstance1.setInstanceId("instance01"); requestInstance1.setServiceName("service01"); - requestInstance1.setUri("/test"); + requestInstance1.setApiPath("/test"); Bulkhead bulkheadInstance1 = instanceBulkheadHandler.getActuator(requestInstance1); dsInstance1.withBulkhead(bulkheadInstance1); @@ -69,7 +69,7 @@ public void test_instance_bulkhead_work() throws Throwable { GovernanceRequest requestInstance2 = new GovernanceRequest(); requestInstance2.setInstanceId("instance02"); requestInstance2.setServiceName("service01"); - requestInstance2.setUri("/test"); + requestInstance2.setApiPath("/test"); Bulkhead bulkheadInstance2 = instanceBulkheadHandler.getActuator(requestInstance2); dsInstance2.withBulkhead(bulkheadInstance2); diff --git a/governance/src/test/java/org/apache/servicecomb/governance/InstanceIsolationTest.java b/governance/src/test/java/org/apache/servicecomb/governance/InstanceIsolationTest.java index b781dc4c5cc..d8a7e729935 100644 --- a/governance/src/test/java/org/apache/servicecomb/governance/InstanceIsolationTest.java +++ b/governance/src/test/java/org/apache/servicecomb/governance/InstanceIsolationTest.java @@ -69,7 +69,7 @@ public void test_instance_isolation_work() throws Throwable { GovernanceRequest request = new GovernanceRequest(); request.setInstanceId("instance01"); request.setServiceName("service01"); - request.setUri("/test"); + request.setApiPath("/test"); CircuitBreaker circuitBreaker = instanceIsolationHandler.getActuator(request); ds.withCircuitBreaker(circuitBreaker); @@ -89,7 +89,7 @@ public void test_instance_isolation_work() throws Throwable { GovernanceRequest request2 = new GovernanceRequest(); request2.setInstanceId("instance02"); request2.setServiceName("service01"); - request2.setUri("/test"); + request2.setApiPath("/test"); CircuitBreaker circuitBreaker2 = instanceIsolationHandler.getActuator(request2); ds2.withCircuitBreaker(circuitBreaker2); diff --git a/governance/src/test/java/org/apache/servicecomb/governance/LoadBalancerTest.java b/governance/src/test/java/org/apache/servicecomb/governance/LoadBalancerTest.java index d18a57a5e69..2a5fb064587 100644 --- a/governance/src/test/java/org/apache/servicecomb/governance/LoadBalancerTest.java +++ b/governance/src/test/java/org/apache/servicecomb/governance/LoadBalancerTest.java @@ -43,7 +43,7 @@ public LoadBalancerTest() { @Test public void test_loadbalance_random() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/loadrandom"); + request.setApiPath("/loadrandom"); request.setServiceName("loadrandom"); LoadBalance loadBalance = loadBalanceHandler.getActuator(request); Assert.assertEquals("Random", loadBalance.getRule()); @@ -52,7 +52,7 @@ public void test_loadbalance_random() { @Test public void test_loadbalance_roundRobin() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/loadroundRobin"); + request.setApiPath("/loadroundRobin"); request.setServiceName("loadroundRobin"); LoadBalance loadBalance = loadBalanceHandler.getActuator(request); Assert.assertEquals("RoundRobin", loadBalance.getRule()); diff --git a/governance/src/test/java/org/apache/servicecomb/governance/MapperTest.java b/governance/src/test/java/org/apache/servicecomb/governance/MapperTest.java index 8ee861bc957..222af7f0ea7 100644 --- a/governance/src/test/java/org/apache/servicecomb/governance/MapperTest.java +++ b/governance/src/test/java/org/apache/servicecomb/governance/MapperTest.java @@ -43,7 +43,7 @@ public void setMapperHandler(MapperHandler mapperHandler, @Qualifier("mapperHand @Test public void test_mapper_work() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/mapper/v1"); + request.setApiPath("/mapper/v1"); Mapper mapper = mapperHandler.getActuator(request); Assertions.assertEquals(2, mapper.target().size()); Assertions.assertEquals("127.0.0.1", mapper.target().get("host")); @@ -53,7 +53,7 @@ public void test_mapper_work() { @Test public void test_mapper2_work() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/mapper/v1"); + request.setApiPath("/mapper/v1"); Mapper mapper = mapperHandler2.getActuator(request); Assertions.assertEquals(2, mapper.target().size()); Assertions.assertEquals("127.0.0.1", mapper.target().get("host")); diff --git a/governance/src/test/java/org/apache/servicecomb/governance/OperatorTest.java b/governance/src/test/java/org/apache/servicecomb/governance/OperatorTest.java index 82af0908300..25a8490d4e5 100644 --- a/governance/src/test/java/org/apache/servicecomb/governance/OperatorTest.java +++ b/governance/src/test/java/org/apache/servicecomb/governance/OperatorTest.java @@ -46,7 +46,7 @@ public void setRequestProcessor(RequestProcessor requestProcessor) { @Test public void test_unknown_operator() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/test"); + request.setApiPath("/test"); Matcher matcher = new Matcher(); RawOperator apiPath = new RawOperator(); apiPath.put("unknown", "/test"); @@ -57,7 +57,7 @@ public void test_unknown_operator() { @Test public void test_exact_api_path_match() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/bulkhead"); + request.setApiPath("/bulkhead"); Matcher matcher = new Matcher(); RawOperator apiPath = new RawOperator(); apiPath.put("exact", "/bulkhead"); @@ -68,7 +68,7 @@ public void test_exact_api_path_match() { @Test public void test_prefix_api_path_match() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/bulkhead/hello"); + request.setApiPath("/bulkhead/hello"); Matcher matcher = new Matcher(); RawOperator apiPath = new RawOperator(); apiPath.put("prefix", "/bulkhead"); @@ -79,7 +79,7 @@ public void test_prefix_api_path_match() { @Test public void test_prefix_api_path_not_match_null() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/bulkhead/hello"); + request.setApiPath("/bulkhead/hello"); Matcher matcher = new Matcher(); RawOperator apiPath = new RawOperator(); apiPath.put("prefix", null); @@ -90,7 +90,7 @@ public void test_prefix_api_path_not_match_null() { @Test public void test_suffix_api_path_match() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/api/bulkhead"); + request.setApiPath("/api/bulkhead"); Matcher matcher = new Matcher(); RawOperator apiPath = new RawOperator(); apiPath.put("suffix", "/bulkhead"); @@ -101,7 +101,7 @@ public void test_suffix_api_path_match() { @Test public void test_suffix_api_path_not_match_null() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/api/bulkhead"); + request.setApiPath("/api/bulkhead"); Matcher matcher = new Matcher(); RawOperator apiPath = new RawOperator(); apiPath.put("suffix", null); @@ -112,18 +112,18 @@ public void test_suffix_api_path_not_match_null() { @Test public void test_exact_api_path_not_match() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/bulkhead/"); + request.setApiPath("/bulkhead/"); Matcher matcher = new Matcher(); RawOperator apiPath = new RawOperator(); apiPath.put("exact", "/bulkhead"); matcher.setApiPath(apiPath); Assertions.assertFalse(requestProcessor.match(request, matcher)); - request.setUri("/bulkhead"); - request.setUri(null); + request.setApiPath("/bulkhead"); + request.setApiPath(null); Assertions.assertFalse(requestProcessor.match(request, matcher)); - request.setUri("/bulkhead"); + request.setApiPath("/bulkhead"); apiPath.clear(); matcher.setApiPath(apiPath); Assertions.assertFalse(requestProcessor.match(request, matcher)); @@ -132,7 +132,7 @@ public void test_exact_api_path_not_match() { @Test public void test_exact_api_path_match_header_match() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/bulkhead"); + request.setApiPath("/bulkhead"); request.setMethod("GET"); Map reqHeaders = new HashMap<>(); reqHeaders.put("header1", "value1"); @@ -153,7 +153,7 @@ public void test_exact_api_path_match_header_match() { @Test public void test_exact_api_path_match_header_not_match() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/bulkhead"); + request.setApiPath("/bulkhead"); request.setMethod("GET"); Map reqHeaders = new HashMap<>(); reqHeaders.put("header1", "value2"); diff --git a/governance/src/test/java/org/apache/servicecomb/governance/TimeLimiterHandlerTest.java b/governance/src/test/java/org/apache/servicecomb/governance/TimeLimiterHandlerTest.java index 5fd484f5ec4..dbefeba6ba9 100644 --- a/governance/src/test/java/org/apache/servicecomb/governance/TimeLimiterHandlerTest.java +++ b/governance/src/test/java/org/apache/servicecomb/governance/TimeLimiterHandlerTest.java @@ -42,7 +42,7 @@ public void setInstanceIsolationHandler(TimeLimiterHandler timeLimiterHandler) { @Test public void testMatchPriorityPolicy() { GovernanceRequest request = new GovernanceRequest(); - request.setUri("/timeLimiter"); + request.setApiPath("/timeLimiter"); TimeLimiterPolicy policy = timeLimiterHandler.matchPolicy(request); Assertions.assertEquals("demo-timeLimiter", policy.getName()); TimeLimiter timeLimiter = timeLimiterHandler.getActuator(request); diff --git a/governance/src/test/java/org/apache/servicecomb/governance/mockclasses/ClassWithAnnotation.java b/governance/src/test/java/org/apache/servicecomb/governance/mockclasses/ClassWithAnnotation.java index a9d1f7ccd75..263d442f40b 100644 --- a/governance/src/test/java/org/apache/servicecomb/governance/mockclasses/ClassWithAnnotation.java +++ b/governance/src/test/java/org/apache/servicecomb/governance/mockclasses/ClassWithAnnotation.java @@ -17,23 +17,23 @@ package org.apache.servicecomb.governance.mockclasses; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.mockclasses.service.MockProfileClassUserService; -import org.apache.servicecomb.governance.marker.GovernanceRequest; import org.apache.servicecomb.governance.utils.CustomMatch; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.stream.Collectors; -import java.util.stream.Stream; - @Component -public class ClassWithAnnotation implements CustomMatch { - @Autowired - MockProfileClassUserService service; +public class ClassWithAnnotation implements CustomMatch { + @Autowired + MockProfileClassUserService service; - @Override - public boolean matchRequest(GovernanceRequest request, String parameters) { - String profileValue = service.getUser(); - return Stream.of(parameters.split(",")).collect(Collectors.toSet()).contains(profileValue); - } + @Override + public boolean matchRequest(GovernanceRequestExtractor requestExtractor, String parameters) { + String profileValue = service.getUser(); + return Stream.of(parameters.split(",")).collect(Collectors.toSet()).contains(profileValue); + } } diff --git a/governance/src/test/java/org/apache/servicecomb/governance/mockclasses/CustomMatchDemo.java b/governance/src/test/java/org/apache/servicecomb/governance/mockclasses/CustomMatchDemo.java index 67181c43b80..dba57a48dd5 100644 --- a/governance/src/test/java/org/apache/servicecomb/governance/mockclasses/CustomMatchDemo.java +++ b/governance/src/test/java/org/apache/servicecomb/governance/mockclasses/CustomMatchDemo.java @@ -17,17 +17,17 @@ package org.apache.servicecomb.governance.mockclasses; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.governance.utils.CustomMatch; -import org.apache.servicecomb.governance.marker.GovernanceRequest; public class CustomMatchDemo implements CustomMatch { - private CustomMatchDemo() { + private CustomMatchDemo() { - } + } - @Override - public boolean matchRequest(GovernanceRequest request, String parameters) { - return true; - } + @Override + public boolean matchRequest(GovernanceRequestExtractor requestExtractor, String parameters) { + return true; + } } diff --git a/governance/src/test/java/org/apache/servicecomb/router/ExampleDistributor.java b/governance/src/test/java/org/apache/servicecomb/router/ExampleDistributor.java index 7c6aa04f945..42fdfad0367 100644 --- a/governance/src/test/java/org/apache/servicecomb/router/ExampleDistributor.java +++ b/governance/src/test/java/org/apache/servicecomb/router/ExampleDistributor.java @@ -21,8 +21,8 @@ import org.springframework.stereotype.Component; @Component -public class ExampleDistributor extends AbstractRouterDistributor { +public class ExampleDistributor extends AbstractRouterDistributor { public ExampleDistributor() { - init(a -> a, ServiceIns::getVersion, ServiceIns::getServerName, ServiceIns::getTags); + init(ServiceIns::getVersion, ServiceIns::getServerName, ServiceIns::getTags); } } diff --git a/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorDynamicConfig2Test.java b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorDynamicConfig2Test.java index f3f36e45399..c3901878bdd 100644 --- a/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorDynamicConfig2Test.java +++ b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorDynamicConfig2Test.java @@ -70,7 +70,7 @@ public class RouterDistributorDynamicConfig2Test { private RouterFilter routerFilter; - private RouterDistributor testDistributor; + private RouterDistributor testDistributor; @Autowired public void setEnvironment(Environment environment) { @@ -83,7 +83,7 @@ public void setRouterFilter(RouterFilter routerFilter) { } @Autowired - public void setTestDistributor(RouterDistributor testDistributor) { + public void setTestDistributor(RouterDistributor testDistributor) { this.testDistributor = testDistributor; } diff --git a/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorDynamicConfigTest.java b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorDynamicConfigTest.java index f57ed65794b..47228c7b82b 100644 --- a/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorDynamicConfigTest.java +++ b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorDynamicConfigTest.java @@ -88,7 +88,7 @@ public class RouterDistributorDynamicConfigTest { private RouterFilter routerFilter; - private RouterDistributor testDistributor; + private RouterDistributor testDistributor; @Autowired public void setEnvironment(Environment environment) { @@ -101,7 +101,7 @@ public void setRouterFilter(RouterFilter routerFilter) { } @Autowired - public void setTestDistributor(RouterDistributor testDistributor) { + public void setTestDistributor(RouterDistributor testDistributor) { this.testDistributor = testDistributor; } diff --git a/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorFileConfigTest.java b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorFileConfigTest.java index 9e5be69376f..19f0f47f35f 100644 --- a/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorFileConfigTest.java +++ b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorFileConfigTest.java @@ -36,7 +36,7 @@ public class RouterDistributorFileConfigTest { private RouterFilter routerFilter; - private RouterDistributor routerDistributor; + private RouterDistributor routerDistributor; @Autowired public void setRouterFilter(RouterFilter routerFilter) { @@ -44,7 +44,7 @@ public void setRouterFilter(RouterFilter routerFilter) { } @Autowired - public void setRouterDistributor(RouterDistributor routerDistributor) { + public void setRouterDistributor(RouterDistributor routerDistributor) { this.routerDistributor = routerDistributor; } diff --git a/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorFileWeightLessTest.java b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorFileWeightLessTest.java new file mode 100644 index 00000000000..763e2eeca09 --- /dev/null +++ b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorFileWeightLessTest.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.router; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.apache.servicecomb.router.distribute.RouterDistributor; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@ContextConfiguration(locations = "classpath:META-INF/spring/*.xml", initializers = ConfigDataApplicationContextInitializer.class) +public class RouterDistributorFileWeightLessTest { + private static final String TARGET_SERVICE_NAME = "test_server2"; + + private RouterFilter routerFilter; + + private RouterDistributor routerDistributor; + + @Autowired + public void setRouterFilter(RouterFilter routerFilter) { + this.routerFilter = routerFilter; + } + + @Autowired + public void setRouterDistributor(RouterDistributor routerDistributor) { + this.routerDistributor = routerDistributor; + } + + @Test + public void testDistribute() { + List list = initServiceList(); + HashMap header = new HashMap<>(); + List listOfServers = routerFilter + .getFilteredListOfServers(list, TARGET_SERVICE_NAME, header, routerDistributor); + Assertions.assertNotNull(listOfServers); + for (ServiceIns server : listOfServers) { + Assertions.assertEquals(TARGET_SERVICE_NAME, server.getServerName()); + } + int serverNum1 = 0; + int unSetTagNum = 0; + + for (int i = 0; i < 10; i++) { + List serverList = routerFilter + .getFilteredListOfServers(list, TARGET_SERVICE_NAME, header, routerDistributor); + for (ServiceIns serviceIns : serverList) { + if ("01".equals(serviceIns.getId())) { + serverNum1++; + } else if ("02".equals(serviceIns.getId())) { + unSetTagNum++; + } + } + } + boolean flag = false; + if (Math.round(unSetTagNum * 1.0 / serverNum1) == 4) { + flag = true; + } + Assertions.assertTrue(flag); + } + + List initServiceList() { + ServiceIns serviceIns1 = new ServiceIns("01", "test_server2"); + ServiceIns serviceIns2 = new ServiceIns("02", "test_server2"); + serviceIns1.setVersion("1.0"); + serviceIns2.setVersion("2.0"); + serviceIns1.addTags("x-group", "red"); + serviceIns2.addTags("x-group", "green"); + List list = new ArrayList<>(); + list.add(serviceIns1); + list.add(serviceIns2); + return list; + } +} diff --git a/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorFileWithFallbackTest.java b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorFileWithFallbackTest.java new file mode 100644 index 00000000000..e4078e5c3ac --- /dev/null +++ b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorFileWithFallbackTest.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.router; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.apache.servicecomb.router.distribute.RouterDistributor; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@ContextConfiguration(locations = "classpath:META-INF/spring/*.xml", initializers = ConfigDataApplicationContextInitializer.class) +public class RouterDistributorFileWithFallbackTest { + private static final String TARGET_SERVICE_NAME_WITHOUT_FALLBACK = "test_server3"; + + private static final String TARGET_SERVICE_NAME_WITH_FALLBACK = "test_server4"; + + private static final String TARGET_SERVICE_NAME_ROUTE_FALLBACK = "test_server5"; + + private RouterFilter routerFilter; + + private RouterDistributor routerDistributor; + + @Autowired + public void setRouterFilter(RouterFilter routerFilter) { + this.routerFilter = routerFilter; + } + + @Autowired + public void setRouterDistributor(RouterDistributor routerDistributor) { + this.routerDistributor = routerDistributor; + } + + @Test + public void testDistributeWithoutFallback() { + List list = initServiceList(TARGET_SERVICE_NAME_WITHOUT_FALLBACK); + HashMap header = new HashMap<>(); + header.put("canary", "canary"); + List listOfServers = routerFilter + .getFilteredListOfServers(list, TARGET_SERVICE_NAME_WITHOUT_FALLBACK, header, routerDistributor); + Assertions.assertNotNull(listOfServers); + for (ServiceIns server : listOfServers) { + Assertions.assertEquals(TARGET_SERVICE_NAME_WITHOUT_FALLBACK, server.getServerName()); + } + int serverNum1 = 0; + int serverNum2 = 0; + + for (int i = 0; i < 10; i++) { + List serverList = routerFilter + .getFilteredListOfServers(list, TARGET_SERVICE_NAME_WITHOUT_FALLBACK, header, routerDistributor); + for (ServiceIns serviceIns : serverList) { + if ("01".equals(serviceIns.getId())) { + serverNum1++; + } else if ("02".equals(serviceIns.getId())) { + serverNum2++; + } + } + } + Assertions.assertTrue(serverNum2 == serverNum1); + } + + @Test + public void testDistributeWithFallback() { + List list = initServiceList(TARGET_SERVICE_NAME_WITH_FALLBACK); + HashMap header = new HashMap<>(); + header.put("canary", "canary"); + List listOfServers = routerFilter + .getFilteredListOfServers(list, TARGET_SERVICE_NAME_WITH_FALLBACK, header, routerDistributor); + Assertions.assertNotNull(listOfServers); + for (ServiceIns server : listOfServers) { + Assertions.assertEquals(TARGET_SERVICE_NAME_WITH_FALLBACK, server.getServerName()); + } + int serverNum1 = 0; + int serverNum2 = 0; + + for (int i = 0; i < 10; i++) { + List serverList = routerFilter + .getFilteredListOfServers(list, TARGET_SERVICE_NAME_WITH_FALLBACK, header, routerDistributor); + for (ServiceIns serviceIns : serverList) { + if ("01".equals(serviceIns.getId())) { + serverNum1++; + } else if ("02".equals(serviceIns.getId())) { + serverNum2++; + } + } + } + Assertions.assertTrue((serverNum2 + serverNum1) == serverNum1); + } + + @Test + public void testDistributeRouteAndFallbackHaveSame() { + List list = initServiceList(TARGET_SERVICE_NAME_ROUTE_FALLBACK); + HashMap header = new HashMap<>(); + header.put("canary", "canary"); + List listOfServers = routerFilter + .getFilteredListOfServers(list, TARGET_SERVICE_NAME_ROUTE_FALLBACK, header, routerDistributor); + Assertions.assertNotNull(listOfServers); + for (ServiceIns server : listOfServers) { + Assertions.assertEquals(TARGET_SERVICE_NAME_ROUTE_FALLBACK, server.getServerName()); + } + int serverNum1 = 0; + int serverNum2 = 0; + + for (int i = 0; i < 20; i++) { + List serverList = routerFilter + .getFilteredListOfServers(list, TARGET_SERVICE_NAME_ROUTE_FALLBACK, header, routerDistributor); + for (ServiceIns serviceIns : serverList) { + if ("01".equals(serviceIns.getId())) { + serverNum1++; + } else if ("02".equals(serviceIns.getId())) { + serverNum2++; + } + } + } + boolean flag = false; + if (Math.round(serverNum1 * 1.0 / serverNum2) == 3) { + flag = true; + } + Assertions.assertTrue(flag); + } + + List initServiceList(String serviceName) { + ServiceIns serviceIns1 = new ServiceIns("01", serviceName); + ServiceIns serviceIns2 = new ServiceIns("02", serviceName); + serviceIns1.setVersion("1.0"); + serviceIns2.setVersion("2.0"); + serviceIns1.addTags("x-group", "red"); + serviceIns2.addTags("x-group", "green"); + List list = new ArrayList<>(); + list.add(serviceIns1); + list.add(serviceIns2); + return list; + } +} diff --git a/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorGlobalConfigTest.java b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorGlobalConfigTest.java new file mode 100644 index 00000000000..587e7fc7d58 --- /dev/null +++ b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorGlobalConfigTest.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.router; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.servicecomb.governance.event.GovernanceConfigurationChangedEvent; +import org.apache.servicecomb.governance.event.GovernanceEventManager; +import org.apache.servicecomb.router.cache.RouterRuleCache; +import org.apache.servicecomb.router.distribute.RouterDistributor; +import org.junit.Before; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.Environment; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@ContextConfiguration(locations = "classpath:META-INF/spring/*.xml", initializers = ConfigDataApplicationContextInitializer.class) +public class RouterDistributorGlobalConfigTest { + private static final String TARGET_SERVICE_NAME = "test_server"; + + public static final String CONFIG_KEY = RouterRuleCache.ROUTE_RULE_PREFIX + TARGET_SERVICE_NAME; + + private static final String RULE_STRING = "" + + " - precedence: 1\n" + + " match:\n" + + " headers: #header匹配\n" + + " appId:\n" + + " regex: 01\n" + + " caseInsensitive: false # 是否区分大小写,默认为false,区分大小写\n" + + " userId:\n" + + " exact: 02\n" + + " route:\n" + + " - weight: 100\n" + + " tags:\n" + + " version: 2.0\n" + + " - precedence: 2\n" + + " match:\n" + + " headers: #header匹配\n" + + " appId:\n" + + " regex: 01\n" + + " caseInsensitive: false # 是否区分大小写,默认为false,区分大小写\n" + + " userId:\n" + + " exact: 03\n" + + " route:\n" + + " - weight: 100\n" + + " tags:\n" + + " version: 1.0\n"; + + private Environment environment; + + private RouterFilter routerFilter; + + private RouterDistributor testDistributor; + + @Autowired + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + @Autowired + public void setRouterFilter(RouterFilter routerFilter) { + this.routerFilter = routerFilter; + } + + @Autowired + public void setTestDistributor(RouterDistributor testDistributor) { + this.testDistributor = testDistributor; + } + + public RouterDistributorGlobalConfigTest() { + } + + private final Map dynamicValues = new HashMap<>(); + + @Before + public void setUp() { + ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) environment; + + if (configurableEnvironment.getPropertySources().contains("testDynamicChange")) { + configurableEnvironment.getPropertySources().remove("testDynamicChange"); + } + + configurableEnvironment.getPropertySources() + .addFirst(new EnumerablePropertySource>("testDynamicChange", dynamicValues) { + @Override + public Object getProperty(String s) { + return this.getSource().get(s); + } + + @Override + public String[] getPropertyNames() { + return this.getSource().keySet().toArray(new String[0]); + } + }); + + dynamicValues.put(RouterRuleCache.GLOBAL_ROUTE_RULE_KEY, RULE_STRING); + postConfigurationChangedEvent(RouterRuleCache.GLOBAL_ROUTE_RULE_KEY); + } + + @Test + public void testUseGlobalRouteRule() { + Map headers = new HashMap<>(); + headers.put("userId", "03"); + headers.put("appId", "01"); + + List serverList = new ArrayList<>(); + ServiceIns ins1 = new ServiceIns("01", TARGET_SERVICE_NAME); + ins1.setVersion("1.0"); + ServiceIns ins2 = new ServiceIns("02", TARGET_SERVICE_NAME); + ins2.addTags("app", "a"); + ins2.setVersion("2.0"); + serverList.add(ins1); + serverList.add(ins2); + + List resultServerList = mainFilter(serverList, headers); + Assertions.assertEquals(1, resultServerList.size()); + Assertions.assertEquals("01", resultServerList.get(0).getId()); + } + + @Test + public void testUseProviderRouteRule() { + String rule = "" + + " - precedence: 1\n" + + " match:\n" + + " headers: #header匹配\n" + + " appId:\n" + + " regex: 01\n" + + " caseInsensitive: false # 是否区分大小写,默认为false,区分大小写\n" + + " userId:\n" + + " exact: 03\n" + + " route:\n" + + " - weight: 100\n" + + " tags:\n" + + " version: 2.0\n" + + " - precedence: 2\n" + + " match:\n" + + " headers: #header匹配\n" + + " appId:\n" + + " regex: 01\n" + + " caseInsensitive: false # 是否区分大小写,默认为false,区分大小写\n" + + " userId:\n" + + " exact: 02\n" + + " route:\n" + + " - weight: 100\n" + + " tags:\n" + + " version: 1.0\n"; + dynamicValues.put(CONFIG_KEY, rule); + postConfigurationChangedEvent(CONFIG_KEY); + + Map headers = new HashMap<>(); + headers.put("userId", "03"); + headers.put("appId", "01"); + + List serverList = new ArrayList<>(); + ServiceIns ins1 = new ServiceIns("01", TARGET_SERVICE_NAME); + ins1.setVersion("1.0"); + ServiceIns ins2 = new ServiceIns("02", TARGET_SERVICE_NAME); + ins2.addTags("app", "a"); + ins2.setVersion("2.0"); + serverList.add(ins1); + serverList.add(ins2); + + List resultServerList = mainFilter(serverList, headers); + Assertions.assertEquals(1, resultServerList.size()); + Assertions.assertEquals("02", resultServerList.get(0).getId()); + } + + private List mainFilter(List serverList, Map headers) { + return routerFilter + .getFilteredListOfServers(serverList, TARGET_SERVICE_NAME, headers, + testDistributor); + } + + private void postConfigurationChangedEvent(String changKey) { + Set changedKeys = new HashSet<>(); + changedKeys.add(changKey); + GovernanceConfigurationChangedEvent newEvent = new GovernanceConfigurationChangedEvent(changedKeys); + GovernanceEventManager.post(newEvent); + } +} diff --git a/governance/src/test/resources/application.yaml b/governance/src/test/resources/application.yaml index e6ba6c73b5e..5b3fc8aaf0c 100644 --- a/governance/src/test/resources/application.yaml +++ b/governance/src/test/resources/application.yaml @@ -235,3 +235,61 @@ servicecomb: - weight: 80 tags: x-group: green + + test_server2: | # 服务名 + - precedence: 1 + route: + - weight: 20 + tags: + x-group: red + + test_server3: | # 服务名 + - precedence: 2 + match: + headers: + canary: + exact: 'canary' + caseInsensitive: false + route: + - weight: 20 + tags: + version: 1.0.0 + - weight: 80 + tags: + version: 2.0.0 + test_server4: | # 服务名 + - precedence: 2 + match: + headers: + canary: + exact: 'canary' + caseInsensitive: false + route: + - weight: 20 + tags: + version: 1.0.0 + - weight: 80 + tags: + version: 2.0.0 + fallback: + - weight: 100 + tags: + x-group: red + test_server5: | # 服务名 + - precedence: 2 + match: + headers: + canary: + exact: 'canary' + caseInsensitive: false + route: + - weight: 50 + tags: + x-group: red + fallback: + - weight: 50 + tags: + x-group: red + - weight: 50 + tags: + x-group: green \ No newline at end of file diff --git a/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ConsumerInstanceBulkheadHandler.java b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ConsumerInstanceBulkheadHandler.java index 65e43465cb7..fd561c680ff 100644 --- a/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ConsumerInstanceBulkheadHandler.java +++ b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ConsumerInstanceBulkheadHandler.java @@ -26,7 +26,7 @@ import org.apache.servicecomb.core.governance.MatchType; import org.apache.servicecomb.foundation.common.utils.BeanUtils; import org.apache.servicecomb.governance.handler.InstanceBulkheadHandler; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.swagger.invocation.AsyncResponse; import org.apache.servicecomb.swagger.invocation.Response; import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData; @@ -52,9 +52,7 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except } Supplier> next = createBusinessCompletionStageSupplier(invocation); DecorateCompletionStage dcs = Decorators.ofCompletionStage(next); - GovernanceRequest request = MatchType.createGovHttpRequest(invocation); - request.setServiceName(invocation.getMicroserviceName()); - request.setInstanceId(invocation.getEndpoint().getMicroserviceInstance().getInstanceId()); + GovernanceRequestExtractor request = MatchType.createGovHttpRequest(invocation); addBulkhead(dcs, request); @@ -76,7 +74,7 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except }); } - private void addBulkhead(DecorateCompletionStage dcs, GovernanceRequest request) { + private void addBulkhead(DecorateCompletionStage dcs, GovernanceRequestExtractor request) { Bulkhead bulkhead = instanceBulkheadHandler.getActuator(request); if (bulkhead != null) { dcs.withBulkhead(bulkhead); diff --git a/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ConsumerInstanceIsolationHandler.java b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ConsumerInstanceIsolationHandler.java index c6809de521f..2a1d2ea3ff5 100644 --- a/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ConsumerInstanceIsolationHandler.java +++ b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ConsumerInstanceIsolationHandler.java @@ -17,19 +17,21 @@ package org.apache.servicecomb.handler.governance; +import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Supplier; +import javax.ws.rs.core.Response.Status; + import org.apache.servicecomb.core.Handler; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.core.governance.MatchType; +import org.apache.servicecomb.foundation.common.event.EventManager; import org.apache.servicecomb.foundation.common.utils.BeanUtils; import org.apache.servicecomb.governance.handler.InstanceIsolationHandler; -import org.apache.servicecomb.governance.marker.GovernanceRequest; -import org.apache.servicecomb.registry.api.MicroserviceKey; -import org.apache.servicecomb.registry.api.event.MicroserviceInstanceChangedEvent; -import org.apache.servicecomb.registry.api.event.ServiceCenterEventBus; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; +import org.apache.servicecomb.governance.policy.CircuitBreakerPolicy; import org.apache.servicecomb.swagger.invocation.AsyncResponse; import org.apache.servicecomb.swagger.invocation.Response; import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData; @@ -55,10 +57,14 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except } Supplier> next = createBusinessCompletionStageSupplier(invocation); DecorateCompletionStage dcs = Decorators.ofCompletionStage(next); - GovernanceRequest request = MatchType.createGovHttpRequest(invocation); - request.setServiceName(invocation.getMicroserviceName()); - request.setInstanceId(invocation.getEndpoint().getMicroserviceInstance().getInstanceId()); + GovernanceRequestExtractor request = MatchType.createGovHttpRequest(invocation); + CircuitBreakerPolicy circuitBreakerPolicy = instanceIsolationHandler.matchPolicy(request); + if (circuitBreakerPolicy != null && circuitBreakerPolicy.isForceOpen()) { + asyncResp.consumerFail(new InvocationException(Status.SERVICE_UNAVAILABLE, + "Policy " + circuitBreakerPolicy.getName() + " forced open and deny requests")); + return; + } addCircuitBreaker(dcs, request); dcs.get().whenComplete((r, e) -> { @@ -69,7 +75,7 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except if (e instanceof CallNotPermittedException) { LOGGER.warn("instance isolation circuitBreaker is open by policy : {}", e.getMessage()); - ServiceCenterEventBus.getEventBus().post(createMicroserviceInstanceChangedEvent(invocation)); + EventManager.post(createInstanceIsolatedEvent(circuitBreakerPolicy, request)); // return 503 so that consumer can retry asyncResp.complete( Response.failResp(new InvocationException(503, "instance isolation circuitBreaker is open.", @@ -80,16 +86,13 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except }); } - private Object createMicroserviceInstanceChangedEvent(Invocation invocation) { - MicroserviceInstanceChangedEvent event = new MicroserviceInstanceChangedEvent(); - MicroserviceKey key = new MicroserviceKey(); - key.setAppId(invocation.getAppId()); - key.setServiceName(invocation.getMicroserviceName()); - event.setKey(key); - return event; + private Object createInstanceIsolatedEvent(CircuitBreakerPolicy circuitBreakerPolicy, + GovernanceRequestExtractor requestExtractor) { + return new InstanceIsolatedEvent(requestExtractor.instanceId(), + Duration.parse(circuitBreakerPolicy.getWaitDurationInOpenState())); } - private void addCircuitBreaker(DecorateCompletionStage dcs, GovernanceRequest request) { + private void addCircuitBreaker(DecorateCompletionStage dcs, GovernanceRequestExtractor request) { CircuitBreaker circuitBreaker = instanceIsolationHandler.getActuator(request); if (circuitBreaker != null) { dcs.withCircuitBreaker(circuitBreaker); diff --git a/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/InstanceIsolatedEvent.java b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/InstanceIsolatedEvent.java new file mode 100644 index 00000000000..32be6982025 --- /dev/null +++ b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/InstanceIsolatedEvent.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.handler.governance; + +import java.time.Duration; + +public class InstanceIsolatedEvent { + private final String instanceId; + + private final Duration waitDurationInHalfOpenState; + + public InstanceIsolatedEvent(String instanceId, Duration waitDurationInHalfOpenState) { + this.instanceId = instanceId; + this.waitDurationInHalfOpenState = waitDurationInHalfOpenState; + } + + public String getInstanceId() { + return instanceId; + } + + public Duration getWaitDurationInHalfOpenState() { + return waitDurationInHalfOpenState; + } +} diff --git a/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/InstanceIsolationDiscoveryFilter.java b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/InstanceIsolationDiscoveryFilter.java new file mode 100644 index 00000000000..0191c3f2b4b --- /dev/null +++ b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/InstanceIsolationDiscoveryFilter.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.handler.governance; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx; +import org.apache.servicecomb.foundation.common.event.EventManager; +import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; +import org.apache.servicecomb.registry.discovery.DiscoveryContext; +import org.apache.servicecomb.registry.discovery.DiscoveryFilter; +import org.apache.servicecomb.registry.discovery.DiscoveryTreeNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.eventbus.Subscribe; +import com.netflix.config.DynamicPropertyFactory; + +public class InstanceIsolationDiscoveryFilter implements DiscoveryFilter { + private static final Logger LOGGER = LoggerFactory.getLogger(InstanceIsolationDiscoveryFilter.class); + + private static final String KEY_ISOLATED = "isolated"; + + private final Object lock = new Object(); + + private final Map isolatedInstances = new ConcurrentHashMapEx<>(); + + public InstanceIsolationDiscoveryFilter() { + EventManager.register(this); + } + + @Subscribe + public void onInstanceIsolatedEvent(InstanceIsolatedEvent event) { + synchronized (lock) { + for (Iterator iterator = isolatedInstances.keySet().iterator(); iterator.hasNext(); ) { + Long duration = isolatedInstances.get(iterator.next()); + if (System.currentTimeMillis() - duration > 0) { + iterator.remove(); + } + } + + isolatedInstances.put(event.getInstanceId(), + System.currentTimeMillis() + event.getWaitDurationInHalfOpenState().toMillis()); + LOGGER.info("isolate instance {} for {}ms", event.getInstanceId(), + event.getWaitDurationInHalfOpenState().toMillis()); + } + } + + @Override + public boolean enabled() { + return DynamicPropertyFactory.getInstance().getBooleanProperty( + "servicecomb.loadbalance.filter.instance.isolation.enabled", true).get(); + } + + @Override + public int getOrder() { + return Short.MAX_VALUE - 1; + } + + @Override + public boolean isGroupingFilter() { + return true; + } + + @Override + public DiscoveryTreeNode discovery(DiscoveryContext context, DiscoveryTreeNode parent) { + Map instances = parent.data(); + if (isolatedInstances.isEmpty() || instances.isEmpty()) { + return parent; + } + + boolean changed = false; + Map result = new HashMap<>(instances.size()); + for (Entry item : instances.entrySet()) { + Long duration = isolatedInstances.get(item.getKey()); + if (duration == null) { + result.put(item.getKey(), item.getValue()); + continue; + } + + if (System.currentTimeMillis() - duration < 0) { + changed = true; + continue; + } + + synchronized (lock) { + isolatedInstances.remove(item.getKey()); + LOGGER.info("try to recover instance {}", item.getKey()); + } + result.put(item.getKey(), item.getValue()); + } + + if (!changed || result.size() == 0) { + return parent; + } + + // Create new child. And all later DiscoveryFilter will re-calculate based on this result. + DiscoveryTreeNode child = new DiscoveryTreeNode().subName(parent, KEY_ISOLATED).data(result); + parent.child(KEY_ISOLATED, child); + Invocation invocation = context.getInputParameters(); + LOGGER.info("instance isolation discovery filter changed, current cached size {}/{}/{}", + invocation.getAppId(), invocation.getMicroserviceName(), result.size()); + return child; + } +} diff --git a/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ProviderGovernanceHandler.java b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ProviderGovernanceHandler.java index f3142ab77e9..ff53c1545ca 100644 --- a/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ProviderGovernanceHandler.java +++ b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ProviderGovernanceHandler.java @@ -28,7 +28,7 @@ import org.apache.servicecomb.governance.handler.BulkheadHandler; import org.apache.servicecomb.governance.handler.CircuitBreakerHandler; import org.apache.servicecomb.governance.handler.RateLimitingHandler; -import org.apache.servicecomb.governance.marker.GovernanceRequest; +import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor; import org.apache.servicecomb.swagger.invocation.AsyncResponse; import org.apache.servicecomb.swagger.invocation.Response; import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData; @@ -59,7 +59,7 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except Supplier> next = createBusinessCompletionStageSupplier(invocation); DecorateCompletionStage dcs = Decorators.ofCompletionStage(next); - GovernanceRequest request = MatchType.createGovHttpRequest(invocation); + GovernanceRequestExtractor request = MatchType.createGovHttpRequest(invocation); addRateLimiting(dcs, request); addCircuitBreaker(dcs, request); @@ -91,21 +91,21 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except }); } - private void addBulkhead(DecorateCompletionStage dcs, GovernanceRequest request) { + private void addBulkhead(DecorateCompletionStage dcs, GovernanceRequestExtractor request) { Bulkhead bulkhead = bulkheadHandler.getActuator(request); if (bulkhead != null) { dcs.withBulkhead(bulkhead); } } - private void addCircuitBreaker(DecorateCompletionStage dcs, GovernanceRequest request) { + private void addCircuitBreaker(DecorateCompletionStage dcs, GovernanceRequestExtractor request) { CircuitBreaker circuitBreaker = circuitBreakerHandler.getActuator(request); if (circuitBreaker != null) { dcs.withCircuitBreaker(circuitBreaker); } } - private void addRateLimiting(DecorateCompletionStage dcs, GovernanceRequest request) { + private void addRateLimiting(DecorateCompletionStage dcs, GovernanceRequestExtractor request) { RateLimiter rateLimiter = rateLimitingHandler.getActuator(request); if (rateLimiter != null) { dcs.withRateLimiter(rateLimiter); diff --git a/handlers/handler-governance/src/main/resources/META-INF/services/org.apache.servicecomb.registry.discovery.DiscoveryFilter b/handlers/handler-governance/src/main/resources/META-INF/services/org.apache.servicecomb.registry.discovery.DiscoveryFilter index 6222972176a..447d294c092 100644 --- a/handlers/handler-governance/src/main/resources/META-INF/services/org.apache.servicecomb.registry.discovery.DiscoveryFilter +++ b/handlers/handler-governance/src/main/resources/META-INF/services/org.apache.servicecomb.registry.discovery.DiscoveryFilter @@ -15,3 +15,4 @@ # limitations under the License. # +org.apache.servicecomb.handler.governance.InstanceIsolationDiscoveryFilter diff --git a/handlers/handler-governance/src/test/java/org/apache/servicecomb/handler/governance/TestInstanceIsolationDiscoveryFilter.java b/handlers/handler-governance/src/test/java/org/apache/servicecomb/handler/governance/TestInstanceIsolationDiscoveryFilter.java new file mode 100644 index 00000000000..155c3380e1f --- /dev/null +++ b/handlers/handler-governance/src/test/java/org/apache/servicecomb/handler/governance/TestInstanceIsolationDiscoveryFilter.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.handler.governance; + +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.core.SCBEngine; +import org.apache.servicecomb.core.Transport; +import org.apache.servicecomb.core.registry.discovery.EndpointDiscoveryFilter; +import org.apache.servicecomb.core.transport.TransportManager; +import org.apache.servicecomb.foundation.common.cache.VersionedCache; +import org.apache.servicecomb.foundation.common.event.EventManager; +import org.apache.servicecomb.registry.DiscoveryManager; +import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; +import org.apache.servicecomb.registry.api.registry.MicroserviceInstanceStatus; +import org.apache.servicecomb.registry.cache.InstanceCacheManager; +import org.apache.servicecomb.registry.discovery.DiscoveryContext; +import org.apache.servicecomb.registry.discovery.DiscoveryTree; +import org.apache.servicecomb.registry.discovery.DiscoveryTreeNode; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class TestInstanceIsolationDiscoveryFilter { + @Test + @SuppressWarnings("unchecked") + public void test_instance_isolation_correct() throws Exception { + DiscoveryTree discoveryTree = new DiscoveryTree(); + DiscoveryContext discoveryContext = new DiscoveryContext(); + discoveryTree.addFilter(new InstanceIsolationDiscoveryFilter()); + discoveryTree.addFilter(new EndpointDiscoveryFilter()); + + Invocation invocation = Mockito.mock(Invocation.class); + discoveryContext.setInputParameters(invocation); + Mockito.when(invocation.getConfigTransportName()).thenReturn("rest"); + + TransportManager transportManager = Mockito.mock(TransportManager.class); + SCBEngine.getInstance().setTransportManager(transportManager); + Transport transport = Mockito.mock(Transport.class); + Mockito.when(transportManager.findTransport("rest")).thenReturn(transport); + Mockito.when(transport.parseAddress("rest://localhost:9090")).thenReturn("9090"); + Mockito.when(transport.parseAddress("rest://localhost:9091")).thenReturn("9091"); + + Map service1 = new HashMap<>(); + MicroserviceInstance instance1 = Mockito.mock(MicroserviceInstance.class); + MicroserviceInstance instance2 = Mockito.mock(MicroserviceInstance.class); + Mockito.when(instance1.getInstanceId()).thenReturn("instance1"); + Mockito.when(instance1.getStatus()).thenReturn(MicroserviceInstanceStatus.UP); + Mockito.when(instance1.getEndpoints()).thenReturn(Collections.singletonList("rest://localhost:9090")); + Mockito.when(instance2.getInstanceId()).thenReturn("instance2"); + Mockito.when(instance2.getStatus()).thenReturn(MicroserviceInstanceStatus.UP); + Mockito.when(instance2.getEndpoints()).thenReturn(Collections.singletonList("rest://localhost:9092")); + service1.put(instance1.getInstanceId(), instance1); + service1.put(instance2.getInstanceId(), instance2); + + InstanceCacheManager instanceCacheManager = Mockito.mock(InstanceCacheManager.class); + DiscoveryManager.INSTANCE = Mockito.spy(DiscoveryManager.INSTANCE); + Mockito.when(DiscoveryManager.INSTANCE.getInstanceCacheManager()).thenReturn(instanceCacheManager); + + VersionedCache expects0 = new VersionedCache().autoCacheVersion().name("0+").data(service1); + Mockito.when(instanceCacheManager.getOrCreateVersionedCache("app", "service1", + "0+")).thenReturn(expects0); + + DiscoveryTreeNode result = discoveryTree.discovery(discoveryContext, "app", "service1", "0+"); + Assertions.assertEquals(2, ((List) result.data()).size()); + + // isolate + EventManager.post(new InstanceIsolatedEvent("instance1", Duration.ofMillis(8))); + result = discoveryTree.discovery(discoveryContext, "app", "service1", "0+"); + Assertions.assertEquals(1, ((List) result.data()).size()); + //recover + Thread.sleep(10); + result = discoveryTree.discovery(discoveryContext, "app", "service1", "0+"); + Assertions.assertEquals(2, ((List) result.data()).size()); + } +} diff --git a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/Configuration.java b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/Configuration.java index d4ea2e661c5..9becdbc149d 100644 --- a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/Configuration.java +++ b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/Configuration.java @@ -213,11 +213,7 @@ private int getThreshold(String microservice, String threshold) { ROOT + microservice + "." + FILTER_ISOLATION + threshold, ROOT + FILTER_ISOLATION + threshold); try { - int result = Integer.parseInt(p); - if (result > 0) { - return result; - } - return defaultValue; + return Integer.parseInt(p); } catch (NumberFormatException e) { return defaultValue; } diff --git a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/LoadbalanceHandler.java b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/LoadbalanceHandler.java index f3fddd1fe9e..b1df5bafc2f 100644 --- a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/LoadbalanceHandler.java +++ b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/LoadbalanceHandler.java @@ -89,7 +89,6 @@ public LoadbalanceHandler() { private void preCheck() { // Old configurations check.Just print an error, because configurations may given in dynamic and fail on runtime. - String policyName = DynamicPropertyFactory.getInstance() .getStringProperty("servicecomb.loadbalance.NFLoadBalancerRuleClassName", null) .get(); @@ -115,8 +114,9 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except response.handle(async); }; - if (handleSuppliedEndpoint(invocation, asyncResp)) { + if (handleSuppliedEndpoint(invocation)) { invocation.addLocalContext(RetryContext.RETRY_LOAD_BALANCE, false); + invocation.next(asyncResp); return; } invocation.addLocalContext(RetryContext.RETRY_LOAD_BALANCE, true); @@ -137,15 +137,13 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except // user's can invoke a service by supplying target Endpoint. // in this case, we do not using load balancer, and no stats of server calculated, no retrying. - private boolean handleSuppliedEndpoint(Invocation invocation, - AsyncResponse asyncResp) throws Exception { + private boolean handleSuppliedEndpoint(Invocation invocation) throws Exception { if (invocation.getEndpoint() != null) { - invocation.next(asyncResp); return true; } if (supportDefinedEndpoint) { - return defineEndpointAndHandle(invocation, asyncResp); + return defineEndpointAndHandle(invocation); } return false; @@ -162,7 +160,7 @@ private Endpoint parseEndpoint(String endpointUri) throws Exception { return new Endpoint(transport, endpointUri); } - private boolean defineEndpointAndHandle(Invocation invocation, AsyncResponse asyncResp) throws Exception { + private boolean defineEndpointAndHandle(Invocation invocation) throws Exception { Object endpoint = invocation.getLocalContext(SERVICECOMB_SERVER_ENDPOINT); if (endpoint == null) { return false; @@ -173,7 +171,6 @@ private boolean defineEndpointAndHandle(Invocation invocation, AsyncResponse asy } invocation.setEndpoint((Endpoint) endpoint); - invocation.next(asyncResp); return true; } @@ -233,7 +230,7 @@ private ServiceCombServer chooseServer(Invocation invocation, LoadBalancer chose } } - LOGGER.info("operation failed {}, retry to instance [{}], last instance [{}], trace id {}", + LOGGER.warn("operation failed {}, retry to instance [{}], last instance [{}], trace id {}", invocation.getMicroserviceQualifiedName(), nextServer == null ? "" : nextServer.getHostPort(), lastServer == null ? "" : lastServer.getHostPort(), diff --git a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/RandomRuleExt.java b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/RandomRuleExt.java index 5f8d26c0ff8..318b45f3839 100644 --- a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/RandomRuleExt.java +++ b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/RandomRuleExt.java @@ -31,7 +31,7 @@ public ServiceCombServer choose(List servers, Invocation invo if (servers.isEmpty()) { return null; } - int index = Math.abs(ThreadLocalRandom.current().nextInt()) % servers.size(); + int index = ThreadLocalRandom.current().nextInt(servers.size()); return servers.get(index); } } diff --git a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/ServiceCombLoadBalancerStats.java b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/ServiceCombLoadBalancerStats.java index 0b99139c38d..2f23a6f7ecb 100644 --- a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/ServiceCombLoadBalancerStats.java +++ b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/ServiceCombLoadBalancerStats.java @@ -17,6 +17,7 @@ package org.apache.servicecomb.loadbalance; +import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; @@ -27,6 +28,11 @@ import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils; import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; import org.apache.servicecomb.registry.consumer.MicroserviceInstancePing; +import org.apache.servicecomb.registry.DiscoveryManager; +import org.apache.servicecomb.registry.RegistrationManager; +import org.apache.servicecomb.registry.consumer.MicroserviceVersions; + +import org.apache.servicecomb.registry.definition.MicroserviceNameParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -157,17 +163,30 @@ public ServiceCombServerStats load(ServiceCombServer server) { timer = new Timer("LoadBalancerStatsTimer", true); timer.schedule(new TimerTask() { - private final MicroserviceInstancePing ping = SPIServiceUtils.getPriorityHighestService(MicroserviceInstancePing.class); + private final MicroserviceInstancePing ping = SPIServiceUtils + .getPriorityHighestService(MicroserviceInstancePing.class); @Override public void run() { try { Map allServers = pingView; allServers.forEach((server, stats) -> { - if ((System.currentTimeMillis() - stats.getLastVisitTime() > timerIntervalInMillis) && !ping - .ping(server.getInstance())) { - LOGGER.info("ping mark server {} failure.", server.getInstance().getInstanceId()); - stats.markFailure(); + //get all microservice instances + MicroserviceVersions microserviceVersions = DiscoveryManager.INSTANCE.getOrCreateMicroserviceVersions( + new MicroserviceNameParser(RegistrationManager.INSTANCE.getAppId(), server.getMicroserviceName()) + .getAppId(), server.getMicroserviceName()); + List microserviceInstanceList = microserviceVersions.getInstances(); + for (MicroserviceInstance instance : microserviceInstanceList) { + //check if the instance still up + if (server.getInstance().getInstanceId().equals(instance.getInstanceId())) { + //check test interval + if ((System.currentTimeMillis() - stats.getLastVisitTime() > timerIntervalInMillis) + && !ping.ping(server.getInstance())) { + LOGGER.info("ping mark server {} failure.", server.getInstance().getInstanceId()); + stats.markFailure(); + } + break; + } } }); serverStatsCache.cleanUp(); diff --git a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/event/IsolationServerEvent.java b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/event/IsolationServerEvent.java index 50a51d69261..e9559de0e96 100644 --- a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/event/IsolationServerEvent.java +++ b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/event/IsolationServerEvent.java @@ -20,7 +20,7 @@ import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.foundation.common.event.AlarmEvent; import org.apache.servicecomb.loadbalance.ServiceCombServerStats; -import org.apache.servicecomb.loadbalance.filterext.IsolationDiscoveryFilter; +import org.apache.servicecomb.loadbalance.filterext.IsolationServerListFilterExt; import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; public class IsolationServerEvent extends AlarmEvent { @@ -50,9 +50,13 @@ public class IsolationServerEvent extends AlarmEvent { private final long singleTestTime; + private int instancesTotalNum; + + private int isolationInstancesNum; + public IsolationServerEvent(Invocation invocation, MicroserviceInstance instance, ServiceCombServerStats serverStats, - IsolationDiscoveryFilter.Settings settings, Type type, Endpoint endpoint) { + IsolationServerListFilterExt.Settings settings, Type type, Endpoint endpoint) { super(type); this.microserviceName = invocation.getMicroserviceName(); this.endpoint = endpoint; @@ -110,4 +114,20 @@ public int getMinIsolationTime() { public Endpoint getEndpoint() { return endpoint; } + + public int getInstancesTotalNum() { + return instancesTotalNum; + } + + public void setInstancesTotalNum(int instancesTotalNum) { + this.instancesTotalNum = instancesTotalNum; + } + + public int getIsolationInstancesNum() { + return isolationInstancesNum; + } + + public void setIsolationInstancesNum(int isolationInstancesNum) { + this.isolationInstancesNum = isolationInstancesNum; + } } diff --git a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/filterext/IsolationDiscoveryFilter.java b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/filterext/IsolationServerListFilterExt.java similarity index 85% rename from handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/filterext/IsolationDiscoveryFilter.java rename to handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/filterext/IsolationServerListFilterExt.java index bbbda45e465..dab5e0b54b7 100644 --- a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/filterext/IsolationDiscoveryFilter.java +++ b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/filterext/IsolationServerListFilterExt.java @@ -39,9 +39,9 @@ /** * Isolate instances by error metrics */ -public class IsolationDiscoveryFilter implements ServerListFilterExt { +public class IsolationServerListFilterExt implements ServerListFilterExt { - private static final Logger LOGGER = LoggerFactory.getLogger(IsolationDiscoveryFilter.class); + private static final Logger LOGGER = LoggerFactory.getLogger(IsolationServerListFilterExt.class); private final DynamicBooleanProperty emptyProtection = DynamicPropertyFactory.getInstance() .getBooleanProperty(EMPTY_INSTANCE_PROTECTION, false); @@ -65,7 +65,7 @@ public int getOrder() { return ORDER_ISOLATION; } - public IsolationDiscoveryFilter() { + public IsolationServerListFilterExt() { emptyProtection.addCallback(() -> { boolean newValue = emptyProtection.get(); LOGGER.info("{} changed from {} to {}", EMPTY_INSTANCE_PROTECTION, emptyProtection, newValue); @@ -88,11 +88,19 @@ public List getFilteredListOfServers(List List filteredServers = new ArrayList<>(); Settings settings = createSettings(invocation); + + // record instance isolation event + List isolationEvents = new ArrayList<>(); servers.forEach((server) -> { - if (allowVisit(invocation, server, settings)) { + if (allowVisit(invocation, server, settings, isolationEvents)) { filteredServers.add(server); } }); + isolationEvents.forEach((event) -> { + event.setInstancesTotalNum(servers.size()); + event.setIsolationInstancesNum(servers.size() - filteredServers.size()); + eventBus.post(event); + }); if (filteredServers.isEmpty() && emptyProtection.get()) { LOGGER.warn("All servers have been isolated, allow one of them based on load balance rule."); return servers; @@ -114,7 +122,8 @@ private Settings createSettings(Invocation invocation) { return settings; } - private boolean allowVisit(Invocation invocation, ServiceCombServer server, Settings settings) { + private boolean allowVisit(Invocation invocation, ServiceCombServer server, Settings settings, + List isolationEvents) { ServiceCombServerStats serverStats = ServiceCombLoadBalancerStats.INSTANCE.getServiceCombServerStats(server); if (!checkThresholdAllowed(settings, serverStats)) { if (serverStats.isIsolated() @@ -124,11 +133,9 @@ private boolean allowVisit(Invocation invocation, ServiceCombServer server, Sett if (!serverStats.isIsolated()) { // checkThresholdAllowed is not concurrent control, may print several logs/events in current access. serverStats.markIsolated(true); - eventBus.post( - new IsolationServerEvent(invocation, server.getInstance(), serverStats, - settings, Type.OPEN, server.getEndpoint())); - LOGGER.warn("Isolate service {}'s instance {}.", - invocation.getMicroserviceName(), + isolationEvents.add(new IsolationServerEvent(invocation, server.getInstance(), serverStats, settings, + Type.OPEN, server.getEndpoint())); + LOGGER.warn("Isolate service {}'s instance {}.", invocation.getMicroserviceName(), server.getInstance().getInstanceId()); } return false; @@ -140,10 +147,9 @@ private boolean allowVisit(Invocation invocation, ServiceCombServer server, Sett return false; } serverStats.markIsolated(false); - eventBus.post(new IsolationServerEvent(invocation, server.getInstance(), serverStats, - settings, Type.CLOSE, server.getEndpoint())); - LOGGER.warn("Recover service {}'s instance {} from isolation.", - invocation.getMicroserviceName(), + isolationEvents.add(new IsolationServerEvent(invocation, server.getInstance(), serverStats, settings, + Type.CLOSE, server.getEndpoint())); + LOGGER.warn("Recover service {}'s instance {} from isolation.", invocation.getMicroserviceName(), server.getInstance().getInstanceId()); } return true; diff --git a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/filterext/ZoneAwareDiscoveryFilter.java b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/filterext/ZoneAwareDiscoveryFilter.java index d31597b5eb0..93f386835b8 100644 --- a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/filterext/ZoneAwareDiscoveryFilter.java +++ b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/filterext/ZoneAwareDiscoveryFilter.java @@ -29,6 +29,9 @@ import com.netflix.config.DynamicPropertyFactory; public class ZoneAwareDiscoveryFilter implements ServerListFilterExt { + public static final String CONFIG_RATIO = "servicecomb.loadbalance.filter.zoneaware.ratio"; + + public static final String CONFIG_RATIO_CEILING = "servicecomb.loadbalance.filter.zoneaware.ratioCeiling"; @Override public int getOrder() { @@ -42,6 +45,16 @@ public boolean enabled() { .get(); } + private int getRatio() { + return DynamicPropertyFactory.getInstance() + .getIntProperty(CONFIG_RATIO, 30).get(); + } + + private int getRatioCeiling(int defaultValue) { + return DynamicPropertyFactory.getInstance() + .getIntProperty(CONFIG_RATIO_CEILING, defaultValue).get(); + } + @Override public List getFilteredListOfServers(List servers, Invocation invocation) { @@ -59,14 +72,32 @@ public List getFilteredListOfServers(List instancesNoMatch.add(server); } }); - if (!instancesRegionAndAZMatch.isEmpty()) { + + int ratio = getRatio(); + int ratioCeiling = getRatioCeiling(100 - ratio); + + if (hasEnoughMembers(servers.size(), instancesRegionAndAZMatch.size(), ratio, ratioCeiling)) { return instancesRegionAndAZMatch; - } else if (!instancesAZMatch.isEmpty()) { + } else { + instancesAZMatch.addAll(instancesRegionAndAZMatch); + } + + if (hasEnoughMembers(servers.size(), instancesAZMatch.size(), ratio, ratioCeiling)) { return instancesAZMatch; + } else { + instancesNoMatch.addAll(instancesAZMatch); } return instancesNoMatch; } + private boolean hasEnoughMembers(int totalSize, int groupSize, int ratio, int ratioCeiling) { + if (totalSize == 0 || groupSize == 0) { + return false; + } + int actual = Math.floorDiv(groupSize * 100, totalSize); + return actual >= ratio && actual <= ratioCeiling; + } + private boolean regionAndAZMatch(MicroserviceInstance myself, MicroserviceInstance target) { if (myself == null || myself.getDataCenterInfo() == null) { // when instance have no datacenter info, it will match all other datacenters diff --git a/handlers/handler-loadbalance/src/main/resources/META-INF/services/org.apache.servicecomb.loadbalance.ServerListFilterExt b/handlers/handler-loadbalance/src/main/resources/META-INF/services/org.apache.servicecomb.loadbalance.ServerListFilterExt index 9e8a0847940..a355341ff46 100644 --- a/handlers/handler-loadbalance/src/main/resources/META-INF/services/org.apache.servicecomb.loadbalance.ServerListFilterExt +++ b/handlers/handler-loadbalance/src/main/resources/META-INF/services/org.apache.servicecomb.loadbalance.ServerListFilterExt @@ -15,5 +15,5 @@ # limitations under the License. # -org.apache.servicecomb.loadbalance.filterext.IsolationDiscoveryFilter +org.apache.servicecomb.loadbalance.filterext.IsolationServerListFilterExt org.apache.servicecomb.loadbalance.filterext.ZoneAwareDiscoveryFilter \ No newline at end of file diff --git a/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/TestLoadBalanceHandler2.java b/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/TestLoadBalanceHandler2.java index e2a353db471..a422efe1252 100644 --- a/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/TestLoadBalanceHandler2.java +++ b/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/TestLoadBalanceHandler2.java @@ -17,6 +17,8 @@ package org.apache.servicecomb.loadbalance; +import static org.mockito.Mockito.when; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -40,6 +42,7 @@ import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; import org.apache.servicecomb.loadbalance.event.IsolationServerEvent; import org.apache.servicecomb.loadbalance.filter.ServerDiscoveryFilter; +import org.apache.servicecomb.loadbalance.filterext.ZoneAwareDiscoveryFilter; import org.apache.servicecomb.localregistry.LocalRegistryStore; import org.apache.servicecomb.registry.DiscoveryManager; import org.apache.servicecomb.registry.api.registry.DataCenterInfo; @@ -59,11 +62,11 @@ import com.google.common.eventbus.Subscribe; +import io.swagger.models.Operation; +import io.swagger.models.Swagger; import mockit.Mock; import mockit.MockUp; -import static org.mockito.Mockito.when; - public class TestLoadBalanceHandler2 { private Holder mockTimeMillis; @@ -85,7 +88,7 @@ public static void afterClass() { @Before public void setUp() { - + ArchaiusUtils.setProperty(ZoneAwareDiscoveryFilter.CONFIG_RATIO, 0); // avoid mock ServiceCombLoadBalancerStats.INSTANCE.init(); TestServiceCombServerStats.releaseTryingChance(); @@ -111,6 +114,8 @@ public void testZoneAwareAndIsolationFilterWorks() throws Exception { InvocationRuntimeType invocationRuntimeType = Mockito.mock(InvocationRuntimeType.class); SchemaMeta schemaMeta = Mockito.mock(SchemaMeta.class); when(operationMeta.getSchemaMeta()).thenReturn(schemaMeta); + when(operationMeta.getSwaggerOperation()).thenReturn(new Operation()); + when(schemaMeta.getSwagger()).thenReturn(new Swagger()); MicroserviceMeta microserviceMeta = Mockito.mock(MicroserviceMeta.class); when(schemaMeta.getMicroserviceMeta()).thenReturn(microserviceMeta); when(schemaMeta.getMicroserviceName()).thenReturn("testMicroserviceName"); @@ -257,6 +262,8 @@ public void testIsolationEventWithEndpoint() throws Exception { InvocationRuntimeType invocationRuntimeType = Mockito.mock(InvocationRuntimeType.class); SchemaMeta schemaMeta = Mockito.mock(SchemaMeta.class); when(operationMeta.getSchemaMeta()).thenReturn(schemaMeta); + when(operationMeta.getSwaggerOperation()).thenReturn(new Operation()); + when(schemaMeta.getSwagger()).thenReturn(new Swagger()); MicroserviceMeta microserviceMeta = Mockito.mock(MicroserviceMeta.class); when(schemaMeta.getMicroserviceMeta()).thenReturn(microserviceMeta); when(schemaMeta.getMicroserviceName()).thenReturn("testMicroserviceName"); @@ -344,6 +351,8 @@ public void testZoneAwareAndIsolationFilterWorksEmptyInstanceProtectionEnabled() InvocationRuntimeType invocationRuntimeType = Mockito.mock(InvocationRuntimeType.class); SchemaMeta schemaMeta = Mockito.mock(SchemaMeta.class); when(operationMeta.getSchemaMeta()).thenReturn(schemaMeta); + when(operationMeta.getSwaggerOperation()).thenReturn(new Operation()); + when(schemaMeta.getSwagger()).thenReturn(new Swagger()); MicroserviceMeta microserviceMeta = Mockito.mock(MicroserviceMeta.class); when(schemaMeta.getMicroserviceMeta()).thenReturn(microserviceMeta); when(schemaMeta.getMicroserviceName()).thenReturn("testMicroserviceName"); @@ -465,6 +474,9 @@ public void testZoneAwareAndIsolationFilterWorksEmptyInstanceProtectionEnabled() Assertions.assertEquals("rest://localhost:9090", server.getEndpoint().getEndpoint()); ServiceCombLoadBalancerStats.INSTANCE.markFailure(server2); ServiceCombLoadBalancerStats.INSTANCE.markFailure(server2); + ServiceCombLoadBalancerStats.INSTANCE.markFailure(server2); + ServiceCombLoadBalancerStats.INSTANCE.markFailure(server2); + ServiceCombLoadBalancerStats.INSTANCE.markFailure(server2); loadBalancer = handler.getOrCreateLoadBalancer(invocation); server = loadBalancer.chooseServer(invocation); Assertions.assertEquals("rest://localhost:9091", server.getEndpoint().getEndpoint()); @@ -472,7 +484,8 @@ public void testZoneAwareAndIsolationFilterWorksEmptyInstanceProtectionEnabled() @Test public void testZoneAwareAndIsolationFilterUsingMockedInvocationWorks() throws Exception { - Invocation invocation = new NonSwaggerInvocation("testApp", "testMicroserviceName", "0.0.0+", (inv, aysnc) -> aysnc.success("OK")); + Invocation invocation = new NonSwaggerInvocation("testApp", "testMicroserviceName", "0.0.0+", + (inv, aysnc) -> aysnc.success("OK")); InstanceCacheManager instanceCacheManager = Mockito.mock(InstanceCacheManager.class); TransportManager transportManager = Mockito.mock(TransportManager.class); @@ -602,7 +615,8 @@ public void testZoneAwareAndIsolationFilterUsingMockedInvocationWorks() throws E public void testStatusFilterUsingMockedInvocationWorks() throws Exception { ArchaiusUtils.setProperty("servicecomb.loadbalance.filter.status.enabled", "false"); - Invocation invocation = new NonSwaggerInvocation("testApp", "testMicroserviceName", "0.0.0+", (inv, aysnc) -> aysnc.success("OK")); + Invocation invocation = new NonSwaggerInvocation("testApp", "testMicroserviceName", "0.0.0+", + (inv, aysnc) -> aysnc.success("OK")); InstanceCacheManager instanceCacheManager = Mockito.mock(InstanceCacheManager.class); TransportManager transportManager = Mockito.mock(TransportManager.class); @@ -736,6 +750,8 @@ public void testConfigEndpoint() { InvocationRuntimeType invocationRuntimeType = Mockito.mock(InvocationRuntimeType.class); SchemaMeta schemaMeta = Mockito.mock(SchemaMeta.class); when(operationMeta.getSchemaMeta()).thenReturn(schemaMeta); + when(operationMeta.getSwaggerOperation()).thenReturn(new Operation()); + when(schemaMeta.getSwagger()).thenReturn(new Swagger()); MicroserviceMeta microserviceMeta = Mockito.mock(MicroserviceMeta.class); when(schemaMeta.getMicroserviceMeta()).thenReturn(microserviceMeta); when(schemaMeta.getMicroserviceName()).thenReturn("testMicroserviceName"); @@ -775,8 +791,10 @@ public void testConfigEndpoint() { LoadbalanceHandler handler = new LoadbalanceHandler(); try { handler.handle(invocation, asyncResp); + } catch (IndexOutOfBoundsException e) { + // swallow, IndexOutOfBoundsException is expected because the handler chain of Invocation is not mocked } catch (Exception e) { - + throw new IllegalStateException(e); } Assertions.assertEquals("rest://localhost:9092", invocation.getEndpoint().getEndpoint()); @@ -790,7 +808,8 @@ public void testConfigEndpoint() { } catch (Exception e) { } - Assertions.assertEquals("rest://127.0.0.1:8080?sslEnabled=true&protocol=http2", invocation.getEndpoint().getEndpoint()); + Assertions.assertEquals("rest://127.0.0.1:8080?sslEnabled=true&protocol=http2", + invocation.getEndpoint().getEndpoint()); // reset invocation.setEndpoint(null); diff --git a/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/TestServiceCombLoadBalancerStats.java b/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/TestServiceCombLoadBalancerStats.java index c3172b1a649..a16bf1c31ab 100644 --- a/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/TestServiceCombLoadBalancerStats.java +++ b/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/TestServiceCombLoadBalancerStats.java @@ -17,27 +17,39 @@ package org.apache.servicecomb.loadbalance; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import mockit.Deencapsulation; +import mockit.Expectations; +import mockit.Injectable; +import mockit.Mock; +import mockit.MockUp; +import mockit.Mocked; + import org.apache.servicecomb.core.Transport; import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils; +import org.apache.servicecomb.registry.DiscoveryManager; +import org.apache.servicecomb.registry.RegistrationManager; import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; import org.apache.servicecomb.registry.cache.CacheEndpoint; +import org.apache.servicecomb.registry.consumer.AppManager; import org.apache.servicecomb.registry.consumer.MicroserviceInstancePing; +import org.apache.servicecomb.registry.consumer.MicroserviceVersions; import org.awaitility.Awaitility; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; -import mockit.Deencapsulation; -import mockit.Expectations; -import mockit.Injectable; -import mockit.Mocked; import org.junit.jupiter.api.Assertions; public class TestServiceCombLoadBalancerStats { + + List instanceList = new ArrayList<>(); + @Before public void before() { // Ensure clean all of mocked server cache before running testMultiThread @@ -46,6 +58,33 @@ public void before() { Deencapsulation.getField(ServiceCombLoadBalancerStats.INSTANCE, "pingView"); pingView.clear(); ServiceCombLoadBalancerStats.INSTANCE.init(); + MicroserviceInstance instance1 = new MicroserviceInstance(); + instance1.setInstanceId("instance1"); + instanceList.add(instance1); + MicroserviceInstance instance2 = new MicroserviceInstance(); + instance2.setInstanceId("instance2"); + instanceList.add(instance2); + MockUp mockUpRegistrationManager = new MockUp() { + @Mock + public String getAppId() { + return "test_app"; + } + }; + MockUp mockUpMicroserviceVersions = new MockUp() { + @Mock + public List getInstances() { + return instanceList; + } + }; + AppManager appManager = new AppManager(); + MicroserviceVersions microserviceVersions = new MicroserviceVersions(appManager, "test_app", "test_microservice"); + MockUp mockUpDiscoveryManager = new MockUp() { + + @Mock + public MicroserviceVersions getOrCreateMicroserviceVersions(String appId, String microserviceName) { + return microserviceVersions; + } + }; } @AfterClass @@ -76,7 +115,7 @@ public void testServiceExpire(@Injectable Transport transport, @Mocked SPIServic serviceCombLoadBalancerStats.setTimerIntervalInMillis(500); serviceCombLoadBalancerStats.init(); - ServiceCombServer serviceCombServer = new ServiceCombServer(null, transport, + ServiceCombServer serviceCombServer = new ServiceCombServer("test_microservice", transport, new CacheEndpoint("rest://localhost:8080", instance)); serviceCombLoadBalancerStats.markSuccess(serviceCombServer); ServiceCombServerStats stats = serviceCombLoadBalancerStats.getServiceCombServerStats(serviceCombServer); @@ -124,7 +163,7 @@ public void testMultiThread(@Injectable Transport transport) throws Exception { long time = System.currentTimeMillis(); MicroserviceInstance instance = new MicroserviceInstance(); instance.setInstanceId("instance2"); - ServiceCombServer serviceCombServer = new ServiceCombServer(null, transport, + ServiceCombServer serviceCombServer = new ServiceCombServer("test_microservice", transport, new CacheEndpoint("rest://localhost:8080", instance)); CountDownLatch latch = new CountDownLatch(10); @@ -171,4 +210,54 @@ public void testMultiThread(@Injectable Transport transport) throws Exception { ServiceCombLoadBalancerStats.INSTANCE.getServiceCombServerStats(serviceCombServer).getFailedRequests() > 20); } + + @Test + public void testMultiThread2(@Injectable Transport transport) throws Exception { + long time = System.currentTimeMillis(); + MicroserviceInstance instance = new MicroserviceInstance(); + instance.setInstanceId("instance2"); + //clear instances to mock instance2 down in cse + instanceList.clear(); + ServiceCombServer serviceCombServer = new ServiceCombServer("test_microservice", transport, + new CacheEndpoint("rest://localhost:8080", instance)); + + CountDownLatch latch = new CountDownLatch(10); + for (int i = 0; i < 10; i++) { + new Thread(() -> { + ServiceCombLoadBalancerStats.INSTANCE.markFailure(serviceCombServer); + ServiceCombLoadBalancerStats.INSTANCE.markFailure(serviceCombServer); + ServiceCombLoadBalancerStats.INSTANCE.markSuccess(serviceCombServer); + ServiceCombLoadBalancerStats.INSTANCE.markSuccess(serviceCombServer); + latch.countDown(); + }).start(); + } + latch.await(30, TimeUnit.SECONDS); + Assertions.assertEquals( + ServiceCombLoadBalancerStats.INSTANCE.getServiceCombServerStats(serviceCombServer).getTotalRequests(), + 4 * 10); + Assertions.assertEquals( + ServiceCombLoadBalancerStats.INSTANCE.getServiceCombServerStats(serviceCombServer).getFailedRate(), 50); + Assertions.assertEquals( + ServiceCombLoadBalancerStats.INSTANCE.getServiceCombServerStats(serviceCombServer).getSuccessRate(), 50); + Assertions.assertEquals( + ServiceCombLoadBalancerStats.INSTANCE.getServiceCombServerStats(serviceCombServer).getSuccessRequests(), 20); + Assertions.assertTrue( + ServiceCombLoadBalancerStats.INSTANCE.getServiceCombServerStats(serviceCombServer).getLastVisitTime() <= System + .currentTimeMillis() + && ServiceCombLoadBalancerStats.INSTANCE.getServiceCombServerStats(serviceCombServer).getLastVisitTime() + >= time); + + // time consuming test for timers, taking about 20 seconds. ping timer will not update instance status because instance2 is out of up instances + Assertions.assertTrue( + ServiceCombLoadBalancerStats.INSTANCE.getServiceCombServerStats(serviceCombServer).getFailedRate() <= 50); + long beginTime = System.currentTimeMillis(); + long rate = ServiceCombLoadBalancerStats.INSTANCE.getServiceCombServerStats(serviceCombServer).getFailedRequests(); + while (System.currentTimeMillis() - beginTime <= 22000) { + Thread.sleep(2000); + System.out.println("failedRequests: " + rate); + } + + Assertions.assertEquals(20, + ServiceCombLoadBalancerStats.INSTANCE.getServiceCombServerStats(serviceCombServer).getFailedRequests()); + } } diff --git a/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/filter/IsolationDiscoveryFilterTest.java b/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/filter/IsolationServerListFilterExtTest.java similarity index 97% rename from handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/filter/IsolationDiscoveryFilterTest.java rename to handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/filter/IsolationServerListFilterExtTest.java index 78b4d0cfdf1..03a70137787 100644 --- a/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/filter/IsolationDiscoveryFilterTest.java +++ b/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/filter/IsolationServerListFilterExtTest.java @@ -28,7 +28,7 @@ import org.apache.servicecomb.loadbalance.ServiceCombServer; import org.apache.servicecomb.loadbalance.ServiceCombServerStats; import org.apache.servicecomb.loadbalance.TestServiceCombServerStats; -import org.apache.servicecomb.loadbalance.filterext.IsolationDiscoveryFilter; +import org.apache.servicecomb.loadbalance.filterext.IsolationServerListFilterExt; import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; import org.apache.servicecomb.registry.cache.CacheEndpoint; import org.junit.jupiter.api.AfterEach; @@ -42,9 +42,9 @@ import mockit.Mocked; @TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class IsolationDiscoveryFilterTest { +public class IsolationServerListFilterExtTest { - private IsolationDiscoveryFilter filter; + private IsolationServerListFilterExt filter; private List servers; @@ -73,7 +73,7 @@ public void before() { ServiceCombLoadBalancerStats.INSTANCE.getServiceCombServerStats(serviceCombServer); } - filter = new IsolationDiscoveryFilter(); + filter = new IsolationServerListFilterExt(); TestServiceCombServerStats.releaseTryingChance(); } diff --git a/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/filterext/TestZoneAwareDiscoveryFilter.java b/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/filterext/TestZoneAwareDiscoveryFilter.java new file mode 100644 index 00000000000..18c790b7c50 --- /dev/null +++ b/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/filterext/TestZoneAwareDiscoveryFilter.java @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.loadbalance.filterext; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.servicecomb.config.ConfigUtil; +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.core.Transport; +import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; +import org.apache.servicecomb.loadbalance.ServiceCombServer; +import org.apache.servicecomb.registry.RegistrationManager; +import org.apache.servicecomb.registry.api.registry.DataCenterInfo; +import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; +import org.apache.servicecomb.registry.cache.CacheEndpoint; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class TestZoneAwareDiscoveryFilter { + @BeforeEach + public void setUp() { + ConfigUtil.createLocalConfig(); + } + + @AfterEach + public void tearDown() { + ArchaiusUtils.resetConfig(); + RegistrationManager.renewInstance(); + } + + @Test + public void test_not_enough_instance() { + ArchaiusUtils.setProperty(ZoneAwareDiscoveryFilter.CONFIG_RATIO, 50); + ArchaiusUtils.setProperty(ZoneAwareDiscoveryFilter.CONFIG_RATIO_CEILING, 70); + ZoneAwareDiscoveryFilter filter = new ZoneAwareDiscoveryFilter(); + + // set up data + MicroserviceInstance myself = Mockito.mock(MicroserviceInstance.class); + RegistrationManager registrationManager = Mockito.mock(RegistrationManager.class); + Mockito.when(registrationManager.getMicroserviceInstance()).thenReturn(myself); + RegistrationManager.setINSTANCE(registrationManager); + DataCenterInfo myDcInfo = new DataCenterInfo(); + myDcInfo.setName("test"); + myDcInfo.setRegion("test-Region"); + myDcInfo.setAvailableZone("test-zone"); + Mockito.when(myself.getDataCenterInfo()).thenReturn(myDcInfo); + + MicroserviceInstance discoveryInstance = Mockito.mock(MicroserviceInstance.class); + List allMatchEndpoint = new ArrayList<>(); + allMatchEndpoint.add("rest://localhost:9090"); + Mockito.when(discoveryInstance.getEndpoints()).thenReturn(allMatchEndpoint); + CacheEndpoint allmatchCacheEndpoint = Mockito.mock(CacheEndpoint.class); + Mockito.when(allmatchCacheEndpoint.getEndpoint()).thenReturn("rest://localhost:9090"); + Transport transport = Mockito.mock(Transport.class); + Mockito.when(allmatchCacheEndpoint.getInstance()).thenReturn(discoveryInstance); + ServiceCombServer allmatchInstance = new ServiceCombServer("test", transport, allmatchCacheEndpoint); + DataCenterInfo info = new DataCenterInfo(); + info.setName("test"); + info.setRegion("test-Region"); + info.setAvailableZone("test-zone"); + Mockito.when(discoveryInstance.getDataCenterInfo()).thenReturn(info); + Mockito.when(discoveryInstance.getInstanceId()).thenReturn("allmatchInstance"); + + MicroserviceInstance regionMatchDiscoveryInstance = Mockito.mock(MicroserviceInstance.class); + List regionMatchEndpoint = new ArrayList<>(); + regionMatchEndpoint.add("rest://localhost:9091"); + Mockito.when(regionMatchDiscoveryInstance.getEndpoints()).thenReturn(regionMatchEndpoint); + CacheEndpoint regionMatchCacheEndpoint = Mockito.mock(CacheEndpoint.class); + Mockito.when(regionMatchCacheEndpoint.getEndpoint()).thenReturn("rest://localhost:9091"); + Mockito.when(regionMatchCacheEndpoint.getInstance()).thenReturn(regionMatchDiscoveryInstance); + ServiceCombServer regionMatchInstance = new ServiceCombServer("test", transport, regionMatchCacheEndpoint); + DataCenterInfo regionMatchInfo = new DataCenterInfo(); + regionMatchInfo.setName("test"); + regionMatchInfo.setRegion("test-Region"); + regionMatchInfo.setAvailableZone("test-zone2"); + Mockito.when(regionMatchDiscoveryInstance.getDataCenterInfo()).thenReturn(regionMatchInfo); + Mockito.when(regionMatchDiscoveryInstance.getInstanceId()).thenReturn("regionMatchInstance"); + + MicroserviceInstance noneMatchDiscoveryInstance = Mockito.mock(MicroserviceInstance.class); + List noMatchEndpoint = new ArrayList<>(); + noMatchEndpoint.add("rest://localhost:9092"); + Mockito.when(noneMatchDiscoveryInstance.getEndpoints()).thenReturn(noMatchEndpoint); + CacheEndpoint noneMatchCacheEndpoint = Mockito.mock(CacheEndpoint.class); + Mockito.when(noneMatchCacheEndpoint.getEndpoint()).thenReturn("rest://localhost:9092"); + Mockito.when(noneMatchCacheEndpoint.getInstance()).thenReturn(noneMatchDiscoveryInstance); + ServiceCombServer noneMatchInstance = new ServiceCombServer("test", transport, noneMatchCacheEndpoint); + DataCenterInfo noneMatchInfo = new DataCenterInfo(); + noneMatchInfo.setName("test"); + noneMatchInfo.setRegion("test-Region2"); + noneMatchInfo.setAvailableZone("test-zone2"); + Mockito.when(noneMatchDiscoveryInstance.getDataCenterInfo()).thenReturn(noneMatchInfo); + Mockito.when(noneMatchDiscoveryInstance.getInstanceId()).thenReturn("noneMatchInstance"); + + // run test + Invocation invocation = Mockito.mock(Invocation.class); + List data = Arrays.asList(allmatchInstance, regionMatchInstance, noneMatchInstance); + List result = filter.getFilteredListOfServers(data, invocation); + + // check result + Assertions.assertEquals(2, result.size()); + Assertions.assertEquals("regionMatchInstance", result.get(0).getInstance().getInstanceId()); + Assertions.assertEquals("allmatchInstance", result.get(1).getInstance().getInstanceId()); + } + + @Test + public void test_enough_instance() { + ArchaiusUtils.setProperty(ZoneAwareDiscoveryFilter.CONFIG_RATIO, 0); + ZoneAwareDiscoveryFilter filter = new ZoneAwareDiscoveryFilter(); + + // set up data + MicroserviceInstance myself = Mockito.mock(MicroserviceInstance.class); + RegistrationManager registrationManager = Mockito.mock(RegistrationManager.class); + Mockito.when(registrationManager.getMicroserviceInstance()).thenReturn(myself); + RegistrationManager.setINSTANCE(registrationManager); + DataCenterInfo myDcInfo = new DataCenterInfo(); + myDcInfo.setName("test"); + myDcInfo.setRegion("test-Region"); + myDcInfo.setAvailableZone("test-zone"); + Mockito.when(myself.getDataCenterInfo()).thenReturn(myDcInfo); + + MicroserviceInstance discoveryInstance = Mockito.mock(MicroserviceInstance.class); + List allMatchEndpoint = new ArrayList<>(); + allMatchEndpoint.add("rest://localhost:9090"); + Mockito.when(discoveryInstance.getEndpoints()).thenReturn(allMatchEndpoint); + CacheEndpoint allmatchCacheEndpoint = Mockito.mock(CacheEndpoint.class); + Mockito.when(allmatchCacheEndpoint.getEndpoint()).thenReturn("rest://localhost:9090"); + Transport transport = Mockito.mock(Transport.class); + Mockito.when(allmatchCacheEndpoint.getInstance()).thenReturn(discoveryInstance); + ServiceCombServer allmatchInstance = new ServiceCombServer("test", transport, allmatchCacheEndpoint); + DataCenterInfo info = new DataCenterInfo(); + info.setName("test"); + info.setRegion("test-Region"); + info.setAvailableZone("test-zone"); + Mockito.when(discoveryInstance.getDataCenterInfo()).thenReturn(info); + Mockito.when(discoveryInstance.getInstanceId()).thenReturn("allmatchInstance"); + + MicroserviceInstance regionMatchDiscoveryInstance = Mockito.mock(MicroserviceInstance.class); + List regionMatchEndpoint = new ArrayList<>(); + regionMatchEndpoint.add("rest://localhost:9091"); + Mockito.when(regionMatchDiscoveryInstance.getEndpoints()).thenReturn(regionMatchEndpoint); + CacheEndpoint regionMatchCacheEndpoint = Mockito.mock(CacheEndpoint.class); + Mockito.when(regionMatchCacheEndpoint.getEndpoint()).thenReturn("rest://localhost:9091"); + Mockito.when(regionMatchCacheEndpoint.getInstance()).thenReturn(regionMatchDiscoveryInstance); + ServiceCombServer regionMatchInstance = new ServiceCombServer("test", transport, regionMatchCacheEndpoint); + DataCenterInfo regionMatchInfo = new DataCenterInfo(); + regionMatchInfo.setName("test"); + regionMatchInfo.setRegion("test-Region"); + regionMatchInfo.setAvailableZone("test-zone2"); + Mockito.when(regionMatchDiscoveryInstance.getDataCenterInfo()).thenReturn(regionMatchInfo); + Mockito.when(regionMatchDiscoveryInstance.getInstanceId()).thenReturn("regionMatchInstance"); + + MicroserviceInstance noneMatchDiscoveryInstance = Mockito.mock(MicroserviceInstance.class); + List noMatchEndpoint = new ArrayList<>(); + noMatchEndpoint.add("rest://localhost:9092"); + Mockito.when(noneMatchDiscoveryInstance.getEndpoints()).thenReturn(noMatchEndpoint); + CacheEndpoint noneMatchCacheEndpoint = Mockito.mock(CacheEndpoint.class); + Mockito.when(noneMatchCacheEndpoint.getEndpoint()).thenReturn("rest://localhost:9092"); + Mockito.when(noneMatchCacheEndpoint.getInstance()).thenReturn(noneMatchDiscoveryInstance); + ServiceCombServer noneMatchInstance = new ServiceCombServer("test", transport, noneMatchCacheEndpoint); + DataCenterInfo noneMatchInfo = new DataCenterInfo(); + noneMatchInfo.setName("test"); + noneMatchInfo.setRegion("test-Region2"); + noneMatchInfo.setAvailableZone("test-zone2"); + Mockito.when(noneMatchDiscoveryInstance.getDataCenterInfo()).thenReturn(noneMatchInfo); + Mockito.when(noneMatchDiscoveryInstance.getInstanceId()).thenReturn("noneMatchInstance"); + + // run test + Invocation invocation = Mockito.mock(Invocation.class); + List data = Arrays.asList(allmatchInstance, regionMatchInstance, noneMatchInstance); + List result = filter.getFilteredListOfServers(data, invocation); + + // check result + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("allmatchInstance", result.get(0).getInstance().getInstanceId()); + } +} diff --git a/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/AuthHandlerBoot.java b/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/AuthHandlerBoot.java index 5eb06ced398..cc838719420 100644 --- a/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/AuthHandlerBoot.java +++ b/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/AuthHandlerBoot.java @@ -17,9 +17,9 @@ package org.apache.servicecomb; import org.apache.servicecomb.core.BootListener; -import org.apache.servicecomb.foundation.common.utils.RSAKeyPairEntry; -import org.apache.servicecomb.foundation.common.utils.RSAUtils; -import org.apache.servicecomb.foundation.token.RSAKeypair4Auth; +import org.apache.servicecomb.foundation.common.utils.KeyPairEntry; +import org.apache.servicecomb.foundation.common.utils.KeyPairUtils; +import org.apache.servicecomb.foundation.token.Keypair4Auth; import org.apache.servicecomb.registry.RegistrationManager; import org.apache.servicecomb.registry.definition.DefinitionConst; import org.springframework.stereotype.Component; @@ -39,11 +39,11 @@ public void onBootEvent(BootEvent event) { if (!EventType.BEFORE_REGISTRY.equals(event.getEventType())) { return; } - RSAKeyPairEntry rsaKeyPairEntry = RSAUtils.generateRSAKeyPair(); - RSAKeypair4Auth.INSTANCE.setPrivateKey(rsaKeyPairEntry.getPrivateKey()); - RSAKeypair4Auth.INSTANCE.setPublicKey(rsaKeyPairEntry.getPublicKey()); - RSAKeypair4Auth.INSTANCE.setPublicKeyEncoded(rsaKeyPairEntry.getPublicKeyEncoded()); + KeyPairEntry keyPairEntry = KeyPairUtils.generateRSAKeyPair(); + Keypair4Auth.INSTANCE.setPrivateKey(keyPairEntry.getPrivateKey()); + Keypair4Auth.INSTANCE.setPublicKey(keyPairEntry.getPublicKey()); + Keypair4Auth.INSTANCE.setPublicKeyEncoded(keyPairEntry.getPublicKeyEncoded()); RegistrationManager.INSTANCE.getMicroserviceInstance().getProperties().put(DefinitionConst.INSTANCE_PUBKEY_PRO, - rsaKeyPairEntry.getPublicKeyEncoded()); + keyPairEntry.getPublicKeyEncoded()); } } diff --git a/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/RSAAuthenticationToken.java b/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/AuthenticationToken.java similarity index 87% rename from handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/RSAAuthenticationToken.java rename to handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/AuthenticationToken.java index aa88091b70e..18fdcd5128f 100644 --- a/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/RSAAuthenticationToken.java +++ b/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/AuthenticationToken.java @@ -22,7 +22,7 @@ * token: instanceId@@generateTime@randomCode@sign(instanceId@@generateTime@randomCode) * */ -public class RSAAuthenticationToken { +public class AuthenticationToken { public static final long TOKEN_ACTIVE_TIME = 24 * 60 * 60 * 1000; @@ -40,7 +40,7 @@ public class RSAAuthenticationToken { private final String plainToken; - public RSAAuthenticationToken(String instanceId, String serviceId, long generateTime, + public AuthenticationToken(String instanceId, String serviceId, long generateTime, String randomCode, String sign) { this.instanceId = instanceId; this.generateTime = generateTime; @@ -79,12 +79,12 @@ public String format() { return tokenFormat; } - public static RSAAuthenticationToken fromStr(String token) { + public static AuthenticationToken fromStr(String token) { String[] tokenArr = token.split("@"); if (tokenArr.length != 5) { return null; } - return new RSAAuthenticationToken(tokenArr[0], tokenArr[1], + return new AuthenticationToken(tokenArr[0], tokenArr[1], Long.parseLong(tokenArr[2]), tokenArr[3], tokenArr[4]); } @@ -94,10 +94,10 @@ public String getServiceId() { @Override public boolean equals(Object obj) { - if (!(obj instanceof RSAAuthenticationToken)) { + if (!(obj instanceof AuthenticationToken)) { return false; } - RSAAuthenticationToken token = (RSAAuthenticationToken) obj; + AuthenticationToken token = (AuthenticationToken) obj; if (!token.plainToken().equals(this.plainToken())) { return false; } diff --git a/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/consumer/ConsumerAuthHandler.java b/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/consumer/ConsumerAuthHandler.java index ef5e60ab45e..e5f59e00854 100644 --- a/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/consumer/ConsumerAuthHandler.java +++ b/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/consumer/ConsumerAuthHandler.java @@ -34,7 +34,7 @@ */ public class ConsumerAuthHandler implements Handler { - private RSAConsumerTokenManager authenticationTokenManager = new RSAConsumerTokenManager(); + private ConsumerTokenManager authenticationTokenManager = new ConsumerTokenManager(); @Override public void handle(Invocation invocation, AsyncResponse asyncResp) throws Exception { @@ -49,7 +49,7 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except invocation.next(asyncResp); } - public void setAuthenticationTokenManager(RSAConsumerTokenManager authenticationTokenManager) { + public void setAuthenticationTokenManager(ConsumerTokenManager authenticationTokenManager) { this.authenticationTokenManager = authenticationTokenManager; } } diff --git a/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/consumer/RSAConsumerTokenManager.java b/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/consumer/ConsumerTokenManager.java similarity index 75% rename from handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/consumer/RSAConsumerTokenManager.java rename to handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/consumer/ConsumerTokenManager.java index 7304ec3a5ec..e266d927b15 100644 --- a/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/consumer/RSAConsumerTokenManager.java +++ b/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/consumer/ConsumerTokenManager.java @@ -18,20 +18,20 @@ import java.security.PrivateKey; -import org.apache.servicecomb.authentication.RSAAuthenticationToken; -import org.apache.servicecomb.foundation.common.utils.RSAUtils; -import org.apache.servicecomb.foundation.token.RSAKeypair4Auth; +import org.apache.servicecomb.authentication.AuthenticationToken; +import org.apache.servicecomb.foundation.common.utils.KeyPairUtils; +import org.apache.servicecomb.foundation.token.Keypair4Auth; import org.apache.servicecomb.registry.RegistrationManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class RSAConsumerTokenManager { +public class ConsumerTokenManager { - private static final Logger LOGGER = LoggerFactory.getLogger(RSAConsumerTokenManager.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ConsumerTokenManager.class); private final Object lock = new Object(); - private RSAAuthenticationToken token; + private AuthenticationToken token; public String getToken() { @@ -46,7 +46,7 @@ public String getToken() { } public String createToken() { - PrivateKey privateKey = RSAKeypair4Auth.INSTANCE.getPrivateKey(); + PrivateKey privateKey = Keypair4Auth.INSTANCE.getPrivateKey(); String instanceId = RegistrationManager.INSTANCE.getMicroserviceInstance().getInstanceId(); String serviceId = RegistrationManager.INSTANCE.getMicroservice().getServiceId(); @@ -60,8 +60,8 @@ public String createToken() { long generateTime = System.currentTimeMillis(); try { String plain = String.format("%s@%s@%s@%s", instanceId, serviceId, generateTime, randomCode); - String sign = RSAUtils.sign(plain, privateKey); - token = RSAAuthenticationToken.fromStr(String.format("%s@%s", plain, sign)); + String sign = KeyPairUtils.sign(plain, privateKey); + token = AuthenticationToken.fromStr(String.format("%s@%s", plain, sign)); } catch (Exception e) { LOGGER.error("create token error", e); return null; @@ -73,12 +73,12 @@ public String createToken() { * the TTL of Token is 24 hours * client token will expired 15 minutes early */ - public boolean isExpired(RSAAuthenticationToken token) { + public boolean isExpired(AuthenticationToken token) { if (null == token) { return true; } long generateTime = token.getGenerateTime(); - long expiredDate = generateTime + RSAAuthenticationToken.TOKEN_ACTIVE_TIME - 15 * 60 * 1000; + long expiredDate = generateTime + AuthenticationToken.TOKEN_ACTIVE_TIME - 15 * 60 * 1000; long now = System.currentTimeMillis(); return now > expiredDate; } diff --git a/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/provider/ProviderAuthHanlder.java b/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/provider/ProviderAuthHanlder.java index de327098861..02290e11065 100644 --- a/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/provider/ProviderAuthHanlder.java +++ b/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/provider/ProviderAuthHanlder.java @@ -25,7 +25,7 @@ public class ProviderAuthHanlder implements Handler { - private final RSAProviderTokenManager authenticationTokenManager = new RSAProviderTokenManager(); + private final ProviderTokenManager authenticationTokenManager = new ProviderTokenManager(); @Override public void handle(Invocation invocation, AsyncResponse asyncResp) throws Exception { diff --git a/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/provider/RSAProviderTokenManager.java b/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/provider/ProviderTokenManager.java similarity index 81% rename from handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/provider/RSAProviderTokenManager.java rename to handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/provider/ProviderTokenManager.java index 76b2e3bf981..31f6edb3e2a 100644 --- a/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/provider/RSAProviderTokenManager.java +++ b/handlers/handler-publickey-auth/src/main/java/org/apache/servicecomb/authentication/provider/ProviderTokenManager.java @@ -22,8 +22,8 @@ import java.security.spec.InvalidKeySpecException; import java.util.concurrent.TimeUnit; -import org.apache.servicecomb.authentication.RSAAuthenticationToken; -import org.apache.servicecomb.foundation.common.utils.RSAUtils; +import org.apache.servicecomb.authentication.AuthenticationToken; +import org.apache.servicecomb.foundation.common.utils.KeyPairUtils; import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; import org.apache.servicecomb.registry.cache.MicroserviceInstanceCache; import org.apache.servicecomb.registry.definition.DefinitionConst; @@ -34,11 +34,11 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -public class RSAProviderTokenManager { +public class ProviderTokenManager { - private static final Logger LOGGER = LoggerFactory.getLogger(RSAProviderTokenManager.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ProviderTokenManager.class); - private final Cache validatedToken = CacheBuilder.newBuilder() + private final Cache validatedToken = CacheBuilder.newBuilder() .expireAfterAccess(getExpiredTime(), TimeUnit.MILLISECONDS) .build(); @@ -46,7 +46,7 @@ public class RSAProviderTokenManager { public boolean valid(String token) { try { - RSAAuthenticationToken rsaToken = RSAAuthenticationToken.fromStr(token); + AuthenticationToken rsaToken = AuthenticationToken.fromStr(token); if (null == rsaToken) { LOGGER.error("token format is error, perhaps you need to set auth handler at consumer"); return false; @@ -71,21 +71,21 @@ public boolean valid(String token) { } } - public boolean isValidToken(RSAAuthenticationToken rsaToken) + public boolean isValidToken(AuthenticationToken rsaToken) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { String sign = rsaToken.getSign(); String content = rsaToken.plainToken(); String publicKey = getPublicKeyFromInstance(rsaToken.getInstanceId(), rsaToken.getServiceId()); - return RSAUtils.verify(publicKey, sign, content); + return KeyPairUtils.verify(publicKey, sign, content); } protected int getExpiredTime() { return 60 * 60 * 1000; } - private boolean tokenExpired(RSAAuthenticationToken rsaToken) { + private boolean tokenExpired(AuthenticationToken rsaToken) { long generateTime = rsaToken.getGenerateTime(); - long expired = generateTime + RSAAuthenticationToken.TOKEN_ACTIVE_TIME + 15 * 60 * 1000; + long expired = generateTime + AuthenticationToken.TOKEN_ACTIVE_TIME + 15 * 60 * 1000; long now = System.currentTimeMillis(); return now > expired; } @@ -101,7 +101,7 @@ private String getPublicKeyFromInstance(String instanceId, String serviceId) { } @VisibleForTesting - Cache getValidatedToken() { + Cache getValidatedToken() { return validatedToken; } } diff --git a/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestAuthHandlerBoot.java b/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestAuthHandlerBoot.java index a8650092fee..8c61ef7f882 100644 --- a/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestAuthHandlerBoot.java +++ b/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestAuthHandlerBoot.java @@ -23,7 +23,7 @@ import org.apache.servicecomb.core.SCBEngine; import org.apache.servicecomb.core.bootstrap.SCBBootstrap; import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; -import org.apache.servicecomb.foundation.token.RSAKeypair4Auth; +import org.apache.servicecomb.foundation.token.Keypair4Auth; import org.apache.servicecomb.registry.RegistrationManager; import org.apache.servicecomb.registry.api.registry.Microservice; import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; @@ -58,8 +58,8 @@ public void testGenerateRSAKey() { BootEvent bootEvent = new BootEvent(); bootEvent.setEventType(BootListener.EventType.BEFORE_REGISTRY); authHandlerBoot.onBootEvent(bootEvent); - Assertions.assertNotNull(RSAKeypair4Auth.INSTANCE.getPrivateKey()); - Assertions.assertNotNull(RSAKeypair4Auth.INSTANCE.getPublicKey()); + Assertions.assertNotNull(Keypair4Auth.INSTANCE.getPrivateKey()); + Assertions.assertNotNull(Keypair4Auth.INSTANCE.getPublicKey()); } @Test diff --git a/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestRSAAuthenticationToken.java b/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestAuthenticationToken.java similarity index 88% rename from handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestRSAAuthenticationToken.java rename to handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestAuthenticationToken.java index d0ed1cff7b8..9555a9066bd 100644 --- a/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestRSAAuthenticationToken.java +++ b/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestAuthenticationToken.java @@ -21,12 +21,12 @@ import java.security.SignatureException; import java.security.spec.InvalidKeySpecException; -import org.apache.servicecomb.foundation.common.utils.RSAUtils; +import org.apache.servicecomb.foundation.common.utils.KeyPairUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestRSAAuthenticationToken { +public class TestAuthenticationToken { @Test @@ -34,7 +34,7 @@ public void testRSAAuthenticationToken() throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, SignatureException { String tokenstr = "e8a04b54cf2711e7b701286ed488fc20@c8636e5acf1f11e7b701286ed488fc20@1511315597475@9t0tp8ce80SUM5ts6iRGjFJMvCdQ7uvhpyh0RM7smKm3p4wYOrojr4oT1Pnwx7xwgcgEFbQdwPJxIMfivpQ1rHGqiLp67cjACvJ3Ke39pmeAVhybsLADfid6oSjscFaJ@WBYouF6hXYrXzBA31HC3VX8Bw9PNgJUtVqOPAaeW9ye3q/D7WWb0M+XMouBIWxWY6v9Un1dGu5Rkjlx6gZbnlHkb2VO8qFR3Y6lppooWCirzpvEBRjlJQu8LPBur0BCfYGq8XYrEZA2NU6sg2zXieqCSiX6BnMnBHNn4cR9iZpk="; - RSAAuthenticationToken token = RSAAuthenticationToken.fromStr(tokenstr); + AuthenticationToken token = AuthenticationToken.fromStr(tokenstr); String contents = token.plainToken(); Assertions.assertEquals( "e8a04b54cf2711e7b701286ed488fc20@c8636e5acf1f11e7b701286ed488fc20@1511315597475@9t0tp8ce80SUM5ts6iRGjFJMvCdQ7uvhpyh0RM7smKm3p4wYOrojr4oT1Pnwx7xwgcgEFbQdwPJxIMfivpQ1rHGqiLp67cjACvJ3Ke39pmeAVhybsLADfid6oSjscFaJ", @@ -45,7 +45,7 @@ public void testRSAAuthenticationToken() sign); String pubKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxKl5TNUTec7fL2degQcCk6vKf3c0wsfNK5V6elKzjWxm0MwbRj/UeR20VSnicBmVIOWrBS9LiERPPvjmmWUOSS2vxwr5XfhBhZ07gCAUNxBOTzgMo5nE45DhhZu5Jzt5qSV6o10Kq7+fCCBlDZ1UoWxZceHkUt5AxcrhEDulFjQIDAQAB"; - Assertions.assertTrue(RSAUtils.verify(pubKey, sign, contents)); + Assertions.assertTrue(KeyPairUtils.verify(pubKey, sign, contents)); } @@ -53,7 +53,7 @@ public void testRSAAuthenticationToken() public void testRSAAuthenticationTokenError() { String tokenstr = "e8a04b54cf2711e7b701286ed488fc20@c8636e5acf1f11e7b701286ed488fc20@1511315597475@WBYouF6hXYrXzBA31HC3VX8Bw9PNgJUtVqOPAaeW9ye3q/D7WWb0M+XMouBIWxWY6v9Un1dGu5Rkjlx6gZbnlHkb2VO8qFR3Y6lppooWCirzpvEBRjlJQu8LPBur0BCfYGq8XYrEZA2NU6sg2zXieqCSiX6BnMnBHNn4cR9iZpk="; - RSAAuthenticationToken token = RSAAuthenticationToken.fromStr(tokenstr); + AuthenticationToken token = AuthenticationToken.fromStr(tokenstr); Assertions.assertNull(token); } @@ -62,17 +62,17 @@ public void testRSAAuthenticationTokenError() { public void testEqual() { String tokenstr = "e8a04b54cf2711e7b701286ed488fc20@c8636e5acf1f11e7b701286ed488fc20@1511315597475@9t0tp8ce80SUM5ts6iRGjFJMvCdQ7uvhpyh0RM7smKm3p4wYOrojr4oT1Pnwx7xwgcgEFbQdwPJxIMfivpQ1rHGqiLp67cjACvJ3Ke39pmeAVhybsLADfid6oSjscFaJ@WBYouF6hXYrXzBA31HC3VX8Bw9PNgJUtVqOPAaeW9ye3q/D7WWb0M+XMouBIWxWY6v9Un1dGu5Rkjlx6gZbnlHkb2VO8qFR3Y6lppooWCirzpvEBRjlJQu8LPBur0BCfYGq8XYrEZA2NU6sg2zXieqCSiX6BnMnBHNn4cR9iZpk="; - RSAAuthenticationToken token = RSAAuthenticationToken.fromStr(tokenstr); + AuthenticationToken token = AuthenticationToken.fromStr(tokenstr); Assertions.assertNotEquals(token, null); - RSAAuthenticationToken token2 = RSAAuthenticationToken.fromStr( + AuthenticationToken token2 = AuthenticationToken.fromStr( "e8a04b54cf2711e7b701286ed488fc20@c8636e5acf1f11e7b701286ed488fc20@1511315597475@9t0tp8ce80SUM5ts6iRGjFJMvCdQ7uvhpyh0RM7smKm3p4wYOrojr4oT1Pnwx7xwgcgEFbQdwPJxIMfivpQ1rHGqiLp67cjACvJ3Ke39pmeAVhybsLADfid6oSjscFaJ@WBYouF6hXYrXzBA31HC3VX8Bw9PNgJUtVqOPAaeW9ye3q/D7WWb0M+XMouBIWxWY6v9Un1dGu5Rkjlx6gZbnlHkb2VO8qFR3Y6lppooWCirzpvEBRjlJQu8LPBur0BCfYGq8XYrEZA2NU6sg2zXieqCSiX6BnMnBHNn4cR9iZpk"); Assertions.assertNotEquals(token2, token); - RSAAuthenticationToken token3 = RSAAuthenticationToken.fromStr( + AuthenticationToken token3 = AuthenticationToken.fromStr( "e8a0a4b54cf2711e7b701286ed488fc20@c8636e5acf1f11e7b701286ed488fc20@1511315597475@9t0tp8ce80SUM5ts6iRGjFJMvCdQ7uvhpyh0RM7smKm3p4wYOrojr4oT1Pnwx7xwgcgEFbQdwPJxIMfivpQ1rHGqiLp67cjACvJ3Ke39pmeAVhybsLADfid6oSjscFaJ@WBYouF6hXYrXzBA31HC3VX8Bw9PNgJUtVqOPAaeW9ye3q/D7WWb0M+XMouBIWxWY6v9Un1dGu5Rkjlx6gZbnlHkb2VO8qFR3Y6lppooWCirzpvEBRjlJQu8LPBur0BCfYGq8XYrEZA2NU6sg2zXieqCSiX6BnMnBHNn4cR9iZpk"); Assertions.assertNotEquals(token3, token); - RSAAuthenticationToken token4 = RSAAuthenticationToken.fromStr( + AuthenticationToken token4 = AuthenticationToken.fromStr( "e8a04b54cf2711e7b701286ed488fc20@c8636e5acf1f11e7b701286ed488fc20@1511315597475@9t0tp8ce80SUM5ts6iRGjFJMvCdQ7uvhpyh0RM7smKm3p4wYOrojr4oT1Pnwx7xwgcgEFbQdwPJxIMfivpQ1rHGqiLp67cjACvJ3Ke39pmeAVhybsLADfid6oSjscFaJ@WBYouF6hXYrXzBA31HC3VX8Bw9PNgJUtVqOPAaeW9ye3q/D7WWb0M+XMouBIWxWY6v9Un1dGu5Rkjlx6gZbnlHkb2VO8qFR3Y6lppooWCirzpvEBRjlJQu8LPBur0BCfYGq8XYrEZA2NU6sg2zXieqCSiX6BnMnBHNn4cR9iZpk="); Assertions.assertEquals(token4, token); } diff --git a/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestConsumerAuthHandler.java b/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestConsumerAuthHandler.java index 652eb0f1aa3..a8ae8ac2419 100644 --- a/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestConsumerAuthHandler.java +++ b/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/TestConsumerAuthHandler.java @@ -17,7 +17,7 @@ package org.apache.servicecomb.authentication; import org.apache.servicecomb.authentication.consumer.ConsumerAuthHandler; -import org.apache.servicecomb.authentication.consumer.RSAConsumerTokenManager; +import org.apache.servicecomb.authentication.consumer.ConsumerTokenManager; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.swagger.invocation.AsyncResponse; import org.junit.jupiter.api.BeforeEach; @@ -30,11 +30,11 @@ public class TestConsumerAuthHandler { AsyncResponse asyncResp = null; - RSAConsumerTokenManager tokenManager = null; + ConsumerTokenManager tokenManager = null; @Test public void testHandler() throws Exception { - tokenManager = Mockito.mock(RSAConsumerTokenManager.class); + tokenManager = Mockito.mock(ConsumerTokenManager.class); Mockito.when(tokenManager.getToken()).thenReturn("testtoken"); ConsumerAuthHandler consumerAuthHandler = new ConsumerAuthHandler(); consumerAuthHandler.setAuthenticationTokenManager(tokenManager); @@ -44,7 +44,7 @@ public void testHandler() throws Exception { @Test public void testHandlerException() throws Exception { - tokenManager = Mockito.mock(RSAConsumerTokenManager.class); + tokenManager = Mockito.mock(ConsumerTokenManager.class); Mockito.when(tokenManager.getToken()).thenReturn(null); ConsumerAuthHandler consumerAuthHandler = new ConsumerAuthHandler(); consumerAuthHandler.setAuthenticationTokenManager(tokenManager); diff --git a/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/provider/TestRSAProviderTokenManager.java b/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/provider/TestProviderTokenManager.java similarity index 74% rename from handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/provider/TestRSAProviderTokenManager.java rename to handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/provider/TestProviderTokenManager.java index 7e41e60e7a7..db94319eb4b 100644 --- a/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/provider/TestRSAProviderTokenManager.java +++ b/handlers/handler-publickey-auth/src/test/java/org/apache/servicecomb/authentication/provider/TestProviderTokenManager.java @@ -19,13 +19,13 @@ import java.util.HashMap; import java.util.Map; -import org.apache.servicecomb.authentication.RSAAuthenticationToken; -import org.apache.servicecomb.authentication.consumer.RSAConsumerTokenManager; +import org.apache.servicecomb.authentication.AuthenticationToken; +import org.apache.servicecomb.authentication.consumer.ConsumerTokenManager; import org.apache.servicecomb.config.ConfigUtil; -import org.apache.servicecomb.foundation.common.utils.RSAKeyPairEntry; -import org.apache.servicecomb.foundation.common.utils.RSAUtils; +import org.apache.servicecomb.foundation.common.utils.KeyPairEntry; +import org.apache.servicecomb.foundation.common.utils.KeyPairUtils; import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; -import org.apache.servicecomb.foundation.token.RSAKeypair4Auth; +import org.apache.servicecomb.foundation.token.Keypair4Auth; import org.apache.servicecomb.registry.RegistrationManager; import org.apache.servicecomb.registry.api.registry.Microservice; import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; @@ -41,7 +41,7 @@ import org.mockito.MockedStatic; import org.mockito.Mockito; -public class TestRSAProviderTokenManager { +public class TestProviderTokenManager { @BeforeEach @@ -58,7 +58,7 @@ public void teardown() { public void testTokenExpired() { String tokenStr = "e8a04b54cf2711e7b701286ed488fc20@c8636e5acf1f11e7b701286ed488fc20@1511315597475@9t0tp8ce80SUM5ts6iRGjFJMvCdQ7uvhpyh0RM7smKm3p4wYOrojr4oT1Pnwx7xwgcgEFbQdwPJxIMfivpQ1rHGqiLp67cjACvJ3Ke39pmeAVhybsLADfid6oSjscFaJ@WBYouF6hXYrXzBA31HC3VX8Bw9PNgJUtVqOPAaeW9ye3q/D7WWb0M+XMouBIWxWY6v9Un1dGu5Rkjlx6gZbnlHkb2VO8qFR3Y6lppooWCirzpvEBRjlJQu8LPBur0BCfYGq8XYrEZA2NU6sg2zXieqCSiX6BnMnBHNn4cR9iZpk="; - RSAProviderTokenManager tokenManager = new RSAProviderTokenManager(); + ProviderTokenManager tokenManager = new ProviderTokenManager(); MicroserviceInstance microserviceInstance = new MicroserviceInstance(); Map properties = new HashMap<>(); microserviceInstance.setProperties(properties); @@ -73,21 +73,21 @@ public void testTokenExpiredRemoveInstance() throws Exception { String tokenStr = "e8a04b54cf2711e7b701286ed488fc20@c8636e5acf1f11e7b701286ed488fc20@1511315597475@9t0tp8ce80SUM5ts6iRGjFJMvCdQ7uvhpyh0RM7smKm3p4wYOrojr4oT1Pnwx7xwgcgEFbQdwPJxIMfivpQ1rHGqiLp67cjACvJ3Ke39pmeAVhybsLADfid6oSjscFaJ@WBYouF6hXYrXzBA31HC3VX8Bw9PNgJUtVqOPAaeW9ye3q/D7WWb0M+XMouBIWxWY6v9Un1dGu5Rkjlx6gZbnlHkb2VO8qFR3Y6lppooWCirzpvEBRjlJQu8LPBur0BCfYGq8XYrEZA2NU6sg2zXieqCSiX6BnMnBHNn4cR9iZpk="; - RSAAuthenticationToken token = Mockito.spy(RSAAuthenticationToken.fromStr(tokenStr)); - RSAProviderTokenManager tokenManager = Mockito.spy(new RSAProviderTokenManager() { + AuthenticationToken token = Mockito.spy(AuthenticationToken.fromStr(tokenStr)); + ProviderTokenManager tokenManager = Mockito.spy(new ProviderTokenManager() { @Override protected int getExpiredTime() { return 500; } }); - try (MockedStatic rsaAuthenticationTokenMockedStatic = Mockito.mockStatic(RSAAuthenticationToken.class)) { - rsaAuthenticationTokenMockedStatic.when(() -> RSAAuthenticationToken.fromStr(tokenStr)).thenReturn(token); + try (MockedStatic rsaAuthenticationTokenMockedStatic = Mockito.mockStatic(AuthenticationToken.class)) { + rsaAuthenticationTokenMockedStatic.when(() -> AuthenticationToken.fromStr(tokenStr)).thenReturn(token); Mockito.when(token.getGenerateTime()).thenReturn(System.currentTimeMillis()); Mockito.doReturn(true).when(tokenManager).isValidToken(token); Assertions.assertTrue(tokenManager.valid(tokenStr)); - Cache cache = tokenManager + Cache cache = tokenManager .getValidatedToken(); Assertions.assertTrue(cache.asMap().containsKey(token)); @@ -98,38 +98,38 @@ protected int getExpiredTime() { @Test public void testTokenFromValidatePool() { - RSAKeyPairEntry rsaKeyPairEntry = RSAUtils.generateRSAKeyPair(); - RSAKeypair4Auth.INSTANCE.setPrivateKey(rsaKeyPairEntry.getPrivateKey()); - RSAKeypair4Auth.INSTANCE.setPublicKey(rsaKeyPairEntry.getPublicKey()); - RSAKeypair4Auth.INSTANCE.setPublicKeyEncoded(rsaKeyPairEntry.getPublicKeyEncoded()); + KeyPairEntry keyPairEntry = KeyPairUtils.generateRSAKeyPair(); + Keypair4Auth.INSTANCE.setPrivateKey(keyPairEntry.getPrivateKey()); + Keypair4Auth.INSTANCE.setPublicKey(keyPairEntry.getPublicKey()); + Keypair4Auth.INSTANCE.setPublicKeyEncoded(keyPairEntry.getPublicKeyEncoded()); String serviceId = "c8636e5acf1f11e7b701286ed488fc20"; String instanceId = "e8a04b54cf2711e7b701286ed488fc20"; - RSAConsumerTokenManager rsaConsumerTokenManager = new RSAConsumerTokenManager(); + ConsumerTokenManager consumerTokenManager = new ConsumerTokenManager(); MicroserviceInstance microserviceInstance = new MicroserviceInstance(); microserviceInstance.setInstanceId(instanceId); Map properties = new HashMap<>(); microserviceInstance.setProperties(properties); - properties.put(DefinitionConst.INSTANCE_PUBKEY_PRO, rsaKeyPairEntry.getPublicKeyEncoded()); + properties.put(DefinitionConst.INSTANCE_PUBKEY_PRO, keyPairEntry.getPublicKeyEncoded()); Microservice microservice = new Microservice(); microservice.setServiceId(serviceId); RegistrationManager.INSTANCE = Mockito.spy(RegistrationManager.INSTANCE); Mockito.when(RegistrationManager.INSTANCE.getMicroservice()).thenReturn(microservice); Mockito.when(RegistrationManager.INSTANCE.getMicroserviceInstance()).thenReturn(microserviceInstance); //Test Consumer first create token - String token = rsaConsumerTokenManager.getToken(); + String token = consumerTokenManager.getToken(); Assertions.assertNotNull(token); // use cache token - Assertions.assertEquals(token, rsaConsumerTokenManager.getToken()); + Assertions.assertEquals(token, consumerTokenManager.getToken()); try (MockedStatic microserviceInstanceCacheMockedStatic = Mockito.mockStatic(MicroserviceInstanceCache.class)) { microserviceInstanceCacheMockedStatic.when(() -> MicroserviceInstanceCache.getOrCreate(serviceId, instanceId)) .thenReturn(microserviceInstance); microserviceInstanceCacheMockedStatic.when(() -> MicroserviceInstanceCache.getOrCreate(serviceId)) .thenReturn(microservice); - RSAProviderTokenManager rsaProviderTokenManager = new RSAProviderTokenManager(); + ProviderTokenManager providerTokenManager = new ProviderTokenManager(); //first validate need to verify use RSA - Assertions.assertTrue(rsaProviderTokenManager.valid(token)); + Assertions.assertTrue(providerTokenManager.valid(token)); // second validate use validated pool - Assertions.assertTrue(rsaProviderTokenManager.valid(token)); + Assertions.assertTrue(providerTokenManager.valid(token)); } } } diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/RouterInvokeFilter.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/RouterAddHeaderFilter.java similarity index 93% rename from handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/RouterInvokeFilter.java rename to handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/RouterAddHeaderFilter.java index b40bf543dc8..708e0aa29dd 100644 --- a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/RouterInvokeFilter.java +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/RouterAddHeaderFilter.java @@ -24,6 +24,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.servicecomb.common.rest.filter.HttpServerFilter; +import org.apache.servicecomb.core.Const; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.foundation.common.utils.JsonUtils; import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx; @@ -36,9 +37,9 @@ import com.netflix.config.DynamicPropertyFactory; import com.netflix.config.DynamicStringProperty; -public class RouterInvokeFilter implements HttpServerFilter { +public class RouterAddHeaderFilter implements HttpServerFilter { - private static final Logger LOGGER = LoggerFactory.getLogger(RouterInvokeFilter.class); + private static final Logger LOGGER = LoggerFactory.getLogger(RouterAddHeaderFilter.class); private static final String SERVICECOMB_ROUTER_HEADER = "servicecomb.router.header"; @@ -55,6 +56,11 @@ public boolean enabled() { return true; } + @Override + public boolean enabledForTransport(String transport) { + return HttpServerFilter.super.enabledForTransport(transport) || Const.WEBSOCKET.equals(transport); + } + @Override public Response afterReceiveRequest(Invocation invocation, HttpServletRequestEx httpServletRequestEx) { diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/RouterServerListFilter.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/RouterServerListFilter.java index 0e5ae7d095b..b7a3cbc7a6d 100644 --- a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/RouterServerListFilter.java +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/RouterServerListFilter.java @@ -26,7 +26,6 @@ import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils; import org.apache.servicecomb.loadbalance.ServerListFilterExt; import org.apache.servicecomb.loadbalance.ServiceCombServer; -import org.apache.servicecomb.registry.api.registry.Microservice; import org.apache.servicecomb.router.RouterFilter; import org.apache.servicecomb.router.distribute.RouterDistributor; import org.slf4j.Logger; @@ -47,7 +46,7 @@ public class RouterServerListFilter implements ServerListFilterExt { public static final String ROUTER_HEADER = "X-RouterContext"; @SuppressWarnings("unchecked") - private final RouterDistributor routerDistributor = BeanUtils + private final RouterDistributor routerDistributor = BeanUtils .getBean(RouterDistributor.class); private final RouterFilter routerFilter = BeanUtils.getBean(RouterFilter.class); diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/ServiceCombRouterDistributor.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/ServiceCombRouterDistributor.java new file mode 100644 index 00000000000..e59cf3c3e8c --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/custom/ServiceCombRouterDistributor.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.router.custom; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.servicecomb.loadbalance.ServiceCombServer; +import org.apache.servicecomb.router.distribute.AbstractRouterDistributor; +import org.springframework.stereotype.Component; + +@Component +public class ServiceCombRouterDistributor extends + AbstractRouterDistributor { + + public ServiceCombRouterDistributor() { + init( + instance -> MicroserviceCache.getInstance() + .getService(instance.getInstance().getServiceId()).getVersion(), + instance -> MicroserviceCache.getInstance() + .getService(instance.getInstance().getServiceId()).getServiceName(), + instance -> { + Map properties = new HashMap<>(); + properties.putAll(MicroserviceCache.getInstance() + .getService(instance.getInstance().getServiceId()).getProperties()); + properties.putAll(instance.getInstance().getProperties()); + return properties; + }); + } +} diff --git a/handlers/handler-router/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpServerFilter b/handlers/handler-router/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpServerFilter index 02a728b1987..065e42fadc4 100644 --- a/handlers/handler-router/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpServerFilter +++ b/handlers/handler-router/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpServerFilter @@ -15,4 +15,4 @@ # limitations under the License. # -org.apache.servicecomb.router.custom.RouterInvokeFilter \ No newline at end of file +org.apache.servicecomb.router.custom.RouterAddHeaderFilter \ No newline at end of file diff --git a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/HttpClientRequestWrapper.java b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/HttpClientRequestWrapper.java index a8196907673..738a84d201f 100644 --- a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/HttpClientRequestWrapper.java +++ b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/HttpClientRequestWrapper.java @@ -19,9 +19,13 @@ import brave.http.HttpClientRequest; +import com.netflix.config.DynamicPropertyFactory; import org.apache.servicecomb.core.Invocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class HttpClientRequestWrapper extends HttpClientRequest { + private static final Logger LOG = LoggerFactory.getLogger(HttpClientRequestWrapper.class); private Invocation invocation; @@ -50,6 +54,13 @@ public String method() { @Override public String path() { + if (DynamicPropertyFactory.getInstance().getBooleanProperty(TracingConfiguration.TRACING_WORK_WITH_THIRDPARTY, false).get()) { + try { + return TracingConfiguration.createRequestPath(invocation); + } catch (Exception e) { + LOG.warn("generate rest path failed: {}", e.getMessage()); + } + } return invocation.getOperationMeta().getOperationPath(); } diff --git a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/HttpServeRequestWrapper.java b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/HttpServeRequestWrapper.java index 5503cc485b6..f4d6fe9eba5 100644 --- a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/HttpServeRequestWrapper.java +++ b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/HttpServeRequestWrapper.java @@ -19,9 +19,15 @@ import brave.http.HttpServerRequest; +import com.netflix.config.DynamicPropertyFactory; + import org.apache.servicecomb.core.Invocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class HttpServeRequestWrapper extends HttpServerRequest { + private static final Logger LOG = LoggerFactory.getLogger(HttpServeRequestWrapper.class); + private Invocation invocation; HttpServeRequestWrapper() { @@ -43,6 +49,13 @@ public String method() { @Override public String path() { + if (DynamicPropertyFactory.getInstance().getBooleanProperty(TracingConfiguration.TRACING_WORK_WITH_THIRDPARTY, false).get()) { + try { + return TracingConfiguration.createRequestPath(invocation); + } catch (Exception e) { + LOG.warn("generate rest path failed: {}", e.getMessage()); + } + } return invocation.getOperationMeta().getOperationPath(); } diff --git a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/TracingConfiguration.java b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/TracingConfiguration.java index 3665dd87186..4d63ed2c160 100644 --- a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/TracingConfiguration.java +++ b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/TracingConfiguration.java @@ -26,8 +26,14 @@ import java.text.MessageFormat; +import org.apache.commons.lang3.StringUtils; +import org.apache.servicecomb.common.rest.RestConst; +import org.apache.servicecomb.common.rest.definition.RestOperationMeta; import org.apache.servicecomb.config.BootStrapProperties; import org.apache.servicecomb.config.DynamicProperties; +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.foundation.common.net.URIEndpointObject; +import org.apache.servicecomb.registry.definition.DefinitionConst; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -48,6 +54,8 @@ class TracingConfiguration { private String apiVersion = CONFIG_TRACING_COLLECTOR_API_V2; + public static final String TRACING_WORK_WITH_THIRDPARTY = "servicecomb.tracing.workWithThirdParty"; + @Bean Sender sender(DynamicProperties dynamicProperties) { apiVersion = dynamicProperties.getStringProperty(CONFIG_TRACING_COLLECTOR_API_VERSION, @@ -97,4 +105,20 @@ CurrentTraceContext currentTraceContext() { HttpTracing httpTracing(Tracing tracing) { return HttpTracing.create(tracing); } + + public static String createRequestPath(Invocation invocation) throws Exception { + URIEndpointObject address = (URIEndpointObject) invocation.getEndpoint().getAddress(); + String urlPrefix = address.getFirst(DefinitionConst.URL_PREFIX); + RestOperationMeta swaggerRestOperation = invocation.getOperationMeta().getExtData(RestConst.SWAGGER_REST_OPERATION); + String path = (String) invocation.getHandlerContext().get(RestConst.REST_CLIENT_REQUEST_PATH); + if (path == null) { + path = swaggerRestOperation.getPathBuilder().createRequestPath(invocation.getSwaggerArguments()); + } + + if (StringUtils.isEmpty(urlPrefix) || path.startsWith(urlPrefix)) { + return path; + } + + return urlPrefix + path; + } } diff --git a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinConsumerTracingHandler.java b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinConsumerTracingHandler.java index 7883134131a..8d823ffb94a 100644 --- a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinConsumerTracingHandler.java +++ b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinConsumerTracingHandler.java @@ -24,10 +24,7 @@ public class ZipkinConsumerTracingHandler extends ZipkinTracingHandler { public ZipkinConsumerTracingHandler() { - this(new ZipkinConsumerDelegate(BeanUtils.getContext().getBean(HttpTracing.class))); + super(BeanUtils.getContext().getBean(HttpTracing.class)); } - private ZipkinConsumerTracingHandler(ZipkinTracingDelegate tracingHandler) { - super(tracingHandler); - } } diff --git a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinProviderTracingHandler.java b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinProviderTracingHandler.java index abec4f33004..c66459c0a8e 100644 --- a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinProviderTracingHandler.java +++ b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinProviderTracingHandler.java @@ -24,10 +24,6 @@ public class ZipkinProviderTracingHandler extends ZipkinTracingHandler { public ZipkinProviderTracingHandler() { - this(new ZipkinProviderDelegate(BeanUtils.getContext().getBean(HttpTracing.class))); - } - - private ZipkinProviderTracingHandler(ZipkinProviderDelegate tracingHandler) { - super(tracingHandler); + super(BeanUtils.getContext().getBean(HttpTracing.class)); } } diff --git a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinTracingFilter.java b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinTracingFilter.java index f896004ebad..6dd8d5442c2 100644 --- a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinTracingFilter.java +++ b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinTracingFilter.java @@ -39,9 +39,8 @@ public class ZipkinTracingFilter implements Filter { public static final String NAME = "zipkin"; - private ZipkinConsumerDelegate consumer; - - private ZipkinProviderDelegate producer; + @Autowired + private HttpTracing httpTracing; @Nonnull @Override @@ -49,12 +48,6 @@ public String getName() { return NAME; } - @Autowired - public void setHttpTracing(HttpTracing httpTracing) { - this.consumer = new ZipkinConsumerDelegate(httpTracing); - this.producer = new ZipkinProviderDelegate(httpTracing); - } - @SuppressWarnings({"try", "unused"}) @Override public CompletableFuture onFilter(Invocation invocation, FilterNode nextNode) { @@ -69,9 +62,9 @@ public CompletableFuture onFilter(Invocation invocation, FilterNode ne private ZipkinTracingDelegate collectTracing(Invocation invocation) { if (PRODUCER.equals(invocation.getInvocationType())) { - return producer; + return new ZipkinProviderDelegate(httpTracing); } - return consumer; + return new ZipkinConsumerDelegate(httpTracing); } } diff --git a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinTracingHandler.java b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinTracingHandler.java index 7990d0dfa28..5d6593663b7 100644 --- a/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinTracingHandler.java +++ b/handlers/handler-tracing-zipkin/src/main/java/org/apache/servicecomb/tracing/zipkin/ZipkinTracingHandler.java @@ -17,6 +17,7 @@ package org.apache.servicecomb.tracing.zipkin; +import brave.http.HttpTracing; import org.apache.servicecomb.core.Handler; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.swagger.invocation.AsyncResponse; @@ -25,31 +26,30 @@ import brave.Span; import brave.Tracer.SpanInScope; -import brave.Tracing; + +import static org.apache.servicecomb.swagger.invocation.InvocationType.PRODUCER; class ZipkinTracingHandler implements Handler { private static final Logger LOGGER = LoggerFactory.getLogger(ZipkinTracingHandler.class); - private final Tracing tracer; - - private final ZipkinTracingDelegate tracingDelegate; + private final HttpTracing httpTracing; - ZipkinTracingHandler(ZipkinTracingDelegate tracingDelegate) { - this.tracer = tracingDelegate.tracer(); - this.tracingDelegate = tracingDelegate; + ZipkinTracingHandler(HttpTracing httpTracing) { + this.httpTracing = httpTracing; } @SuppressWarnings({"try", "unused"}) @Override public void handle(Invocation invocation, AsyncResponse asyncResp) throws Exception { + ZipkinTracingDelegate tracingDelegate = collectTracing(invocation); Span span = tracingDelegate.createSpan(invocation); - try (SpanInScope scope = tracer.tracer().withSpanInScope(span)) { + try (SpanInScope scope = tracingDelegate.tracer().tracer().withSpanInScope(span)) { LOGGER.debug("{}: Generated tracing span for {}", tracingDelegate.name(), invocation.getOperationName()); - invocation.next(onResponse(invocation, asyncResp, span)); + invocation.next(onResponse(invocation, asyncResp, span, tracingDelegate)); } catch (Exception e) { LOGGER.debug("{}: Failed invocation on {}", tracingDelegate.name(), @@ -61,7 +61,7 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except } } - private AsyncResponse onResponse(Invocation invocation, AsyncResponse asyncResp, Span span) { + private AsyncResponse onResponse(Invocation invocation, AsyncResponse asyncResp, Span span, ZipkinTracingDelegate tracingDelegate) { return response -> { Throwable error = response.isFailed() ? response.getResult() : null; tracingDelegate.onResponse(span, response, error); @@ -74,4 +74,12 @@ private AsyncResponse onResponse(Invocation invocation, AsyncResponse asyncResp, asyncResp.handle(response); }; } + + private ZipkinTracingDelegate collectTracing(Invocation invocation) { + if (PRODUCER.equals(invocation.getInvocationType())) { + return new ZipkinProviderDelegate(httpTracing); + } + + return new ZipkinConsumerDelegate(httpTracing); + } } diff --git a/handlers/handler-tracing-zipkin/src/test/java/org/apache/servicecomb/tracing/zipkin/ZipkinTracingHandlerTest.java b/handlers/handler-tracing-zipkin/src/test/java/org/apache/servicecomb/tracing/zipkin/ZipkinTracingHandlerTest.java index 7773e98a2cb..f3203206086 100644 --- a/handlers/handler-tracing-zipkin/src/test/java/org/apache/servicecomb/tracing/zipkin/ZipkinTracingHandlerTest.java +++ b/handlers/handler-tracing-zipkin/src/test/java/org/apache/servicecomb/tracing/zipkin/ZipkinTracingHandlerTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import brave.http.HttpTracing; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.swagger.invocation.AsyncResponse; import org.apache.servicecomb.swagger.invocation.Response; @@ -43,8 +44,6 @@ public class ZipkinTracingHandlerTest { private final AsyncResponse asyncResponse = Mockito.mock(AsyncResponse.class); - private final ZipkinTracingDelegate delegate = Mockito.mock(ZipkinTracingDelegate.class); - private final Tracing tracing = Tracing.newBuilder().build(); private final Span span = Mockito.mock(Span.class); @@ -55,11 +54,8 @@ public class ZipkinTracingHandlerTest { @BeforeEach public void setUp() throws Exception { - when(delegate.tracer()).thenReturn(tracing); - when(delegate.createSpan(invocation)).thenReturn(span); - when(delegate.name()).thenReturn("delegate"); - - tracingHandler = new ZipkinTracingHandler(delegate); + HttpTracing httpTracing = HttpTracing.create(tracing); + tracingHandler = new ZipkinTracingHandler(httpTracing); } @Test @@ -73,7 +69,6 @@ public void delegatesResponseAndHandleResponse() throws Exception { argumentCaptor.getValue().handle(response); verify(asyncResponse).handle(response); - verify(delegate).onResponse(span, response, null); } @Test @@ -90,7 +85,6 @@ public void delegatesErrorResponseAndHandleResponse() throws Exception { argumentCaptor.getValue().handle(response); verify(asyncResponse).handle(response); - verify(delegate).onResponse(span, response, exception); } @Test @@ -103,7 +97,5 @@ public void delegatesErrorOnInvocationFailure() throws Exception { } catch (Exception e) { Assertions.assertEquals(exception, e); } - - verify(delegate).onResponse(span, null, exception); } } diff --git a/huawei-cloud/dashboard/src/main/java/org/apache/servicecomb/huaweicloud/dashboard/monitor/DefaultMonitorDataPublisher.java b/huawei-cloud/dashboard/src/main/java/org/apache/servicecomb/huaweicloud/dashboard/monitor/DefaultMonitorDataPublisher.java index de350f78779..920f0b10396 100644 --- a/huawei-cloud/dashboard/src/main/java/org/apache/servicecomb/huaweicloud/dashboard/monitor/DefaultMonitorDataPublisher.java +++ b/huawei-cloud/dashboard/src/main/java/org/apache/servicecomb/huaweicloud/dashboard/monitor/DefaultMonitorDataPublisher.java @@ -45,6 +45,8 @@ import org.apache.servicecomb.huaweicloud.dashboard.monitor.model.MonitorDataProvider; import org.apache.servicecomb.huaweicloud.dashboard.monitor.model.MonitorDataPublisher; +import com.netflix.config.DynamicPropertyFactory; + public class DefaultMonitorDataPublisher implements MonitorDataPublisher { private static final String SSL_KEY = "mc.consumer"; @@ -76,7 +78,11 @@ private DashboardAddressManager createDashboardAddressManager() { throw new IllegalStateException("dashboard address is not configured."); } - return new DashboardAddressManager(addresses, EventManager.getEventBus()); + String region = DynamicPropertyFactory.getInstance(). + getStringProperty("servicecomb.datacenter.region", "").get(); + String availableZone = DynamicPropertyFactory.getInstance(). + getStringProperty("servicecomb.datacenter.availableZone", "").get(); + return new DashboardAddressManager(addresses, region, availableZone, EventManager.getEventBus()); } private HttpTransport createHttpTransport(DashboardAddressManager addressManager, RequestConfig requestConfig, @@ -111,8 +117,9 @@ private HttpTransport createHttpTransport(DashboardAddressManager addressManager private static RequestAuthHeaderProvider getRequestAuthHeaderProvider(List authHeaderProviders) { return signRequest -> { + String host = signRequest != null && signRequest.getEndpoint() != null ? signRequest.getEndpoint().getHost() : ""; Map headers = new HashMap<>(); - authHeaderProviders.forEach(provider -> headers.putAll(provider.authHeaders())); + authHeaderProviders.forEach(provider -> headers.putAll(provider.authHeaders(host))); return headers; }; } diff --git a/huawei-cloud/servicestage/src/main/java/org/apache/servicecomb/huaweicloud/servicestage/AKSKAuthHeaderProvider.java b/huawei-cloud/servicestage/src/main/java/org/apache/servicecomb/huaweicloud/servicestage/AKSKAuthHeaderProvider.java index f291000297c..f7a4ce4e18d 100644 --- a/huawei-cloud/servicestage/src/main/java/org/apache/servicecomb/huaweicloud/servicestage/AKSKAuthHeaderProvider.java +++ b/huawei-cloud/servicestage/src/main/java/org/apache/servicecomb/huaweicloud/servicestage/AKSKAuthHeaderProvider.java @@ -68,7 +68,7 @@ public class AKSKAuthHeaderProvider implements AuthHeaderProvider { private boolean enabled; - private final boolean loaded = false; + private boolean loaded = false; public AKSKAuthHeaderProvider() { this(ConfigUtil.createLocalConfig()); @@ -103,6 +103,7 @@ private synchronized void load() { headers.put(X_SERVICE_AK, getAccessKey()); headers.put(X_SERVICE_SHAAKSK, getSecretKey()); headers.put(X_SERVICE_PROJECT, getProject()); + loaded = true; } } diff --git a/inspector/src/test/resources/schema1.yaml b/inspector/src/test/resources/schema1.yaml index 6bf42c9c096..1c719a65eeb 100644 --- a/inspector/src/test/resources/schema1.yaml +++ b/inspector/src/test/resources/schema1.yaml @@ -19,7 +19,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.metrics.core.publish.MetricsRestPublisher" - x-java-interface: "cse.gen.perfTest.perf1.metricsEndpoint.MetricsRestPublisherIntf" basePath: "/metrics" schemes: - "http" diff --git a/inspector/src/test/resources/schema2.yaml b/inspector/src/test/resources/schema2.yaml index eee3faba7e5..3658f3436ff 100644 --- a/inspector/src/test/resources/schema2.yaml +++ b/inspector/src/test/resources/schema2.yaml @@ -19,7 +19,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.metrics.core.publish.HealthCheckerRestPublisher" - x-java-interface: "cse.gen.perfTest.perf1.healthEndpoint.HealthCheckerRestPublisherIntf" basePath: "/health" schemes: - "http" diff --git a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/invocation/AbstractInvocationMeters.java b/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/invocation/AbstractInvocationMeters.java index 9491120d3ea..d517de17018 100644 --- a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/invocation/AbstractInvocationMeters.java +++ b/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/invocation/AbstractInvocationMeters.java @@ -22,6 +22,7 @@ import org.apache.servicecomb.core.event.InvocationFinishEvent; import org.apache.servicecomb.core.event.InvocationStartEvent; import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx; +import org.apache.servicecomb.metrics.core.publish.PublishUtils; import org.apache.servicecomb.swagger.invocation.Response; import com.netflix.spectator.api.Id; @@ -55,7 +56,7 @@ protected AbstractInvocationMeter getOrCreateMeters(Invocation invocation, Respo .append(invocationName) .append(invocation.getRealTransportName()) .append(invocation.getMicroserviceQualifiedName()) - .append(response.getStatusCode()); + .append(PublishUtils.refactorStatus(invocation, response)); if (keyBuilder.length() > maxKeyLen) { maxKeyLen = keyBuilder.length(); } @@ -69,7 +70,7 @@ protected AbstractInvocationMeter getOrCreateMeters(Invocation invocation, Respo MeterInvocationConst.TAG_OPERATION, invocation.getMicroserviceQualifiedName(), MeterInvocationConst.TAG_STATUS, - String.valueOf(response.getStatusCode())); + PublishUtils.refactorStatus(invocation, response)); AbstractInvocationMeter meter = createMeter(id); SpectatorUtils.registerMeter(registry, meter); diff --git a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/CpuMeter.java b/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/CpuMeter.java index 587c11419ec..72416123a6b 100644 --- a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/CpuMeter.java +++ b/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/CpuMeter.java @@ -16,55 +16,33 @@ */ package org.apache.servicecomb.metrics.core.meter.os; +import java.lang.management.ManagementFactory; import java.util.List; -import org.apache.servicecomb.metrics.core.meter.os.cpu.OsCpuUsage; -import org.apache.servicecomb.metrics.core.meter.os.cpu.ProcessCpuUsage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import com.google.common.annotations.VisibleForTesting; import com.netflix.spectator.api.Id; import com.netflix.spectator.api.Measurement; +import com.sun.management.OperatingSystemMXBean; public class CpuMeter { - private static final Logger LOGGER = LoggerFactory.getLogger(CpuMeter.class); + private OperatingSystemMXBean osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class); - // read from /proc/stat - private final OsCpuUsage allCpuUsage; + private final Id allCpuUsage; - // read from /proc/self/stat /proc/uptime - private final ProcessCpuUsage processCpuUsage; + private final Id processCpuUsage; public CpuMeter(Id id) { - allCpuUsage = new OsCpuUsage(id.withTag(OsMeter.OS_TYPE, OsMeter.OS_TYPE_ALL_CPU)); - processCpuUsage = new ProcessCpuUsage(id.withTag(OsMeter.OS_TYPE, OsMeter.OS_TYPE_PROCESS_CPU)); - - //must refresh all first - update(); - allCpuUsage.setUsage(0); - processCpuUsage.setUsage(0); + allCpuUsage = id.withTag(OsMeter.OS_TYPE, OsMeter.OS_TYPE_ALL_CPU); + processCpuUsage = id.withTag(OsMeter.OS_TYPE, OsMeter.OS_TYPE_PROCESS_CPU); } public void calcMeasurements(List measurements, long msNow) { - update(); - measurements.add(new Measurement(allCpuUsage.getId(), msNow, allCpuUsage.getUsage())); - measurements.add(new Measurement(processCpuUsage.getId(), msNow, processCpuUsage.getUsage())); - } - - public void update() { - try { - allCpuUsage.update(); - processCpuUsage.update(); - } catch (Throwable e) { - LOGGER.error("Failed to update usage", e); - } - } - - public OsCpuUsage getAllCpuUsage() { - return allCpuUsage; + measurements.add(new Measurement(allCpuUsage, msNow, osBean.getSystemCpuLoad())); + measurements.add(new Measurement(processCpuUsage, msNow, osBean.getProcessCpuLoad())); } - public ProcessCpuUsage getProcessCpuUsage() { - return processCpuUsage; + @VisibleForTesting + public void setOsBean(OperatingSystemMXBean bean) { + this.osBean = bean; } } diff --git a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/cpu/CpuUtils.java b/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/cpu/CpuUtils.java deleted file mode 100644 index 75eb83d9d1f..00000000000 --- a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/cpu/CpuUtils.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 - * - * http://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 org.apache.servicecomb.metrics.core.meter.os.cpu; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.io.Files; - -public final class CpuUtils { - private static final Logger LOGGER = LoggerFactory.getLogger(CpuUtils.class); - - public static final File PROC_STAT = new File("/proc/stat"); - - public static final File UPTIME = new File("/proc/uptime"); - - public static final File SELF_PROCESS = new File("/proc/self/stat"); - - private CpuUtils() { - } - - public static String[] readAndSplitFirstLine(File file) throws IOException { - return Files.asCharSource(file, StandardCharsets.UTF_8).readFirstLine().trim().split("\\s+"); - } - - public static double summary(String[] stats, int start, int len) { - double total = 0; - for (int idx = start; idx < start + len; idx++) { - total += Double.parseDouble(stats[idx]); - } - return total; - } - - public static double readProcSelfBusy() throws IOException { - String[] stats = readAndSplitFirstLine(SELF_PROCESS); - return summary(stats, 13, 2); - } - - public static double readProcStatTotal() throws IOException { - String[] stats = readAndSplitFirstLine(PROC_STAT); - return summary(stats, 1, 8); - } - - public static double readUptimeTotal() throws IOException { - String[] uptime = readAndSplitFirstLine(UPTIME); - return Double.parseDouble(uptime[0]); - } - - private static boolean isBetween(long x, long lower, long upper) { - return lower <= x && x <= upper; - } - - /** - * unit of /proc/uptime is seconds - * unit of /proc/self/stat is jiffies - * hence, we should calculate userHZ to get process cpu rate - * - * @return userHZ - */ - public static int calcHertz() { - double up1, up2, seconds; - double jiffies; - - for (; ; ) { - try { - up1 = readUptimeTotal(); - jiffies = readProcStatTotal(); - up2 = readUptimeTotal(); - } catch (Throwable e) { - LOGGER.error("Failed to calc hertz, should never happened, try again.", e); - continue; - } - - if (0 == (long) ((up2 - up1) * 1000.0 / up1)) { - break; - } - } - - seconds = (up1 + up2) / 2; - long hz = Math.round(jiffies / seconds / Runtime.getRuntime().availableProcessors()); - /* S/390 (sometimes) */ - if (isBetween(hz, 9, 11)) { - return 10; - } - - /* user-mode Linux */ - if (isBetween(hz, 18, 22)) { - return 20; - } - - /* ia64 emulator */ - if (isBetween(hz, 30, 34)) { - return 32; - } - - if (isBetween(hz, 48, 52)) { - return 50; - } - - if (isBetween(hz, 58, 61)) { - return 60; - } - - /* StrongARM /Shark */ - if (isBetween(hz, 62, 65)) { - return 64; - } - - /* normal Linux */ - if (isBetween(hz, 95, 105)) { - return 100; - } - - /* MIPS, ARM */ - if (isBetween(hz, 124, 132)) { - return 128; - } - - /* normal << 1 */ - if (isBetween(hz, 195, 204)) { - return 200; - } - - if (isBetween(hz, 247, 252)) { - return 250; - } - - if (isBetween(hz, 253, 260)) { - return 256; - } - - /* normal << 2 */ - if (isBetween(hz, 393, 408)) { - return 400; - } - - /* SMP WinNT */ - if (isBetween(hz, 410, 600)) { - return 500; - } - - /* normal << 3 */ - if (isBetween(hz, 790, 808)) { - return 800; - } - - /* ARM */ - if (isBetween(hz, 990, 1010)) { - return 1000; - } - - /* Alpha, ia64 */ - if (isBetween(hz, 1015, 1035)) { - return 1024; - } - - /* Alpha */ - if (isBetween(hz, 1180, 1220)) { - return 1200; - } - - LOGGER.warn("Unknown HZ value! ({}) Assume {}.\n", hz, 100); - return 100; - } -} diff --git a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/cpu/OsCpuUsage.java b/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/cpu/OsCpuUsage.java deleted file mode 100644 index 364ca10b474..00000000000 --- a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/cpu/OsCpuUsage.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 - * - * http://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 org.apache.servicecomb.metrics.core.meter.os.cpu; - -import java.io.IOException; - -import com.netflix.spectator.api.Id; - -/* - * unit : 1 jiffies - * more details : - * http://man7.org/linux/man-pages/man5/proc.5.html - * CMD : /proc/stat - * cpu 2445171 599297 353967 24490633 11242 0 10780 2993 0 0 - * cpu user nice system idle iowait irq softirq stealstolen guest guest_nice - * 0 1 2 3 4 5 6 7 8 9 10 - * total = user + nice + system + idle + iowait + irq + softirq + stealstolen - * busy = total - idle - */ -public class OsCpuUsage extends AbstractCpuUsage { - - private final Period total = new Period(); - - private final Period idle = new Period(); - - public OsCpuUsage(Id id) { - super(id); - } - - public void update() throws IOException { - String[] stats = CpuUtils.readAndSplitFirstLine(CpuUtils.PROC_STAT); - long currentIdle = Long.parseLong(stats[4]); - double totalCpu = CpuUtils.summary(stats, 1, 8); - idle.update(currentIdle); - total.update(totalCpu); - updateUsage(total.period - idle.period, total.period, false); - } -} diff --git a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/cpu/ProcessCpuUsage.java b/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/cpu/ProcessCpuUsage.java deleted file mode 100644 index 00ec8cb5ec8..00000000000 --- a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/meter/os/cpu/ProcessCpuUsage.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 - * - * http://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 org.apache.servicecomb.metrics.core.meter.os.cpu; - -import java.io.IOException; - -import com.netflix.spectator.api.Id; - -/* - * unit : 1 jiffies - * more details : - * http://man7.org/linux/man-pages/man5/proc.5.html - * CMD : /proc/[pid]/stat - * 6754 (kubelet) S 1 995 995 0 -1 4202752 193281 592501546 0 12 1152076 907044 87991 113319 .. - * pid comm state ppid pgrp session tty_nr tpgid flags minflt cminflt majflt cmajflt utime stime cutime cstime - * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - * busy = utime + stime - * CMD: /proc/uptime - * total = uptime[0] * cpuNum * userHZ - * - */ -public class ProcessCpuUsage extends AbstractCpuUsage { - - private final Period busy = new Period(); - - private final Period total = new Period(); - - private final int userHZ = CpuUtils.calcHertz(); - - public ProcessCpuUsage(Id id) { - super(id); - } - - public void update() throws IOException { - double processBusy = CpuUtils.readProcSelfBusy(); - double uptime = CpuUtils.readUptimeTotal(); - busy.update(processBusy); - total.update(uptime * userHZ * cpuCount); - updateUsage(busy.period, total.period, true); - } -} diff --git a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/publish/PublishUtils.java b/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/publish/PublishUtils.java index 3196120d7b4..940d0f51dd3 100644 --- a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/publish/PublishUtils.java +++ b/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/publish/PublishUtils.java @@ -19,16 +19,24 @@ import java.util.HashMap; import java.util.Map; +import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.foundation.metrics.publish.spectator.MeasurementNode; +import org.apache.servicecomb.foundation.vertx.http.VertxServerRequestToHttpServletRequest; import org.apache.servicecomb.metrics.core.meter.invocation.MeterInvocationConst; import org.apache.servicecomb.metrics.core.publish.model.invocation.OperationPerf; import org.apache.servicecomb.metrics.core.publish.model.invocation.OperationPerfGroup; import org.apache.servicecomb.metrics.core.publish.model.invocation.OperationPerfGroups; import org.apache.servicecomb.metrics.core.publish.model.invocation.PerfInfo; +import org.apache.servicecomb.swagger.invocation.Response; import com.netflix.spectator.api.Statistic; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.ext.web.RoutingContext; + public final class PublishUtils { + public static final String EMPTY_RESULT = "-"; + private PublishUtils() { } @@ -73,4 +81,18 @@ public static void addOperationPerfGroups(OperationPerfGroups operationPerfGroup OperationPerf operationPerf = createOperationPerf(operation, statusNode); group.addOperationPerf(operationPerf); } + + public static String refactorStatus(Invocation invocation, Response response) { + if (invocation.getRequestEx() instanceof VertxServerRequestToHttpServletRequest) { + RoutingContext context = ((VertxServerRequestToHttpServletRequest) invocation.getRequestEx()).getContext(); + if (context == null) { + return String.valueOf(response.getStatusCode()); + } + HttpServerResponse contextResponse = context.response(); + if (response.getStatusCode() == 200 && contextResponse.closed() && !contextResponse.ended()) { + return EMPTY_RESULT; + } + } + return String.valueOf(response.getStatusCode()); + } } diff --git a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/publish/SlowInvocationLogger.java b/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/publish/SlowInvocationLogger.java index a72f56250a2..3faf950d3f2 100644 --- a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/publish/SlowInvocationLogger.java +++ b/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/publish/SlowInvocationLogger.java @@ -102,7 +102,7 @@ private void logSlowProducer(Invocation invocation, Response response, Operation restOperationMeta.getHttpMethod(), restOperationMeta.getAbsolutePath(), collectClientAddress(invocation), - response.getStatusCode(), + PublishUtils.refactorStatus(invocation, response), formatTime(stageTrace.calcTotalTime()), formatTime(stageTrace.calcInvocationPrepareTime()), formatTime(stageTrace.calcThreadPoolQueueTime()), diff --git a/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/TestOsMeterInitializer.java b/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/TestOsMeterInitializer.java index 83bc88bc271..d9ca5fa2e4c 100644 --- a/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/TestOsMeterInitializer.java +++ b/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/TestOsMeterInitializer.java @@ -17,8 +17,6 @@ package org.apache.servicecomb.metrics.core; import java.io.File; -import java.lang.management.ManagementFactory; -import java.lang.management.RuntimeMXBean; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @@ -29,21 +27,21 @@ import org.apache.servicecomb.metrics.core.meter.os.CpuMeter; import org.apache.servicecomb.metrics.core.meter.os.NetMeter; import org.apache.servicecomb.metrics.core.meter.os.OsMeter; -import org.apache.servicecomb.metrics.core.meter.os.cpu.CpuUtils; import org.apache.servicecomb.metrics.core.meter.os.net.InterfaceUsage; import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.mockito.Mockito; import com.google.common.eventbus.EventBus; -import com.google.common.io.Files; import com.netflix.spectator.api.DefaultRegistry; import com.netflix.spectator.api.ManualClock; +import com.netflix.spectator.api.Measurement; import com.netflix.spectator.api.Registry; +import com.sun.management.OperatingSystemMXBean; -import mockit.Expectations; import mockit.Mock; import mockit.MockUp; import mockit.Mocked; -import org.junit.jupiter.api.Assertions; public class TestOsMeterInitializer { GlobalRegistry globalRegistry = new GlobalRegistry(new ManualClock()); @@ -54,49 +52,19 @@ public class TestOsMeterInitializer { EventBus eventBus; @Test - public void init(@Mocked Runtime runtime, @Mocked RuntimeMXBean mxBean) { + public void init() { List list = new ArrayList<>(); list.add("13 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1"); list.add("useless"); list.add("eth0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"); - new MockUp() { - //Files.readFirstLine - @Mock - public String readFirstLine(File file, Charset encoding) { - return list.get(0); - } - }; + new MockUp() { @Mock public List readLines(File file, Charset encoding) { return list; } }; - new MockUp() { - @Mock - public int calcHertz() { - return 100; - } - }; - new MockUp() { - @Mock - RuntimeMXBean getRuntimeMXBean() { - return mxBean; - } - }; - new MockUp() { - @Mock - public Runtime getRuntime() { - return runtime; - } - }; - new Expectations() { - { - runtime.availableProcessors(); - result = 2; - } - }; globalRegistry.add(registry); OsMetersInitializer osMetersInitializer = new OsMetersInitializer(); osMetersInitializer.setOsLinux(true); @@ -106,10 +74,15 @@ public Runtime getRuntime() { Assertions.assertNotNull(osMeter.getCpuMeter()); Assertions.assertNotNull(osMeter.getNetMeter()); CpuMeter cpuMeter = osMeter.getCpuMeter(); + OperatingSystemMXBean systemMXBean = Mockito.mock(OperatingSystemMXBean.class); + Mockito.when(systemMXBean.getSystemCpuLoad()).thenReturn(0.2); + Mockito.when(systemMXBean.getProcessCpuLoad()).thenReturn(0.1); + cpuMeter.setOsBean(systemMXBean); NetMeter netMeter = osMeter.getNetMeter(); - Assertions.assertEquals(0.0, cpuMeter.getProcessCpuUsage().getUsage(), 0.0); - - Assertions.assertEquals(0.0, cpuMeter.getAllCpuUsage().getUsage(), 0.0); + List measurements = new ArrayList<>(); + cpuMeter.calcMeasurements(measurements, 0); + Assertions.assertEquals(0.2, measurements.get(0).value(), 0.0); + Assertions.assertEquals(0.1, measurements.get(1).value(), 0.0); Map interfaceInfoMap = netMeter.getInterfaceUsageMap(); Assertions.assertEquals(1, interfaceInfoMap.size()); diff --git a/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/meter/os/TestCpuMeter.java b/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/meter/os/TestCpuMeter.java index 8295bdceed9..1d73f20cce3 100644 --- a/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/meter/os/TestCpuMeter.java +++ b/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/meter/os/TestCpuMeter.java @@ -16,177 +16,75 @@ */ package org.apache.servicecomb.metrics.core.meter.os; -import java.io.File; import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.RuntimeMXBean; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; -import org.apache.servicecomb.metrics.core.meter.os.cpu.CpuUtils; import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.mockito.Mockito; -import com.google.common.io.CharSource; -import com.google.common.io.Files; import com.netflix.spectator.api.Id; import com.netflix.spectator.api.Measurement; +import com.sun.management.OperatingSystemMXBean; -import mockit.Expectations; -import mockit.Mock; -import mockit.MockUp; import mockit.Mocked; -import org.junit.jupiter.api.Assertions; public class TestCpuMeter { @Test - public void testRefreshCpuSuccess(@Mocked Id id, @Mocked Runtime runtime, @Mocked RuntimeMXBean mxBean, - @Mocked CharSource charSource) throws IOException { - new MockUp() { - @Mock - public CharSource asCharSource(File file, Charset encoding) { - return charSource; - } - }; - new MockUp() { - @Mock - RuntimeMXBean getRuntimeMXBean() { - return mxBean; - } - }; - new MockUp() { - @Mock - public int calcHertz() { - return 4; - } - }; - new MockUp() { - @Mock - public Runtime getRuntime() { - return runtime; - } - }; - new Expectations() { - { - runtime.availableProcessors(); - result = 2; - charSource.readFirstLine(); - result = "1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1"; - } - }; + public void testRefreshCpuSuccess(@Mocked Id id) throws IOException { + CpuMeter cpuMeter = new CpuMeter(id); - Assertions.assertEquals(0.0, cpuMeter.getAllCpuUsage().getUsage(), 0.0); - Assertions.assertEquals(0.0, cpuMeter.getProcessCpuUsage().getUsage(), 0.0); - - new Expectations() { - { - charSource.readFirstLine(); - result = "2 2 2 2 2 2 2 2 2 0 0 2 2 2 2 2 2 2 2 2 2"; - } - }; - cpuMeter.update(); - - Assertions.assertEquals(0.875, cpuMeter.getAllCpuUsage().getUsage(), 0.0); - Assertions.assertEquals(0.5, cpuMeter.getProcessCpuUsage().getUsage(), 0.0); + OperatingSystemMXBean systemMXBean = Mockito.mock(OperatingSystemMXBean.class); + Mockito.when(systemMXBean.getSystemCpuLoad()).thenReturn(0.2); + Mockito.when(systemMXBean.getProcessCpuLoad()).thenReturn(0.1); + cpuMeter.setOsBean(systemMXBean); + List measurements = new ArrayList<>(); + cpuMeter.calcMeasurements(measurements, 0); + + Assertions.assertEquals(0.2, measurements.get(0).value(), 0.0); + Assertions.assertEquals(0.1, measurements.get(1).value(), 0.0); + + Mockito.when(systemMXBean.getSystemCpuLoad()).thenReturn(0.875); + Mockito.when(systemMXBean.getProcessCpuLoad()).thenReturn(0.5); + measurements = new ArrayList<>(); + cpuMeter.calcMeasurements(measurements, 0); + + Assertions.assertEquals(0.875, measurements.get(0).value(), 0.0); + Assertions.assertEquals(0.5, measurements.get(1).value(), 0.0); } @Test - public void testRefreshError(@Mocked Id id, @Mocked Runtime runtime, @Mocked RuntimeMXBean mxBean, - @Mocked CharSource charSource) throws IOException { - - new MockUp() { - @Mock - public CharSource asCharSource(File file, Charset encoding) { - return charSource; - } - }; - new MockUp() { - @Mock - public int calcHertz() { - return 4; - } - }; - new MockUp() { - @Mock - RuntimeMXBean getRuntimeMXBean() { - return mxBean; - } - }; - new MockUp() { - @Mock - public Runtime getRuntime() { - return runtime; - } - }; - new Expectations() { - { - runtime.availableProcessors(); - result = 2; - charSource.readFirstLine(); - result = "1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1"; - } - }; + public void testRefreshError(@Mocked Id id) throws IOException { + CpuMeter cpuMeter = new CpuMeter(id); - Assertions.assertEquals(0.0, cpuMeter.getAllCpuUsage().getUsage(), 0.0); - Assertions.assertEquals(0.0, cpuMeter.getProcessCpuUsage().getUsage(), 0.0); - new Expectations() { - { - charSource.readFirstLine(); - result = "1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1"; - } - }; - cpuMeter.update(); - - Assertions.assertEquals(0.0, cpuMeter.getAllCpuUsage().getUsage(), 0.0); - Assertions.assertEquals(0.0, cpuMeter.getProcessCpuUsage().getUsage(), 0.0); + OperatingSystemMXBean systemMXBean = Mockito.mock(OperatingSystemMXBean.class); + Mockito.when(systemMXBean.getSystemCpuLoad()).thenReturn(0.2); + Mockito.when(systemMXBean.getProcessCpuLoad()).thenReturn(0.1); + cpuMeter.setOsBean(systemMXBean); + List measurements = new ArrayList<>(); + cpuMeter.calcMeasurements(measurements, 0); + + Assertions.assertEquals(0.2, measurements.get(0).value(), 0.0); + Assertions.assertEquals(0.1, measurements.get(1).value(), 0.0); + + cpuMeter.calcMeasurements(measurements, 0); + + Assertions.assertEquals(0.2, measurements.get(0).value(), 0.0); + Assertions.assertEquals(0.1, measurements.get(1).value(), 0.0); } @Test - public void testCalcMeasurements(@Mocked Id id, @Mocked Runtime runtime, @Mocked RuntimeMXBean mxBean, - @Mocked CharSource charSource) throws IOException { + public void testCalcMeasurements(@Mocked Id id) throws IOException { List measurements = new ArrayList<>(); - new MockUp() { - @Mock - public CharSource asCharSource(File file, Charset encoding) { - return charSource; - } - }; - new MockUp() { - @Mock - public int calcHertz() { - return 4; - } - }; - new MockUp() { - @Mock - RuntimeMXBean getRuntimeMXBean() { - return mxBean; - } - }; - new MockUp() { - @Mock - public Runtime getRuntime() { - return runtime; - } - }; - new Expectations() { - { - runtime.availableProcessors(); - result = 2; - charSource.readFirstLine(); - result = "1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1"; - } - }; CpuMeter cpuMeter = new CpuMeter(id); + OperatingSystemMXBean systemMXBean = Mockito.mock(OperatingSystemMXBean.class); + Mockito.when(systemMXBean.getSystemCpuLoad()).thenReturn(0.875); + Mockito.when(systemMXBean.getProcessCpuLoad()).thenReturn(0.5); + cpuMeter.setOsBean(systemMXBean); - new Expectations() { - { - charSource.readFirstLine(); - result = "2 2 2 2 2 2 2 2 2 0 0 2 2 2 2 2 2 2 2 2 2"; - } - }; cpuMeter.calcMeasurements(measurements, 0); Assertions.assertEquals(2, measurements.size()); Measurement measurement = measurements.get(0); diff --git a/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/meter/os/TestOsMeter.java b/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/meter/os/TestOsMeter.java index 375627d9bef..4aaeedc5951 100644 --- a/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/meter/os/TestOsMeter.java +++ b/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/meter/os/TestOsMeter.java @@ -19,36 +19,30 @@ import java.io.File; import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.RuntimeMXBean; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import org.apache.commons.io.FileUtils; -import org.apache.servicecomb.metrics.core.meter.os.cpu.CpuUtils; import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.mockito.Mockito; import com.google.common.collect.Lists; -import com.google.common.io.CharSource; -import com.google.common.io.Files; import com.netflix.spectator.api.DefaultRegistry; import com.netflix.spectator.api.ManualClock; import com.netflix.spectator.api.Measurement; import com.netflix.spectator.api.Registry; +import com.sun.management.OperatingSystemMXBean; -import mockit.Expectations; import mockit.Mock; import mockit.MockUp; -import mockit.Mocked; -import org.junit.jupiter.api.Assertions; public class TestOsMeter { Registry registry = new DefaultRegistry(new ManualClock()); @Test - public void testCalcMeasurement(@Mocked Runtime runtime, @Mocked RuntimeMXBean mxBean, - @Mocked CharSource charSource) throws IOException { + public void testCalcMeasurement() throws IOException { List list = new ArrayList<>(); list.add("useless"); list.add("useless"); @@ -59,49 +53,18 @@ public List readLines(File file, Charset encoding) { return list; } }; - new MockUp() { - @Mock - public int calcHertz() { - return 4; - } - }; - new MockUp() { - @Mock - public CharSource asCharSource(File file, Charset encoding) { - return charSource; - } - }; - new MockUp() { - @Mock - RuntimeMXBean getRuntimeMXBean() { - return mxBean; - } - }; - new MockUp() { - @Mock - public Runtime getRuntime() { - return runtime; - } - }; - new Expectations() { - { - runtime.availableProcessors(); - result = 2; - charSource.readFirstLine(); - result = "1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1"; - } - }; + OsMeter osMeter = new OsMeter(registry); list.clear(); list.add("useless"); list.add("useless"); list.add("eth0: 1 1 0 0 0 0 0 1 1 1 1 0 0 0 0 0"); - new Expectations() { - { - charSource.readFirstLine(); - result = "2 2 2 2 2 2 2 2 2 0 0 2 2 2 2 2 2 2 2 2 2"; - } - }; + + OperatingSystemMXBean systemMXBean = Mockito.mock(OperatingSystemMXBean.class); + Mockito.when(systemMXBean.getSystemCpuLoad()).thenReturn(0.875); + Mockito.when(systemMXBean.getProcessCpuLoad()).thenReturn(0.5); + osMeter.getCpuMeter().setOsBean(systemMXBean); + osMeter.calcMeasurements(1, 1); ArrayList measurements = Lists.newArrayList(osMeter.measure()); Assertions.assertEquals(6, measurements.size()); diff --git a/pom.xml b/pom.xml index e6f3a22f6db..941b88ff988 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,6 @@ 1.8 -Dfile.encoding=UTF-8 true - -Werror @@ -407,7 +406,6 @@ true true - ${werror.properties} -Xlint:all -Xlint:-classfile @@ -656,7 +654,6 @@ [11,17) - -Djdk.attach.allowAttachSelf --add-opens java.base/java.lang=ALL-UNNAMED @@ -670,7 +667,6 @@ [17,) - -Djdk.attach.allowAttachSelf --add-opens java.base/java.io=ALL-UNNAMED diff --git a/providers/provider-pojo/src/main/java/org/apache/servicecomb/provider/pojo/RpcReferenceBeanDefinitionRegistry.java b/providers/provider-pojo/src/main/java/org/apache/servicecomb/provider/pojo/RpcReferenceBeanDefinitionRegistry.java new file mode 100644 index 00000000000..2e7c7e16406 --- /dev/null +++ b/providers/provider-pojo/src/main/java/org/apache/servicecomb/provider/pojo/RpcReferenceBeanDefinitionRegistry.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.provider.pojo; + +import org.apache.servicecomb.provider.pojo.reference.RpcReferenceProcessor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.stereotype.Component; + +/** + * In order to support FactoryBean eager inject @RpcReference, add RpcReferenceProcessor + * before FactoryBean is initialized. + */ +@Component +public class RpcReferenceBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor { + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + beanFactory.addBeanPostProcessor(new RpcReferenceProcessor(beanFactory)); + } +} diff --git a/providers/provider-pojo/src/main/java/org/apache/servicecomb/provider/pojo/definition/PojoConsumerMeta.java b/providers/provider-pojo/src/main/java/org/apache/servicecomb/provider/pojo/definition/PojoConsumerMeta.java index c07a075391e..db061fef7e4 100644 --- a/providers/provider-pojo/src/main/java/org/apache/servicecomb/provider/pojo/definition/PojoConsumerMeta.java +++ b/providers/provider-pojo/src/main/java/org/apache/servicecomb/provider/pojo/definition/PojoConsumerMeta.java @@ -28,8 +28,6 @@ import org.apache.servicecomb.core.provider.consumer.MicroserviceReferenceConfig; import org.apache.servicecomb.swagger.engine.SwaggerConsumer; import org.apache.servicecomb.swagger.engine.SwaggerConsumerOperation; -import org.apache.servicecomb.swagger.generator.OperationGenerator; -import org.apache.servicecomb.swagger.generator.SwaggerGenerator; public class PojoConsumerMeta { private final MicroserviceReferenceConfig microserviceReferenceConfig; @@ -43,16 +41,11 @@ public PojoConsumerMeta(MicroserviceReferenceConfig microserviceReferenceConfig, this.microserviceReferenceConfig = microserviceReferenceConfig; this.schemaMeta = schemaMeta; - SwaggerGenerator intfSwaggerGenerator = SwaggerGenerator.create(swaggerConsumer.getConsumerIntf()); - intfSwaggerGenerator.scanClassAnnotation(); for (SwaggerConsumerOperation swaggerConsumerOperation : swaggerConsumer.getOperations().values()) { String operationId = swaggerConsumerOperation.getSwaggerOperation().getOperationId(); // SwaggerConsumer has make sure can find operationMeta OperationMeta operationMeta = schemaMeta.ensureFindOperation(operationId); - OperationGenerator intfOperationGenerator = intfSwaggerGenerator - .createOperationGenerator(swaggerConsumerOperation.getConsumerMethod()); - intfOperationGenerator.generateResponse(); PojoConsumerOperationMeta pojoConsumerOperationMeta = new PojoConsumerOperationMeta(this, operationMeta, swaggerConsumerOperation); diff --git a/providers/provider-pojo/src/main/java/org/apache/servicecomb/provider/pojo/reference/RpcReferenceProcessor.java b/providers/provider-pojo/src/main/java/org/apache/servicecomb/provider/pojo/reference/RpcReferenceProcessor.java index 0663ac00185..ba5ded3f12c 100644 --- a/providers/provider-pojo/src/main/java/org/apache/servicecomb/provider/pojo/reference/RpcReferenceProcessor.java +++ b/providers/provider-pojo/src/main/java/org/apache/servicecomb/provider/pojo/reference/RpcReferenceProcessor.java @@ -23,14 +23,15 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.context.EmbeddedValueResolverAware; -import org.springframework.stereotype.Component; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringValueResolver; -@Component -public class RpcReferenceProcessor implements BeanPostProcessor, EmbeddedValueResolverAware { - private StringValueResolver resolver; +public class RpcReferenceProcessor implements BeanPostProcessor { + private final ConfigurableBeanFactory beanFactory; + + public RpcReferenceProcessor(ConfigurableBeanFactory beanFactory) { + this.beanFactory = beanFactory; + } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { @@ -62,11 +63,6 @@ protected void processConsumerField(Object bean, Field field) { handleReferenceField(bean, field, reference); } - @Override - public void setEmbeddedValueResolver(StringValueResolver resolver) { - this.resolver = resolver; - } - private void handleReferenceMethod(Object bean, String beanName, Method method, RpcReference reference) throws BeansException { try { @@ -86,7 +82,7 @@ private void handleReferenceField(Object obj, Field field, private PojoReferenceMeta createPojoReferenceMeta(RpcReference reference, Class consumerInterface) { String microserviceName = reference.microserviceName(); - microserviceName = resolver.resolveStringValue(microserviceName); + microserviceName = beanFactory.resolveEmbeddedValue(microserviceName); PojoReferenceMeta pojoReference = new PojoReferenceMeta(); pojoReference.setMicroserviceName(microserviceName); diff --git a/providers/provider-pojo/src/test/java/org/apache/servicecomb/provider/pojo/reference/TestRpcReferenceProcessor.java b/providers/provider-pojo/src/test/java/org/apache/servicecomb/provider/pojo/reference/TestRpcReferenceProcessor.java index d32a9b9c5d6..72fe8ec30ab 100644 --- a/providers/provider-pojo/src/test/java/org/apache/servicecomb/provider/pojo/reference/TestRpcReferenceProcessor.java +++ b/providers/provider-pojo/src/test/java/org/apache/servicecomb/provider/pojo/reference/TestRpcReferenceProcessor.java @@ -28,9 +28,13 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; public class TestRpcReferenceProcessor { - RpcReferenceProcessor consumers = new RpcReferenceProcessor(); + static ConfigurableBeanFactory beanFactory = Mockito.mock(ConfigurableBeanFactory.class); + + RpcReferenceProcessor consumers = new RpcReferenceProcessor(beanFactory); @BeforeEach public void setUp() { @@ -56,7 +60,7 @@ public void testReference() { Assertions.assertNull(bean.person); - consumers.setEmbeddedValueResolver((strVal) -> strVal); + Mockito.when(beanFactory.resolveEmbeddedValue("test")).thenReturn("test"); Assertions.assertSame(bean, consumers.postProcessBeforeInitialization(bean, "id")); Assertions.assertNotNull(bean.person); diff --git a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java index 97d0e9e8cb7..e5b370729bf 100644 --- a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java +++ b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java @@ -40,6 +40,7 @@ import org.apache.servicecomb.core.provider.consumer.InvokerUtils; import org.apache.servicecomb.core.provider.consumer.MicroserviceReferenceConfig; import org.apache.servicecomb.core.provider.consumer.ReferenceConfig; +import org.apache.servicecomb.registry.definition.DefinitionConst; import org.apache.servicecomb.swagger.invocation.Response; import org.apache.servicecomb.swagger.invocation.context.InvocationContext; import org.apache.servicecomb.swagger.invocation.exception.ExceptionFactory; @@ -177,7 +178,7 @@ public ClientHttpResponse execute() { } protected RequestMeta createRequestMeta(String httpMethod, URI uri) { - String microserviceName = uri.getAuthority(); + String microserviceName = parseMicroserviceName(uri); MicroserviceReferenceConfig microserviceReferenceConfig = SCBEngine.getInstance() .createMicroserviceReferenceConfig(microserviceName); @@ -200,6 +201,12 @@ protected RequestMeta createRequestMeta(String httpMethod, URI uri) { return new RequestMeta(referenceConfig, swaggerRestOperation, pathParams); } + private String parseMicroserviceName(URI uri) { + String microserviceName = uri.getAuthority(); + return microserviceName.replace(CseUriTemplateHandler.APP_SERVICE_SEPARATOR_INTERNAL, + DefinitionConst.APP_SERVICE_SEPARATOR); + } + protected String findUriPath(URI uri) { return uri.getRawPath(); } diff --git a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseRequestCallback.java b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseRequestCallback.java index e452b0e4bc2..fd1e33cb967 100644 --- a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseRequestCallback.java +++ b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseRequestCallback.java @@ -26,13 +26,13 @@ public class CseRequestCallback implements RequestCallback { private final Object requestBody; - private final RequestCallback orgCallback; + private final RequestCallback targetCallback; private final Type responseType; - public CseRequestCallback(Object requestBody, RequestCallback orgCallback, Type responseType) { + public CseRequestCallback(Object requestBody, RequestCallback targetCallback, Type responseType) { this.requestBody = requestBody; - this.orgCallback = orgCallback; + this.targetCallback = targetCallback; this.responseType = responseType; } @@ -41,7 +41,7 @@ public CseRequestCallback(Object requestBody, RequestCallback orgCallback, Type */ @Override public void doWithRequest(ClientHttpRequest request) throws IOException { - orgCallback.doWithRequest(request); + targetCallback.doWithRequest(request); CseClientHttpRequest req = (CseClientHttpRequest) request; req.setResponseType(overrideResponseType()); diff --git a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseRestTemplate.java b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseRestTemplate.java index c9b98d3aa53..b4f3923b004 100644 --- a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseRestTemplate.java +++ b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseRestTemplate.java @@ -170,6 +170,13 @@ public RequestCallback httpEntityCallback(Object requestBody, Type responseT return cseCallback; } + @Override + public RequestCallback acceptHeaderRequestCallback(Class responseType) { + RequestCallback callback = super.acceptHeaderRequestCallback(responseType); + CseRequestCallback cseCallback = new CseRequestCallback(null, callback, responseType); + return cseCallback; + } + @Override public boolean isAcceptable(String uri) { return uri.startsWith(RestConst.URI_PREFIX) || uri.startsWith(RestConst.URI_PREFIX_NEW); diff --git a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseUriTemplateHandler.java b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseUriTemplateHandler.java index f41d6490a51..08f2b9da880 100644 --- a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseUriTemplateHandler.java +++ b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseUriTemplateHandler.java @@ -17,158 +17,43 @@ package org.apache.servicecomb.provider.springmvc.reference; import java.net.URI; -import java.net.URISyntaxException; -import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.apache.servicecomb.common.rest.RestConst; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; +import org.apache.servicecomb.registry.definition.DefinitionConst; +import org.springframework.web.util.DefaultUriBuilderFactory; -/** - * 默认不支持下面第1个场景,需要做出修正 - * cse://app:ms/path to cse://app/ms/path - * cse://ms/path to cse://ms/path - */ -@SuppressWarnings("deprecation") -// TODO : upgrade to spring 5 will having warning's , we'll fix it later -public class CseUriTemplateHandler extends org.springframework.web.util.DefaultUriTemplateHandler { - private static final String SCHEME_PATTERN = "([^:/?#]+):"; - - private static final String USERINFO_PATTERN = "([^@\\[/?#]*)"; - - private static final String HOST_IPV4_PATTERN = "[^\\[/?#:]*"; - - private static final String HOST_IPV6_PATTERN = "\\[[\\p{XDigit}:.]*[%\\p{Alnum}]*]"; - - private static final String HOST_PATTERN = "(" + HOST_IPV6_PATTERN + "|" + HOST_IPV4_PATTERN + ")"; - - private static final String PORT_PATTERN = "(\\d*(?:\\{[^/]+?})?)"; - - private static final String PATH_PATTERN = "([^?#]*)"; - - private static final String QUERY_PATTERN = "([^#]*)"; - - private static final String LAST_PATTERN = "(.*)"; - - // Regex patterns that matches URIs. See RFC 3986, appendix B - private static final Pattern URI_PATTERN = Pattern.compile( - "^(" + SCHEME_PATTERN + ")?" + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + - ")?" + ")?" + PATH_PATTERN + "(\\?" + QUERY_PATTERN + ")?" + "(#" + LAST_PATTERN + ")?"); +public class CseUriTemplateHandler extends DefaultUriBuilderFactory { + // Must be one of URI unreserved `ALPHA / DIGIT / "-" / "." / "_" / "~"` + public static final String APP_SERVICE_SEPARATOR_INTERNAL = "~"; public CseUriTemplateHandler() { - setStrictEncoding(true); } @Override - protected URI expandInternal(String uriTemplate, Map uriVariables) { - UriComponentsBuilder uriComponentsBuilder = initUriComponentsBuilder(uriTemplate); - UriComponents uriComponents = expandAndEncode(uriComponentsBuilder, uriVariables); - return createUri(uriTemplate, uriComponents); + public URI expand(String uriTemplate, Map uriVars) { + return super.expand(parseUrl(uriTemplate), uriVars); } @Override - protected URI expandInternal(String uriTemplate, Object... uriVariables) { - UriComponentsBuilder uriComponentsBuilder = initUriComponentsBuilder(uriTemplate); - UriComponents uriComponents = expandAndEncode(uriComponentsBuilder, uriVariables); - return createUri(uriTemplate, uriComponents); + public URI expand(String uriTemplate, Object... uriVars) { + return super.expand(parseUrl(uriTemplate), uriVars); } - @Override - protected UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) { - UriComponentsBuilder builder = fromUriString(uriTemplate); - if (shouldParsePath() && !isStrictEncoding()) { - List pathSegments = builder.build().getPathSegments(); - builder.replacePath(null); - for (String pathSegment : pathSegments) { - builder.pathSegment(pathSegment); - } + private static String parseUrl(String uriTemplate) { + int indexSchema = -1; + if (uriTemplate.startsWith(RestConst.URI_PREFIX)) { + indexSchema = RestConst.URI_PREFIX.length(); } - return builder; - } - - private static UriComponentsBuilder fromUriString(String uri) { - Assert.notNull(uri, "URI must not be null"); - Matcher matcher = URI_PATTERN.matcher(uri); - if (matcher.matches()) { - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - String scheme = matcher.group(2); - String userInfo = matcher.group(5); - String host = matcher.group(6); - String port = matcher.group(8); - String path = matcher.group(9); - String query = matcher.group(11); - String fragment = matcher.group(13); - boolean opaque = false; - if (StringUtils.hasLength(scheme)) { - String rest = uri.substring(scheme.length()); - if (!rest.startsWith(":/")) { - opaque = true; - } - } - builder.scheme(scheme); - if (opaque) { - String ssp = uri.substring(scheme.length()).substring(1); - if (StringUtils.hasLength(fragment)) { - ssp = ssp.substring(0, ssp.length() - (fragment.length() + 1)); - } - builder.schemeSpecificPart(ssp); - } else { - builder.userInfo(userInfo); - builder.host(host); - if (StringUtils.hasLength(port)) { - builder.port(port); - } - builder.path(path); - builder.query(query); - } - if (StringUtils.hasText(fragment)) { - builder.fragment(fragment); - } - return builder; - } else { - throw new IllegalArgumentException("[" + uri + "] is not a valid URI"); + if (uriTemplate.startsWith(RestConst.URI_PREFIX_NEW)) { + indexSchema = RestConst.URI_PREFIX_NEW.length(); } - } - - private URI createUri(String uriTemplate, UriComponents uriComponents) { - String strUri = uriComponents.toUriString(); - - Matcher matcher = URI_PATTERN.matcher(uriTemplate); - matcher.matches(); // should always be true - String scheme = matcher.group(2); - String host = matcher.group(6); - - if (isCrossApp(uriTemplate, scheme, host)) { - int idx; - if (RestConst.SCHEME.equals(scheme)) { - idx = strUri.indexOf('/', RestConst.URI_PREFIX.length()); - } else { - idx = strUri.indexOf('/', RestConst.URI_PREFIX_NEW.length()); - } - strUri = strUri.substring(0, idx) + ":" + strUri.substring(idx + 1); + if (indexSchema != -1) { + int indexPath = uriTemplate.indexOf("/", indexSchema); + String host = uriTemplate.substring(indexSchema, indexPath); + host = host.replace(DefinitionConst.APP_SERVICE_SEPARATOR, APP_SERVICE_SEPARATOR_INTERNAL); + return uriTemplate.substring(0, indexSchema) + host + uriTemplate.substring(indexPath); } - - try { - // Avoid further encoding (in the case of strictEncoding=true) - return new URI(strUri); - } catch (URISyntaxException ex) { - throw new IllegalStateException("Could not create URI object: " + ex.getMessage(), ex); - } - } - - private boolean isCrossApp(String uriTemplate, String scheme, String host) { - int pos; - if (RestConst.SCHEME.equals(scheme)) { - pos = RestConst.URI_PREFIX.length() + host.length(); - } else { - pos = RestConst.URI_PREFIX_NEW.length() + host.length(); - } - - return uriTemplate.charAt(pos) == ':'; + return uriTemplate; } } diff --git a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/RestTemplateCopyHeaderFilter.java b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/RestTemplateCopyHeaderFilter.java index e8b674d2d14..d3bc0e4b71b 100644 --- a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/RestTemplateCopyHeaderFilter.java +++ b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/RestTemplateCopyHeaderFilter.java @@ -21,6 +21,7 @@ import org.apache.servicecomb.common.rest.RestConst; import org.apache.servicecomb.common.rest.filter.HttpClientFilter; +import org.apache.servicecomb.core.Const; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx; import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx; @@ -37,6 +38,11 @@ public int getOrder() { return -10000; } + @Override + public boolean enabledForTransport(String transport) { + return HttpClientFilter.super.enabledForTransport(transport) || Const.WEBSOCKET.equals(transport); + } + @Override public CompletableFuture beforeSendRequestAsync(Invocation invocation, HttpServletRequestEx requestEx) { HttpHeaders httpHeaders = (HttpHeaders) invocation.getHandlerContext().get(RestConst.CONSUMER_HEADER); diff --git a/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/TestCseClientHttpRequest.java b/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/TestCseClientHttpRequest.java index 779711805e4..50dc59bce16 100644 --- a/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/TestCseClientHttpRequest.java +++ b/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/TestCseClientHttpRequest.java @@ -25,6 +25,7 @@ import org.apache.servicecomb.foundation.common.Holder; import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; import org.apache.servicecomb.registry.DiscoveryManager; +import org.apache.servicecomb.registry.RegistrationManager; import org.apache.servicecomb.swagger.invocation.Response; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; @@ -52,6 +53,7 @@ public static void classSetup() { public static void classTeardown() { scbEngine.destroy(); DiscoveryManager.renewInstance(); + RegistrationManager.renewInstance(); ArchaiusUtils.resetConfig(); } diff --git a/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/TestCseUriTemplateHandler.java b/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/TestCseUriTemplateHandler.java index a692262ef9e..9407ded239c 100644 --- a/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/TestCseUriTemplateHandler.java +++ b/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/TestCseUriTemplateHandler.java @@ -28,16 +28,33 @@ public class TestCseUriTemplateHandler { public void testCrossApp() { CseUriTemplateHandler handler = new CseUriTemplateHandler(); URI uri = handler.expand("cse://{ap}{p}:ms/{path}?q={query}", "ap", "p", "path", "query"); - Assertions.assertEquals("cse://app:ms/path?q=query", uri.toString()); + Assertions.assertEquals("cse://app~ms/path?q=query", uri.toString()); Map vars = new HashMap<>(); vars.put("app", "app"); vars.put("path", "path"); vars.put("q", "query"); uri = handler.expand("cse://{app}:ms/{path}?q={q}", vars); - Assertions.assertEquals("cse://app:ms/path?q=query", uri.toString()); + Assertions.assertEquals("cse://app~ms/path?q=query", uri.toString()); uri = handler.expand("cse://ms/{path}?q={query}", "path", "query"); Assertions.assertEquals("cse://ms/path?q=query", uri.toString()); } + + @Test + public void testCrossAppServiceComb() { + CseUriTemplateHandler handler = new CseUriTemplateHandler(); + URI uri = handler.expand("servicecomb://{ap}{p}:ms/{path}?q={query}", "ap", "p", "path", "query"); + Assertions.assertEquals("servicecomb://app~ms/path?q=query", uri.toString()); + + Map vars = new HashMap<>(); + vars.put("app", "app"); + vars.put("path", "path"); + vars.put("q", "query"); + uri = handler.expand("servicecomb://{app}:ms/{path}?q={q}", vars); + Assertions.assertEquals("servicecomb://app~ms/path?q=query", uri.toString()); + + uri = handler.expand("servicecomb://ms/{path}?q={query}", "path", "query"); + Assertions.assertEquals("servicecomb://ms/path?q=query", uri.toString()); + } } diff --git a/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/async/CseAsyncClientHttpRequestTest.java b/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/async/CseAsyncClientHttpRequestTest.java index c58d77b3788..6045044b0c2 100644 --- a/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/async/CseAsyncClientHttpRequestTest.java +++ b/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/async/CseAsyncClientHttpRequestTest.java @@ -28,6 +28,7 @@ import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; import org.apache.servicecomb.provider.springmvc.reference.CseClientHttpResponse; import org.apache.servicecomb.registry.DiscoveryManager; +import org.apache.servicecomb.registry.RegistrationManager; import org.apache.servicecomb.swagger.invocation.Response; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; @@ -55,6 +56,7 @@ public static void classSetup() { public static void classTeardown() { scbEngine.destroy(); DiscoveryManager.renewInstance(); + RegistrationManager.renewInstance(); ArchaiusUtils.resetConfig(); } diff --git a/service-registry/registry-lightweight/src/main/java/org/apache/servicecomb/registry/lightweight/AbstractLightweightDiscovery.java b/service-registry/registry-lightweight/src/main/java/org/apache/servicecomb/registry/lightweight/AbstractLightweightDiscovery.java index 11cf2c8da9f..6ba3b15f8ba 100644 --- a/service-registry/registry-lightweight/src/main/java/org/apache/servicecomb/registry/lightweight/AbstractLightweightDiscovery.java +++ b/service-registry/registry-lightweight/src/main/java/org/apache/servicecomb/registry/lightweight/AbstractLightweightDiscovery.java @@ -45,6 +45,8 @@ public abstract class AbstractLightweightDiscovery implements Discovery, Initial protected AppManager appManager; + protected String revision; + @Autowired public AbstractLightweightDiscovery setEventBus(EventBus eventBus) { this.eventBus = eventBus; @@ -120,8 +122,11 @@ public MicroserviceInstance getMicroserviceInstance(String serviceId, String ins // ignore versionRule, instances only filter by consumer logic @Override - public MicroserviceInstances findServiceInstances(String appId, String serviceName, String uselessVersionRule, - String revision) { - return store.findServiceInstances(appId, serviceName, revision); + public MicroserviceInstances findServiceInstances(String appId, String serviceName, String uselessVersionRule) { + MicroserviceInstances microserviceInstances = store.findServiceInstances(appId, serviceName, this.revision); + if (!microserviceInstances.isMicroserviceNotExist() && microserviceInstances.isNeedRefresh()) { + this.revision = microserviceInstances.getRevision(); + } + return microserviceInstances; } } diff --git a/service-registry/registry-local/src/main/java/org/apache/servicecomb/localregistry/LocalDiscovery.java b/service-registry/registry-local/src/main/java/org/apache/servicecomb/localregistry/LocalDiscovery.java index 56c43932059..9c6aafac072 100644 --- a/service-registry/registry-local/src/main/java/org/apache/servicecomb/localregistry/LocalDiscovery.java +++ b/service-registry/registry-local/src/main/java/org/apache/servicecomb/localregistry/LocalDiscovery.java @@ -73,9 +73,8 @@ public MicroserviceInstance getMicroserviceInstance(String serviceId, String ins } @Override - public MicroserviceInstances findServiceInstances(String appId, String serviceName, String versionRule, - String revision) { - return localDiscoveryStore.findServiceInstances(appId, serviceName, versionRule, revision); + public MicroserviceInstances findServiceInstances(String appId, String serviceName, String versionRule) { + return localDiscoveryStore.findServiceInstances(appId, serviceName, versionRule); } @Override diff --git a/service-registry/registry-local/src/main/java/org/apache/servicecomb/localregistry/LocalRegistryStore.java b/service-registry/registry-local/src/main/java/org/apache/servicecomb/localregistry/LocalRegistryStore.java index db5ce5d232f..3526cd0fe9d 100644 --- a/service-registry/registry-local/src/main/java/org/apache/servicecomb/localregistry/LocalRegistryStore.java +++ b/service-registry/registry-local/src/main/java/org/apache/servicecomb/localregistry/LocalRegistryStore.java @@ -208,8 +208,7 @@ public MicroserviceInstance findMicroserviceInstance(String serviceId, String in } // local registry do not care about version and revision - public MicroserviceInstances findServiceInstances(String appId, String serviceName, String versionRule, - String revision) { + public MicroserviceInstances findServiceInstances(String appId, String serviceName, String versionRule) { MicroserviceInstances microserviceInstances = new MicroserviceInstances(); FindInstancesResponse findInstancesResponse = new FindInstancesResponse(); List instances = new ArrayList<>(); diff --git a/service-registry/registry-schema-discovery/src/main/java/org/apache/servicecomb/schemadiscovery/SchemaDiscovery.java b/service-registry/registry-schema-discovery/src/main/java/org/apache/servicecomb/schemadiscovery/SchemaDiscovery.java index 283bc60503a..0fddfb4a483 100644 --- a/service-registry/registry-schema-discovery/src/main/java/org/apache/servicecomb/schemadiscovery/SchemaDiscovery.java +++ b/service-registry/registry-schema-discovery/src/main/java/org/apache/servicecomb/schemadiscovery/SchemaDiscovery.java @@ -103,8 +103,7 @@ public MicroserviceInstance getMicroserviceInstance(String serviceId, String ins } @Override - public MicroserviceInstances findServiceInstances(String appId, String serviceName, String versionRule, - String revision) { + public MicroserviceInstances findServiceInstances(String appId, String serviceName, String versionRule) { MicroserviceInstances microserviceInstances = new MicroserviceInstances(); microserviceInstances.setMicroserviceNotExist(true); return microserviceInstances; diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/RegistryUtils.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/RegistryUtils.java index b8b70a3573a..72022f6a18e 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/RegistryUtils.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/RegistryUtils.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; @@ -52,16 +53,24 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.annotations.VisibleForTesting; import com.google.common.eventbus.Subscribe; +import com.netflix.config.DynamicPropertyFactory; public final class RegistryUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(RegistryUtils.class); + public static final String SERVICECOMB_SERVICE_REGISTRY_REPEATED_INITIALIZATION_ALLOWED + = "servicecomb.service.registry.repeated.initialization.allowed"; + /** * The default ServiceRegistry instance */ private static volatile ServiceRegistry serviceRegistry; + private static AtomicBoolean running = new AtomicBoolean(false); + private static final Map EXTRA_SERVICE_REGISTRIES = new LinkedHashMap<>(); private static AggregateServiceRegistryCache aggregateServiceRegistryCache; @@ -71,7 +80,11 @@ private RegistryUtils() { public static synchronized void init() { if (serviceRegistry != null) { - return; + if (DynamicPropertyFactory.getInstance() + .getBooleanProperty(SERVICECOMB_SERVICE_REGISTRY_REPEATED_INITIALIZATION_ALLOWED, false).get()) { + return; + } + throw new IllegalStateException("Registry has already bean initialized and not allowed to initialize twice."); } initializeServiceRegistriesWithConfig(ConfigUtil.createLocalConfig()); @@ -109,17 +122,32 @@ private static void initializeServiceRegistries(Configuration configuration) { } public static void run() { - executeOnEachServiceRegistry(ServiceRegistry::run); + if (running.compareAndSet(false, true)) { + executeOnEachServiceRegistry(ServiceRegistry::run); + return; + } + if (DynamicPropertyFactory.getInstance() + .getBooleanProperty(SERVICECOMB_SERVICE_REGISTRY_REPEATED_INITIALIZATION_ALLOWED, false).get()) { + return; + } + throw new IllegalStateException("Registry has already bean initialized and not allowed to initialize twice."); } public static void destroy() { + running.set(false); executeOnEachServiceRegistry(ServiceRegistry::destroy); + if (serviceRegistry != null) { + serviceRegistry = null; + } + EXTRA_SERVICE_REGISTRIES.clear(); } + @VisibleForTesting public static ServiceRegistry getServiceRegistry() { return serviceRegistry; } + @VisibleForTesting public static void setServiceRegistry(ServiceRegistry serviceRegistry) { RegistryUtils.serviceRegistry = serviceRegistry; initAggregateServiceRegistryCache(); @@ -148,7 +176,7 @@ public static MicroserviceInstance getMicroserviceInstance() { public static List findServiceInstance(String appId, String serviceName, - String versionRule) { + String versionRule) { MicroserviceCache serviceCache = aggregateServiceRegistryCache.findServiceCache( MicroserviceCacheKey.builder() .appId(appId).serviceName(serviceName) @@ -185,7 +213,7 @@ public static Microservice getMicroservice(String microserviceId) { } public static MicroserviceInstances findServiceInstances(String appId, String serviceName, - String versionRule) { + String versionRule) { MicroserviceCache serviceCache = aggregateServiceRegistryCache.findServiceCache( MicroserviceCacheKey.builder() .appId(appId) @@ -252,8 +280,8 @@ public static T getResultFromFirstValidServiceRegistry(Function action) { - if (null != getServiceRegistry()) { - action.accept(getServiceRegistry()); + if (null != serviceRegistry) { + action.accept(serviceRegistry); } if (!EXTRA_SERVICE_REGISTRIES.isEmpty()) { EXTRA_SERVICE_REGISTRIES.forEach((k, v) -> action.accept(v)); @@ -295,7 +323,7 @@ public static void validateRegistryName(String name) { public static ServiceRegistry getServiceRegistry(String registryName) { if (ServiceRegistry.DEFAULT_REGISTRY_NAME.equals(registryName)) { - return getServiceRegistry(); + return serviceRegistry; } return EXTRA_SERVICE_REGISTRIES.get(registryName); diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/ServiceCenterDiscovery.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/ServiceCenterDiscovery.java index af6caaceeea..a24fe14e493 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/ServiceCenterDiscovery.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/ServiceCenterDiscovery.java @@ -74,8 +74,7 @@ public MicroserviceInstance getMicroserviceInstance(String serviceId, String ins } @Override - public MicroserviceInstances findServiceInstances(String appId, String serviceName, String versionRule, - String revision) { + public MicroserviceInstances findServiceInstances(String appId, String serviceName, String versionRule) { return RegistryUtils.findServiceInstances(appId, serviceName, versionRule); diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/request/UpdatePropertiesRequest.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/request/UpdatePropertiesRequest.java index 5e08a22a9b4..17809e33dc0 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/request/UpdatePropertiesRequest.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/api/request/UpdatePropertiesRequest.java @@ -19,12 +19,16 @@ import java.util.Map; +import org.apache.servicecomb.registry.api.registry.Framework; + /** * 更新微服务和实例properties的request */ public class UpdatePropertiesRequest { private Map properties; + private Framework framework; + public Map getProperties() { return properties; } @@ -32,4 +36,12 @@ public Map getProperties() { public void setProperties(Map properties) { this.properties = properties; } + + public Framework getFramework() { + return framework; + } + + public void setFramework(Framework framework) { + this.framework = framework; + } } diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/RBACBootStrapService.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/RBACBootStrapService.java index d5a0710608d..a3671a3eb4d 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/RBACBootStrapService.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/RBACBootStrapService.java @@ -33,6 +33,7 @@ import org.apache.servicecomb.foundation.ssl.SSLOption; import org.apache.servicecomb.http.client.auth.DefaultRequestAuthHeaderProvider; import org.apache.servicecomb.http.client.common.HttpConfiguration.SSLProperties; +import org.apache.servicecomb.registry.api.event.ServiceCenterEventBus; import org.apache.servicecomb.service.center.client.ServiceCenterAddressManager; import org.apache.servicecomb.service.center.client.ServiceCenterClient; import org.apache.servicecomb.serviceregistry.config.ServiceRegistryConfig; @@ -60,13 +61,14 @@ public void startup(Environment environment) { } ServiceCenterAddressManager addressManager = createAddressManager(environment); + addressManager.setEventBus(ServiceCenterEventBus.getEventBus()); SSLProperties sslProperties = createSSLProperties(environment, "sc.consumer"); sslProperties.setEnabled(addressManager.sslEnabled()); // header: x-domain-name and url: /v1/{project}/ are all token from getTenantName。 ServiceCenterClient serviceCenterClient = new ServiceCenterClient( addressManager, sslProperties, new DefaultRequestAuthHeaderProvider(), getTenantName(environment), - new HashMap<>(0) + new HashMap<>(0), environment ); Map clients = new HashMap<>(1); clients @@ -91,8 +93,10 @@ Cipher getCipher(String cipherName) { } private ServiceCenterAddressManager createAddressManager(Environment environment) { + String region = environment.getProperty("servicecomb.datacenter.region", ""); + String availableZone = environment.getProperty("servicecomb.datacenter.availableZone", ""); return new ServiceCenterAddressManager(getTenantName(environment), - getRBACAddressList(environment), EventManager.getEventBus()); + getRBACAddressList(environment), region, availableZone, EventManager.getEventBus()); } private SSLProperties createSSLProperties(Environment environment, String tag) { diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenAuthHeaderProvider.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenAuthHeaderProvider.java index 0f075568313..b040f516f65 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenAuthHeaderProvider.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenAuthHeaderProvider.java @@ -26,8 +26,8 @@ public class TokenAuthHeaderProvider implements AuthHeaderProvider { @Override - public Map authHeaders() { - String token = TokenCacheManager.getInstance().getToken(RBACBootStrapService.DEFAULT_REGISTRY_NAME); + public Map authHeaders(String host) { + String token = TokenCacheManager.getInstance().getToken(host); if (StringUtils.isEmpty(token)) { return new HashMap<>(); } diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenCacheManager.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenCacheManager.java index d07d2add0c1..31d6a0ee911 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenCacheManager.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/auth/TokenCacheManager.java @@ -17,9 +17,8 @@ package org.apache.servicecomb.serviceregistry.auth; +import java.net.URI; import java.util.Map; -import java.util.Objects; -import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -28,12 +27,11 @@ import org.apache.commons.lang3.StringUtils; import org.apache.servicecomb.foundation.auth.Cipher; -import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx; +import org.apache.servicecomb.http.client.event.OperationEvents.UnAuthorizedOperationEvent; import org.apache.servicecomb.registry.api.event.ServiceCenterEventBus; import org.apache.servicecomb.service.center.client.ServiceCenterClient; import org.apache.servicecomb.service.center.client.model.RbacTokenRequest; import org.apache.servicecomb.service.center.client.model.RbacTokenResponse; -import org.apache.servicecomb.serviceregistry.event.NotPermittedEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,17 +52,15 @@ public final class TokenCacheManager { private static final TokenCacheManager INSTANCE = new TokenCacheManager(); - - private final Map tokenCacheMap; - private Map serviceCenterClients; + private TokenCache tokenCache; + public static TokenCacheManager getInstance() { return INSTANCE; } private TokenCacheManager() { - tokenCacheMap = new ConcurrentHashMapEx<>(); } public void setServiceCenterClients(Map serviceCenterClients) { @@ -72,24 +68,17 @@ public void setServiceCenterClients(Map serviceCent } public void addTokenCache(String registryName, String accountName, String password, Cipher cipher) { - Objects.requireNonNull(registryName, "registryName should not be null!"); - if (tokenCacheMap.containsKey(registryName)) { - LOGGER.warn("duplicate token cache registration for serviceRegistry[{}]", registryName); - return; - } - - tokenCacheMap.put(registryName, new TokenCache(registryName, accountName, password, cipher)); + tokenCache = new TokenCache(registryName, accountName, password, cipher); } - public String getToken(String registryName) { - return Optional.ofNullable(tokenCacheMap.get(registryName)) - .map(TokenCache::getToken) - .orElse(null); + public String getToken(String host) { + if (tokenCache == null) { + return null; + } + return tokenCache.getToken(host); } public class TokenCache { - private static final String UN_AUTHORIZED_CODE_HALF_OPEN = "401302"; - private static final long TOKEN_REFRESH_TIME_IN_SECONDS = 20 * 60 * 1000; private final String registryName; @@ -104,10 +93,6 @@ public class TokenCache { private final Cipher cipher; - private int lastStatusCode; - - private String lastErrorCode; - public TokenCache(String registryName, String accountName, String password, Cipher cipher) { this.registryName = registryName; @@ -132,12 +117,12 @@ public void run() { .build(new CacheLoader() { @Override public String load(String key) throws Exception { - return createHeaders(); + return createHeaders(key); } @Override public ListenableFuture reload(String key, String oldValue) throws Exception { - return Futures.submit(() -> createHeaders(), executorService); + return Futures.submit(() -> createHeaders(key), executorService); } }); ServiceCenterEventBus.getEventBus().register(this); @@ -145,29 +130,28 @@ public ListenableFuture reload(String key, String oldValue) throws Excep } @Subscribe - public void onNotPermittedEvent(NotPermittedEvent event) { - this.executorService.submit(() -> { - if (lastStatusCode == Status.UNAUTHORIZED.getStatusCode() && UN_AUTHORIZED_CODE_HALF_OPEN - .equals(lastErrorCode)) { - cache.refresh(registryName); - } - }); + public void onUnAuthorizedOperationEvent(UnAuthorizedOperationEvent event) { + LOGGER.warn("address {} unAuthorized, refresh cache token!", event.getAddress()); + cache.refresh(getHostByAddress(event.getAddress())); } - private String createHeaders() { - LOGGER.info("start to create RBAC headers"); + private String getHostByAddress(String address) { + try { + URI uri = URI.create(address); + return uri.getHost(); + } catch (Exception e) { + LOGGER.error("get host by address [{}] error!", address, e); + return registryName; + } + } + private String createHeaders(String host) { + LOGGER.info("start to create RBAC headers for host: {}", host); ServiceCenterClient serviceCenterClient = serviceCenterClients.get(this.registryName); - RbacTokenRequest request = new RbacTokenRequest(); request.setName(accountName); request.setPassword(new String(cipher.decrypt(password.toCharArray()))); - - RbacTokenResponse rbacTokenResponse = serviceCenterClient.queryToken(request); - - this.lastStatusCode = rbacTokenResponse.getStatusCode(); - this.lastErrorCode = rbacTokenResponse.getErrorCode(); - + RbacTokenResponse rbacTokenResponse = serviceCenterClient.queryToken(request, ""); if (Status.UNAUTHORIZED.getStatusCode() == rbacTokenResponse.getStatusCode() || Status.FORBIDDEN.getStatusCode() == rbacTokenResponse.getStatusCode()) { // password wrong, do not try anymore @@ -179,8 +163,14 @@ private String createHeaders() { LOGGER.warn("service center do not support RBAC token, you should not config account info"); return INVALID_TOKEN; } + if (Status.INTERNAL_SERVER_ERROR.getStatusCode() == rbacTokenResponse.getStatusCode()) { + // return null for server_error, so the token information can be re-fetched on the next call. + // It will prompt 'CacheLoader returned null for key xxx' + LOGGER.warn("service center query RBAC token error!"); + return null; + } - LOGGER.info("refresh token successfully {}", rbacTokenResponse.getStatusCode()); + LOGGER.info("refresh host [{}] token successfully {}", host, rbacTokenResponse.getStatusCode()); return rbacTokenResponse.getToken(); } @@ -188,13 +178,16 @@ protected long refreshTime() { return TOKEN_REFRESH_TIME_IN_SECONDS; } - public String getToken() { + public String getToken(String host) { if (!enabled()) { return null; } - + String address = host; + if (StringUtils.isEmpty(address)) { + address = registryName; + } try { - return cache.get(registryName); + return cache.get(address); } catch (Exception e) { LOGGER.error("failed to create token", e); return null; diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/IpPortManager.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/IpPortManager.java index f1dce18cae2..f2808189f18 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/IpPortManager.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/IpPortManager.java @@ -33,6 +33,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.netflix.config.DynamicPropertyFactory; + public class IpPortManager { private static final Logger LOGGER = LoggerFactory.getLogger(IpPortManager.class); @@ -66,11 +68,22 @@ public IpPortManager(ServiceRegistryConfig serviceRegistryConfig) { throw new IllegalArgumentException("Service center address is required to start the application."); } List addresses = defaultIpPort.stream().map(IpPort::toString).collect(Collectors.toList()); - addressManger = new ServiceRegistryAddressManager(addresses, EventManager.getEventBus()); + addressManger = new ServiceRegistryAddressManager(addresses, "", "", EventManager.getEventBus()); + addressManger.constructAffinityAddress(serviceRegistryConfig.getOriginAddress(), getRegion(), getAvailableZone()); classificationAddress = new ClassificationAddress(serviceRegistryConfig, instanceCacheManager); LOGGER.info("Initial service center address is {}", getAvailableAddress()); } + private String getRegion() { + return DynamicPropertyFactory.getInstance(). + getStringProperty("servicecomb.datacenter.region", "").get(); + } + + private String getAvailableZone() { + return DynamicPropertyFactory.getInstance(). + getStringProperty("servicecomb.datacenter.availableZone", "").get(); + } + // we have to do this operation after the first time setup has already done public void initAutoDiscovery() { if (!autoDiscoveryInited && this.serviceRegistryConfig.isRegistryAutoDiscovery()) { @@ -88,4 +101,16 @@ public IpPort getAvailableAddress() { public void recordState(String address) { addressManger.recordFailState(address); } + + public void recordSuccessState(String address) { + addressManger.recordSuccessState(address); + } + + public List getIsolationAddresses() { + return addressManger.getIsolationAddresses(); + } + + public IpPort transformIpPort(String address) { + return addressManger.transformIpPort(address); + } } diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/ServiceRegistryClient.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/ServiceRegistryClient.java index eca2f8d682f..7dae87eedc9 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/ServiceRegistryClient.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/ServiceRegistryClient.java @@ -22,6 +22,7 @@ import org.apache.servicecomb.foundation.vertx.AsyncResultCallback; import org.apache.servicecomb.registry.api.event.MicroserviceInstanceChangedEvent; +import org.apache.servicecomb.registry.api.registry.Framework; import org.apache.servicecomb.registry.api.registry.Microservice; import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; import org.apache.servicecomb.registry.api.registry.MicroserviceInstanceStatus; @@ -69,7 +70,7 @@ public interface ServiceRegistryClient { /** * 更新微服务properties */ - boolean updateMicroserviceProperties(String microserviceId, Map serviceProperties); + boolean updateMicroserviceProperties(String microserviceId, Map serviceProperties, Framework framework); /** * @@ -180,4 +181,9 @@ MicroserviceInstances findServiceInstances(String consumerId, String appId, Stri * @return whether this operation success */ boolean updateMicroserviceInstanceStatus(String microserviceId, String instanceId, MicroserviceInstanceStatus status); + + /** + * Check service center isolation addresses + */ + void checkIsolationAddressAvailable(); } diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/EmptyAuthHeaderProvider.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/EmptyAuthHeaderProvider.java index 37828b4ad15..79dc86cfdbc 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/EmptyAuthHeaderProvider.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/EmptyAuthHeaderProvider.java @@ -24,7 +24,7 @@ public class EmptyAuthHeaderProvider implements AuthHeaderProvider { @Override - public Map authHeaders() { + public Map authHeaders(String host) { return new HashMap<>(0); } } diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/RegistryHttpClientOptionsSPI.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/RegistryHttpClientOptionsSPI.java index d7b5c3cac45..6f55120ffca 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/RegistryHttpClientOptionsSPI.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/RegistryHttpClientOptionsSPI.java @@ -137,6 +137,11 @@ public int getKeepAliveTimeout() { return result; } + @Override + public boolean enableLogActivity() { + return false; + } + @Override public int getHttp2MultiplexingLimit() { return HttpClientOptions.DEFAULT_HTTP2_MULTIPLEXING_LIMIT; diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/ServiceRegistryClientImpl.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/ServiceRegistryClientImpl.java index 9c5ed1d989d..ff54002e184 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/ServiceRegistryClientImpl.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/client/http/ServiceRegistryClientImpl.java @@ -35,8 +35,11 @@ import org.apache.servicecomb.foundation.common.net.IpPort; import org.apache.servicecomb.foundation.common.utils.JsonUtils; import org.apache.servicecomb.foundation.vertx.AsyncResultCallback; +import org.apache.servicecomb.http.client.event.OperationEvents.UnAuthorizedOperationEvent; +import org.apache.servicecomb.http.client.utils.ServiceCombServiceAvailableUtils; import org.apache.servicecomb.registry.api.event.MicroserviceInstanceChangedEvent; import org.apache.servicecomb.registry.api.registry.FindInstancesResponse; +import org.apache.servicecomb.registry.api.registry.Framework; import org.apache.servicecomb.registry.api.registry.Microservice; import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; import org.apache.servicecomb.registry.api.registry.MicroserviceInstanceStatus; @@ -62,7 +65,6 @@ import org.apache.servicecomb.serviceregistry.client.IpPortManager; import org.apache.servicecomb.serviceregistry.client.ServiceRegistryClient; import org.apache.servicecomb.serviceregistry.config.ServiceRegistryConfig; -import org.apache.servicecomb.serviceregistry.event.NotPermittedEvent; import org.apache.servicecomb.registry.api.event.ServiceCenterEventBus; import org.apache.servicecomb.serviceregistry.task.HeartbeatResult; import org.apache.servicecomb.serviceregistry.task.MicroserviceInstanceHeartbeatTask; @@ -89,6 +91,8 @@ public final class ServiceRegistryClientImpl implements ServiceRegistryClient { private static final String ERR_SCHEMA_NOT_EXISTS = "400016"; + private static final String ADDRESS_CHECK_PATH = "/v4/default/registry/health/readiness"; + private final IpPortManager ipPortManager; // key是本进程的微服务id和服务管理中心的id @@ -148,7 +152,7 @@ public Handler syncHandler(CountDownLatch countDownLatch, Clas return; } holder.setStatusCode(response.statusCode()); - sendUnAuthorizedEvent(response); + sendUnAuthorizedEvent(response, requestContext); response.exceptionHandler(e -> { LOGGER.error("error in processing response.", e); countDownLatch.countDown(); @@ -228,7 +232,7 @@ private Handler syncHandlerEx(CountDownLatch countDownLatch, Holde return; } - sendUnAuthorizedEvent(response); + sendUnAuthorizedEvent(response, requestContext); response.exceptionHandler(e -> { LOGGER.error("error in processing response.", e); countDownLatch.countDown(); @@ -243,10 +247,18 @@ private Handler syncHandlerEx(CountDownLatch countDownLatch, Holde }; } - private void sendUnAuthorizedEvent(HttpClientResponse response) { + private void sendUnAuthorizedEvent(HttpClientResponse response, RequestContext requestContext) { if (response.statusCode() == Status.UNAUTHORIZED.getStatusCode()) { - ServiceCenterEventBus.getEventBus().post(new NotPermittedEvent()); + ServiceCenterEventBus.getEventBus().post(new UnAuthorizedOperationEvent(getAddressWithProtocol(requestContext))); + } + } + + private String getAddressWithProtocol(RequestContext requestContext) { + String ipAndPort = requestContext.getIpPort().toString(); + if (ipAndPort.startsWith("http")) { + return ipAndPort; } + return "https://" + ipAndPort; } private Handler syncHandlerForInstances(CountDownLatch countDownLatch, @@ -274,11 +286,13 @@ private Handler syncHandlerForInstances(CountDownLatch countDownLa switch (response.statusCode()) { case 304: mInstances.setNeedRefresh(false); + ipPortManager.recordSuccessState(requestContext.getIpPort().toString()); break; case 200: mInstances .setInstancesResponse(JsonUtils.readValue(bodyBuffer.getBytes(), FindInstancesResponse.class)); mInstances.setNeedRefresh(true); + ipPortManager.recordSuccessState(requestContext.getIpPort().toString()); break; case 400: { @SuppressWarnings("unchecked") @@ -288,6 +302,7 @@ private Handler syncHandlerForInstances(CountDownLatch countDownLa mInstances.setNeedRefresh(false); } LOGGER.warn("failed to findInstances: " + bodyBuffer); + ipPortManager.recordState(requestContext.getIpPort().toString()); } break; default: @@ -815,13 +830,15 @@ private void watchErrorHandler(Throwable e, String selfMicroserviceId, } @Override - public boolean updateMicroserviceProperties(String microserviceId, Map serviceProperties) { + public boolean updateMicroserviceProperties(String microserviceId, Map serviceProperties, + Framework framework) { Holder holder = new Holder<>(); IpPort ipPort = ipPortManager.getAvailableAddress(); try { UpdatePropertiesRequest request = new UpdatePropertiesRequest(); request.setProperties(serviceProperties); + request.setFramework(framework); byte[] body = JsonUtils.writeValueAsBytes(request); if (LOGGER.isDebugEnabled()) { @@ -962,6 +979,47 @@ public boolean updateMicroserviceInstanceStatus(String microserviceId, String in return false; } + @Override + public void checkIsolationAddressAvailable() { + Holder holder = new Holder<>(); + List isolationAddresses = ipPortManager.getIsolationAddresses(); + for (String address : isolationAddresses) { + CountDownLatch countDownLatch = new CountDownLatch(1); + restClientUtil.get(ipPortManager.transformIpPort(address), + ADDRESS_CHECK_PATH, + new RequestParam(), + addressSyncHandler(countDownLatch, GetAllServicesResponse.class, holder)); + try { + countDownLatch.await(); + if (holder.statusCode == 200) { + ipPortManager.recordSuccessState(address); + return; + } + if (holder.statusCode == 404 && ServiceCombServiceAvailableUtils.telnetCheckAddress(address)) { + ipPortManager.recordSuccessState(address); + return; + } + } catch (Exception e) { + LOGGER.error("check service center isolation address [{}] failed", address); + } + } + } + + public Handler addressSyncHandler(CountDownLatch countDownLatch, Class cls, + Holder holder) { + return restResponse -> { + HttpClientResponse response = restResponse.getResponse(); + if (response == null) { + countDownLatch.countDown(); + holder.setStatusCode(500); + return; + } + holder.setStatusCode(response.statusCode()); + sendUnAuthorizedEvent(response, restResponse.getRequestContext()); + countDownLatch.countDown(); + }; + } + @Subscribe public void onMicroserviceHeartbeatTask(MicroserviceInstanceHeartbeatTask event) { if (HeartbeatResult.SUCCESS.equals(event.getHeartbeatResult())) { diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/config/ServiceRegistryConfig.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/config/ServiceRegistryConfig.java index 4e5b3a825dd..7cf3dafe2fb 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/config/ServiceRegistryConfig.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/config/ServiceRegistryConfig.java @@ -132,6 +132,8 @@ public class ServiceRegistryConfig { private Function serviceRegistryClientConstructor = serviceRegistry -> new ServiceRegistryClientImpl(this); + private List originAddress; + public ServiceRegistryConfig() { } @@ -446,4 +448,13 @@ public ServiceRegistryConfig setServiceRegistryClientConstructor( public ServiceRegistryClient createServiceRegistryClient(ServiceRegistry serviceRegistry) { return this.serviceRegistryClientConstructor.apply(serviceRegistry); } + + public List getOriginAddress() { + return originAddress; + } + + public ServiceRegistryConfig setOriginAddress(List originAddress) { + this.originAddress = originAddress; + return this; + } } diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/config/ServiceRegistryConfigBuilder.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/config/ServiceRegistryConfigBuilder.java index ae6b7fd080f..647337db378 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/config/ServiceRegistryConfigBuilder.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/config/ServiceRegistryConfigBuilder.java @@ -46,12 +46,15 @@ class ServiceRegistryConfigBuilder { private boolean ssl; + private List originAddress; + public ServiceRegistryConfig build() { return new ServiceRegistryConfig() .setHttpVersion(getHttpVersion()) .setInstances(getInstances()) .setIpPort(getIpPort()) .setSsl(isSsl()) + .setOriginAddress(getOriginAddress()) .setClientName(RegistryHttpClientOptionsSPI.CLIENT_NAME) .setWatchClientName(RegistryWatchHttpClientOptionsSPI.CLIENT_NAME) .setConnectionTimeout(getConnectionTimeout()) @@ -110,11 +113,16 @@ public boolean isSsl() { return this.ssl; } + public List getOriginAddress() { + return originAddress; + } + public ArrayList getIpPort() { List uriList = Objects .requireNonNull(Deployment.getSystemBootStrapInfo(ServiceCenterDefaultDeploymentProvider.SYSTEM_KEY_SERVICE_CENTER), "no sc address found!") .getAccessURL(); + this.originAddress = uriList; ArrayList ipPortList = new ArrayList<>(); uriList.forEach(anUriList -> { try { diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/refresh/ServiceRegistryAddressManager.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/refresh/ServiceRegistryAddressManager.java index 3ced076229c..69f11ed5cde 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/refresh/ServiceRegistryAddressManager.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/refresh/ServiceRegistryAddressManager.java @@ -17,29 +17,32 @@ package org.apache.servicecomb.serviceregistry.refresh; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; import java.net.URI; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; import org.apache.servicecomb.foundation.common.net.IpPort; +import org.apache.servicecomb.foundation.common.net.NetUtils; import org.apache.servicecomb.foundation.common.net.URIEndpointObject; import org.apache.servicecomb.http.client.common.AbstractAddressManager; +import org.apache.servicecomb.http.client.common.URLEndPoint; import org.apache.servicecomb.http.client.event.RefreshEndpointEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; public class ServiceRegistryAddressManager extends AbstractAddressManager { - private static final Logger LOGGER = LoggerFactory.getLogger(ServiceRegistryAddressManager.class); - private static final String URI_PREFIX = "rest://"; - public ServiceRegistryAddressManager(List addresses, EventBus eventBus) { - super(addresses); + private static final String ZONE = "availableZone"; + + private static final String REGION = "region"; + + public ServiceRegistryAddressManager(List addresses, String ownRegion, String ownAvailableZone, + EventBus eventBus) { + super(addresses, ownRegion, ownAvailableZone); eventBus.register(this); } @@ -52,19 +55,7 @@ protected String normalizeUri(String endpoint) { return new URIEndpointObject(endpoint).toString(); } - @Override - protected boolean telnetTest(String address) { - IpPort ipPort = transformIpPort(address); - try (Socket s = new Socket()) { - s.connect(new InetSocketAddress(ipPort.getHostOrIp(), ipPort.getPort()), 3000); - return true; - } catch (IOException e) { - LOGGER.warn("ping endpoint {} failed, It will be quarantined again.", address); - } - return false; - } - - private IpPort transformIpPort(String address) { + public IpPort transformIpPort(String address) { URI uri = URI.create(URI_PREFIX + address); return new IpPort(uri.getHost(), uri.getPort()); } @@ -73,4 +64,35 @@ private IpPort transformIpPort(String address) { public void onRefreshEndpointEvent(RefreshEndpointEvent event) { refreshEndpoint(event, RefreshEndpointEvent.SERVICE_CENTER_NAME); } + + public void constructAffinityAddress(List addresses, String ownRegion, String ownAvailableZone) { + boolean isAffinityAddress = addresses.stream().anyMatch(addr -> addr.contains(ZONE) || addr.contains(REGION)); + if (!isAffinityAddress || (StringUtils.isEmpty(ownRegion) && StringUtils.isEmpty(ownAvailableZone))) { + return; + } + Set sameZone = new HashSet<>(); + Set sameRegion = new HashSet<>(); + for (String address : addresses) { + URI uri = URI.create(address); + String ipPort = NetUtils.parseIpPort(uri).toString(); + if (isMatchRegionAndZone(address, ownRegion, ownAvailableZone)) { + sameZone.add(ipPort); + } else { + sameRegion.add(ipPort); + } + } + refreshAffinityAddress(sameZone, sameRegion); + } + + private boolean isMatchRegionAndZone(String address, String ownRegion, String ownAvailableZone) { + try { + URLEndPoint endPoint = new URLEndPoint(address); + if (!StringUtils.equals(ownRegion, endPoint.getFirst(REGION))) { + return false; + } + return StringUtils.equals(ownAvailableZone, endPoint.getFirst(ZONE)); + } catch (Exception e) { + return false; + } + } } diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/registry/AbstractServiceRegistry.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/registry/AbstractServiceRegistry.java index cf32dde8c9a..0a7f9d6f1c7 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/registry/AbstractServiceRegistry.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/registry/AbstractServiceRegistry.java @@ -201,7 +201,7 @@ public MicroserviceCache findMicroserviceCache(MicroserviceCacheKey microservice @Override public boolean updateMicroserviceProperties(Map properties) { boolean success = srClient.updateMicroserviceProperties(microservice.getServiceId(), - properties); + properties, microservice.getFramework()); if (success) { microservice.setProperties(properties); } diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/task/MicroserviceInstanceRegisterTask.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/task/MicroserviceInstanceRegisterTask.java index 135d48c4a80..0e98fb3b5a1 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/task/MicroserviceInstanceRegisterTask.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/task/MicroserviceInstanceRegisterTask.java @@ -16,11 +16,15 @@ */ package org.apache.servicecomb.serviceregistry.task; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + import org.apache.commons.lang3.StringUtils; import org.apache.servicecomb.registry.RegistrationManager; -import org.apache.servicecomb.serviceregistry.adapter.EnvAdapterManager; import org.apache.servicecomb.registry.api.registry.Microservice; import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; +import org.apache.servicecomb.serviceregistry.adapter.EnvAdapterManager; import org.apache.servicecomb.serviceregistry.client.ServiceRegistryClient; import org.apache.servicecomb.serviceregistry.config.ServiceRegistryConfig; import org.slf4j.Logger; @@ -36,6 +40,10 @@ public class MicroserviceInstanceRegisterTask extends AbstractRegisterTask { private final MicroserviceInstance microserviceInstance; + private final ScheduledExecutorService addrCheckExecutor; + + private boolean isAddrCheckInit = false; + public MicroserviceInstanceRegisterTask(EventBus eventBus, ServiceRegistryConfig serviceRegistryConfig, ServiceRegistryClient srClient, Microservice microservice) { @@ -43,6 +51,7 @@ public MicroserviceInstanceRegisterTask(EventBus eventBus, ServiceRegistryConfig this.serviceRegistryConfig = serviceRegistryConfig; this.microserviceInstance = microservice.getInstance(); + this.addrCheckExecutor = Executors.newScheduledThreadPool(1, (t) -> new Thread(t, "sc-addr-check")); } @Subscribe @@ -58,12 +67,15 @@ public void onMicroserviceRegisterTask(MicroserviceRegisterTask task) { @Override protected boolean doRegister() { LOGGER.info("running microservice instance register task."); - String hostName = ""; + String hostName; if (serviceRegistryConfig.isPreferIpAddress()) { hostName = RegistrationManager.getPublishAddress(); } else { hostName = RegistrationManager.getPublishHostName(); } + if (hostName.length() > 64) { + hostName = hostName.substring(0, 64); + } microserviceInstance.setHostName(hostName); microserviceInstance.getHealthCheck().setInterval(serviceRegistryConfig.getHeartbeatInterval()); microserviceInstance.getHealthCheck().setTimes(serviceRegistryConfig.getResendHeartBeatTimes()); @@ -87,6 +99,18 @@ protected boolean doRegister() { microserviceInstance.getEndpoints(), microserviceInstance.getHealthCheck().getTTL()); + if (!isAddrCheckInit) { + addrCheckExecutor.scheduleWithFixedDelay(new CheckAddressTask(), 0, + serviceRegistryConfig.getHeartbeatInterval(), TimeUnit.SECONDS); + isAddrCheckInit = true; + } return true; } + + class CheckAddressTask implements Runnable { + @Override + public void run() { + srClient.checkIsolationAddressAvailable(); + } + } } diff --git a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/task/MicroserviceRegisterTask.java b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/task/MicroserviceRegisterTask.java index 8193526bfe9..cb6d15d61fa 100644 --- a/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/task/MicroserviceRegisterTask.java +++ b/service-registry/registry-service-center/src/main/java/org/apache/servicecomb/serviceregistry/task/MicroserviceRegisterTask.java @@ -90,20 +90,19 @@ protected boolean doRegister() { // Need to update microservice properties if we have modified or added properties of microservices. Microservice microserviceTemp = new Microservice(); EnvAdapterManager.INSTANCE.processMicroserviceWithAdapters(microserviceTemp); - if (!microserviceTemp.getProperties().isEmpty()) { - Map propertiesTemp = microserviceTemp.getProperties(); - Microservice srClientMicroservice = srClient.getMicroservice(serviceId); - if (srClientMicroservice != null) { - microserviceTemp.setProperties(srClientMicroservice.getProperties()); - microserviceTemp.getProperties().putAll(propertiesTemp); - } - if (srClient.updateMicroserviceProperties(serviceId, microserviceTemp.getProperties())) { - LOGGER.info("microservice is already registered. Update microservice properties successfully. properties=[{}]", - microserviceTemp.getProperties()); - } else { - LOGGER.error("microservice is already registered. Update microservice properties failed. properties=[{}]", - microserviceTemp.getProperties()); - } + Map propertiesTemp = microserviceTemp.getProperties(); + Microservice srClientMicroservice = srClient.getMicroservice(serviceId); + if (srClientMicroservice != null) { + microserviceTemp.setProperties(srClientMicroservice.getProperties()); + microserviceTemp.getProperties().putAll(propertiesTemp); + } + if (srClient.updateMicroserviceProperties(serviceId, microserviceTemp.getProperties(), + microservice.getFramework())) { + LOGGER.info("microservice is already registered. Update microservice properties successfully. properties=[{}]", + microserviceTemp.getProperties()); + } else { + LOGGER.error("microservice is already registered. Update microservice properties failed. properties=[{}]", + microserviceTemp.getProperties()); } LOGGER.info( "Microservice exists in service center, no need to register. id=[{}] appId=[{}], name=[{}], version=[{}], env=[{}]", diff --git a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/TestRegistry.java b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/TestRegistry.java index 9a93381109a..1c0e966404c 100644 --- a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/TestRegistry.java +++ b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/TestRegistry.java @@ -39,6 +39,7 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.mockito.Mockito; @@ -78,6 +79,11 @@ public void setUp() throws Exception { inMemoryConfig.clear(); } + @AfterEach + public void tearDown() throws Exception { + RegistryUtils.destroy(); + } + @SuppressWarnings("deprecation") @Test public void testDelegate() { diff --git a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/cache/TestMicroserviceInstanceCache.java b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/cache/TestMicroserviceInstanceCache.java index cdd84019dbe..b838cd86bac 100644 --- a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/cache/TestMicroserviceInstanceCache.java +++ b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/cache/TestMicroserviceInstanceCache.java @@ -20,30 +20,32 @@ import org.apache.servicecomb.config.ConfigUtil; import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; import org.apache.servicecomb.registry.DiscoveryManager; +import org.apache.servicecomb.registry.api.registry.Microservice; +import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; import org.apache.servicecomb.registry.cache.MicroserviceInstanceCache; import org.apache.servicecomb.serviceregistry.RegistryUtils; import org.apache.servicecomb.serviceregistry.ServiceRegistry; -import org.apache.servicecomb.registry.api.registry.Microservice; -import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; import org.apache.servicecomb.serviceregistry.client.ServiceRegistryClient; -import org.junit.AfterClass; +import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import mockit.Expectations; import mockit.Mock; import mockit.MockUp; import mockit.Mocked; -import org.junit.jupiter.api.Assertions; public class TestMicroserviceInstanceCache { @Before public void setup() { ConfigUtil.installDynamicConfig(); + RegistryUtils.init(); } - @AfterClass - public static void classTeardown() { + @After + public void teardown() { + RegistryUtils.destroy(); ArchaiusUtils.resetConfig(); } @@ -75,12 +77,7 @@ public Microservice getMicroservice(String microserviceId) { public void testGetOrCreateMicroserviceInstance(@Mocked ServiceRegistry serviceRegistry, @Mocked ServiceRegistryClient client, @Mocked MicroserviceInstance instance) { - new MockUp() { - @Mock - ServiceRegistry getServiceRegistry() { - return serviceRegistry; - } - }; + RegistryUtils.setServiceRegistry(serviceRegistry); new Expectations() { { serviceRegistry.getServiceRegistryClient(); diff --git a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/LocalServiceRegistryClientImpl.java b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/LocalServiceRegistryClientImpl.java index 379a285e178..47a2371dd3c 100644 --- a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/LocalServiceRegistryClientImpl.java +++ b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/LocalServiceRegistryClientImpl.java @@ -35,6 +35,7 @@ import org.apache.servicecomb.foundation.vertx.AsyncResultCallback; import org.apache.servicecomb.registry.api.event.MicroserviceInstanceChangedEvent; import org.apache.servicecomb.registry.api.registry.FindInstancesResponse; +import org.apache.servicecomb.registry.api.registry.Framework; import org.apache.servicecomb.registry.api.registry.Microservice; import org.apache.servicecomb.registry.api.registry.MicroserviceInstance; import org.apache.servicecomb.registry.api.registry.MicroserviceInstanceStatus; @@ -382,7 +383,8 @@ public Holder> getSchemas(String microserviceId) { } @Override - public boolean updateMicroserviceProperties(String microserviceId, Map serviceProperties) { + public boolean updateMicroserviceProperties(String microserviceId, Map serviceProperties, + Framework framework) { Microservice microservice = microserviceIdMap.get(microserviceId); if (microservice == null) { throw new IllegalArgumentException("Invalid serviceId, serviceId=" + microserviceId); @@ -456,4 +458,9 @@ public boolean updateMicroserviceInstanceStatus(String microserviceId, String in microserviceInstance.setStatus(status); return true; } + + @Override + public void checkIsolationAddressAvailable() { + // NOT SUPPORT + } } diff --git a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/http/MockAuthHeaderProvider.java b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/http/MockAuthHeaderProvider.java index ea210a6c197..64ae22092e9 100644 --- a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/http/MockAuthHeaderProvider.java +++ b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/client/http/MockAuthHeaderProvider.java @@ -24,7 +24,7 @@ public class MockAuthHeaderProvider implements AuthHeaderProvider { @Override - public Map authHeaders() { + public Map authHeaders(String host) { HashMap headers = new HashMap<>(); headers.put("X-Service-AK", "blah..."); return headers; diff --git a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/diagnosis/instance/TestInstanceCacheCheckerMock.java b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/diagnosis/instance/TestInstanceCacheCheckerMock.java index 9de7f5ee552..a1c93befc82 100644 --- a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/diagnosis/instance/TestInstanceCacheCheckerMock.java +++ b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/diagnosis/instance/TestInstanceCacheCheckerMock.java @@ -73,6 +73,7 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { ArchaiusUtils.resetConfig(); + RegistryUtils.destroy(); } private Holder createFindServiceInstancesResult() { @@ -196,8 +197,8 @@ MicroserviceInstances findServiceInstances(String appId, String serviceName, instanceCacheResult.setMicroserviceName(microserviceName); instanceCacheResult.setStatus(Status.UNKNOWN); instanceCacheResult.setPulledInstances(new ArrayList<>()); - instanceCacheResult.setDetail( - "revision is different, will be synchronized in next pull. local revision=first, remote revision=second"); + instanceCacheResult.setDetail("revision is different, will be synchronized in next pull. " + + "local revision=Fv/f6YTFogBi3MiMIkVuh2DZLvU=, remote revision=second"); expectedSummary.getProducers().add(instanceCacheResult); expectedSummary.setStatus(Status.UNKNOWN); diff --git a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/diagnosis/instance/TestInstanceCacheCheckerWithoutMock.java b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/diagnosis/instance/TestInstanceCacheCheckerWithoutMock.java index a99f849e02b..8d833905f36 100644 --- a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/diagnosis/instance/TestInstanceCacheCheckerWithoutMock.java +++ b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/diagnosis/instance/TestInstanceCacheCheckerWithoutMock.java @@ -69,6 +69,7 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { ArchaiusUtils.resetConfig(); + RegistryUtils.destroy(); } @Test @@ -101,7 +102,8 @@ public void check_StaticMicroservice() { MicroserviceVersionRule microserviceVersionRule = DiscoveryManager.INSTANCE.getAppManager() .getOrCreateMicroserviceVersionRule(appId, microserviceName, DefinitionConst.VERSION_RULE_ALL); - Assertions.assertEquals(microserviceName, microserviceVersionRule.getLatestMicroserviceVersion().getMicroserviceName()); + Assertions.assertEquals(microserviceName, + microserviceVersionRule.getLatestMicroserviceVersion().getMicroserviceName()); InstanceCacheSummary instanceCacheSummary = checker.check(); diff --git a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/refresh/ServiceRegistryAddressManagerTest.java b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/refresh/ServiceRegistryAddressManagerTest.java index d6a181b4f48..ff8547c4d99 100644 --- a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/refresh/ServiceRegistryAddressManagerTest.java +++ b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/refresh/ServiceRegistryAddressManagerTest.java @@ -44,8 +44,8 @@ class ServiceRegistryAddressManagerTest { public void addressManagerTest() { IpPort ipPort = new IpPort("127.0.0.1", 30103); addresses.add(ipPort.toString()); - addressManager1 = new ServiceRegistryAddressManager(addresses, new EventBus()); - addressManager2 = new ServiceRegistryAddressManager(addresses, new EventBus()); + addressManager1 = new ServiceRegistryAddressManager(addresses, "", "", new EventBus()); + addressManager2 = new ServiceRegistryAddressManager(addresses, "", "", new EventBus()); Assertions.assertNotNull(addressManager1); Assertions.assertNotNull(addressManager2); @@ -70,7 +70,7 @@ public void onRefreshEndpointEvent() { Map> zoneAndRegion = new HashMap<>(); zoneAndRegion.put("sameZone", addressAZ); zoneAndRegion.put("sameRegion", addressRG); - addressManager1 = new ServiceRegistryAddressManager(addresses, new EventBus()); + addressManager1 = new ServiceRegistryAddressManager(addresses, "", "", new EventBus()); RefreshEndpointEvent event = new RefreshEndpointEvent(zoneAndRegion, "SERVICECENTER"); addressManager1.refreshEndpoint(event, "SERVICECENTER"); @@ -88,7 +88,7 @@ public void addressIPV6Test() { Map> zoneAndRegion = new HashMap<>(); zoneAndRegion.put("sameZone", addressAZ); zoneAndRegion.put("sameRegion", new ArrayList<>()); - addressManager1 = new ServiceRegistryAddressManager(addresses, EventManager.getEventBus()); + addressManager1 = new ServiceRegistryAddressManager(addresses, "", "", EventManager.getEventBus()); RefreshEndpointEvent event = new RefreshEndpointEvent(zoneAndRegion, "SERVICECENTER"); addressManager1.refreshEndpoint(event, "SERVICECENTER"); diff --git a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/registry/TestLocalServiceRegistry.java b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/registry/TestLocalServiceRegistry.java index c9ee3a0edc5..c81dd2a5a41 100644 --- a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/registry/TestLocalServiceRegistry.java +++ b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/registry/TestLocalServiceRegistry.java @@ -28,6 +28,8 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; import com.netflix.config.ConcurrentCompositeConfiguration; import com.netflix.config.ConcurrentMapConfiguration; @@ -35,7 +37,6 @@ import com.netflix.config.DynamicPropertyFactory; import mockit.Deencapsulation; -import org.junit.jupiter.api.Assertions; public class TestLocalServiceRegistry { private static final AbstractConfiguration inMemoryConfig = new ConcurrentMapConfiguration(); @@ -63,6 +64,11 @@ public void setUp() throws Exception { inMemoryConfig.clear(); } + @AfterEach + public void tearDown() throws Exception { + RegistryUtils.destroy(); + } + @Test public void testLifeCycle() { ServiceRegistry serviceRegistry = LocalServiceRegistryFactory.createLocal(); diff --git a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/registry/TestRemoteServiceRegistry.java b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/registry/TestRemoteServiceRegistry.java index dc769fa07c0..bce8f3c378b 100644 --- a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/registry/TestRemoteServiceRegistry.java +++ b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/registry/TestRemoteServiceRegistry.java @@ -23,13 +23,15 @@ import org.apache.commons.configuration.Configuration; import org.apache.servicecomb.config.ConfigUtil; import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils; -import org.apache.servicecomb.serviceregistry.event.ShutdownEvent; import org.apache.servicecomb.serviceregistry.RegistryUtils; import org.apache.servicecomb.serviceregistry.ServiceRegistry; import org.apache.servicecomb.serviceregistry.client.LocalServiceRegistryClientImpl; import org.apache.servicecomb.serviceregistry.client.ServiceRegistryClient; import org.apache.servicecomb.serviceregistry.config.ServiceRegistryConfig; +import org.apache.servicecomb.serviceregistry.event.ShutdownEvent; import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; import com.google.common.eventbus.EventBus; @@ -37,7 +39,6 @@ import mockit.Injectable; import mockit.Mock; import mockit.MockUp; -import org.junit.jupiter.api.Assertions; public class TestRemoteServiceRegistry { static class TestingRemoteServiceRegistry extends RemoteServiceRegistry { @@ -52,6 +53,11 @@ protected ServiceRegistryClient createServiceRegistryClient() { } } + @AfterEach + public void tearDown() throws Exception { + RegistryUtils.destroy(); + } + @Test public void testLifeCycle(@Injectable ServiceRegistryConfig config, @Injectable ServiceRegistry registry) throws InterruptedException { diff --git a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/task/TestMicroserviceRegisterTask.java b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/task/TestMicroserviceRegisterTask.java index aa0d1349ce6..c600386e736 100644 --- a/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/task/TestMicroserviceRegisterTask.java +++ b/service-registry/registry-service-center/src/test/java/org/apache/servicecomb/serviceregistry/task/TestMicroserviceRegisterTask.java @@ -527,7 +527,6 @@ public void testLocalSchemaAndServiceCenterSchemaDiff(@Mocked ServiceRegistryCli "info:\n" + " version: \"1.0.0\"\n" + " title: \"swagger definition for org.apache.servicecomb.demo.jaxrs.server.RequestClientTimeOut\"\n" + - " x-java-interface: \"cse.gen.jaxrstest.jaxrs.clientreqtimeout.RequestClientTimeOutIntf\"\n" + "basePath: \"/clientreqtimeout\"\n" + "consumes:\n" + "- \"application/json\"\n" + @@ -569,7 +568,6 @@ public void testLocalSchemaAndServiceCenterSchemaDiff(@Mocked ServiceRegistryCli "info:\n" + " version: \"1.0.0\"\n" + " title: \"swagger definition for org.apache.servicecomb.demo.jaxrs.server.RequestClientTimeOut\"\n" + - " x-java-interface: \"cse.gen.jaxrstest.jaxrs.clientreqtimeout.RequestClientTimeOutIntf\"\n" + "basePath: \"/clientreqtimeout\"\n" + "consumes:\n" + "- \"application/json\"\n" + @@ -621,7 +619,6 @@ public void testLocalSchemaAndServiceCenterSchemaDiff(@Mocked ServiceRegistryCli "info:\n" + " version: \"1.0.0\"\n" + " title: \"swagger definition for org.apache.servicecomb.demo.jaxrs.server.RequestClientTimeOut\"\n" + - " x-java-interface: \"cse.gen.jaxrstest.jaxrs.clientreqtimeout.RequestClientTimeOutIntf\"\n" + "basePath: \"/clientreqtimeout\"\n" + "consumes:\n" + "- \"application/json\"\n" + @@ -660,7 +657,6 @@ public void testLocalSchemaAndServiceCenterSchemaDiff(@Mocked ServiceRegistryCli "info:\n" + " version: \"1.0.0\"\n" + " title: \"swagger definition for org.apache.servicecomb.demo.jaxrs.server.RequestClientTimeOut\"\n" + - " x-java-interface: \"cse.gen.jaxrstest.jaxrs.clientreqtimeout.RequestClientTimeOutIntf\"\n" + "basePath: \"/clientreqtimeout\"\n" + "consumes:\n" + "- \"application/json\"\n" + @@ -747,7 +743,6 @@ public void testLocalSchemaAndServiceCenterSchemaIgnoreDiff(@Mocked ServiceRegis "info:\n" + " version: \"1.0.0\"\n" + " title: \"swagger definition for org.apache.servicecomb.demo.jaxrs.server.RequestClientTimeOut\"\n" + - " x-java-interface: \"cse.gen.jaxrstest.jaxrs.clientreqtimeout.RequestClientTimeOutIntf\"\n" + "basePath: \"/clientreqtimeout\"\n" + "consumes:\n" + "- \"application/json\"\n" + @@ -789,7 +784,6 @@ public void testLocalSchemaAndServiceCenterSchemaIgnoreDiff(@Mocked ServiceRegis "info:\n" + " version: \"1.0.0\"\n" + " title: \"swagger definition for org.apache.servicecomb.demo.jaxrs.server.RequestClientTimeOut\"\n" + - " x-java-interface: \"cse.gen.jaxrstest.jaxrs.clientreqtimeout.RequestClientTimeOutIntf\"\n" + "basePath: \"/clientreqtimeout\"\n" + "consumes:\n" + "- \"application/json\"\n" + @@ -866,7 +860,6 @@ public void testLocalSchemaAndServiceCenterSchemaNoDiff(@Mocked ServiceRegistryC "info:\n" + " version: \"1.0.0\"\n" + " title: \"swagger definition for org.apache.servicecomb.demo.jaxrs.server.RequestClientTimeOut\"\n" + - " x-java-interface: \"cse.gen.jaxrstest.jaxrs.clientreqtimeout.RequestClientTimeOutIntf\"\n" + "basePath: \"/clientreqtimeout\"\n" + "consumes:\n" + "- \"application/json\"\n" + @@ -908,7 +901,6 @@ public void testLocalSchemaAndServiceCenterSchemaNoDiff(@Mocked ServiceRegistryC "info:\n" + " version: \"1.0.0\"\n" + " title: \"swagger definition for org.apache.servicecomb.demo.jaxrs.server.RequestClientTimeOut\"\n" + - " x-java-interface: \"cse.gen.jaxrstest.jaxrs.clientreqtimeout.RequestClientTimeOutIntf\"\n" + "basePath: \"/clientreqtimeout\"\n" + "consumes:\n" + "- \"application/json\"\n" + diff --git a/service-registry/registry-zero-config/src/main/java/org/apache/servicecomb/zeroconfig/multicast/Multicast.java b/service-registry/registry-zero-config/src/main/java/org/apache/servicecomb/zeroconfig/multicast/Multicast.java index 7f99a773cce..056d3642566 100644 --- a/service-registry/registry-zero-config/src/main/java/org/apache/servicecomb/zeroconfig/multicast/Multicast.java +++ b/service-registry/registry-zero-config/src/main/java/org/apache/servicecomb/zeroconfig/multicast/Multicast.java @@ -52,6 +52,7 @@ public class Multicast { private final DatagramPacket recvPacket = new DatagramPacket(recvBuffer, recvBuffer.length); + @SuppressWarnings("deprecation") public Multicast(Config config) throws IOException { this.bindAddress = initBindAddress(config); this.group = initGroup(config); diff --git a/spring-boot/spring-boot-starters/java-chassis-spring-boot-starter/src/main/java/org/apache/servicecomb/springboot2/starter/ServiceCombSpringConfiguration.java b/spring-boot/spring-boot-starters/java-chassis-spring-boot-starter/src/main/java/org/apache/servicecomb/springboot2/starter/ServiceCombSpringConfiguration.java index 89cf311a317..94b27999e64 100644 --- a/spring-boot/spring-boot-starters/java-chassis-spring-boot-starter/src/main/java/org/apache/servicecomb/springboot2/starter/ServiceCombSpringConfiguration.java +++ b/spring-boot/spring-boot-starters/java-chassis-spring-boot-starter/src/main/java/org/apache/servicecomb/springboot2/starter/ServiceCombSpringConfiguration.java @@ -25,7 +25,7 @@ import org.springframework.context.annotation.ImportResource; @Configuration -@ImportResource({BeanUtils.DEFAULT_BEAN_CORE_RESOURCE, BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE}) +@ImportResource({BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE}) class ServiceCombSpringConfiguration { @Inject public void setCseApplicationListener(SCBApplicationListener applicationListener) { diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/extend/ModelResolverExt.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/extend/ModelResolverExt.java index e0bc42fb4c7..e1bcac77240 100644 --- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/extend/ModelResolverExt.java +++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/extend/ModelResolverExt.java @@ -40,7 +40,6 @@ import org.apache.servicecomb.swagger.extend.property.creator.PropertyCreator; import org.apache.servicecomb.swagger.extend.property.creator.ShortPropertyCreator; import org.apache.servicecomb.swagger.generator.SwaggerConst; -import org.apache.servicecomb.swagger.generator.SwaggerGeneratorFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; @@ -109,9 +108,7 @@ private void addPropertyCreator(PropertyCreator creator) { @VisibleForTesting protected void setType(JavaType type, Map vendorExtensions) { - if (SwaggerGeneratorFeature.isLocalExtJavaClassInVendor()) { - vendorExtensions.put(SwaggerConst.EXT_JAVA_CLASS, type.toCanonical()); - } + vendorExtensions.put(SwaggerConst.EXT_JAVA_CLASS, type.toCanonical()); } private void checkType(JavaType type) { diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/SwaggerGenerator.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/SwaggerGenerator.java index c3d98c9c9ea..0f3d8d93a5a 100644 --- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/SwaggerGenerator.java +++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/SwaggerGenerator.java @@ -44,8 +44,6 @@ static SwaggerGenerator create(Class cls) { throw new IllegalStateException("impossible, must be bug. can not generate swagger for " + cls.getName()); } - SwaggerGeneratorFeature getSwaggerGeneratorFeature(); - /** * support placeholder * @param basePath diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/SwaggerGeneratorFeature.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/SwaggerGeneratorFeature.java deleted file mode 100644 index 398e5f546e0..00000000000 --- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/SwaggerGeneratorFeature.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 - * - * http://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 org.apache.servicecomb.swagger.generator; - -public class SwaggerGeneratorFeature { - private static final ThreadLocal featureThreadLocal = new ThreadLocal<>(); - - public static ThreadLocal getFeatureThreadLocal() { - return featureThreadLocal; - } - - public static boolean isLocalExtJavaClassInVendor() { - SwaggerGeneratorFeature feature = featureThreadLocal.get(); - return feature != null ? feature.extJavaClassInVendor : true; - } - - // packageName and extJavaInVender is unnecessary, new invocation mechanism not depend them - // just remain them for compatible - private String packageName = "gen.swagger"; - - private boolean extJavaClassInVendor = true; - - private boolean extJavaInterfaceInVendor = true; - - public String getPackageName() { - return packageName; - } - - public void setPackageName(String packageName) { - this.packageName = packageName; - } - - public boolean isExtJavaClassInVendor() { - return extJavaClassInVendor; - } - - public SwaggerGeneratorFeature setExtJavaClassInVendor(boolean extJavaClassInVendor) { - this.extJavaClassInVendor = extJavaClassInVendor; - - return this; - } - - public boolean isExtJavaInterfaceInVendor() { - return extJavaInterfaceInVendor; - } - - public SwaggerGeneratorFeature setExtJavaInterfaceInVendor(boolean extJavaInterfaceInVendor) { - this.extJavaInterfaceInVendor = extJavaInterfaceInVendor; - - return this; - } -} diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/AbstractOperationGenerator.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/AbstractOperationGenerator.java index 302482b3082..b31c91154e3 100644 --- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/AbstractOperationGenerator.java +++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/AbstractOperationGenerator.java @@ -16,11 +16,50 @@ */ package org.apache.servicecomb.swagger.generator.core; +import static org.apache.servicecomb.swagger.generator.SwaggerGeneratorUtils.collectAnnotations; +import static org.apache.servicecomb.swagger.generator.SwaggerGeneratorUtils.findMethodAnnotationProcessor; +import static org.apache.servicecomb.swagger.generator.SwaggerGeneratorUtils.findParameterProcessors; +import static org.apache.servicecomb.swagger.generator.SwaggerGeneratorUtils.findResponseTypeProcessor; +import static org.apache.servicecomb.swagger.generator.SwaggerGeneratorUtils.isContextParameter; +import static org.apache.servicecomb.swagger.generator.SwaggerGeneratorUtils.postProcessOperation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; + +import org.apache.commons.lang3.StringUtils; +import org.apache.servicecomb.config.inject.PlaceholderResolver; +import org.apache.servicecomb.swagger.SwaggerUtils; +import org.apache.servicecomb.swagger.generator.MethodAnnotationProcessor; +import org.apache.servicecomb.swagger.generator.OperationGenerator; +import org.apache.servicecomb.swagger.generator.ParameterGenerator; +import org.apache.servicecomb.swagger.generator.ParameterProcessor; +import org.apache.servicecomb.swagger.generator.ResponseTypeProcessor; +import org.apache.servicecomb.swagger.generator.SwaggerConst; +import org.apache.servicecomb.swagger.generator.core.model.HttpParameterType; +import org.apache.servicecomb.swagger.generator.core.utils.MethodUtils; + import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; import com.fasterxml.jackson.databind.type.TypeFactory; import com.google.common.reflect.TypeToken; + import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiParam; @@ -44,42 +83,6 @@ import io.swagger.models.properties.StringProperty; import io.swagger.util.Json; import io.swagger.util.ReflectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.servicecomb.config.inject.PlaceholderResolver; -import org.apache.servicecomb.swagger.SwaggerUtils; -import org.apache.servicecomb.swagger.generator.MethodAnnotationProcessor; -import org.apache.servicecomb.swagger.generator.OperationGenerator; -import org.apache.servicecomb.swagger.generator.ParameterGenerator; -import org.apache.servicecomb.swagger.generator.ParameterProcessor; -import org.apache.servicecomb.swagger.generator.ResponseTypeProcessor; -import org.apache.servicecomb.swagger.generator.SwaggerConst; -import org.apache.servicecomb.swagger.generator.core.model.HttpParameterType; -import org.apache.servicecomb.swagger.generator.core.utils.MethodUtils; - -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.MediaType; -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.Comparator; -import java.util.stream.Collectors; - -import static org.apache.servicecomb.swagger.generator.SwaggerGeneratorUtils.collectAnnotations; -import static org.apache.servicecomb.swagger.generator.SwaggerGeneratorUtils.findMethodAnnotationProcessor; -import static org.apache.servicecomb.swagger.generator.SwaggerGeneratorUtils.findParameterProcessors; -import static org.apache.servicecomb.swagger.generator.SwaggerGeneratorUtils.findResponseTypeProcessor; -import static org.apache.servicecomb.swagger.generator.SwaggerGeneratorUtils.isContextParameter; -import static org.apache.servicecomb.swagger.generator.SwaggerGeneratorUtils.postProcessOperation; public abstract class AbstractOperationGenerator implements OperationGenerator { protected AbstractSwaggerGenerator swaggerGenerator; @@ -169,8 +172,8 @@ public void generate() { protected void scanMethodAnnotation() { for (Annotation annotation : Arrays.stream(method.getAnnotations()) - .sorted(Comparator.comparing(a -> a.annotationType().getName())) - .collect(Collectors.toList()) + .sorted(Comparator.comparing(a -> a.annotationType().getName())) + .collect(Collectors.toList()) ) { MethodAnnotationProcessor processor = findMethodAnnotationProcessor(annotation.annotationType()); if (processor == null) { @@ -261,12 +264,12 @@ protected void initMethodParameterGenerators(Map> metho } protected boolean isAggregatedParameter(ParameterGenerator parameterGenerator, - java.lang.reflect.Parameter methodParameter) { + java.lang.reflect.Parameter methodParameter) { return false; } protected void extractAggregatedParameterGenerators(Map> methodAnnotationMap, - java.lang.reflect.Parameter methodParameter) { + java.lang.reflect.Parameter methodParameter) { JavaType javaType = TypeFactory.defaultInstance().constructType(methodParameter.getParameterizedType()); BeanDescription beanDescription = Json.mapper().getSerializationConfig().introspect(javaType); for (BeanPropertyDefinition propertyDefinition : beanDescription.findProperties()) { @@ -293,6 +296,16 @@ protected void initRemainMethodAnnotationsParameterGenerators(Map> initMethodAnnotationByParameterName() { Map> methodAnnotations = new LinkedHashMap<>(); + + for (Annotation annotation : clazz.getAnnotations()) { + if (annotation instanceof ApiImplicitParams) { + for (ApiImplicitParam apiImplicitParam : ((ApiImplicitParams) annotation).value()) { + addMethodAnnotationByParameterName(methodAnnotations, apiImplicitParam.name(), apiImplicitParam); + } + continue; + } + } + for (Annotation annotation : method.getAnnotations()) { if (annotation instanceof ApiImplicitParams) { for (ApiImplicitParam apiImplicitParam : ((ApiImplicitParams) annotation).value()) { @@ -309,7 +322,7 @@ private Map> initMethodAnnotationByParameterName() { } private void addMethodAnnotationByParameterName(Map> methodAnnotations, String name, - Annotation annotation) { + Annotation annotation) { if (StringUtils.isEmpty(name)) { throw new IllegalStateException(String.format("%s.name should not be empty. method=%s:%s", annotation.annotationType().getSimpleName(), @@ -375,7 +388,7 @@ protected Parameter createParameter(HttpParameterType httpParameterType) { } protected void fillParameter(Swagger swagger, Parameter parameter, String parameterName, JavaType type, - List annotations) { + List annotations) { for (Annotation annotation : annotations) { ParameterProcessor processor = findParameterProcessors(annotation.annotationType()); if (processor != null) { @@ -395,7 +408,7 @@ protected void fillParameter(Swagger swagger, Parameter parameter, String parame if (parameter instanceof AbstractSerializableParameter) { io.swagger.util.ParameterProcessor.applyAnnotations(swagger, parameter, type, annotations); annotations.stream().forEach(annotation -> { - if (NOT_NULL_ANNOTATIONS.contains(annotation.annotationType().getSimpleName())){ + if (NOT_NULL_ANNOTATIONS.contains(annotation.annotationType().getSimpleName())) { parameter.setRequired(true); } }); @@ -426,7 +439,7 @@ protected void fillBodyParameter(Swagger swagger, Parameter parameter, Type type convertAnnotationProperty(((JavaType) type).getRawClass()); } else { ((JavaType) type).getBindings().getTypeParameters(). - forEach(javaType -> convertAnnotationProperty(javaType.getRawClass())); + forEach(javaType -> convertAnnotationProperty(javaType.getRawClass())); } mergeBodyParameter((BodyParameter) parameter, newBodyParameter); @@ -434,7 +447,7 @@ protected void fillBodyParameter(Swagger swagger, Parameter parameter, Type type private void convertAnnotationProperty(Class beanClass) { Map definitions = swagger.getDefinitions(); - if (definitions == null){ + if (definitions == null) { return; } Field[] fields = beanClass.getDeclaredFields(); @@ -446,7 +459,7 @@ private void convertAnnotationProperty(Class beanClass) { if (properties != null) { Arrays.stream(fields).forEach(field -> { boolean requireItem = Arrays.stream(field.getAnnotations()). - anyMatch(annotation -> NOT_NULL_ANNOTATIONS.contains(annotation.annotationType().getSimpleName())); + anyMatch(annotation -> NOT_NULL_ANNOTATIONS.contains(annotation.annotationType().getSimpleName())); if (requireItem) { Property property = properties.get(field.getName()); if (property != null) { diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/AbstractSwaggerGenerator.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/AbstractSwaggerGenerator.java index fe82fd9c9e3..288f57c6f66 100644 --- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/AbstractSwaggerGenerator.java +++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/AbstractSwaggerGenerator.java @@ -39,7 +39,6 @@ import org.apache.servicecomb.swagger.generator.OperationGenerator; import org.apache.servicecomb.swagger.generator.SwaggerConst; import org.apache.servicecomb.swagger.generator.SwaggerGenerator; -import org.apache.servicecomb.swagger.generator.SwaggerGeneratorFeature; import org.apache.servicecomb.swagger.generator.core.utils.MethodUtils; import io.swagger.annotations.Api; @@ -58,8 +57,6 @@ * */ public abstract class AbstractSwaggerGenerator implements SwaggerGenerator { - protected SwaggerGeneratorFeature swaggerGeneratorFeature = new SwaggerGeneratorFeature(); - protected Class cls; protected Swagger swagger; @@ -105,41 +102,26 @@ public void setHttpMethod(String httpMethod) { this.httpMethod = httpMethod.toLowerCase(Locale.US); } - public SwaggerGeneratorFeature getSwaggerGeneratorFeature() { - return swaggerGeneratorFeature; - } - public Swagger generate() { LOGGER.info("generate schema from [{}]", cls); scanClassAnnotation(); - ThreadLocal featureThreadLocal = SwaggerGeneratorFeature.getFeatureThreadLocal(); - featureThreadLocal.set(swaggerGeneratorFeature); - try { - scanMethods(); - addOperationsToSwagger(); + scanMethods(); + addOperationsToSwagger(); - correctSwagger(); + correctSwagger(); - return swagger; - } finally { - featureThreadLocal.remove(); - } + return swagger; } public void scanClassAnnotation() { - ThreadLocal featureThreadLocal = SwaggerGeneratorFeature.getFeatureThreadLocal(); - featureThreadLocal.set(swaggerGeneratorFeature); - try { - for (Annotation annotation : cls.getAnnotations()) { - ClassAnnotationProcessor processor = findClassAnnotationProcessor(annotation.annotationType()); - if (processor == null) { - continue; - } - processor.process(this, annotation); + + for (Annotation annotation : cls.getAnnotations()) { + ClassAnnotationProcessor processor = findClassAnnotationProcessor(annotation.annotationType()); + if (processor == null) { + continue; } - } finally { - featureThreadLocal.remove(); + processor.process(this, annotation); } } @@ -203,21 +185,9 @@ private void correctInfo() { } protected void setJavaInterface(Info info) { - if (!swaggerGeneratorFeature.isExtJavaInterfaceInVendor()) { - return; - } - if (cls.isInterface()) { info.setVendorExtension(SwaggerConst.EXT_JAVA_INTF, cls.getName()); - return; } - - if (StringUtils.isEmpty(swaggerGeneratorFeature.getPackageName())) { - return; - } - - String intfName = swaggerGeneratorFeature.getPackageName() + "." + cls.getSimpleName() + "Intf"; - info.setVendorExtension(SwaggerConst.EXT_JAVA_INTF, intfName); } @Override diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/processor/response/CompletableFutureProcessor.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/processor/response/CompletableFutureProcessor.java index 09fc57537a3..983f0553dcf 100644 --- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/processor/response/CompletableFutureProcessor.java +++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/processor/response/CompletableFutureProcessor.java @@ -17,8 +17,17 @@ package org.apache.servicecomb.swagger.generator.core.processor.response; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.concurrent.CompletableFuture; +import org.apache.servicecomb.swagger.generator.OperationGenerator; +import org.apache.servicecomb.swagger.generator.ResponseTypeProcessor; +import org.apache.servicecomb.swagger.generator.SwaggerGenerator; +import org.apache.servicecomb.swagger.generator.SwaggerGeneratorUtils; + +import io.swagger.models.Model; + public class CompletableFutureProcessor extends DefaultResponseTypeProcessor { public CompletableFutureProcessor() { extractActualType = true; @@ -28,4 +37,22 @@ public CompletableFutureProcessor() { public Class getProcessType() { return CompletableFuture.class; } + + @Override + public Model process(SwaggerGenerator swaggerGenerator, OperationGenerator operationGenerator, + Type genericResponseType) { + if (!(genericResponseType instanceof ParameterizedType)) { + return super.process(swaggerGenerator, operationGenerator, genericResponseType); + } + + final Type[] actualTypeArguments = ((ParameterizedType) genericResponseType).getActualTypeArguments(); + if (actualTypeArguments.length != 1) { + // fallback to legacy mode + return super.process(swaggerGenerator, operationGenerator, genericResponseType); + } + final Type actualTypeArgument = actualTypeArguments[0]; + final ResponseTypeProcessor actualResponseTypeProcessor = + SwaggerGeneratorUtils.findResponseTypeProcessor(actualTypeArgument); + return actualResponseTypeProcessor.process(swaggerGenerator, operationGenerator, actualTypeArgument); + } } diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/unittest/UnitTestSwaggerUtils.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/unittest/UnitTestSwaggerUtils.java index f26513275ae..81193c902ea 100644 --- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/unittest/UnitTestSwaggerUtils.java +++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/unittest/UnitTestSwaggerUtils.java @@ -24,13 +24,13 @@ import org.apache.commons.io.IOUtils; import org.apache.servicecomb.swagger.generator.SwaggerGenerator; +import org.junit.jupiter.api.Assertions; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectWriter; import io.swagger.models.Swagger; import io.swagger.util.Yaml; -import org.junit.jupiter.api.Assertions; public final class UnitTestSwaggerUtils { private static final ObjectWriter writer = Yaml.pretty(); @@ -71,8 +71,6 @@ public static Swagger parse(String content) { public static SwaggerGenerator testSwagger(String resPath, Class cls, String... methods) { SwaggerGenerator generator = SwaggerGenerator.create(cls); generator.replaceMethodWhiteList(methods); - generator.getSwaggerGeneratorFeature().setPackageName("gen.cse.ms.ut"); - Swagger swagger = generator.generate(); String schema = pretty(swagger); diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/pojo/PojoOperationGenerator.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/pojo/PojoOperationGenerator.java index cd6c5eb8cc4..acca28c7fb9 100644 --- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/pojo/PojoOperationGenerator.java +++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/pojo/PojoOperationGenerator.java @@ -29,8 +29,6 @@ import org.apache.commons.lang3.StringUtils; import org.apache.servicecomb.swagger.SwaggerUtils; import org.apache.servicecomb.swagger.generator.ParameterGenerator; -import org.apache.servicecomb.swagger.generator.SwaggerConst; -import org.apache.servicecomb.swagger.generator.SwaggerGeneratorFeature; import org.apache.servicecomb.swagger.generator.core.AbstractOperationGenerator; import org.apache.servicecomb.swagger.generator.core.AbstractSwaggerGenerator; import org.apache.servicecomb.swagger.generator.core.model.HttpParameterType; @@ -100,15 +98,6 @@ private void wrapParametersToBody(List bodyFields) { } swagger.addDefinition(simpleRef, bodyModel); - SwaggerGeneratorFeature feature = swaggerGenerator.getSwaggerGeneratorFeature(); - // bodyFields.size() > 1 is no reason, just because old version do this...... - // if not care for this, then can just delete all logic about EXT_JAVA_CLASS/EXT_JAVA_INTF - if (feature.isExtJavaClassInVendor() - && bodyFields.size() > 1 - && StringUtils.isNotEmpty(feature.getPackageName())) { - bodyModel.getVendorExtensions().put(SwaggerConst.EXT_JAVA_CLASS, feature.getPackageName() + "." + simpleRef); - } - RefModel refModel = new RefModel(); refModel.setReference("#/definitions/" + simpleRef); diff --git a/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/converter/ConverterMgrTest.java b/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/converter/ConverterMgrTest.java index de118854404..be64fb4a3d7 100644 --- a/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/converter/ConverterMgrTest.java +++ b/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/converter/ConverterMgrTest.java @@ -51,7 +51,7 @@ public void should_use_swagger_type_when_model_type_is_available() { @Test public void should_use_Object_when_model_type_is_not_available() { Model model = swagger.getDefinitions().get("testEnumBody"); - assertThat(SwaggerUtils.getClassName(model.getVendorExtensions())).isEqualTo("gen.cse.ms.ut.testEnumBody"); + assertThat(SwaggerUtils.getClassName(model.getVendorExtensions())).isNull(); assertThat(ConverterMgr.findJavaType(swagger, model).getRawClass()).isEqualTo(Object.class); } diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/allMethod.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/allMethod.yaml index 5390865f020..b5805ad65e3 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/allMethod.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/allMethod.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" @@ -683,7 +682,6 @@ definitions: - "YELLOW" - "BLUE" x-java-class: "org.apache.servicecomb.foundation.test.scaffolding.model.Color" - x-java-class: "gen.cse.ms.ut.testEnumBody" testFloatBody: type: "object" properties: @@ -781,7 +779,6 @@ definitions: type: "object" additionalProperties: $ref: "#/definitions/User" - x-java-class: "gen.cse.ms.ut.testMultiParamBody" testOneEnumBody: type: "object" properties: diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/allType.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/allType.yaml index c9802302e22..5fa759c7810 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/allType.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/allType.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/apiOperation.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/apiOperation.yaml index 9e324333613..44a35116b80 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/apiOperation.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/apiOperation.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/apiResponse.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/apiResponse.yaml index 51d9e6d3832..b78cbc93491 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/apiResponse.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/apiResponse.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/array.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/array.yaml index 1786c4f46e9..1dfaee4679e 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/array.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/array.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/boolean.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/boolean.yaml index 481f990cfe3..eae2c8d64b2 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/boolean.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/boolean.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/booleanObject.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/booleanObject.yaml index 89d45bed286..6fa5d081e6b 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/booleanObject.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/booleanObject.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/byte.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/byte.yaml index 37145cdfece..533b43f8111 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/byte.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/byte.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/byteObject.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/byteObject.yaml index 30e4f38fc4d..4376e8bdde1 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/byteObject.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/byteObject.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/bytes.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/bytes.yaml index 4226256b20a..ef35cb3533b 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/bytes.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/bytes.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/bytesObject.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/bytesObject.yaml index 5b6754f397e..a807afb63f2 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/bytesObject.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/bytesObject.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/char.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/char.yaml index 16965f75416..aeaea6b81fc 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/char.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/char.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/charObject.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/charObject.yaml index 990a2c96464..af8dba598ed 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/charObject.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/charObject.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/completableFuture.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/completableFuture.yaml index 212ceca38a1..583d6085abd 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/completableFuture.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/completableFuture.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/date.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/date.yaml index a6d176355c9..b15132ecbb8 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/date.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/date.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/double.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/double.yaml index b58aa2cd905..5c151cba16b 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/double.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/double.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/doubleObject.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/doubleObject.yaml index f1f2a83d693..dc1d342cbcf 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/doubleObject.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/doubleObject.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/emptySwaggerDefinition.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/emptySwaggerDefinition.yaml index 09cba695e81..042836aed70 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/emptySwaggerDefinition.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/emptySwaggerDefinition.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.processor.annotation.SwaggerDefinitionProcessorTest$EmptySwaggerDefinition" - x-java-interface: "gen.cse.ms.ut.EmptySwaggerDefinitionIntf" basePath: "/EmptySwaggerDefinition" schemes: - "http" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/enum.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/enum.yaml index 91e0a673bd4..7e6c3d6c256 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/enum.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/enum.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" @@ -57,4 +56,3 @@ definitions: - "YELLOW" - "BLUE" x-java-class: "org.apache.servicecomb.foundation.test.scaffolding.model.Color" - x-java-class: "gen.cse.ms.ut.testEnumBody" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/float.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/float.yaml index 500cdb22ddf..e884c5c83a2 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/float.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/float.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/floatObject.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/floatObject.yaml index 56d19670eb4..57f73607272 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/floatObject.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/floatObject.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/ignoreRequest.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/ignoreRequest.yaml index 8c9fbed3463..3e2ea767dbf 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/ignoreRequest.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/ignoreRequest.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/int.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/int.yaml index 1fc4b15f949..d0ec83c9ed6 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/int.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/int.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/intObject.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/intObject.yaml index ed16f263a6b..df2bcdaf2a7 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/intObject.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/intObject.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/list.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/list.yaml index c894e8bed1e..c283baf71f2 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/list.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/list.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/long.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/long.yaml index 1de9207efe8..e8ee23caebd 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/long.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/long.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/longObject.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/longObject.yaml index eeb67d3bdbc..b7da712380f 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/longObject.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/longObject.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/map.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/map.yaml index cd7f8123a0a..65e306c0907 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/map.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/map.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/mapList.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/mapList.yaml index c94873c4e1e..16394f372d1 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/mapList.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/mapList.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/multiParam.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/multiParam.yaml index 8e69b26e07c..d5c171161af 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/multiParam.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/multiParam.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" @@ -182,4 +181,3 @@ definitions: type: "object" additionalProperties: $ref: "#/definitions/User" - x-java-class: "gen.cse.ms.ut.testMultiParamBody" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/nestedListString.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/nestedListString.yaml index 59d8190b16d..e35280afced 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/nestedListString.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/nestedListString.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/object.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/object.yaml index 0679237261e..13da8f71df7 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/object.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/object.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/oneEnum.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/oneEnum.yaml index fc280b0607b..027b47b8418 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/oneEnum.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/oneEnum.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/part.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/part.yaml index 7ff945592c6..296fbfd37b5 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/part.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/part.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/partArray.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/partArray.yaml index 1883eda88dc..9a5b96e76e3 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/partArray.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/partArray.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/partList.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/partList.yaml index 63dbd57a4ba..1123a227597 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/partList.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/partList.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/responseHeader.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/responseHeader.yaml index bbefa65eb8e..880278306b2 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/responseHeader.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/responseHeader.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/security-apikey.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/security-apikey.yaml index d2e323690dd..87bea82f3dd 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/security-apikey.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/security-apikey.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.processor.annotation.SwaggerDefinitionProcessorTest$SecurityApiKey" - x-java-interface: "gen.cse.ms.ut.SecurityApiKeyIntf" basePath: "/SecurityApiKey" schemes: - "http" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/security-basic.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/security-basic.yaml index c2d76976cb1..8f77366c8c8 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/security-basic.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/security-basic.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.processor.annotation.SwaggerDefinitionProcessorTest$SecurityBasic" - x-java-interface: "gen.cse.ms.ut.SecurityBasicIntf" basePath: "/SecurityBasic" schemes: - "http" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/security-oauth2.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/security-oauth2.yaml index 5d88c26aced..6a881d1cbfa 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/security-oauth2.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/security-oauth2.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.processor.annotation.SwaggerDefinitionProcessorTest$SecurityOAuth2" - x-java-interface: "gen.cse.ms.ut.SecurityOAuth2Intf" basePath: "/SecurityOAuth2" schemes: - "http" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/set.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/set.yaml index aec08067b51..13ef03e354e 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/set.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/set.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/short.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/short.yaml index 5c07dc5f24e..aedfb892228 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/short.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/short.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/shortObject.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/shortObject.yaml index b24bc5328dc..69765e3da16 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/shortObject.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/shortObject.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/string.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/string.yaml index 3b0ce3d953b..fcb40e6729e 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/string.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/string.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/testCompletableFutureOptional.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/testCompletableFutureOptional.yaml index e6a8e0ff320..9f6be201d63 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/testCompletableFutureOptional.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/testCompletableFutureOptional.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/testOptional.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/testOptional.yaml index 8778e96891a..0659bd40210 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/testOptional.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/testOptional.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-core/src/test/resources/schemas/wrapToBodyWithDesc.yaml b/swagger/swagger-generator/generator-core/src/test/resources/schemas/wrapToBodyWithDesc.yaml index 470b3346c57..0cc30156a07 100644 --- a/swagger/swagger-generator/generator-core/src/test/resources/schemas/wrapToBodyWithDesc.yaml +++ b/swagger/swagger-generator/generator-core/src/test/resources/schemas/wrapToBodyWithDesc.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.core.schema.Schema" - x-java-interface: "gen.cse.ms.ut.SchemaIntf" basePath: "/Schema" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/aggregatedParam.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/aggregatedParam.yaml index a074d0c78c7..d2e27a7a1e2 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/aggregatedParam.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/aggregatedParam.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/beanParamDefaultBody.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/beanParamDefaultBody.yaml index 768741ed1af..9e2ba1bc7d6 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/beanParamDefaultBody.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/beanParamDefaultBody.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/beanParamWithJsonIgnoredTagged.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/beanParamWithJsonIgnoredTagged.yaml index 6ce1e66e5b0..71a1bcf67b7 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/beanParamWithJsonIgnoredTagged.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/beanParamWithJsonIgnoredTagged.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/beanParamWithPart.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/beanParamWithPart.yaml index 4423f07ec6f..0be30358c22 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/beanParamWithPart.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/beanParamWithPart.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/consumes.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/consumes.yaml index 64c35c1d05c..598e5ec8b5e 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/consumes.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/consumes.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.model.ConsumesAndProduces" - x-java-interface: "gen.cse.ms.ut.ConsumesAndProducesIntf" basePath: "/ConsumesAndProduces" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/cookie.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/cookie.yaml index 896793e188c..76deee78431 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/cookie.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/cookie.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/dynamicStatusEnum.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/dynamicStatusEnum.yaml index 906054f0382..d9c881af349 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/dynamicStatusEnum.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/dynamicStatusEnum.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/echo.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/echo.yaml index 38496655032..59da8f389ed 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/echo.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/echo.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/emptyContract.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/emptyContract.yaml index 11bcbbaa586..35eba10cb55 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/emptyContract.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/emptyContract.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/emptyPath.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/emptyPath.yaml index f76bd99fdf8..db5d2c6f068 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/emptyPath.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/emptyPath.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/enumBody.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/enumBody.yaml index b5b78b0232a..64350d3a3d2 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/enumBody.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/enumBody.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/form.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/form.yaml index 911269c20de..4543363fb83 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/form.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/form.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/jdkStatusEnum.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/jdkStatusEnum.yaml index 67efb0def4c..d5d4f110562 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/jdkStatusEnum.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/jdkStatusEnum.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/nestedListString.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/nestedListString.yaml index aa02f5688f2..f6662a5fb1c 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/nestedListString.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/nestedListString.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/patch.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/patch.yaml index 6a89b056f59..6c8dce0bb4c 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/patch.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/patch.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/query.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/query.yaml index be256d1d65a..06d27a2092c 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/query.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/query.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/rawJsonStringMethod.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/rawJsonStringMethod.yaml index a482f6c6625..bd8d7e07933 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/rawJsonStringMethod.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/rawJsonStringMethod.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/response.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/response.yaml index b4db5ae2b39..ba3414221c0 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/response.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/response.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/responseText.yaml b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/responseText.yaml index c596de61aac..39e4061f415 100644 --- a/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/responseText.yaml +++ b/swagger/swagger-generator/generator-jaxrs/src/test/resources/schemas/responseText.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.jaxrs.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/MethodEmptyPath.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/MethodEmptyPath.yaml index 958dc7d43d7..075776a42c4 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/MethodEmptyPath.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/MethodEmptyPath.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.MethodEmptyPath" - x-java-interface: "gen.cse.ms.ut.MethodEmptyPathIntf" basePath: "/MethodEmptyPath" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/asyncResponseEntity.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/asyncResponseEntity.yaml index cb3125f8d0f..717d1cd790e 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/asyncResponseEntity.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/asyncResponseEntity.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/cookie.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/cookie.yaml index 5ec6b6d7f95..5689a29303b 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/cookie.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/cookie.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/defaultParameter.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/defaultParameter.yaml index f8f4de1c4c4..96cc06e831a 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/defaultParameter.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/defaultParameter.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.MethodDefaultParameter" - x-java-interface: "gen.cse.ms.ut.MethodDefaultParameterIntf" basePath: "/MethodDefaultParameter" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/emptyPath.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/emptyPath.yaml index 7b292cb45e7..d22a04cc107 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/emptyPath.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/emptyPath.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/enumBody.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/enumBody.yaml index 449ac41cf2c..3224e796714 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/enumBody.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/enumBody.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/inheritHttpMethod.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/inheritHttpMethod.yaml index 69f8051f284..6aa1d9b4ddb 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/inheritHttpMethod.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/inheritHttpMethod.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/mixupAnnotations.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/mixupAnnotations.yaml index ae7adc86d2f..298a819cb55 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/mixupAnnotations.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/mixupAnnotations.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.MethodMixupAnnotations" - x-java-interface: "gen.cse.ms.ut.MethodMixupAnnotationsIntf" basePath: "/MethodMixupAnnotations" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/nestedListString.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/nestedListString.yaml index 4375c9e0b39..fbfc9d61dc9 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/nestedListString.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/nestedListString.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/part.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/part.yaml index fcfa80f5b43..3b68063607b 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/part.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/part.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partAnnotation.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partAnnotation.yaml index 1d12b4283ab..b2ce6b277e6 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partAnnotation.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partAnnotation.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partArray.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partArray.yaml index 579eb52cd0c..d8eb4d1ea01 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partArray.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partArray.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partArrayAnnotation.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partArrayAnnotation.yaml index 510e0a68e21..f3778d115ae 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partArrayAnnotation.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partArrayAnnotation.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partList.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partList.yaml index f1621bf6077..fa6ee7c088e 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partList.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partList.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partListAnnotation.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partListAnnotation.yaml index 1adfd9ac37a..d1a7d0745cb 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partListAnnotation.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/partListAnnotation.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/rawJsonStringMethod.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/rawJsonStringMethod.yaml index 94c17f0b76c..e0370226f5e 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/rawJsonStringMethod.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/rawJsonStringMethod.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/requestMappingHttpMethod.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/requestMappingHttpMethod.yaml index aea11665029..c14b12469e2 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/requestMappingHttpMethod.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/requestMappingHttpMethod.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.ClassMethodNoHttpMethod" - x-java-interface: "gen.cse.ms.ut.ClassMethodNoHttpMethodIntf" basePath: "/c" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/responseEntity.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/responseEntity.yaml index 8784ffedb20..619e5976d54 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/responseEntity.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/responseEntity.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.MethodResponseEntity" - x-java-interface: "gen.cse.ms.ut.MethodResponseEntityIntf" basePath: "/MethodResponseEntity" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/swaggerTestTarget.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/swaggerTestTarget.yaml index e36b6ce1eab..9e88d8acc9b 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/swaggerTestTarget.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/swaggerTestTarget.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.model.SwaggerTestTarget" - x-java-interface: "gen.cse.ms.ut.SwaggerTestTargetIntf" basePath: "/test" consumes: - "text/plain" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/swaggerTestTarget_ValueOverWritePath.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/swaggerTestTarget_ValueOverWritePath.yaml index cb59f48c26c..a2fd8f574f4 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/swaggerTestTarget_ValueOverWritePath.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/swaggerTestTarget_ValueOverWritePath.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.model.SwaggerTestTarget_ValueOverWritePath" - x-java-interface: "gen.cse.ms.ut.SwaggerTestTarget_ValueOverWritePathIntf" basePath: "/testValue" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testBlankMediaType.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testBlankMediaType.yaml index 242dec4d4ce..653591b4af5 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testBlankMediaType.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testBlankMediaType.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.model.TestProducer" - x-java-interface: "gen.cse.ms.ut.TestProducerIntf" basePath: "/" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testCompletableFutureResponseEntityOptional.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testCompletableFutureResponseEntityOptional.yaml index d7970e1a099..a89e23de159 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testCompletableFutureResponseEntityOptional.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testCompletableFutureResponseEntityOptional.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testMultipleMediaType.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testMultipleMediaType.yaml index 4ecccae23fb..c5df7b89152 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testMultipleMediaType.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testMultipleMediaType.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.model.TestProducer" - x-java-interface: "gen.cse.ms.ut.TestProducerIntf" basePath: "/" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testObjectParam.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testObjectParam.yaml index a28f15bfc84..8a7be3bfd53 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testObjectParam.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testObjectParam.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.model.DefaultParameterSchema" - x-java-interface: "gen.cse.ms.ut.DefaultParameterSchemaIntf" basePath: "/" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testResponseEntityOptional.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testResponseEntityOptional.yaml index b31a1731ff3..87c25ff204e 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testResponseEntityOptional.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testResponseEntityOptional.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.Echo" - x-java-interface: "gen.cse.ms.ut.EchoIntf" basePath: "/Echo" consumes: - "a" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testSimpleParam.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testSimpleParam.yaml index a6406ff606d..68a6eccf32d 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testSimpleParam.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testSimpleParam.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.model.DefaultParameterSchema" - x-java-interface: "gen.cse.ms.ut.DefaultParameterSchemaIntf" basePath: "/" consumes: - "application/json" diff --git a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testSingleMediaType.yaml b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testSingleMediaType.yaml index 47f8bd011c4..658d1b80eb2 100644 --- a/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testSingleMediaType.yaml +++ b/swagger/swagger-generator/generator-springmvc/src/test/resources/schemas/testSingleMediaType.yaml @@ -20,7 +20,6 @@ swagger: "2.0" info: version: "1.0.0" title: "swagger definition for org.apache.servicecomb.swagger.generator.springmvc.model.TestProducer" - x-java-interface: "gen.cse.ms.ut.TestProducerIntf" basePath: "/" consumes: - "application/json" diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/AbstractArgumentsMapperCreator.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/AbstractArgumentsMapperCreator.java index e85e4239887..42e5e58db64 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/AbstractArgumentsMapperCreator.java +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/AbstractArgumentsMapperCreator.java @@ -28,9 +28,7 @@ import org.apache.servicecomb.swagger.SwaggerUtils; import org.apache.servicecomb.swagger.generator.core.model.SwaggerOperation; -import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.SerializationConfig; -import com.fasterxml.jackson.databind.type.TypeFactory; import io.swagger.models.parameters.BodyParameter; import io.swagger.models.parameters.Parameter; @@ -91,6 +89,10 @@ * */ public abstract class AbstractArgumentsMapperCreator { + protected int notProcessedSwaggerParamIdx = 0; + + protected boolean isSwaggerBodyField = false; + protected SerializationConfig serializationConfig; // key is context class @@ -159,28 +161,28 @@ protected void doCreateArgumentsMapper() { java.lang.reflect.Parameter[] providerParameters = providerMethod.getParameters(); for (int providerParamIdx = 0; providerParamIdx < providerParameters.length; providerParamIdx++) { java.lang.reflect.Parameter providerParameter = providerParameters[providerParamIdx]; - if (processContextParameter(providerParamIdx, providerParameter)) { + if (processContextParameter(providerParameter)) { continue; } String parameterName = collectParameterName(providerParameter); if (processKnownParameter(providerParamIdx, providerParameter, parameterName)) { processedSwaggerParamters.add(parameterName); + notProcessedSwaggerParamIdx++; continue; } if (processSwaggerBodyField(providerParamIdx, providerParameter, parameterName)) { processedSwaggerParamters.add(parameterName); + isSwaggerBodyField = true; continue; } - JavaType providerType = TypeFactory.defaultInstance().constructType(providerParameter.getParameterizedType()); - if (SwaggerUtils.isBean(providerType)) { - processBeanParameter(providerParamIdx, providerParameter); + if (processBeanParameter(providerParamIdx, providerParameter)) { continue; } - processUnknownParameter(parameterName); + processUnknownParameter(providerParamIdx, providerParameter, parameterName); } for (Parameter parameter : swaggerParameters) { @@ -192,18 +194,17 @@ protected void doCreateArgumentsMapper() { /** * - * @param providerParamIdx * @param providerParameter processing provider parameter * @return true means processed */ - protected boolean processContextParameter(int providerParamIdx, java.lang.reflect.Parameter providerParameter) { + protected boolean processContextParameter(java.lang.reflect.Parameter providerParameter) { ContextArgumentMapperFactory contextFactory = contextFactorys.get(providerParameter.getType()); if (contextFactory == null) { return false; } mappers.add(contextFactory - .create(this.providerMethod.getParameters()[providerParamIdx].getName(), providerParameter.getName())); + .create(providerParameter.getName(), providerParameter.getName())); return true; } @@ -262,9 +263,10 @@ protected abstract ArgumentMapper createSwaggerBodyFieldMapper(int providerParam * @param providerParamIdx * @param providerParameter processing provider parameter */ - protected abstract void processBeanParameter(int providerParamIdx, java.lang.reflect.Parameter providerParameter); + protected abstract boolean processBeanParameter(int providerParamIdx, java.lang.reflect.Parameter providerParameter); - protected abstract void processUnknownParameter(String parameterName); + protected abstract void processUnknownParameter(int providerParamIdx, java.lang.reflect.Parameter providerParameter, + String parameterName); protected abstract void processPendingSwaggerParameter(Parameter parameter); } diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ClientWebSocketArgumentMapper.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ClientWebSocketArgumentMapper.java new file mode 100644 index 00000000000..b2fa71f88bc --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ClientWebSocketArgumentMapper.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.arguments.consumer; + +import java.util.Map; + +import org.apache.servicecomb.swagger.invocation.SwaggerInvocation; +import org.apache.servicecomb.swagger.invocation.ws.ClientWebSocket; + +/** + * Mapping {@link org.apache.servicecomb.swagger.invocation.ws.ClientWebSocket} as RPC parameter. + */ +public class ClientWebSocketArgumentMapper extends ConsumerArgumentMapper { + public static final String SCB_CLIENT_WEBSOCKET_LOCAL_CONTEXT_KEY = "scb-client-websocket"; + + private final String invocationArgumentName; + + public ClientWebSocketArgumentMapper(String invocationArgumentName) { + this.invocationArgumentName = invocationArgumentName; + } + + @Override + public void invocationArgumentToSwaggerArguments(SwaggerInvocation swaggerInvocation, + Map swaggerArguments, Map invocationArguments) { + final ClientWebSocket clientWebSocket = (ClientWebSocket) invocationArguments.get(invocationArgumentName); + swaggerInvocation.addLocalContext(SCB_CLIENT_WEBSOCKET_LOCAL_CONTEXT_KEY, clientWebSocket); + } +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ClientWebSocketArgumentMapperFactory.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ClientWebSocketArgumentMapperFactory.java new file mode 100644 index 00000000000..e77fd7d2636 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ClientWebSocketArgumentMapperFactory.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.arguments.consumer; + +import org.apache.servicecomb.swagger.invocation.arguments.ArgumentMapper; +import org.apache.servicecomb.swagger.invocation.ws.ClientWebSocket; + +/** + * Factory for {@link ClientWebSocketArgumentMapper}. + */ +public class ClientWebSocketArgumentMapperFactory implements ConsumerContextArgumentMapperFactory { + + @Override + public Class getContextClass() { + return ClientWebSocket.class; + } + + @Override + public ArgumentMapper create(String invocationArgumentName, String swaggerArgumentName) { + return new ClientWebSocketArgumentMapper(invocationArgumentName); + } +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ConsumerArgumentsMapperCreator.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ConsumerArgumentsMapperCreator.java index d74965c4269..838a361e937 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ConsumerArgumentsMapperCreator.java +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ConsumerArgumentsMapperCreator.java @@ -23,6 +23,7 @@ import java.util.Map; import org.apache.servicecomb.foundation.common.utils.LambdaMetafactoryUtils; +import org.apache.servicecomb.swagger.SwaggerUtils; import org.apache.servicecomb.swagger.generator.core.model.SwaggerOperation; import org.apache.servicecomb.swagger.invocation.arguments.AbstractArgumentsMapperCreator; import org.apache.servicecomb.swagger.invocation.arguments.ArgumentMapper; @@ -75,7 +76,25 @@ && isAllSameMapper()) { } @Override - protected void processUnknownParameter(String parameterName) { + protected void processUnknownParameter(int providerParamIdx, java.lang.reflect.Parameter providerParameter, + String parameterName) { + + // Make best guess, use the index of swagger to invoke server. + // For compatible to old version behavior + if (!isSwaggerBodyField && notProcessedSwaggerParamIdx < swaggerParameters.size()) { + Parameter parameter = swaggerParameters.get(notProcessedSwaggerParamIdx); + if (parameter != null) { + ArgumentMapper mapper = createKnownParameterMapper(providerParamIdx, notProcessedSwaggerParamIdx); + mappers.add(mapper); + processedSwaggerParamters.add(parameterName); + notProcessedSwaggerParamIdx++; + LOGGER.warn("Old consumer invoke new version producer, parameter({}) is not exist in contract, method={}:{}." + + " Please change consumer parameter name to match swagger.", + parameterName, providerMethod.getDeclaringClass().getName(), providerMethod.getName()); + return; + } + } + // real unknown parameter, new consumer invoke old producer, just ignore this parameter LOGGER.warn("new consumer invoke old version producer, parameter({}) is not exist in contract, method={}:{}.", parameterName, providerMethod.getDeclaringClass().getName(), providerMethod.getName()); @@ -101,7 +120,12 @@ protected ArgumentMapper createSwaggerBodyFieldMapper(int consumerParamIdx, Stri } @Override - protected void processBeanParameter(int consumerParamIdx, java.lang.reflect.Parameter consumerParameter) { + protected boolean processBeanParameter(int consumerParamIdx, java.lang.reflect.Parameter consumerParameter) { + JavaType providerType = TypeFactory.defaultInstance().constructType(consumerParameter.getParameterizedType()); + if (!SwaggerUtils.isBean(providerType)) { + return false; + } + boolean result = false; ConsumerBeanParamMapper mapper = new ConsumerBeanParamMapper( this.providerMethod.getParameters()[consumerParamIdx].getName()); JavaType consumerType = TypeFactory.defaultInstance().constructType(consumerParameter.getParameterizedType()); @@ -118,7 +142,9 @@ protected void processBeanParameter(int consumerParamIdx, java.lang.reflect.Para mapper.addField(parameterName, LambdaMetafactoryUtils.createObjectGetter(propertyDefinition)); processedSwaggerParamters.add(parameterName); + result = true; } mappers.add(mapper); + return result; } } diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerArgumentsMapperCreator.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerArgumentsMapperCreator.java index 852f008a85a..1aa3a225189 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerArgumentsMapperCreator.java +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerArgumentsMapperCreator.java @@ -26,6 +26,7 @@ import java.util.Map; import org.apache.servicecomb.foundation.common.utils.LambdaMetafactoryUtils; +import org.apache.servicecomb.swagger.SwaggerUtils; import org.apache.servicecomb.swagger.generator.core.model.SwaggerOperation; import org.apache.servicecomb.swagger.invocation.arguments.AbstractArgumentsMapperCreator; import org.apache.servicecomb.swagger.invocation.arguments.ArgumentMapper; @@ -36,8 +37,16 @@ import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; import com.fasterxml.jackson.databind.type.TypeFactory; import com.google.common.reflect.TypeToken; +import com.netflix.config.DynamicPropertyFactory; + +import io.swagger.models.parameters.SerializableParameter; public class ProducerArgumentsMapperCreator extends AbstractArgumentsMapperCreator { + // using swagger type instead of method type for primitive method types when deserialize + // query/header/etc. parameters. + private final boolean useWrapperTypeForPrimitives = DynamicPropertyFactory.getInstance() + .getBooleanProperty("servicecomb.rest.parameter.useWrapperTypeForPrimitives", false).get(); + // swagger parameter types relate to producer // because features of @BeanParam/query, and rpc mode parameter wrapper // types is not always equals to producerMethod parameter types directly @@ -61,7 +70,8 @@ public ProducerArgumentsMapper createArgumentsMapper() { } @Override - protected void processUnknownParameter(String parameterName) { + protected void processUnknownParameter(int providerParamIdx, java.lang.reflect.Parameter providerParameter, + String parameterName) { throw new IllegalStateException(String .format("failed to find producer parameter in contract, method=%s:%s, parameter name=%s.", providerMethod.getDeclaringClass().getName(), providerMethod.getName(), parameterName)); @@ -69,7 +79,7 @@ protected void processUnknownParameter(String parameterName) { @Override protected void processPendingSwaggerParameter(io.swagger.models.parameters.Parameter parameter) { - swaggerParameterTypes.put(parameter.getName(), Object.class); + swaggerParameterTypes.put(parameter.getName(), swaggerTypes(parameter, Object.class)); } @Override @@ -78,11 +88,37 @@ protected ArgumentMapper createKnownParameterMapper(int providerParamIdx, Intege Type providerType = TypeToken.of(providerClass) .resolveType(providerMethod.getGenericParameterTypes()[providerParamIdx]) .getType(); - swaggerParameterTypes - .put(swaggerArgumentName, providerType); + if (useWrapperTypeForPrimitives && providerType instanceof Class && ((Class) providerType).isPrimitive()) { + // add swagger type to java type + providerType = swaggerTypes(swaggerParameters.get(swaggerIdx), providerType); + } + swaggerParameterTypes.put(swaggerArgumentName, providerType); return new ProducerArgumentSame(providerMethod.getParameters()[providerParamIdx].getName(), swaggerArgumentName); } + private Type swaggerTypes(io.swagger.models.parameters.Parameter parameter, Type defaultType) { + if (!(parameter instanceof SerializableParameter)) { + return defaultType; + } + SerializableParameter serializableParameter = (SerializableParameter) parameter; + switch (serializableParameter.getType()) { + case "integer": + if ("int64".equalsIgnoreCase(serializableParameter.getFormat())) { + return Long.class; + } + return Integer.class; + case "number": + if ("double".equalsIgnoreCase(serializableParameter.getFormat())) { + return Double.class; + } + return Float.class; + case "boolean": + return Boolean.class; + default: + return defaultType; + } + } + @Override protected ArgumentMapper createSwaggerBodyFieldMapper(int producerParamIdx, String parameterName, int swaggerBodyIdx) { @@ -97,7 +133,11 @@ protected ArgumentMapper createSwaggerBodyFieldMapper(int producerParamIdx, Stri } @Override - protected void processBeanParameter(int producerParamIdx, Parameter producerParameter) { + protected boolean processBeanParameter(int producerParamIdx, Parameter producerParameter) { + JavaType providerType = TypeFactory.defaultInstance().constructType(producerParameter.getParameterizedType()); + if (!SwaggerUtils.isBean(providerType)) { + return false; + } ProducerBeanParamMapper mapper = new ProducerBeanParamMapper( providerMethod.getParameters()[producerParamIdx].getName(), producerParameter.getType()); JavaType producerType = TypeFactory.defaultInstance().constructType(producerParameter.getParameterizedType()); @@ -117,5 +157,6 @@ protected void processBeanParameter(int producerParamIdx, Parameter producerPara processedSwaggerParamters.add(parameterName); } mappers.add(mapper); + return true; } } diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/DefaultExceptionToProducerResponseConverters.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/DefaultExceptionToProducerResponseConverters.java new file mode 100644 index 00000000000..8f307460dc4 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/DefaultExceptionToProducerResponseConverters.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.exception; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils; +import org.apache.servicecomb.swagger.invocation.Response; +import org.apache.servicecomb.swagger.invocation.SwaggerInvocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultExceptionToProducerResponseConverters implements ExceptionToProducerResponseConverters { + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExceptionToProducerResponseConverters.class); + + private final Map, ExceptionToProducerResponseConverter> exceptionToProducerResponseConverters = + new HashMap<>(); + + private ExceptionToProducerResponseConverter defaultConverter; + + /** + * Load the {@link ExceptionToProducerResponseConverter} implementations. Ensure that those converters whose {@link ExceptionToProducerResponseConverter#getOrder()} + * return smaller value takes higher priority. + */ + @SuppressWarnings("unchecked") + public DefaultExceptionToProducerResponseConverters() { + SPIServiceUtils.getSortedService(ExceptionToProducerResponseConverter.class).forEach(converter -> { + if (converter.getExceptionClass() == null) { + if (defaultConverter == null) { + defaultConverter = converter; + } + return; + } + + exceptionToProducerResponseConverters.putIfAbsent(converter.getExceptionClass(), converter); + }); + } + + @Override + public Response convertExceptionToResponse(SwaggerInvocation swaggerInvocation, Throwable e) { + ExceptionToProducerResponseConverter converter = null; + Class clazz = e.getClass(); + while (converter == null) { + converter = exceptionToProducerResponseConverters.get(clazz); + if (clazz == Throwable.class) { + break; + } + clazz = clazz.getSuperclass(); + } + if (converter == null) { + converter = defaultConverter; + } + try { + return converter.convert(swaggerInvocation, e); + } catch (Throwable throwable) { + // In case users do not implement correctly and maybe discovered at runtime to cause asycResponse callback hang. + LOGGER + .error("ExceptionToProducerResponseConverter {} cannot throw exception, please fix it.", converter.getClass(), + throwable); + return Response.failResp(swaggerInvocation.getInvocationType(), e); + } + } +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/ExceptionFactory.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/ExceptionFactory.java index eab0afdcfa5..8f9c37b567f 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/ExceptionFactory.java +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/ExceptionFactory.java @@ -22,6 +22,7 @@ import javax.ws.rs.core.Response.StatusType; +import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils; import org.apache.servicecomb.swagger.invocation.Response; import org.apache.servicecomb.swagger.invocation.SwaggerInvocation; import org.apache.servicecomb.swagger.invocation.context.HttpStatus; @@ -46,7 +47,8 @@ public final class ExceptionFactory { public static final String CONSUMER_INNER_REASON_PHRASE = "Unexpected consumer error, please check logs for details"; - private static final ExceptionToProducerResponseConverters exceptionToProducerResponseConverters = new ExceptionToProducerResponseConverters(); + private static final ExceptionToProducerResponseConverters exceptionToProducerResponseConverters + = SPIServiceUtils.getPriorityHighestService(ExceptionToProducerResponseConverters.class); public static final StatusType CONSUMER_INNER_STATUS = new HttpStatus(CONSUMER_INNER_STATUS_CODE, CONSUMER_INNER_REASON_PHRASE); @@ -72,12 +74,12 @@ public static InvocationException createProducerException(Object errorData) { } private static InvocationException doCreate(StatusType status, - Object errorData) { + Object errorData) { return new InvocationException(status, errorData); } private static InvocationException doCreate(int statusCode, String reasonPhrase, CommonExceptionData data, - Throwable e) { + Throwable e) { return new InvocationException(statusCode, reasonPhrase, data, e); } @@ -113,7 +115,7 @@ public static InvocationException convertProducerException(Throwable e, String e // 则需要创建新的InvocationException实例,此时不允许使用e.getMessage,因为可能会涉及关键信息被传给远端 // 新创建的InvocationException,会使用errorMsg来构建CommonExceptionData private static InvocationException convertException(int statusCode, String reasonPhrase, Throwable e, - String errorMsg) { + String errorMsg) { e = unwrap(e); if (e instanceof InvocationException) { diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/ExceptionToProducerResponseConverters.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/ExceptionToProducerResponseConverters.java index e5b307733f2..5ac627a6710 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/ExceptionToProducerResponseConverters.java +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/ExceptionToProducerResponseConverters.java @@ -16,62 +16,13 @@ */ package org.apache.servicecomb.swagger.invocation.exception; -import java.util.HashMap; -import java.util.Map; - -import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils; import org.apache.servicecomb.swagger.invocation.Response; import org.apache.servicecomb.swagger.invocation.SwaggerInvocation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ExceptionToProducerResponseConverters { - private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionToProducerResponseConverters.class); - - private final Map, ExceptionToProducerResponseConverter> exceptionToProducerResponseConverters = - new HashMap<>(); - private ExceptionToProducerResponseConverter defaultConverter; - - /** - * Load the {@link ExceptionToProducerResponseConverter} implementations. Ensure that those converters whose {@link ExceptionToProducerResponseConverter#getOrder()} - * return smaller value takes higher priority. - */ - @SuppressWarnings("unchecked") - public ExceptionToProducerResponseConverters() { - SPIServiceUtils.getSortedService(ExceptionToProducerResponseConverter.class).forEach(converter -> { - if (converter.getExceptionClass() == null) { - if (defaultConverter == null) { - defaultConverter = converter; - } - return; - } - - exceptionToProducerResponseConverters.putIfAbsent(converter.getExceptionClass(), converter); - }); - } +public interface ExceptionToProducerResponseConverters { + Response convertExceptionToResponse(SwaggerInvocation swaggerInvocation, Throwable e); - public Response convertExceptionToResponse(SwaggerInvocation swaggerInvocation, Throwable e) { - ExceptionToProducerResponseConverter converter = null; - Class clazz = e.getClass(); - while (converter == null) { - converter = exceptionToProducerResponseConverters.get(clazz); - if (clazz == Throwable.class) { - break; - } - clazz = clazz.getSuperclass(); - } - if (converter == null) { - converter = defaultConverter; - } - try { - return converter.convert(swaggerInvocation, e); - } catch (Throwable throwable) { - // In case users do not implement correctly and maybe discovered at runtime to cause asycResponse callback hang. - LOGGER - .error("ExceptionToProducerResponseConverter {} cannot throw exception, please fix it.", converter.getClass(), - throwable); - return Response.failResp(swaggerInvocation.getInvocationType(), e); - } + default int getOrder() { + return 0; } } diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/AbstractBaseWebSocket.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/AbstractBaseWebSocket.java new file mode 100644 index 00000000000..5ecee0cd168 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/AbstractBaseWebSocket.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +import java.util.concurrent.CompletableFuture; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.handler.codec.http.websocketx.WebSocketCloseStatus; + +/** + * AbstractBaseWebSocket + */ +public abstract class AbstractBaseWebSocket implements WebSocket { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractBaseWebSocket.class); + + private WebSocketAdapter webSocketAdapter; + + private Status status = Status.CREATED; + + private CompletableFuture closeFuture; + + private Short closeStatusCode; + + private String closeReason; + + @Override + public CompletableFuture sendMessage(WebSocketMessage message) { + return webSocketAdapter.sendMessage(message); + } + + @Override + public CompletableFuture sendFrame(WebSocketFrame frame) { + return webSocketAdapter.sendFrame(frame); + } + + @Override + public CompletableFuture close() { + return this.close((short) WebSocketCloseStatus.NORMAL_CLOSURE.code(), + WebSocketCloseStatus.NORMAL_CLOSURE.reasonText()); + } + + @Override + public CompletableFuture close(Short closeStatusCode, String closeReason) { + if (closeStatusCode == null || closeReason == null) { + LOGGER.error("a WebSocket is closed by null closeStatusCode or closeReason"); + closeStatusCode = (short) WebSocketCloseStatus.INTERNAL_SERVER_ERROR.code(); + closeReason = WebSocketCloseStatus.INTERNAL_SERVER_ERROR.reasonText(); + } + synchronized (this) { + if (status == Status.WAITING_TO_CLOSE || status == Status.CLOSING || status == Status.CLOSED) { + return CompletableFuture.completedFuture(null); + } + status = Status.WAITING_TO_CLOSE; + this.closeStatusCode = closeStatusCode; + this.closeReason = closeReason; + if (webSocketAdapter == null) { + // the case that close when WebSocket still not complete handshake + closeFuture = new CompletableFuture<>(); + return closeFuture; + } + status = Status.CLOSING; + } + return webSocketAdapter.close(closeStatusCode, closeReason) + .whenComplete((v, t) -> status = Status.CLOSED); + } + + @Override + public void pause() { + status = Status.PAUSED; + webSocketAdapter.pause(); + } + + @Override + public void resume() { + status = Status.RUNNING; + webSocketAdapter.resume(); + } + + /** + * Check whether the message sending queue is full, in which case you should pause your sending action. + * To get to know when to recover sending action, please override the {@link #onWriteQueueDrain()} method + * to subscribe the notification that the message sending queue is ready to accept message again. + * + * @return true if message sending queue is full. + */ + @Override + public boolean writeQueueFull() { + return webSocketAdapter.writeQueueFull(); + } + + /** + * The callback to notify when the message sending queue is ready to accept sending message/frame again. + * Usually this method is used in conjunction with the {@link #writeQueueFull()}. + */ + @Override + public void onWriteQueueDrain() { + } + + public void setWebSocketAdapter(WebSocketAdapter webSocketAdapter) { + this.webSocketAdapter = webSocketAdapter; + } + + public void startWorking() { + synchronized (this) { + if (status == Status.WAITING_TO_CLOSE) { + status = Status.CLOSING; + webSocketAdapter.close(closeStatusCode, closeReason) + .whenComplete((v, t) -> { + status = Status.CLOSED; + if (t != null) { + closeFuture.completeExceptionally(t); + } else { + closeFuture.complete(null); + } + }); + return; + } + status = Status.RUNNING; + } + onConnectionReady(); + } + + @Override + public Status getStatus() { + return status; + } +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/BinaryBytesWebSocketMessage.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/BinaryBytesWebSocketMessage.java new file mode 100644 index 00000000000..42d7c34d2cd --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/BinaryBytesWebSocketMessage.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +public class BinaryBytesWebSocketMessage extends WebSocketMessage { + public BinaryBytesWebSocketMessage(byte[] payload) { + setPayload(payload); + } +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/ClientWebSocket.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/ClientWebSocket.java new file mode 100644 index 00000000000..aa5b77350f5 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/ClientWebSocket.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +/** + * WebSocket interface on client side. + */ +public abstract class ClientWebSocket extends AbstractBaseWebSocket { +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/SerialExecutorWrapper.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/SerialExecutorWrapper.java new file mode 100644 index 00000000000..576d0fd9872 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/SerialExecutorWrapper.java @@ -0,0 +1,223 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.servicecomb.swagger.invocation.InvocationType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SerialExecutorWrapper implements Executor { + + private static final Logger LOGGER = LoggerFactory.getLogger(SerialExecutorWrapper.class); + + private static final int LEAST_QUEUE_CAPACITY = 100; + + private final InvocationType invocationType; + + private final String id; + + private final Queue queue; + + private final Executor workerPool; + + private final int queueCapacity; + + private final int drainThreshold; + + private final int fullThreshold; + + private final AtomicBoolean scheduleFlag; + + private final int maxContinueTimes; + + private QueueDrainSubscriber queueDrainSubscriber; + + private QueueFullSubscriber queueFullSubscriber; + + public SerialExecutorWrapper( + InvocationType invocationType, + String id, Executor workerPool, int queueCapacity, int maxContinueTimes) { + this.invocationType = invocationType; + this.id = id; + this.workerPool = workerPool; + this.queueCapacity = correctQueueCapacity(queueCapacity); + queue = new ArrayBlockingQueue<>(calculateRealQueueSize(), true); + drainThreshold = calculateDrainThreshold(); + fullThreshold = calculateFullThreshold(); + scheduleFlag = new AtomicBoolean(); + this.maxContinueTimes = correctMaxContinueTimes(maxContinueTimes); + } + + /** + * Subscribe queue drain event to resume message queue. + * Use this method in cooperation with {@link #subscribeQueueFullEvent(QueueFullSubscriber)} method. + */ + public void subscribeQueueDrainEvent(QueueDrainSubscriber subscriber) { + if (subscriber != null) { + queueDrainSubscriber = subscriber; + } + } + + /** + * Subscribe queue full event to pause message queue. + * Use this method in cooperation with {@link #subscribeQueueDrainEvent(QueueDrainSubscriber)} method. + */ + public void subscribeQueueFullEvent(QueueFullSubscriber subscriber) { + if (subscriber != null) { + queueFullSubscriber = subscriber; + } + } + + @Override + public void execute(Runnable command) { + Objects.requireNonNull(command, "command must not be null"); + Objects.requireNonNull(queueDrainSubscriber, "queueDrainSubscriber must not be null"); + queue.add(command); + markQueueFull(); + scheduleWorker(); + } + + private void scheduleWorker() { + if (!scheduleFlag.compareAndSet(false, true)) { + // a running task has been set, don't repeat + return; + } + try { + workerPool.execute(() -> { + try { + runTasks(); + } finally { + scheduleFlag.set(false); + if (!queue.isEmpty()) { + scheduleWorker(); + } + } + }); + } catch (Throwable e) { + // in case that the underlying executor queue full, the scheduleFlag should be recovered + scheduleFlag.set(false); + LOGGER.error("[{}]-[{}] failed to execute task in actual thread pool!", invocationType, id, e); + } + } + + private void runTasks() { + int workCount = 1; + while (true) { + final Runnable task = queue.poll(); + if (task == null) { + break; + } + + try { + task.run(); + } catch (Throwable e) { + LOGGER.error("[{}]-[{}] error occurred while executing task[{}]", invocationType, id, task, e); + } + + ++workCount; + if (workCount > maxContinueTimes) { + break; + } + } + + notifyIfQueueDrain(); + } + + private void markQueueFull() { + final QueueFullSubscriber subscriber = queueFullSubscriber; + if (subscriber == null) { + return; + } + + if ((queue.size() < fullThreshold)) { + return; + } + + LOGGER.warn("[{}]-[{}] queue nearly full! queue length: {}/{}", + invocationType, id, queue.size(), calculateRealQueueSize()); + try { + subscriber.run(); + } catch (Throwable e) { + LOGGER.error("[{}]-[{}] error occurred while notifying queue full subscriber[{}]", invocationType, id, + subscriber, e); + } + if (queue.size() < drainThreshold) { + // in case that all tasks has been completed and WebSocket is paused, + // and no task to trigger the WebSocket get resumed + execute(this::notifyIfQueueDrain); + } + } + + private void notifyIfQueueDrain() { + final QueueDrainSubscriber subscriber = queueDrainSubscriber; + if (subscriber == null) { + return; + } + if (queue.size() > drainThreshold) { + return; + } + try { + subscriber.run(); + } catch (Throwable e) { + LOGGER.error("[{}]-[{}] error occurred while notifying queue drain subscriber[{}]", invocationType, id, + subscriber, e); + } + } + + private int calculateRealQueueSize() { + return (int) (queueCapacity * 1.2); + } + + private int calculateDrainThreshold() { + return (int) (queueCapacity * 0.25); + } + + private int calculateFullThreshold() { + return queueCapacity; + } + + private int correctQueueCapacity(int queueCapacity) { + if (queueCapacity < LEAST_QUEUE_CAPACITY) { + LOGGER.warn("queue capacity less than 10 does not make sense, adjust to {}", LEAST_QUEUE_CAPACITY); + return LEAST_QUEUE_CAPACITY; + } + return queueCapacity; + } + + private int correctMaxContinueTimes(int maxContinueTimes) { + if (maxContinueTimes < 1) { + LOGGER.warn("maxContinueTimes less than 1 does not make sense, adjust to 1"); + return 1; + } + return maxContinueTimes; + } + + public interface QueueDrainSubscriber { + void run(); + } + + public interface QueueFullSubscriber { + void run(); + } +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/ServerWebSocket.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/ServerWebSocket.java new file mode 100644 index 00000000000..be7845faf58 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/ServerWebSocket.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +/** + * WebSocket interface on server side. + */ +public abstract class ServerWebSocket extends AbstractBaseWebSocket { +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/ServerWebSocketResponseProcessor.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/ServerWebSocketResponseProcessor.java new file mode 100644 index 00000000000..8a823b1f161 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/ServerWebSocketResponseProcessor.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +import java.lang.reflect.Type; + +import org.apache.servicecomb.swagger.generator.OperationGenerator; +import org.apache.servicecomb.swagger.generator.SwaggerGenerator; +import org.apache.servicecomb.swagger.generator.core.processor.response.DefaultResponseTypeProcessor; + +/** + * ResponseProcessor for {@link ServerWebSocket}. + */ +public class ServerWebSocketResponseProcessor extends DefaultResponseTypeProcessor { + @Override + public Class getProcessType() { + return ServerWebSocket.class; + } + + @Override + public Type extractResponseType(SwaggerGenerator swaggerGenerator, OperationGenerator operationGenerator, + Type genericResponseType) { + return Void.class; + } +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/TextWebSocketMessage.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/TextWebSocketMessage.java new file mode 100644 index 00000000000..69190510ec8 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/TextWebSocketMessage.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +/** + * TextWebSocketMessage + */ +public class TextWebSocketMessage extends WebSocketMessage { + public TextWebSocketMessage(String payload) { + setPayload(payload); + } +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocket.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocket.java new file mode 100644 index 00000000000..e46af5c7360 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocket.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +import java.util.concurrent.CompletableFuture; + +/** + * The WebSocket session interface. This is the root WebSocket type, + * and any concrete implementation class should not inherit from this type directly. + * Please inherit from {@link ServerWebSocket} and {@link ClientWebSocket} instead. + */ +public interface WebSocket { + default void onConnectionReady() { + onOpen(); + resume(); + } + + void onOpen(); + + void onMessage(WebSocketMessage message); + + default void onFrame(WebSocketFrame frame) { + } + + void onError(Throwable t); + + void onClose(Short closeStatusCode, String closeReason); + + CompletableFuture sendMessage(WebSocketMessage message); + + CompletableFuture sendFrame(WebSocketFrame frame); + + CompletableFuture close(); + + CompletableFuture close(Short closeStatusCode, String closeReason); + + void pause(); + + void resume(); + + boolean writeQueueFull(); + + void onWriteQueueDrain(); + + Status getStatus(); + + enum Status { + CREATED, + RUNNING, + PAUSED, + WAITING_TO_CLOSE, + CLOSING, + CLOSED + } +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketActionType.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketActionType.java new file mode 100644 index 00000000000..07d45bc4873 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketActionType.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +/** + * The action types performed by {@link WebSocketAdapter} that the users may be aware of. + */ +public enum WebSocketActionType { + CONNECTION_PREPARE, + ON_OPEN, + ON_MESSAGE_TEXT, + ON_MESSAGE_BINARY, + ON_FRAME, + ON_SEND_QUEUE_DRAIN, + ON_ERROR, + ON_CLOSE, + DO_CLOSE, + DO_PAUSE, + DO_RESUME, + DO_SEND_TEXT, + DO_SEND_BINARY; +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketAdapter.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketAdapter.java new file mode 100644 index 00000000000..dc65e333a68 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketAdapter.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +import java.util.concurrent.CompletableFuture; + +public interface WebSocketAdapter { + + CompletableFuture sendMessage(WebSocketMessage message); + + CompletableFuture sendFrame(WebSocketFrame frame); + + CompletableFuture close(short statusCode, String reason); + + void pause(); + + void resume(); + + boolean writeQueueFull(); +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketFrame.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketFrame.java new file mode 100644 index 00000000000..7b69c5854d1 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketFrame.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +/** + * WebSocketFrame. + * There should be such a concept, but not need to supply such function for now. + */ +public class WebSocketFrame { +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketFrameType.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketFrameType.java new file mode 100644 index 00000000000..a508157a9d2 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketFrameType.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +/** + * Type of WebSocketFrame. Refer to the WebSocket protocol Opcode. + */ +public enum WebSocketFrameType { +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketMessage.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketMessage.java new file mode 100644 index 00000000000..b50a0ac69c9 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketMessage.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +/** + * WebSocket Message root class. + */ +public abstract class WebSocketMessage { + private T payload; + + public T getPayload() { + return payload; + } + + public void setPayload(T payload) { + this.payload = payload; + } +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketPipe.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketPipe.java new file mode 100644 index 00000000000..1e4d06f1a95 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/ws/WebSocketPipe.java @@ -0,0 +1,299 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +import java.util.concurrent.CompletableFuture; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.handler.codec.http.websocketx.WebSocketCloseStatus; + +/** + * A pipe that connecting server and client side {@link WebSocket}s. + */ +public class WebSocketPipe { + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketPipe.class); + + private final String websocketSessionId; + + private final PipeServerWebSocket serverWebSocket; + + private final PipeClientWebSocket clientWebSocket; + + /** + * There is chance that the server and client try to close connection at the same time. + * Use one lock to avoid the client and server websocket deadlock in closing procedure. + */ + private final Object statusLock = new Object(); + + public WebSocketPipe(String websocketSessionId) { + this.websocketSessionId = websocketSessionId; + this.clientWebSocket = new PipeClientWebSocket(); + this.serverWebSocket = new PipeServerWebSocket(); + this.clientWebSocket.connect(serverWebSocket); + this.serverWebSocket.connect(clientWebSocket); + } + + public ClientWebSocket getClientWebSocket() { + return clientWebSocket; + } + + public ServerWebSocket getServerWebSocket() { + return serverWebSocket; + } + + /** + * Be careful to adjust the order of the existing elements or to add new element! + * The order of status indicates the lifecycle sequence of a websocket connection. + */ + private enum PipeWebSocketStatus { + CREATED, + PEER_CONNECTED, + OPENED, + RUNNING, + ERROR, + CLOSING, + CLOSED + } + + private class PipeServerWebSocket extends ServerWebSocket { + PipeClientWebSocket peer; + + private PipeWebSocketStatus status = PipeWebSocketStatus.CREATED; + + void connect(PipeClientWebSocket peer) { + synchronized (statusLock) { + transferStatus(PipeWebSocketStatus.CREATED, PipeWebSocketStatus.PEER_CONNECTED); + this.peer = peer; + } + } + + @Override + public void onConnectionReady() { + pause(); + onOpen(); + } + + @Override + public void onOpen() { + synchronized (statusLock) { + transferStatus(PipeWebSocketStatus.PEER_CONNECTED, PipeWebSocketStatus.OPENED); + if (this.peer.status == PipeWebSocketStatus.OPENED) { + resume(); + transferStatus(PipeWebSocketStatus.OPENED, PipeWebSocketStatus.RUNNING); + peer.resume(); + peer.transferStatus(PipeWebSocketStatus.OPENED, PipeWebSocketStatus.RUNNING); + } + } + } + + @Override + public void onMessage(WebSocketMessage message) { + peer.sendMessage(message) + .whenComplete((v, t) -> { + if (t != null) { + LOGGER.error("[{}] failed to forward message", websocketSessionId, t); + } + }); + if (peer.writeQueueFull()) { + this.pause(); + LOGGER.debug("[{}] pipe paused, direction is server to client", websocketSessionId); + } + } + + @Override + public void onError(Throwable t) { + LOGGER.error("[{}] websocket error", websocketSessionId, t); + synchronized (statusLock) { + transferStatus(PipeWebSocketStatus.ERROR); + safelyClose((short) WebSocketCloseStatus.INTERNAL_SERVER_ERROR.code(), + WebSocketCloseStatus.INTERNAL_SERVER_ERROR.reasonText()); + } + } + + @Override + public void onClose(Short closeStatusCode, String closeReason) { + transferStatus(PipeWebSocketStatus.CLOSED); + safelyClose(closeStatusCode, closeReason); // should close peer + } + + @Override + public void onWriteQueueDrain() { + peer.resume(); + } + + @Override + public CompletableFuture close(Short closeStatusCode, String closeReason) { + synchronized (statusLock) { + if (status == PipeWebSocketStatus.CLOSING || status == PipeWebSocketStatus.CLOSED) { + return CompletableFuture.completedFuture(null); + } + + transferStatus(PipeWebSocketStatus.CLOSING); + if (status.ordinal() < PipeWebSocketStatus.RUNNING.ordinal()) { + transferStatus(PipeWebSocketStatus.CLOSED); + return CompletableFuture.completedFuture(null); + } + return super.close(closeStatusCode, closeReason) + .whenComplete((result, throwable) -> transferStatus(PipeWebSocketStatus.CLOSED)); + } + } + + private void transferStatus(PipeWebSocketStatus newStatus) { + synchronized (statusLock) { + this.status = newStatus; + } + } + + private void transferStatus(PipeWebSocketStatus expectedOldStatus, PipeWebSocketStatus newStatus) { + synchronized (statusLock) { + if (status != expectedOldStatus) { + safelyClose((short) WebSocketCloseStatus.INTERNAL_SERVER_ERROR.code(), + WebSocketCloseStatus.INTERNAL_SERVER_ERROR.reasonText()); + LOGGER.error("[{}] illegal state transfer: from {} to {}", websocketSessionId, expectedOldStatus, newStatus); + throw new IllegalStateException("Illegal state transfer: [" + + expectedOldStatus + + "] to [" + + newStatus + + "]"); + } + status = newStatus; + } + } + + private void safelyClose(Short closeStatusCode, String closeReason) { + try { + close(closeStatusCode, closeReason); + } catch (Throwable e) { + LOGGER.error("[{}] failed to close pipe server websocket", websocketSessionId, e); + } + if (peer == null) { + return; + } + try { + peer.close(closeStatusCode, closeReason); + } catch (Throwable e) { + LOGGER.error("[{}] failed to close pipe client websocket", websocketSessionId, e); + } + } + } + + private class PipeClientWebSocket extends ClientWebSocket { + PipeServerWebSocket peer; + + private PipeWebSocketStatus status = PipeWebSocketStatus.CREATED; + + void connect(PipeServerWebSocket peer) { + transferStatus(PipeWebSocketStatus.CREATED, PipeWebSocketStatus.PEER_CONNECTED); + this.peer = peer; + } + + @Override + public void onConnectionReady() { + pause(); + onOpen(); + } + + @Override + public void onOpen() { + synchronized (statusLock) { + transferStatus(PipeWebSocketStatus.PEER_CONNECTED, PipeWebSocketStatus.OPENED); + if (this.peer.status == PipeWebSocketStatus.OPENED) { + resume(); + transferStatus(PipeWebSocketStatus.OPENED, PipeWebSocketStatus.RUNNING); + peer.resume(); + peer.transferStatus(PipeWebSocketStatus.OPENED, PipeWebSocketStatus.RUNNING); + } + } + } + + @Override + public void onMessage(WebSocketMessage message) { + peer.sendMessage(message) + .whenComplete((v, t) -> { + if (t != null) { + LOGGER.error("[{}] failed to forward message", websocketSessionId, t); + } + }); + if (peer.writeQueueFull()) { + this.pause(); + LOGGER.debug("[{}] pipe paused, direction is client to server", websocketSessionId); + } + } + + @Override + public void onError(Throwable t) { + LOGGER.error("[{}] websocket error", websocketSessionId, t); + synchronized (statusLock) { + transferStatus(PipeWebSocketStatus.ERROR); + safelyClose((short) WebSocketCloseStatus.INTERNAL_SERVER_ERROR.code(), + WebSocketCloseStatus.INTERNAL_SERVER_ERROR.reasonText()); + } + } + + @Override + public void onClose(Short closeStatusCode, String closeReason) { + transferStatus(PipeWebSocketStatus.CLOSED); + safelyClose(closeStatusCode, closeReason); // should close peer + } + + @Override + public void onWriteQueueDrain() { + peer.resume(); + } + + private void transferStatus(PipeWebSocketStatus newStatus) { + synchronized (statusLock) { + this.status = newStatus; + } + } + + private void transferStatus(PipeWebSocketStatus expectedOldStatus, PipeWebSocketStatus newStatus) { + synchronized (statusLock) { + if (status != expectedOldStatus) { + safelyClose((short) WebSocketCloseStatus.INTERNAL_SERVER_ERROR.code(), + WebSocketCloseStatus.INTERNAL_SERVER_ERROR.reasonText()); + LOGGER.error("[{}] illegal state transfer: from {} to {}", websocketSessionId, expectedOldStatus, newStatus); + throw new IllegalStateException("Illegal state transfer: [" + + expectedOldStatus + + "] to [" + + newStatus + + "]"); + } + status = newStatus; + } + } + + private void safelyClose(Short closeStatusCode, String closeReason) { + try { + close(closeStatusCode, closeReason); + } catch (Throwable e) { + LOGGER.error("[{}] failed to close pipe client websocket", websocketSessionId, e); + } + if (peer == null) { + return; + } + try { + peer.close(closeStatusCode, closeReason); + } catch (Throwable e) { + LOGGER.error("[{}] failed to close pipe server websocket", websocketSessionId, e); + } + } + } +} diff --git a/swagger/swagger-invocation/invocation-core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.ResponseTypeProcessor b/swagger/swagger-invocation/invocation-core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.ResponseTypeProcessor index 1a722b7459f..bb2abfe5016 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.ResponseTypeProcessor +++ b/swagger/swagger-invocation/invocation-core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.ResponseTypeProcessor @@ -15,4 +15,5 @@ # limitations under the License. # -org.apache.servicecomb.swagger.invocation.generator.ScbResponseProcessor \ No newline at end of file +org.apache.servicecomb.swagger.invocation.generator.ScbResponseProcessor +org.apache.servicecomb.swagger.invocation.ws.ServerWebSocketResponseProcessor diff --git a/swagger/swagger-invocation/invocation-core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.invocation.arguments.consumer.ConsumerContextArgumentMapperFactory b/swagger/swagger-invocation/invocation-core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.invocation.arguments.consumer.ConsumerContextArgumentMapperFactory index 66a48f5a85c..341e7f8e765 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.invocation.arguments.consumer.ConsumerContextArgumentMapperFactory +++ b/swagger/swagger-invocation/invocation-core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.invocation.arguments.consumer.ConsumerContextArgumentMapperFactory @@ -16,3 +16,4 @@ # org.apache.servicecomb.swagger.invocation.arguments.consumer.ConsumerInvocationContextMapperFactory +org.apache.servicecomb.swagger.invocation.arguments.consumer.ClientWebSocketArgumentMapperFactory diff --git a/swagger/swagger-invocation/invocation-core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.invocation.exception.ExceptionToProducerResponseConverters b/swagger/swagger-invocation/invocation-core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.invocation.exception.ExceptionToProducerResponseConverters new file mode 100644 index 00000000000..919d820f6f6 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.invocation.exception.ExceptionToProducerResponseConverters @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. +# + +org.apache.servicecomb.swagger.invocation.exception.DefaultExceptionToProducerResponseConverters diff --git a/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/exception/TestExceptionToProducerResponseConverters.java b/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/exception/TestDefaultExceptionToProducerResponseConverters.java similarity index 93% rename from swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/exception/TestExceptionToProducerResponseConverters.java rename to swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/exception/TestDefaultExceptionToProducerResponseConverters.java index fe8eaf06933..2747148e07a 100644 --- a/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/exception/TestExceptionToProducerResponseConverters.java +++ b/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/exception/TestDefaultExceptionToProducerResponseConverters.java @@ -25,12 +25,12 @@ import org.apache.servicecomb.swagger.invocation.Response; import org.apache.servicecomb.swagger.invocation.SwaggerInvocation; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import mockit.Expectations; import mockit.Mocked; -import org.junit.jupiter.api.Assertions; -public class TestExceptionToProducerResponseConverters { +public class TestDefaultExceptionToProducerResponseConverters { @Test public void convertExceptionToResponse( @Mocked ExceptionToProducerResponseConverter c1, @@ -58,7 +58,7 @@ public void convertExceptionToResponse( } }; - ExceptionToProducerResponseConverters exceptionToProducerResponseConverters = new ExceptionToProducerResponseConverters(); + DefaultExceptionToProducerResponseConverters exceptionToProducerResponseConverters = new DefaultExceptionToProducerResponseConverters(); Assertions.assertSame(r1, exceptionToProducerResponseConverters.convertExceptionToResponse(null, new Throwable())); @@ -103,7 +103,7 @@ public void convertExceptionToResponse_checkDefaultConverterPriority( } }; - ExceptionToProducerResponseConverters exceptionToProducerResponseConverters = new ExceptionToProducerResponseConverters(); + DefaultExceptionToProducerResponseConverters exceptionToProducerResponseConverters = new DefaultExceptionToProducerResponseConverters(); Assertions.assertSame(r2, exceptionToProducerResponseConverters @@ -160,7 +160,7 @@ public void convertExceptionToResponse_CheckCommonConvertPriority( } }; - ExceptionToProducerResponseConverters exceptionToProducerResponseConverters = new ExceptionToProducerResponseConverters(); + DefaultExceptionToProducerResponseConverters exceptionToProducerResponseConverters = new DefaultExceptionToProducerResponseConverters(); Assertions.assertSame(rR0, exceptionToProducerResponseConverters.convertExceptionToResponse(null, new RuntimeException0_0())); diff --git a/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/ws/SerialExecutorWrapperTest.java b/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/ws/SerialExecutorWrapperTest.java new file mode 100644 index 00000000000..768ffc170e9 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/ws/SerialExecutorWrapperTest.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.ws; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.servicecomb.swagger.invocation.InvocationType; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * SerialExecutorWrapper UT + */ +public class SerialExecutorWrapperTest { + private ExecutorService workerPools; + + @Before + public void before() { + workerPools = Executors.newFixedThreadPool(20); + } + + @After + public void after() { + workerPools.shutdown(); + } + + @Test + public void executeSingleWrapper() throws InterruptedException { + // queueCapacity(100*1.2) + taskInWorking(1) = 121, this is the max task count that not trigger queue full exception + final int taskCount = 121; + final SerialExecutorWrapper wrapper = new SerialExecutorWrapper(InvocationType.PRODUCER, + "testSerialWrapper", workerPools, 10, 3); // queueCapacity will be corrected to 100 + wrapper.subscribeQueueDrainEvent(() -> { + }); + final Object lock = new Object(); + final List resultList = new ArrayList<>(); + final CountDownLatch countDownLatch = new CountDownLatch(taskCount); + synchronized (lock) { + final CountDownLatch continueLoopLatch = new CountDownLatch(1); + wrapper.execute(() -> { + continueLoopLatch.countDown(); + synchronized (lock) { + resultList.add(0); + countDownLatch.countDown(); + } + }); + continueLoopLatch.await(1, TimeUnit.MINUTES); // wait the first task start running to ensure a stable result. + for (int i = 1; i < taskCount; ++i) { + final int index = i; + wrapper.execute(() -> { + resultList.add(index); + countDownLatch.countDown(); + }); + } + MatcherAssert.assertThat(resultList, Matchers.empty()); + } + countDownLatch.await(1, TimeUnit.MINUTES); + MatcherAssert.assertThat(resultList, Matchers.hasSize(taskCount)); + for (int i = 0; i < taskCount; ++i) { + // must execute + MatcherAssert.assertThat(resultList.get(i), Matchers.equalTo(i)); + } + } + + @Test + public void executeSingleWrapperQueueFull() throws InterruptedException { + final int taskCount = 122; // taskCount +1 than executeSingleWrapper method + final SerialExecutorWrapper wrapper = new SerialExecutorWrapper(InvocationType.PRODUCER, + "testSerialWrapper", workerPools, 10, 3); // queueCapacity will be corrected to 100 + wrapper.subscribeQueueDrainEvent(() -> { + }); + final Object lock = new Object(); + final AtomicBoolean exceptionFlag = new AtomicBoolean(); + final List resultList = new ArrayList<>(); + final CountDownLatch countDownLatch = new CountDownLatch(taskCount - 1); + synchronized (lock) { + final CountDownLatch continueLoopLatch = new CountDownLatch(1); + wrapper.execute(() -> { + continueLoopLatch.countDown(); + synchronized (lock) { + resultList.add(0); + countDownLatch.countDown(); + } + }); + continueLoopLatch.await(1, TimeUnit.MINUTES); // wait the first task start running to ensure a stable result. + for (int i = 1; i < taskCount; ++i) { + final int index = i; + try { + wrapper.execute(() -> { + resultList.add(index); + countDownLatch.countDown(); + }); + } catch (Exception e) { + exceptionFlag.set(true); + MatcherAssert.assertThat(i, Matchers.equalTo(taskCount - 1)); + MatcherAssert.assertThat(e, Matchers.instanceOf(IllegalStateException.class)); + } + } + MatcherAssert.assertThat(resultList, Matchers.empty()); + } + countDownLatch.await(1, TimeUnit.MINUTES); + MatcherAssert.assertThat(exceptionFlag.get(), Matchers.equalTo(true)); + MatcherAssert.assertThat(resultList, Matchers.hasSize(taskCount - 1)); + for (int i = 0; i < taskCount - 1; ++i) { + MatcherAssert.assertThat(resultList.get(i), Matchers.equalTo(i)); + } + } +} diff --git a/swagger/swagger-invocation/invocation-validator/src/main/java/org/apache/servicecomb/swagger/invocation/validator/ConfigurationPropertyUtils.java b/swagger/swagger-invocation/invocation-validator/src/main/java/org/apache/servicecomb/swagger/invocation/validator/ConfigurationPropertyUtils.java new file mode 100644 index 00000000000..443b2a8c76d --- /dev/null +++ b/swagger/swagger-invocation/invocation-validator/src/main/java/org/apache/servicecomb/swagger/invocation/validator/ConfigurationPropertyUtils.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.swagger.invocation.validator; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.commons.configuration.Configuration; + +import com.netflix.config.DynamicPropertyFactory; + +public final class ConfigurationPropertyUtils { + private ConfigurationPropertyUtils() { + } + + /** + * 获取key包含prefix前缀的所有配置项 + */ + public static Map getPropertiesWithPrefix(String prefix) { + Object config = DynamicPropertyFactory.getBackingConfigurationSource(); + if (!Configuration.class.isInstance(config)) { + return new HashMap<>(); + } + + return getPropertiesWithPrefix((Configuration) config, prefix); + } + + // caller ensure configuration is valid + public static Map getPropertiesWithPrefix(Configuration configuration, String prefix) { + Map propertiesMap = new HashMap<>(); + + Iterator keysIterator = configuration.getKeys(prefix); + while (keysIterator.hasNext()) { + String key = keysIterator.next(); + propertiesMap.put(key.substring(prefix.length() + 1), String.valueOf(configuration.getProperty(key))); + } + return propertiesMap; + } +} diff --git a/swagger/swagger-invocation/invocation-validator/src/main/java/org/apache/servicecomb/swagger/invocation/validator/ParameterValidator.java b/swagger/swagger-invocation/invocation-validator/src/main/java/org/apache/servicecomb/swagger/invocation/validator/ParameterValidator.java index 9fc573e45b9..22823df75ba 100644 --- a/swagger/swagger-invocation/invocation-validator/src/main/java/org/apache/servicecomb/swagger/invocation/validator/ParameterValidator.java +++ b/swagger/swagger-invocation/invocation-validator/src/main/java/org/apache/servicecomb/swagger/invocation/validator/ParameterValidator.java @@ -16,8 +16,10 @@ */ package org.apache.servicecomb.swagger.invocation.validator; +import java.util.Map; import java.util.Set; +import javax.validation.Configuration; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.validation.Validation; @@ -45,6 +47,8 @@ public class ParameterValidator implements ProducerInvokeExtension { private static final String ENABLE_EL = "servicecomb.filters.validation.useResourceBundleMessageInterpolator"; + public static final String HIBERNATE_VALIDATE_PREFIX = "hibernate.validator"; + private final DynamicBooleanProperty paramValidationEnabled = DynamicPropertyFactory.getInstance() .getBooleanProperty(PARAM_VALIDATION_ENABLED, true); @@ -63,13 +67,7 @@ public void beforeMethodInvoke(SwaggerInvocation invocation, SwaggerProducer throws ConstraintViolationException { if (paramValidationEnabled.get()) { if (null == executableValidator) { - ValidatorFactory factory = - Validation.byDefaultProvider() - .configure() - .parameterNameProvider(new DefaultParameterNameProvider()) - .messageInterpolator(messageInterpolator()) - .buildValidatorFactory(); - executableValidator = factory.getValidator().forExecutables(); + executableValidator = createValidatorFactory().getValidator().forExecutables(); } Set> violations = executableValidator.validateParameters(producerOperation.getProducerInstance(), @@ -83,6 +81,20 @@ public void beforeMethodInvoke(SwaggerInvocation invocation, SwaggerProducer } } + private ValidatorFactory createValidatorFactory() { + Configuration validatorConfiguration = Validation.byDefaultProvider() + .configure() + .parameterNameProvider(new DefaultParameterNameProvider()) + .messageInterpolator(messageInterpolator()); + Map configs = ConfigurationPropertyUtils.getPropertiesWithPrefix(HIBERNATE_VALIDATE_PREFIX); + if (!configs.isEmpty()) { + for (Map.Entry entry : configs.entrySet()) { + validatorConfiguration.addProperty(HIBERNATE_VALIDATE_PREFIX + "." + entry.getKey(), entry.getValue()); + } + } + return validatorConfiguration.buildValidatorFactory(); + } + private AbstractMessageInterpolator messageInterpolator() { if (useResourceBundleMessageInterpolator()) { return new ResourceBundleMessageInterpolator(); diff --git a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayClient.java b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayClient.java index 3e0f98bf548..1493fea1b7f 100644 --- a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayClient.java +++ b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayClient.java @@ -17,6 +17,7 @@ package org.apache.servicecomb.transport.highway; +import java.util.Map; import java.util.concurrent.TimeoutException; import javax.ws.rs.core.Response.Status; @@ -63,7 +64,10 @@ public void init(Vertx vertx) throws Exception { DeploymentOptions deployOptions = VertxUtils.createClientDeployOptions(clientMgr, HighwayConfig.getClientThreadCount()); - VertxUtils.blockDeploy(vertx, ClientVerticle.class, deployOptions); + Map result = VertxUtils.blockDeploy(vertx, ClientVerticle.class, deployOptions); + if (!(boolean) result.get("code")) { + throw new IllegalStateException((String) result.get("message")); + } } @VisibleForTesting @@ -83,7 +87,11 @@ TcpClientConfig createTcpClientConfig() { } SSLCustom sslCustom = SSLCustom.createSSLCustom(sslOption.getSslCustomClass()); VertxTLSBuilder.buildClientOptionsBase(sslOption, sslCustom, tcpClientConfig); - + if (!sslOption.isCheckCNHost()) { + tcpClientConfig.setHostnameVerificationAlgorithm(""); + } else { + tcpClientConfig.setHostnameVerificationAlgorithm("HTTPS"); + } return tcpClientConfig; } diff --git a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerInvoke.java b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerInvoke.java index 2efaf76c443..1149325a03a 100644 --- a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerInvoke.java +++ b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerInvoke.java @@ -142,13 +142,16 @@ private void sendResponse(Map context, Response response) { header.fromMultiMap(response.getHeaders()); ResponseRootSerializer bodySchema = operationProtobuf.findResponseRootSerializer(response.getStatusCode()); - Object body = response.getResult(); - if (response.isFailed()) { - body = ((InvocationException) body).getErrorData(); - } + boolean failed = response.getResult() instanceof InvocationException; try { - Buffer respBuffer = HighwayCodec.encodeResponse(msgId, header, bodySchema, body); + Buffer respBuffer; + if (failed) { + respBuffer = HighwayCodec.encodeResponse(msgId, header, bodySchema, + ((InvocationException) response.getResult()).getErrorData()); + } else { + respBuffer = HighwayCodec.encodeResponse(msgId, header, bodySchema, response.getResult()); + } invocation.getInvocationStageTrace().finishServerFiltersResponse(); connection.write(respBuffer.getByteBuf()); } catch (Exception e) { diff --git a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayTransport.java b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayTransport.java index b283b568c15..f40ff86b183 100644 --- a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayTransport.java +++ b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayTransport.java @@ -18,6 +18,7 @@ package org.apache.servicecomb.transport.highway; import java.util.Collections; +import java.util.Map; import org.apache.servicecomb.core.Const; import org.apache.servicecomb.core.Invocation; @@ -47,7 +48,12 @@ public boolean init() throws Exception { json.put(ENDPOINT_KEY, getEndpoint()); deployOptions.setConfig(json); deployOptions.setWorkerPoolName("pool-worker-transport-highway"); - return VertxUtils.blockDeploy(transportVertx, HighwayServerVerticle.class, deployOptions); + Map result = VertxUtils.blockDeploy(transportVertx, HighwayServerVerticle.class, deployOptions); + if ((boolean) result.get("code")) { + return true; + } else { + throw new IllegalStateException((String) result.get("message")); + } } @Override diff --git a/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/TestHighwayClient.java b/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/TestHighwayClient.java index ee3c63148cd..43db6143352 100644 --- a/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/TestHighwayClient.java +++ b/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/TestHighwayClient.java @@ -17,6 +17,9 @@ package org.apache.servicecomb.transport.highway; +import java.util.HashMap; +import java.util.Map; + import javax.ws.rs.core.Response.Status; import org.apache.servicecomb.codec.protobuf.definition.OperationProtobuf; @@ -97,10 +100,12 @@ public void testLoginTimeout(@Mocked Vertx vertx) { public void testHighwayClientSSL(@Mocked Vertx vertx) throws Exception { new MockUp() { @Mock - boolean blockDeploy(Vertx vertx, + Map blockDeploy(Vertx vertx, Class cls, DeploymentOptions options) { - return true; + Map result = new HashMap<>(); + result.put("code", true); + return result; } }; @@ -114,10 +119,12 @@ private Object doTestSend(Vertx vertx, HighwayClientConnectionPool pool, Highway Object decodedResponse) throws Exception { new MockUp() { @Mock - boolean blockDeploy(Vertx vertx, + Map blockDeploy(Vertx vertx, Class cls, DeploymentOptions options) { - return true; + Map result = new HashMap<>(); + result.put("code", true); + return result; } }; diff --git a/transports/transport-rest/transport-rest-client/pom.xml b/transports/transport-rest/transport-rest-client/pom.xml index 624d5468fb3..edf864b051f 100644 --- a/transports/transport-rest/transport-rest-client/pom.xml +++ b/transports/transport-rest/transport-rest-client/pom.xml @@ -77,10 +77,6 @@ commons-text test - - org.apache.servicecomb - common-protobuf - org.jmockit jmockit diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/Http2TransportHttpClientOptionsSPI.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/Http2TransportHttpClientOptionsSPI.java index ad0b3bb27bf..cfa6e9356be 100644 --- a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/Http2TransportHttpClientOptionsSPI.java +++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/Http2TransportHttpClientOptionsSPI.java @@ -66,4 +66,9 @@ public int getHttp2MaxPoolSize() { public int getIdleTimeoutInSeconds() { return TransportClientConfig.getHttp2ConnectionIdleTimeoutInSeconds(); } + + @Override + public int getKeepAliveTimeout() { + return TransportClientConfig.getHttp2ConnectionKeepAliveTimeoutInSeconds(); + } } diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/HttpTransportHttpClientOptionsSPI.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/HttpTransportHttpClientOptionsSPI.java index ab7a9630970..369443c143c 100644 --- a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/HttpTransportHttpClientOptionsSPI.java +++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/HttpTransportHttpClientOptionsSPI.java @@ -131,6 +131,11 @@ public int getKeepAliveTimeout() { return TransportClientConfig.getConnectionKeepAliveTimeoutInSeconds(); } + @Override + public boolean enableLogActivity() { + return TransportClientConfig.enableLogActivity(); + } + @Override public int getHttp2MultiplexingLimit() { return HttpClientOptions.DEFAULT_HTTP2_MULTIPLEXING_LIMIT; diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientCodecFilter.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientCodecFilter.java index 6a530f256a3..2cf13cbad86 100644 --- a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientCodecFilter.java +++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientCodecFilter.java @@ -16,10 +16,13 @@ */ package org.apache.servicecomb.transport.rest.client; +import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import javax.annotation.Nonnull; +import org.apache.servicecomb.common.rest.RestConst; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.core.filter.ConsumerFilter; import org.apache.servicecomb.core.filter.FilterNode; @@ -67,9 +70,13 @@ public RestClientCodecFilter setDecoder(RestClientDecoder decoder) { public CompletableFuture onFilter(Invocation invocation, FilterNode nextNode) { invocation.getInvocationStageTrace().startGetConnection(); startClientFiltersRequest(invocation); + CompletionStage createRequest = + transportContextFactory.createHttpClientRequest(invocation).toCompletionStage() + .whenComplete((c, e) -> invocation.getInvocationStageTrace().finishGetConnection()); return CompletableFuture.completedFuture(null) - .thenCompose(v -> transportContextFactory.createHttpClientRequest(invocation).toCompletionStage()) + .thenCompose(v -> createRequest) .thenAccept(httpClientRequest -> prepareTransportContext(invocation, httpClientRequest)) + .thenAccept(v -> invocation.onStartSendRequest()) .thenAccept(v -> encoder.encode(invocation)) .thenCompose(v -> nextNode.onFilter(invocation)) .thenApply(response -> decoder.decode(invocation, response)) @@ -81,12 +88,29 @@ protected void startClientFiltersRequest(Invocation invocation) { } protected void prepareTransportContext(Invocation invocation, HttpClientRequest httpClientRequest) { - invocation.getInvocationStageTrace().finishGetConnection(); + copyExtraHttpHeaders(invocation, httpClientRequest); RestClientTransportContext transportContext = transportContextFactory.create(invocation, httpClientRequest); invocation.setTransportContext(transportContext); } + @SuppressWarnings("unchecked") + protected void copyExtraHttpHeaders(Invocation invocation, HttpClientRequest httpClientRequest) { + Map httpHeaders = (Map) invocation.getHandlerContext() + .get(RestConst.CONSUMER_HEADER); + if (httpHeaders == null) { + return; + } + httpHeaders.forEach((key, value) -> { + if ("Content-Length".equalsIgnoreCase(key)) { + return; + } + if (null != value) { + httpClientRequest.putHeader(key, value); + } + }); + } + protected void finishClientFiltersResponse(Invocation invocation) { invocation.getInvocationStageTrace().finishClientFiltersResponse(); } diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientRequestParametersImpl.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientRequestParametersImpl.java index 526b4ec9933..75da6ded4b0 100644 --- a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientRequestParametersImpl.java +++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientRequestParametersImpl.java @@ -108,15 +108,14 @@ public Multimap getUploads() { @SuppressWarnings("unchecked") @Override public void attach(String name, Object partOrList) { - if (partOrList == null) { - LOGGER.debug("null file is ignored, file name = [{}]", name); - return; - } - if (uploads == null) { uploads = ArrayListMultimap.create(); } + if (partOrList == null) { + return; + } + if (partOrList.getClass().isArray()) { for (Object part : (Object[]) partOrList) { uploads.put(name, PartUtils.getSinglePart(name, part)); diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientSender.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientSender.java index 3bd08dbc134..6816d24ec14 100644 --- a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientSender.java +++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientSender.java @@ -75,9 +75,11 @@ public CompletableFuture send() { protected void runInVertxContext() { sendInVertxContext() - .exceptionally(throwable -> { - future.completeExceptionally(throwable); - return null; + .whenComplete((v, e) -> { + if (e != null) { + future.completeExceptionally(e); + } + writeFinished(); }); } @@ -87,11 +89,12 @@ protected CompletableFuture sendInVertxContext() { Multimap uploads = requestParameters.getUploads(); if (uploads == null) { if (requestParameters.getBodyBuffer() != null) { - httpClientRequest.end(requestParameters.getBodyBuffer()); + return CompletableFuture.completedFuture(null).thenCompose( + v -> httpClientRequest.end(requestParameters.getBodyBuffer()).toCompletionStage()); } else { - httpClientRequest.end(); + return CompletableFuture.completedFuture(null).thenCompose( + v -> httpClientRequest.end().toCompletionStage()); } - return CompletableFuture.completedFuture(null); } if (requestParameters.getBodyBuffer() != null) { @@ -100,6 +103,10 @@ protected CompletableFuture sendInVertxContext() { return sendFiles(); } + private void writeFinished() { + invocation.getInvocationStageTrace().finishWriteToBuffer(System.nanoTime()); + } + protected CompletableFuture sendFiles() { CompletableFuture sendFileFuture = CompletableFuture.completedFuture(null); @@ -111,8 +118,7 @@ protected CompletableFuture sendFiles() { sendFileFuture = sendFileFuture.thenCompose(v -> sendFile(entry.getValue(), name, boundary)); } - return sendFileFuture - .thenAccept(v -> httpClientRequest.end(genBoundaryEndBuffer(boundary))); + return sendFileFuture.thenCompose(v -> httpClientRequest.end(genBoundaryEndBuffer(boundary)).toCompletionStage()); } private CompletableFuture sendFile(Part part, String name, String boundary) { @@ -167,8 +173,7 @@ protected void afterSend(Response response, Throwable throwable) { protected void processMetrics() { InvocationStageTrace stageTrace = invocation.getInvocationStageTrace(); - stageTrace.finishWriteToBuffer(System.nanoTime()); - // even failed and did not received response, still set time for it + // even failed and did not receive response, still set time for it // that will help to know the real timeout time stageTrace.finishReceiveResponse(); stageTrace.startClientFiltersResponse(); diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/TransportClientConfig.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/TransportClientConfig.java index d855d4e31fa..8e0ac0d8c05 100644 --- a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/TransportClientConfig.java +++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/TransportClientConfig.java @@ -22,11 +22,14 @@ import com.netflix.config.DynamicPropertyFactory; import io.vertx.core.http.HttpClientOptions; -import io.vertx.core.net.TCPSSLOptions; public final class TransportClientConfig { private static Class restTransportClientCls = RestTransportClient.class; + private static final int DEFAULT_IDLE_TIME_OUT = 150; + + private static final int DEFAULT_KEEP_ALIVE_TIME_OUT = 60; + private TransportClientConfig() { } @@ -46,19 +49,13 @@ public static int getThreadCount() { public static int getHttp2ConnectionMaxPoolSize() { return DynamicPropertyFactory.getInstance().getIntProperty("servicecomb.rest.client.http2.maxPoolSize", - HttpClientOptions.DEFAULT_HTTP2_MAX_POOL_SIZE) + HttpClientOptions.DEFAULT_HTTP2_MAX_POOL_SIZE) .get(); } public static int getHttp2MultiplexingLimit() { return DynamicPropertyFactory.getInstance().getIntProperty("servicecomb.rest.client.http2.multiplexingLimit", - HttpClientOptions.DEFAULT_HTTP2_MULTIPLEXING_LIMIT) - .get(); - } - - public static int getHttp2ConnectionIdleTimeoutInSeconds() { - return DynamicPropertyFactory.getInstance() - .getIntProperty("servicecomb.rest.client.http2.idleTimeoutInSeconds", TCPSSLOptions.DEFAULT_IDLE_TIMEOUT) + HttpClientOptions.DEFAULT_HTTP2_MULTIPLEXING_LIMIT) .get(); } @@ -81,9 +78,15 @@ public static int getConnectionMaxPoolSize() { .get(); } + public static int getHttp2ConnectionIdleTimeoutInSeconds() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.rest.client.http2.connection.idleTimeoutInSeconds", DEFAULT_IDLE_TIME_OUT) + .get(); + } + public static int getConnectionIdleTimeoutInSeconds() { return DynamicPropertyFactory.getInstance() - .getIntProperty("servicecomb.rest.client.connection.idleTimeoutInSeconds", 30) + .getIntProperty("servicecomb.rest.client.connection.idleTimeoutInSeconds", DEFAULT_IDLE_TIME_OUT) .get(); } @@ -94,18 +97,23 @@ public static boolean getConnectionKeepAlive() { } public static int getConnectionKeepAliveTimeoutInSeconds() { - int result = DynamicPropertyFactory.getInstance() + return DynamicPropertyFactory.getInstance() .getIntProperty("servicecomb.rest.client.connection.keepAliveTimeoutInSeconds", - -1) + DEFAULT_KEEP_ALIVE_TIME_OUT) + .get(); + } + + public static boolean enableLogActivity() { + return DynamicPropertyFactory.getInstance() + .getBooleanProperty("servicecomb.rest.client.enableLogActivity", false) + .get(); + } + + public static int getHttp2ConnectionKeepAliveTimeoutInSeconds() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.rest.client.http2.connection.keepAliveTimeoutInSeconds", + DEFAULT_KEEP_ALIVE_TIME_OUT) .get(); - if (result >= 0) { - return result; - } - result = getConnectionIdleTimeoutInSeconds(); - if (result > 1) { - return result - 1; // a bit shorter than ConnectionIdleTimeoutInSeconds - } - return result; } public static boolean getConnectionCompression() { @@ -137,7 +145,7 @@ public static boolean isHttpTransportClientEnabled() { public static int getConnectionTimeoutInMillis() { return DynamicPropertyFactory.getInstance() - .getIntProperty("servicecomb.rest.client.connection.timeoutInMillis", 1000) + .getIntProperty("servicecomb.rest.client.connection.timeoutInMillis", 60000) .get(); } } diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/DefaultHttpClientFilter.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/DefaultHttpClientFilter.java index 9e9c7d7ce6c..c8cc79e0fa9 100644 --- a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/DefaultHttpClientFilter.java +++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/DefaultHttpClientFilter.java @@ -47,6 +47,8 @@ public class DefaultHttpClientFilter implements HttpClientFilter { private static final boolean enabled = DynamicPropertyFactory.getInstance().getBooleanProperty ("servicecomb.http.filter.client.default.enabled", true).get(); + private static final String PRINT_ERROR_RESPONSE = "servicecomb.http.filter.client.default.shouldPrintErrorResponse"; + @Override public int getOrder() { return 10000; @@ -90,14 +92,13 @@ protected Response extractResponse(Invocation invocation, HttpServletResponseEx if (produceProcessor == null) { // This happens outside the runtime such as Servlet filter response. Here we give a default json parser to it // and keep user data not get lose. - String msg = - String.format("method %s, path %s, statusCode %d, reasonPhrase %s, response content-type %s is not supported", - swaggerRestOperation.getHttpMethod(), - swaggerRestOperation.getAbsolutePath(), - responseEx.getStatus(), - responseEx.getStatusType().getReasonPhrase(), - responseEx.getHeader(HttpHeaders.CONTENT_TYPE)); - LOGGER.warn(msg); + LOGGER.warn("Response content-type {} is not supported. Method {}, path {}, statusCode {}, reasonPhrase {}.", + responseEx.getHeader(HttpHeaders.CONTENT_TYPE), + swaggerRestOperation.getHttpMethod(), + swaggerRestOperation.getAbsolutePath(), + responseEx.getStatus(), + responseEx.getStatusType().getReasonPhrase() + ); produceProcessor = ProduceProcessorManager.INSTANCE.findDefaultProcessor(); } @@ -110,14 +111,14 @@ protected Response extractResponse(Invocation invocation, HttpServletResponseEx } return response; } catch (Exception e) { - LOGGER.error("failed to decode response body, exception is [{}]", e.getMessage()); + if (DynamicPropertyFactory.getInstance().getBooleanProperty(PRINT_ERROR_RESPONSE, false).get()) { + LOGGER.error("failed to decode response body. response is {}", responseEx.getBodyBuffer()); + } else { + LOGGER.error("failed to decode response body.", e); + } String msg = - String.format("method %s, path %s, statusCode %d, reasonPhrase %s, response content-type %s is not supported", - swaggerRestOperation.getHttpMethod(), - swaggerRestOperation.getAbsolutePath(), - responseEx.getStatus(), - responseEx.getStatusType().getReasonPhrase(), - responseEx.getHeader(HttpHeaders.CONTENT_TYPE)); + String.format("Failed to decode response body. Operation %s.", + invocation.getMicroserviceQualifiedName()); if (HttpStatus.isSuccess(responseEx.getStatus())) { return Response.createConsumerFail( new InvocationException(400, responseEx.getStatusType().getReasonPhrase(), diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/RestClientInvocation.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/RestClientInvocation.java index 6ddf1e23b47..ded7b6e2ccf 100644 --- a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/RestClientInvocation.java +++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/RestClientInvocation.java @@ -103,11 +103,9 @@ public void invoke(Invocation invocation, AsyncResponse asyncResp) throws Except IpPort ipPort = (IpPort) invocation.getEndpoint().getAddress(); Future requestFuture = createRequest(ipPort, path); - invocation.getInvocationStageTrace().startGetConnection(); + requestFuture.onComplete(r -> invocation.getInvocationStageTrace().finishGetConnection()); requestFuture.compose(clientRequest -> { - invocation.getInvocationStageTrace().finishGetConnection(); - this.clientRequest = clientRequest; clientRequest.putHeader(org.apache.servicecomb.core.Const.TARGET_MICROSERVICE, invocation.getMicroserviceName()); @@ -270,10 +268,6 @@ protected void fail(Throwable e) { InvocationStageTrace stageTrace = invocation.getInvocationStageTrace(); - if (stageTrace.getFinishWriteToBuffer() == 0) { - stageTrace.finishWriteToBuffer(System.nanoTime()); - } - // even failed and did not received response, still set time for it // that will help to know the real timeout time if (stageTrace.getFinishReceiveResponse() == 0) { diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/RestClientRequestWebSocketWrapper.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/RestClientRequestWebSocketWrapper.java new file mode 100644 index 00000000000..29214ba788b --- /dev/null +++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/RestClientRequestWebSocketWrapper.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.transport.rest.client.ws; + +import org.apache.servicecomb.common.rest.codec.RestClientRequest; + +import io.vertx.core.Future; +import io.vertx.core.MultiMap; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.WebSocketConnectOptions; + +public class RestClientRequestWebSocketWrapper implements RestClientRequest { + private final WebSocketConnectOptions webSocketClientRequest; + + public RestClientRequestWebSocketWrapper(WebSocketConnectOptions webSocketClientRequest) { + this.webSocketClientRequest = webSocketClientRequest; + } + + @Override + public void write(Buffer bodyBuffer) { + throw new UnsupportedOperationException("websocket not support http body"); + } + + @Override + public Future end() { + return Future.succeededFuture(); + } + + @Override + public void addCookie(String name, String value) { + throw new UnsupportedOperationException("websocket not support cookie"); + } + + @Override + public void putHeader(String name, String value) { + webSocketClientRequest.putHeader(name, value); + } + + @Override + public MultiMap getHeaders() { + return webSocketClientRequest.getHeaders(); + } + + @Override + public void addForm(String name, Object value) { + throw new UnsupportedOperationException("websocket not support http form"); + } + + @Override + public Buffer getBodyBuffer() { + return Buffer.buffer(); + } + + @Override + public void attach(String name, Object partOrList) { + throw new UnsupportedOperationException("websocket not support http file upload"); + } +} diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/WebSocketClientInvocation.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/WebSocketClientInvocation.java new file mode 100644 index 00000000000..375110b84d9 --- /dev/null +++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/WebSocketClientInvocation.java @@ -0,0 +1,284 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.transport.rest.client.ws; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; + +import javax.ws.rs.core.Response.Status; + +import org.apache.commons.lang3.StringUtils; +import org.apache.servicecomb.common.rest.RestConst; +import org.apache.servicecomb.common.rest.definition.RestOperationMeta; +import org.apache.servicecomb.common.rest.filter.HttpClientFilter; +import org.apache.servicecomb.common.rest.filter.HttpClientFilterBeforeSendRequestExecutor; +import org.apache.servicecomb.core.Const; +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.core.definition.OperationConfig; +import org.apache.servicecomb.core.definition.OperationMeta; +import org.apache.servicecomb.core.invocation.InvocationStageTrace; +import org.apache.servicecomb.foundation.common.net.IpPort; +import org.apache.servicecomb.foundation.common.net.URIEndpointObject; +import org.apache.servicecomb.foundation.common.utils.ExceptionUtils; +import org.apache.servicecomb.foundation.common.utils.JsonUtils; +import org.apache.servicecomb.foundation.vertx.client.ws.WebSocketClientWithContext; +import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx; +import org.apache.servicecomb.foundation.vertx.ws.VertxClientWebSocketRequestToHttpServletRequest; +import org.apache.servicecomb.foundation.vertx.ws.VertxClientWebSocketResponseToHttpServletResponse; +import org.apache.servicecomb.registry.definition.DefinitionConst; +import org.apache.servicecomb.swagger.invocation.AsyncResponse; +import org.apache.servicecomb.swagger.invocation.Response; +import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData; +import org.apache.servicecomb.swagger.invocation.exception.InvocationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.vertx.core.Future; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.WebSocket; +import io.vertx.core.http.WebSocketConnectOptions; + +public class WebSocketClientInvocation { + + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketClientInvocation.class); + + private static final String[] INTERNAL_HEADERS = new String[] { + Const.CSE_CONTEXT, + Const.TARGET_MICROSERVICE + }; + + private Invocation invocation; + + private AsyncResponse asyncResp; + + private RestOperationMeta restOperationMeta; + + private WebSocketConnectOptions webSocketClientRequest; + + private final WebSocketClientWithContext webSocketClientWithContext; + + private final List httpClientFilters; + + private boolean alreadyFailed = false; + + private WebSocket clientWebSocket; + + public WebSocketClientInvocation(WebSocketClientWithContext webSocketClientWithContext, + List httpClientFilters) { + this.webSocketClientWithContext = webSocketClientWithContext; + this.httpClientFilters = httpClientFilters; + } + + public void invoke(Invocation invocation, AsyncResponse asyncResp) throws Exception { + this.invocation = invocation; + this.asyncResp = asyncResp; + + final OperationMeta operationMeta = invocation.getOperationMeta(); + restOperationMeta = operationMeta.getExtData(RestConst.SWAGGER_REST_OPERATION); + + final String path = this.createRequestPath(restOperationMeta); + final IpPort ipPort = (IpPort) invocation.getEndpoint().getAddress(); + + webSocketClientRequest = createWebSocketRequest(ipPort, path); + webSocketClientRequest + .putHeader(Const.TARGET_MICROSERVICE, invocation.getMicroserviceName()); + + final RestClientRequestWebSocketWrapper restClientRequest = + new RestClientRequestWebSocketWrapper(webSocketClientRequest); + invocation.getHandlerContext().put(RestConst.INVOCATION_HANDLER_REQUESTCLIENT, restClientRequest); + + final VertxClientWebSocketRequestToHttpServletRequest requestEx = + new VertxClientWebSocketRequestToHttpServletRequest(webSocketClientRequest); + + executeHttpClientFilters(requestEx) + .thenCompose(v -> { + webSocketClientWithContext.runOnContext(webSocketClient -> { + processServiceCombHeaders(invocation, operationMeta); + webSocketClient.connect(webSocketClientRequest) + .onComplete(r -> invocation.getInvocationStageTrace().finishGetConnection()) + .compose(clientWebSocket -> { + this.clientWebSocket = clientWebSocket + .pause(); + + handleResponse(invocation); + return Future.succeededFuture(); + }) + .onFailure(failure -> { + invocation.getTraceIdLogger() + .error(LOGGER, "Failed to send request, alreadyFailed:{}, local:{}, remote:{}, message={}.", + alreadyFailed, getLocalAddress(), ipPort.getSocketAddress(), + ExceptionUtils.getExceptionMessageWithoutTrace(failure)); + fail(failure); + }); + }); + return CompletableFuture.completedFuture(null); + }) + .exceptionally(failure -> { + invocation.getTraceIdLogger() + .error(LOGGER, "Failed to send request, alreadyFailed:{}, local:{}, remote:{}, message={}.", + alreadyFailed, getLocalAddress(), ipPort.getSocketAddress(), + ExceptionUtils.getExceptionMessageWithoutTrace(failure)); + fail(failure); + return null; + }); + } + + private void handleResponse(Invocation invocation) { + invocation.getResponseExecutor().execute(() -> { + try { + final VertxClientWebSocketResponseToHttpServletResponse responseEx + = new VertxClientWebSocketResponseToHttpServletResponse(clientWebSocket); + for (HttpClientFilter filter : httpClientFilters) { + if (filter.enabled() && filter.enabledForTransport(invocation.getTransportName())) { + final Response response = filter.afterReceiveResponse(invocation, responseEx); + if (response != null) { + complete(response); + return; + } + } + } + } catch (Throwable e) { + fail(e); + } + }); + } + + private void complete(Response response) { + invocation.getInvocationStageTrace().finishClientFiltersResponse(); + asyncResp.complete(response); + } + + private CompletableFuture executeHttpClientFilters(HttpServletRequestEx requestEx) { + HttpClientFilterBeforeSendRequestExecutor exec = + new HttpClientFilterBeforeSendRequestExecutor(httpClientFilters, invocation, requestEx); + return exec.run(); + } + + /** + * If this is a 3rd party invocation, ServiceComb related headers should be removed by default to hide inner + * implementation. Otherwise, the InvocationContext will be set into the request headers. + * + * @see OperationConfig#isClientRequestHeaderFilterEnabled() + * @param invocation invocation determines whether this is an invocation to 3rd party services + * @param operationMeta operationMeta determines whether to remove certain headers and which headers should be removed + */ + private void processServiceCombHeaders(Invocation invocation, OperationMeta operationMeta) { + if (invocation.isThirdPartyInvocation() && operationMeta.getConfig().isClientRequestHeaderFilterEnabled()) { + for (String internalHeaderName : INTERNAL_HEADERS) { + webSocketClientRequest.removeHeader(internalHeaderName); + } + return; + } + this.setCseContext(); + } + + private void setCseContext() { + try { + webSocketClientRequest.putHeader(Const.CSE_CONTEXT, JsonUtils.writeUnicodeValueAsString(invocation.getContext())); + } catch (Throwable e) { + invocation.getTraceIdLogger().error(LOGGER, "Failed to encode and set cseContext, message={}.", + ExceptionUtils.getExceptionMessageWithoutTrace(e)); + } + } + + private HttpMethod getMethod() { + OperationMeta operationMeta = invocation.getOperationMeta(); + RestOperationMeta swaggerRestOperation = operationMeta.getExtData(RestConst.SWAGGER_REST_OPERATION); + String method = swaggerRestOperation.getHttpMethod(); + return HttpMethod.valueOf(method); + } + + WebSocketConnectOptions createWebSocketRequest(IpPort ipPort, String path) { + URIEndpointObject endpoint = (URIEndpointObject) invocation.getEndpoint().getAddress(); + HttpMethod method = getMethod(); + + final WebSocketConnectOptions webSocketConnectOptions = new WebSocketConnectOptions() + .setHost(ipPort.getHostOrIp()) + .setPort(ipPort.getPort()) + .setSsl(endpoint.isSslEnabled()) + .setMethod(method) + .setTimeout(webSocketClientWithContext.getOption().getConnectTimeoutInMillis()) + .setURI(path); + + invocation.getTraceIdLogger() + .debug(LOGGER, "Sending request by websocket, method={}, qualifiedName={}, path={}, endpoint={}.", + method, + invocation.getMicroserviceQualifiedName(), + path, + invocation.getEndpoint().getEndpoint()); + return webSocketConnectOptions; + } + + protected String createRequestPath(RestOperationMeta swaggerRestOperation) throws Exception { + URIEndpointObject address = (URIEndpointObject) invocation.getEndpoint().getAddress(); + String urlPrefix = address.getFirst(DefinitionConst.URL_PREFIX); + + String path = swaggerRestOperation.getPathBuilder().createRequestPath(invocation.getSwaggerArguments()); + + if (StringUtils.isEmpty(urlPrefix) || path.startsWith(urlPrefix)) { + return path; + } + + return urlPrefix + path; + } + + protected void fail(Throwable e) { + if (alreadyFailed) { + return; + } + + alreadyFailed = true; + + InvocationStageTrace stageTrace = invocation.getInvocationStageTrace(); + + // even failed and did not received response, still set time for it + // that will help to know the real timeout time + if (stageTrace.getFinishReceiveResponse() == 0) { + stageTrace.finishReceiveResponse(); + } + if (stageTrace.getStartClientFiltersResponse() == 0) { + stageTrace.startClientFiltersResponse(); + } + + stageTrace.finishClientFiltersResponse(); + + try { + if (e instanceof TimeoutException) { + // give an accurate cause for timeout exception + // The timeout period of 30000ms has been exceeded while executing GET /xxx for server 1.1.1.1:8080 + // should not copy the message to invocationException to avoid leak server ip address + LOGGER.info("Request timeout, Details: {}.", e.getMessage()); + asyncResp.consumerFail(new InvocationException(Status.REQUEST_TIMEOUT, + new CommonExceptionData("Request Timeout."))); + return; + } + asyncResp.fail(invocation.getInvocationType(), e); + } catch (Throwable e1) { + invocation.getTraceIdLogger().error(LOGGER, "failed to invoke asyncResp, message={}" + , ExceptionUtils.getExceptionMessageWithoutTrace(e)); + } + } + + private String getLocalAddress() { + if (clientWebSocket == null || clientWebSocket.localAddress() == null) { + return "not connected"; + } + return clientWebSocket.localAddress().toString(); + } +} diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/WebSocketTransportClient.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/WebSocketTransportClient.java new file mode 100644 index 00000000000..33938c88840 --- /dev/null +++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/WebSocketTransportClient.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.transport.rest.client.ws; + +import java.util.List; + +import org.apache.servicecomb.common.rest.filter.HttpClientFilter; +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils; +import org.apache.servicecomb.foundation.vertx.client.http.HttpClients; +import org.apache.servicecomb.foundation.vertx.client.ws.WebSocketClientWithContext; +import org.apache.servicecomb.swagger.invocation.AsyncResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WebSocketTransportClient { + + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketTransportClient.class); + + private final List httpClientFilters; + + WebSocketTransportClient() { + httpClientFilters = SPIServiceUtils.getSortedService(HttpClientFilter.class); + } + + public void send(Invocation invocation, AsyncResponse asyncResp) { + final WebSocketClientWithContext webSocketClientWithContext = findWebSocketPool(invocation); + final WebSocketClientInvocation webSocketClientInvocation = new WebSocketClientInvocation( + webSocketClientWithContext, httpClientFilters); + try { + webSocketClientInvocation.invoke(invocation, asyncResp); + } catch (Throwable e) { + asyncResp.fail(invocation.getInvocationType(), e); + LOGGER.error("vertx websocket transport send error.", e); + } + } + + protected WebSocketClientWithContext findWebSocketPool(Invocation invocation) { + String clientName = WebSocketTransportClientOptionsSPI.CLIENT_NAME; + return HttpClients.getWebSocketClient(clientName, invocation.isSync(), null); + } +} diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/WebSocketTransportClientManager.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/WebSocketTransportClientManager.java new file mode 100644 index 00000000000..474130fc28d --- /dev/null +++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/WebSocketTransportClientManager.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.transport.rest.client.ws; + +/** + * Manage WebSocketTransportClient + */ +public class WebSocketTransportClientManager { + public static final WebSocketTransportClientManager INSTANCE = new WebSocketTransportClientManager(); + + private final WebSocketTransportClient webSocketTransportClient; + + private WebSocketTransportClientManager() { + webSocketTransportClient = new WebSocketTransportClient(); + } + + public WebSocketTransportClient getWebSocketTransportClient() { + return webSocketTransportClient; + } +} diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/WebSocketTransportClientOptionsSPI.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/WebSocketTransportClientOptionsSPI.java new file mode 100644 index 00000000000..8672e0a7ea0 --- /dev/null +++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/ws/WebSocketTransportClientOptionsSPI.java @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.transport.rest.client.ws; + +import org.apache.servicecomb.foundation.vertx.client.ws.WebSocketClientOptionsSPI; +import org.apache.servicecomb.transport.common.TransportConfigUtils; +import org.apache.servicecomb.transport.rest.client.HttpTransportHttpClientOptionsSPI; + +import com.netflix.config.ConcurrentCompositeConfiguration; +import com.netflix.config.DynamicPropertyFactory; + +import io.vertx.core.VertxOptions; +import io.vertx.core.http.HttpClientOptions; + +public class WebSocketTransportClientOptionsSPI extends WebSocketClientOptionsSPI { + public static final String CLIENT_NAME = "ws-transport-client"; + + public static final String CLIENT_TAG = "websocket.consumer"; + + public static final boolean DEFAULT_CLIENT_COMPRESSION_SUPPORT = false; + + private static final int DEFAULT_IDLE_TIME_OUT = 150; + + @Override + public String clientName() { + return CLIENT_NAME; + } + + @Override + public int getOrder() { + return 100; + } + + @Override + public boolean enabled() { + return DynamicPropertyFactory.getInstance() + .getBooleanProperty("servicecomb.websocket.client.enabled", + true) + .get(); + } + + @Override + public String getConfigTag() { + return CLIENT_TAG; + } + + @Override + public ConcurrentCompositeConfiguration getConfigReader() { + return null; + } + + @Override + public int getEventLoopPoolSize() { + // not reading this, using shared transport vert.x + return -1; + } + + @Override + public boolean useSharedVertx() { + return true; + } + + @Override + public int getInstanceCount() { + return TransportConfigUtils.readVerticleCount( + "servicecomb.websocket.client.verticle-count", + "servicecomb.websocket.client.thread-count"); + } + + @Override + public boolean isWorker() { + return false; + } + + @Override + public String getWorkerPoolName() { + return "pool-worker-transport-client-websocket"; + } + + @Override + public int getWorkerPoolSize() { + return VertxOptions.DEFAULT_WORKER_POOL_SIZE; + } + + @Override + public int getConnectTimeoutInMillis() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.websocket.client.connection.timeoutInMillis", 60000) + .get(); + } + + @Override + public int getIdleTimeoutInSeconds() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.websocket.client.connection.idleTimeoutInSeconds", + DEFAULT_IDLE_TIME_OUT) + .get(); + } + + @Override + public boolean enableLogActivity() { + return DynamicPropertyFactory.getInstance() + .getBooleanProperty("servicecomb.websocket.client.enableLogActivity", false) + .get(); + } + + @Override + public boolean isUseAlpn() { + return DynamicPropertyFactory.getInstance() + .getBooleanProperty("servicecomb.websocket.client.useAlpnEnabled", false) + .get(); + } + + /** + * keep the same to {@link HttpTransportHttpClientOptionsSPI#isProxyEnable()} + */ + @Override + public boolean isProxyEnable() { + return false; + } + + /** + * keep the same to {@link HttpTransportHttpClientOptionsSPI#getProxyHost()} + */ + @Override + public String getProxyHost() { + return null; + } + + /** + * keep the same to {@link HttpTransportHttpClientOptionsSPI#getProxyPort()} + */ + @Override + public int getProxyPort() { + return 0; + } + + /** + * keep the same to {@link HttpTransportHttpClientOptionsSPI#getProxyUsername()} + */ + @Override + public String getProxyUsername() { + return null; + } + + /** + * keep the same to {@link HttpTransportHttpClientOptionsSPI#getProxyPassword()} + */ + @Override + public String getProxyPassword() { + return null; + } + + @Override + public boolean isSsl() { + return true; + } + + // options below are only owned by websocket + @Override + public int getMaxFrameSize() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.websocket.client.maxFrameSize", + HttpClientOptions.DEFAULT_MAX_WEBSOCKET_FRAME_SIZE) + .get(); + } + + @Override + public int getMaxMessageSize() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.websocket.client.maxMessageSize", + HttpClientOptions.DEFAULT_MAX_WEBSOCKET_MESSAGE_SIZE) + .get(); + } + + @Override + public int getMaxConnections() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.websocket.client.connection.maxPoolSize", + HttpClientOptions.DEFAULT_MAX_WEBSOCKETS) + .get(); + } + + @Override + public boolean getTryUsePerFrameCompression() { + return DynamicPropertyFactory.getInstance() + .getBooleanProperty("servicecomb.websocket.client.tryUsePerFrameCompression", + DEFAULT_CLIENT_COMPRESSION_SUPPORT) + .get(); + } + + @Override + public boolean getTryUsePerMessageCompression() { + return DynamicPropertyFactory.getInstance() + .getBooleanProperty("servicecomb.websocket.client.tryUsePerMessageCompression", + DEFAULT_CLIENT_COMPRESSION_SUPPORT) + .get(); + } + + @Override + public int getCompressionLevel() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.websocket.client.compressionLevel", + HttpClientOptions.DEFAULT_WEBSOCKET_COMPRESSION_LEVEL) + .get(); + } + + @Override + public int getClosingTimeoutInSeconds() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.websocket.client.connection.closingTimeoutInSeconds", + HttpClientOptions.DEFAULT_WEBSOCKET_CLOSING_TIMEOUT) + .get(); + } +} diff --git a/transports/transport-rest/transport-rest-client/src/main/resources/META-INF/services/org.apache.servicecomb.foundation.vertx.client.ws.WebSocketClientOptionsSPI b/transports/transport-rest/transport-rest-client/src/main/resources/META-INF/services/org.apache.servicecomb.foundation.vertx.client.ws.WebSocketClientOptionsSPI new file mode 100644 index 00000000000..2be17135dd3 --- /dev/null +++ b/transports/transport-rest/transport-rest-client/src/main/resources/META-INF/services/org.apache.servicecomb.foundation.vertx.client.ws.WebSocketClientOptionsSPI @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. +# + +org.apache.servicecomb.transport.rest.client.ws.WebSocketTransportClientOptionsSPI \ No newline at end of file diff --git a/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/TestTransportClientConfig.java b/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/TestTransportClientConfig.java index 10f43c23c10..4409e29797e 100644 --- a/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/TestTransportClientConfig.java +++ b/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/TestTransportClientConfig.java @@ -48,7 +48,7 @@ public void getConnectionMaxPoolSize() { @Test public void getConnectionIdleTimeoutInSeconds() { - Assertions.assertEquals(30, TransportClientConfig.getConnectionIdleTimeoutInSeconds()); + Assertions.assertEquals(150, TransportClientConfig.getConnectionIdleTimeoutInSeconds()); } @Test @@ -63,7 +63,7 @@ public void getHttp2ConnectionMaxPoolSize() { @Test public void getHttp2ConnectionIdleTimeoutInSeconds() { - Assertions.assertEquals(0, TransportClientConfig.getHttp2ConnectionIdleTimeoutInSeconds()); + Assertions.assertEquals(150, TransportClientConfig.getHttp2ConnectionIdleTimeoutInSeconds()); } @Test diff --git a/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/http/TestDefaultHttpClientFilter.java b/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/http/TestDefaultHttpClientFilter.java index 2e1c18e3be4..d93de48b4b6 100644 --- a/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/http/TestDefaultHttpClientFilter.java +++ b/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/http/TestDefaultHttpClientFilter.java @@ -38,6 +38,7 @@ import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData; import org.apache.servicecomb.swagger.invocation.exception.InvocationException; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.type.SimpleType; @@ -49,7 +50,6 @@ import mockit.Mock; import mockit.MockUp; import mockit.Mocked; -import org.junit.jupiter.api.Assertions; public class TestDefaultHttpClientFilter { private final DefaultHttpClientFilter filter = new DefaultHttpClientFilter(); @@ -155,7 +155,8 @@ ProduceProcessor findProduceProcessor(RestOperationMeta restOperation, HttpServl Assertions.assertEquals(InvocationException.class, response.getResult().getClass()); InvocationException invocationException = response.getResult(); Assertions.assertEquals( - "InvocationException: code=400;msg=CommonExceptionData [message=method null, path null, statusCode 400, reasonPhrase null, response content-type null is not supported]", + "InvocationException: code=400;msg=CommonExceptionData " + + "[message=Failed to decode response body. Operation null.]", invocationException.getMessage()); Assertions.assertEquals( "Unrecognized token 'abc': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n" @@ -164,7 +165,7 @@ ProduceProcessor findProduceProcessor(RestOperationMeta restOperation, HttpServl Assertions.assertEquals(CommonExceptionData.class, invocationException.getErrorData().getClass()); CommonExceptionData commonExceptionData = (CommonExceptionData) invocationException.getErrorData(); Assertions.assertEquals( - "method null, path null, statusCode 400, reasonPhrase null, response content-type null is not supported", + "Failed to decode response body. Operation null.", commonExceptionData.getMessage()); } @@ -202,7 +203,8 @@ ProduceProcessor findProduceProcessor(RestOperationMeta restOperation, HttpServl Assertions.assertEquals(InvocationException.class, response.getResult().getClass()); InvocationException invocationException = response.getResult(); Assertions.assertEquals( - "InvocationException: code=400;msg=CommonExceptionData [message=method null, path null, statusCode 200, reasonPhrase null, response content-type null is not supported]", + "InvocationException: code=400;msg=CommonExceptionData " + + "[message=Failed to decode response body. Operation null.]", invocationException.getMessage()); Assertions.assertEquals( "Unrecognized token 'abc': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n" @@ -211,7 +213,7 @@ ProduceProcessor findProduceProcessor(RestOperationMeta restOperation, HttpServl Assertions.assertEquals(CommonExceptionData.class, invocationException.getErrorData().getClass()); CommonExceptionData commonExceptionData = (CommonExceptionData) invocationException.getErrorData(); Assertions.assertEquals( - "method null, path null, statusCode 200, reasonPhrase null, response content-type null is not supported", + "Failed to decode response body. Operation null.", commonExceptionData.getMessage()); } diff --git a/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/http/TestRestClientInvocation.java b/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/http/TestRestClientInvocation.java index ea5dd4800ed..9e806176fea 100644 --- a/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/http/TestRestClientInvocation.java +++ b/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/http/TestRestClientInvocation.java @@ -179,7 +179,8 @@ public void invoke(@Mocked Response resp) throws Exception { MatcherAssert.assertThat(headers.names(), Matchers.containsInAnyOrder(org.apache.servicecomb.core.Const.TARGET_MICROSERVICE, org.apache.servicecomb.core.Const.CSE_CONTEXT)); - Assertions.assertEquals(TARGET_MICROSERVICE_NAME, headers.get(org.apache.servicecomb.core.Const.TARGET_MICROSERVICE)); + Assertions.assertEquals(TARGET_MICROSERVICE_NAME, + headers.get(org.apache.servicecomb.core.Const.TARGET_MICROSERVICE)); Assertions.assertEquals("{}", headers.get(org.apache.servicecomb.core.Const.CSE_CONTEXT)); Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getStartClientFiltersRequest()); } @@ -216,7 +217,8 @@ public void invoke_3rdPartyServiceExposeServiceCombHeaders(@Mocked Response resp MatcherAssert.assertThat(headers.names(), Matchers.containsInAnyOrder(org.apache.servicecomb.core.Const.TARGET_MICROSERVICE, org.apache.servicecomb.core.Const.CSE_CONTEXT)); - Assertions.assertEquals(TARGET_MICROSERVICE_NAME, headers.get(org.apache.servicecomb.core.Const.TARGET_MICROSERVICE)); + Assertions.assertEquals(TARGET_MICROSERVICE_NAME, + headers.get(org.apache.servicecomb.core.Const.TARGET_MICROSERVICE)); Assertions.assertEquals("{}", headers.get(org.apache.servicecomb.core.Const.CSE_CONTEXT)); Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getStartClientFiltersRequest()); operationConfig.setClientRequestHeaderFilterEnabled(true); @@ -286,11 +288,11 @@ public void testSetCseContext_enable_unicode() throws Exception { when(invocation.getContext()).thenReturn(contextMap); restClientInvocation.setCseContext(); - String context = headers.get(org.apache.servicecomb.core.Const.CSE_CONTEXT); - HttpServletRequestEx requestEx = new MockUp(){ + String context = headers.get(org.apache.servicecomb.core.Const.CSE_CONTEXT); + HttpServletRequestEx requestEx = new MockUp() { @Mock - public String getHeader(String name){ - if (StringUtils.equals(name, org.apache.servicecomb.core.Const.CSE_CONTEXT)){ + public String getHeader(String name) { + if (StringUtils.equals(name, org.apache.servicecomb.core.Const.CSE_CONTEXT)) { return context; } else { return null; @@ -324,11 +326,11 @@ public String writeUnicodeValueAsString(Object value) throws JsonProcessingExcep }; restClientInvocation.setCseContext(); - String context = headers.get(org.apache.servicecomb.core.Const.CSE_CONTEXT); - HttpServletRequestEx requestEx = new MockUp(){ + String context = headers.get(org.apache.servicecomb.core.Const.CSE_CONTEXT); + HttpServletRequestEx requestEx = new MockUp() { @Mock - public String getHeader(String name){ - if (StringUtils.equals(name, org.apache.servicecomb.core.Const.CSE_CONTEXT)){ + public String getHeader(String name) { + if (StringUtils.equals(name, org.apache.servicecomb.core.Const.CSE_CONTEXT)) { return context; } else { return null; @@ -419,7 +421,6 @@ public void processResponseBody_throw() { Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getStartClientFiltersResponse()); Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getFinishClientFiltersResponse()); Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getFinishReceiveResponse()); - Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getFinishWriteToBuffer()); } @Test diff --git a/transports/transport-rest/transport-rest-servlet/src/test/java/org/apache/servicecomb/transport/rest/servlet/TestCseXmlWebApplicationContext.java b/transports/transport-rest/transport-rest-servlet/src/test/java/org/apache/servicecomb/transport/rest/servlet/TestCseXmlWebApplicationContext.java index e80d8bb6d49..93cd301c00d 100644 --- a/transports/transport-rest/transport-rest-servlet/src/test/java/org/apache/servicecomb/transport/rest/servlet/TestCseXmlWebApplicationContext.java +++ b/transports/transport-rest/transport-rest-servlet/src/test/java/org/apache/servicecomb/transport/rest/servlet/TestCseXmlWebApplicationContext.java @@ -49,7 +49,7 @@ public void setup() { public void testGetConfigLocationsEmpty() { String[] result = context.getConfigLocations(); MatcherAssert.assertThat(result, - Matchers.arrayContaining(BeanUtils.DEFAULT_BEAN_CORE_RESOURCE, BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE)); + Matchers.arrayContaining(BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE)); } @Test @@ -57,7 +57,7 @@ public void testGetConfigLocationsEmptyAndDefaultEmpty() { context.setDefaultBeanResource(null); String[] result = context.getConfigLocations(); MatcherAssert.assertThat(result, - Matchers.arrayContaining(BeanUtils.DEFAULT_BEAN_CORE_RESOURCE, BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE)); + Matchers.arrayContaining(BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE)); } @Test @@ -71,7 +71,7 @@ public void testGetConfigLocationsComma() { String[] result = context.getConfigLocations(); MatcherAssert.assertThat(result, Matchers - .arrayContaining(BeanUtils.DEFAULT_BEAN_CORE_RESOURCE, BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE, "a", "b")); + .arrayContaining(BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE, "a", "b")); } @Test @@ -84,7 +84,7 @@ public void testGetConfigLocationsPartEmpty() { }; String[] result = context.getConfigLocations(); MatcherAssert.assertThat(result, Matchers - .arrayContaining(BeanUtils.DEFAULT_BEAN_CORE_RESOURCE, BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE, "a", "b")); + .arrayContaining(BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE, "a", "b")); } @Test @@ -97,7 +97,7 @@ public void testGetConfigLocationsLine() { }; String[] result = context.getConfigLocations(); MatcherAssert.assertThat(result, Matchers - .arrayContaining(BeanUtils.DEFAULT_BEAN_CORE_RESOURCE, BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE, "a", "b")); + .arrayContaining(BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE, "a", "b")); } @Test @@ -110,7 +110,7 @@ public void testGetConfigLocationsMix() { }; String[] result = context.getConfigLocations(); MatcherAssert.assertThat(result, Matchers - .arrayContaining(BeanUtils.DEFAULT_BEAN_CORE_RESOURCE, BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE, "a", "b", "c")); + .arrayContaining(BeanUtils.DEFAULT_BEAN_NORMAL_RESOURCE, "a", "b", "c")); } @Test diff --git a/transports/transport-rest/transport-rest-servlet/src/test/java/org/apache/servicecomb/transport/rest/servlet/TestRestServlet.java b/transports/transport-rest/transport-rest-servlet/src/test/java/org/apache/servicecomb/transport/rest/servlet/TestRestServlet.java index 01855f89f73..b4f2a0a9b06 100644 --- a/transports/transport-rest/transport-rest-servlet/src/test/java/org/apache/servicecomb/transport/rest/servlet/TestRestServlet.java +++ b/transports/transport-rest/transport-rest-servlet/src/test/java/org/apache/servicecomb/transport/rest/servlet/TestRestServlet.java @@ -29,11 +29,11 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import mockit.Deencapsulation; import mockit.Mock; import mockit.MockUp; -import org.junit.jupiter.api.Assertions; public class TestRestServlet { private RestServlet restservlet = null; @@ -51,6 +51,7 @@ public void setUp() { public void tearDown() { restservlet = null; SCBEngine.getInstance().destroy(); + SCBBootstrap.resetSCBEngineForTest(); ArchaiusUtils.resetConfig(); } diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestBodyHandler.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestBodyHandler.java index f6e02fe0223..6492e77e87c 100644 --- a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestBodyHandler.java +++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestBodyHandler.java @@ -24,14 +24,12 @@ import java.io.File; import java.util.List; import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import com.google.common.annotations.VisibleForTesting; import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData; import org.apache.servicecomb.swagger.invocation.exception.ExceptionFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import com.google.common.annotations.VisibleForTesting; import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.http.HttpHeaderValues; @@ -41,10 +39,11 @@ import io.vertx.core.file.FileSystem; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.core.http.HttpVersion; import io.vertx.ext.web.FileUpload; import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.BodyHandler; -import io.vertx.ext.web.handler.impl.BodyHandlerImpl; import io.vertx.ext.web.impl.FileUploadImpl; import io.vertx.ext.web.impl.RoutingContextInternal; @@ -56,8 +55,6 @@ */ public class RestBodyHandler implements BodyHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(BodyHandlerImpl.class); - private long bodyLimit = DEFAULT_BODY_LIMIT; private boolean handleFileUploads; @@ -98,7 +95,9 @@ boolean isDeleteUploadedFilesOnEnd() { @Override public void handle(RoutingContext context) { - HttpServerRequest request = context.request(); + final HttpServerRequest request = context.request(); + final HttpServerResponse response = context.response(); + if (request.headers().contains(HttpHeaders.UPGRADE, HttpHeaders.WEBSOCKET, true)) { context.next(); return; @@ -112,11 +111,59 @@ public void handle(RoutingContext context) { // we need to keep state since we can be called again on reroute if (!((RoutingContextInternal) context).seenHandler(RoutingContextInternal.BODY_HANDLER)) { - long contentLength = isPreallocateBodyBuffer ? parseContentLengthHeader(request) : -1; - BHandler handler = new BHandler(context, contentLength); - request.handler(handler); - request.endHandler(v -> handler.end()); ((RoutingContextInternal) context).visitHandler(RoutingContextInternal.BODY_HANDLER); + + // Check if a request has a request body. + // A request with a body __must__ either have `transfer-encoding` + // or `content-length` headers set. + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3 + final long parsedContentLength = parseContentLengthHeader(request); + // http2 never transmits a `transfer-encoding` as frames are chunks. + final boolean hasTransferEncoding = + request.version() == HttpVersion.HTTP_2 || request.headers().contains(HttpHeaders.TRANSFER_ENCODING); + + if (!hasTransferEncoding && parsedContentLength == -1) { + // there is no "body", so we can skip this handler + context.next(); + return; + } + + // before parsing the body we can already discard a bad request just by inspecting the content-length against + // the body limit, this will reduce load, on the server by totally skipping parsing the request body + if (bodyLimit != -1 && parsedContentLength != -1) { + if (parsedContentLength > bodyLimit) { + context.fail(413); + return; + } + } + + // handle expectations + // https://httpwg.org/specs/rfc7231.html#header.expect + final String expect = request.getHeader(HttpHeaders.EXPECT); + if (expect != null) { + // requirements validation + if (expect.equalsIgnoreCase("100-continue")) { + // A server that receives a 100-continue expectation in an HTTP/1.0 request MUST ignore that expectation. + if (request.version() != HttpVersion.HTTP_1_0) { + // signal the client to continue + response.writeContinue(); + } + } else { + // the server cannot meet the expectation, we only know about 100-continue + context.fail(417); + return; + } + } + + final BHandler handler = new BHandler(context, isPreallocateBodyBuffer ? parsedContentLength : -1); + boolean ended = request.isEnded(); + if (!ended) { + request + // resume the request (if paused) + .handler(handler) + .endHandler(handler::end) + .resume(); + } } else { // on reroute we need to re-merge the form params if that was desired if (mergeFormAttributes && request.isExpectMultipart()) { @@ -187,9 +234,7 @@ private class BHandler implements Handler { boolean failed; - AtomicInteger uploadCount = new AtomicInteger(); - - AtomicBoolean cleanup = new AtomicBoolean(false); + final AtomicInteger uploadCount = new AtomicInteger(); boolean ended; @@ -240,7 +285,7 @@ public BHandler(RoutingContext context, long contentLength) { long size = uploadSize + upload.size(); if (size > bodyLimit) { failed = true; - cancelAndCleanupFileUploads(); + context.cancelAndCleanupFileUploads(); context.fail(413); return; } @@ -249,14 +294,14 @@ public BHandler(RoutingContext context, long contentLength) { // we actually upload to a file with a generated filename uploadCount.incrementAndGet(); String uploadedFileName = new File(uploadsDir, UUID.randomUUID().toString()).getPath(); - FileUploadImpl fileUpload = new FileUploadImpl(uploadedFileName, upload); + FileUploadImpl fileUpload = new FileUploadImpl(context.vertx().fileSystem(), uploadedFileName, upload); fileUploads.add(fileUpload); Future fut = upload.streamToFileSystem(uploadedFileName); fut.onComplete(ar -> { if (fut.succeeded()) { uploadEnded(); } else { - cancelAndCleanupFileUploads(); + context.cancelAndCleanupFileUploads(); context.fail(ar.cause()); } }); @@ -265,13 +310,16 @@ public BHandler(RoutingContext context, long contentLength) { } context.request().exceptionHandler(t -> { - cancelAndCleanupFileUploads(); + context.cancelAndCleanupFileUploads(); + int sc = 200; if (t instanceof DecoderException) { // bad request - context.fail(400, t.getCause()); - } else { - context.fail(t); + sc = 400; + if (t.getCause() != null) { + t = t.getCause(); + } } + context.fail(sc, t); }); } @@ -312,7 +360,7 @@ public void handle(Buffer buff) { uploadSize += buff.length(); if (bodyLimit != -1 && uploadSize > bodyLimit) { failed = true; - cancelAndCleanupFileUploads(); + context.cancelAndCleanupFileUploads(); context.fail(413); } else { // multipart requests will not end up in the request body @@ -335,7 +383,7 @@ void uploadEnded() { } } - void end() { + void end(Void v) { // this marks the end of body parsing, calling doEnd should // only be possible from this moment onwards ended = true; @@ -348,46 +396,24 @@ void end() { void doEnd() { - if (failed) { - cancelAndCleanupFileUploads(); + if (failed || context.failed()) { + context.cancelAndCleanupFileUploads(); return; } if (deleteUploadedFilesOnEnd) { - context.addBodyEndHandler(x -> cancelAndCleanupFileUploads()); + context.addBodyEndHandler(x -> context.cancelAndCleanupFileUploads()); } HttpServerRequest req = context.request(); if (mergeFormAttributes && req.isExpectMultipart()) { req.params().addAll(req.formAttributes()); } - if (context instanceof RoutingContextInternal) { - RoutingContextInternal contextInternal = (RoutingContextInternal) context; - contextInternal.setBody(body); - } + ((RoutingContextInternal) context).setBody(body); // release body as it may take lots of memory body = null; context.next(); } - - /** - * Cancel all unfinished file upload in progress and delete all uploaded files. - */ - private void cancelAndCleanupFileUploads() { - if (cleanup.compareAndSet(false, true) && handleFileUploads) { - for (FileUpload fileUpload : context.fileUploads()) { - FileSystem fileSystem = context.vertx().fileSystem(); - if (!fileUpload.cancel()) { - String uploadedFileName = fileUpload.uploadedFileName(); - fileSystem.delete(uploadedFileName, deleteResult -> { - if (deleteResult.failed()) { - LOGGER.warn("Delete of uploaded file failed: " + uploadedFileName, deleteResult.cause()); - } - }); - } - } - } - } } } diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestServerVerticle.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestServerVerticle.java index 304aa491c88..9edf23126f2 100644 --- a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestServerVerticle.java +++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestServerVerticle.java @@ -57,6 +57,7 @@ import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerResponse; import io.vertx.core.net.impl.ConnectionBase; import io.vertx.ext.web.Router; @@ -68,14 +69,12 @@ public class RestServerVerticle extends AbstractVerticle { private static final String SSL_KEY = "rest.provider"; - private Endpoint endpoint; - private URIEndpointObject endpointObject; @Override public void init(Vertx vertx, Context context) { super.init(vertx, context); - this.endpoint = (Endpoint) context.config().getValue(AbstractTransport.ENDPOINT_KEY); + Endpoint endpoint = (Endpoint) context.config().getValue(AbstractTransport.ENDPOINT_KEY); this.endpointObject = (URIEndpointObject) endpoint.getAddress(); } @@ -90,6 +89,7 @@ public void start(Promise startPromise) throws Exception { return; } Router mainRouter = Router.router(vertx); + mountWebSocketPauseHandler(mainRouter); mountAccessLogHandler(mainRouter); mountCorsHandler(mainRouter); initDispatcher(mainRouter); @@ -165,6 +165,18 @@ void mountGlobalRestFailureHandler(Router mainRouter) { .failureHandler(failureHandler); } + private void mountWebSocketPauseHandler(Router mainRouter) { + mainRouter.route().handler(context -> { + final HttpServerRequest request = context.request(); + if (request.headers().contains( + io.vertx.core.http.HttpHeaders.UPGRADE, io.vertx.core.http.HttpHeaders.WEBSOCKET, true)) { + // pause for websocket, to avoid missing end event, which may cause vert.x HttpServerRequest toWebSocket never complete + request.pause(); + } + context.next(); + }); + } + private void mountAccessLogHandler(Router mainRouter) { if (!AccessLogConfig.INSTANCE.isServerLogEnabled()) { return; @@ -189,7 +201,7 @@ void mountCorsHandler(Router mainRouter) { return; } - CorsHandler corsHandler = getCorsHandler(TransportConfig.getCorsAllowedOrigin()); + CorsHandler corsHandler = getCorsHandler(); // Access-Control-Allow-Credentials corsHandler.allowCredentials(TransportConfig.isCorsAllowCredentials()); // Access-Control-Allow-Headers @@ -211,8 +223,17 @@ void mountCorsHandler(Router mainRouter) { mainRouter.route().handler(corsHandler); } - private CorsHandler getCorsHandler(String corsAllowedOrigin) { - return CorsHandler.create().addOrigin(corsAllowedOrigin); + private CorsHandler getCorsHandler() { + CorsHandler handler = CorsHandler.create(); + Set origin = TransportConfig.getCorsAllowedOrigin(); + if (origin.isEmpty()) { + handler.addOrigin("*"); + } else { + for (String item : origin) { + handler.addOrigin(item); + } + } + return handler; } private void initDispatcher(Router mainRouter) { @@ -250,18 +271,32 @@ private HttpServer createHttpServer() { private HttpServerOptions createDefaultHttpServerOptions() { HttpServerOptions serverOptions = new HttpServerOptions(); - serverOptions.setIdleTimeout(TransportConfig.getConnectionIdleTimeoutInSeconds()); serverOptions.setCompressionSupported(TransportConfig.getCompressed()); serverOptions.setMaxHeaderSize(TransportConfig.getMaxHeaderSize()); serverOptions.setMaxFormAttributeSize(TransportConfig.getMaxFormAttributeSize()); + serverOptions.setMaxFormFields(TransportConfig.getMaxFormFields()); + serverOptions.setMaxFormBufferedBytes(TransportConfig.getMaxFormBufferedBytes()); serverOptions.setCompressionLevel(TransportConfig.getCompressionLevel()); serverOptions.setMaxChunkSize(TransportConfig.getMaxChunkSize()); serverOptions.setDecompressionSupported(TransportConfig.getDecompressionSupported()); serverOptions.setDecoderInitialBufferSize(TransportConfig.getDecoderInitialBufferSize()); - serverOptions.setHttp2ConnectionWindowSize(TransportConfig.getHttp2ConnectionWindowSize()); serverOptions.setMaxInitialLineLength(TransportConfig.getMaxInitialLineLength()); + serverOptions.setLogActivity(TransportConfig.enableLogActivity()); + + // WebSocket config start + serverOptions.setMaxWebSocketFrameSize(TransportConfig.getMaxWebSocketFrameSize()); + serverOptions.setMaxWebSocketMessageSize(TransportConfig.getMaxWebSocketMessageSize()); + serverOptions.setWebSocketClosingTimeout(TransportConfig.getWebSocketClosingTimeoutInSeconds()); + serverOptions.setWebSocketCompressionLevel(TransportConfig.getWebSocketCompressionLevel()); + serverOptions.setPerFrameWebSocketCompressionSupported(TransportConfig.getPerFrameWebSocketCompressionSupported()); + serverOptions.setPerMessageWebSocketCompressionSupported( + TransportConfig.getPerMessageWebSocketCompressionSupported()); + // WebSocket config end + if (endpointObject.isHttp2Enabled()) { serverOptions.setUseAlpn(TransportConfig.getUseAlpn()) + .setHttp2ConnectionWindowSize(TransportConfig.getHttp2ConnectionWindowSize()) + .setIdleTimeout(TransportConfig.getHttp2ConnectionIdleTimeoutInSeconds()) .setInitialSettings(new Http2Settings().setPushEnabled(TransportConfig.getPushEnabled()) .setMaxConcurrentStreams(TransportConfig.getMaxConcurrentStreams()) .setHeaderTableSize(TransportConfig.getHttp2HeaderTableSize()) @@ -269,6 +304,8 @@ private HttpServerOptions createDefaultHttpServerOptions() { .setMaxFrameSize(TransportConfig.getMaxFrameSize()) .setMaxHeaderListSize(TransportConfig.getMaxHeaderListSize()) ); + } else { + serverOptions.setIdleTimeout(TransportConfig.getConnectionIdleTimeoutInSeconds()); } if (endpointObject.isSslEnabled()) { SSLOptionFactory factory = diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/TransportConfig.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/TransportConfig.java index 28665accee0..6452e33c87e 100644 --- a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/TransportConfig.java +++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/TransportConfig.java @@ -32,7 +32,7 @@ import io.vertx.core.http.HttpServerOptions; public final class TransportConfig { - public static final int DEFAULT_SERVER_CONNECTION_IDLE_TIMEOUT_SECOND = 60; + public static final int DEFAULT_SERVER_CONNECTION_IDLE_TIMEOUT_SECOND = 180; public static final boolean DEFAULT_SERVER_COMPRESSION_SUPPORT = false; @@ -66,6 +66,18 @@ public static int getMaxFormAttributeSize() { HttpServerOptions.DEFAULT_MAX_FORM_ATTRIBUTE_SIZE).get(); } + public static int getMaxFormFields() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.rest.server.maxFormFields", + HttpServerOptions.DEFAULT_MAX_FORM_FIELDS).get(); + } + + public static int getMaxFormBufferedBytes() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.rest.server.maxFormBufferedBytes", + HttpServerOptions.DEFAULT_MAX_FORM_BUFFERED_SIZE).get(); + } + public static int getCompressionLevel() { return DynamicPropertyFactory.getInstance() .getIntProperty("servicecomb.rest.server.compressionLevel", @@ -103,6 +115,13 @@ public static int getConnectionIdleTimeoutInSeconds() { .get(); } + public static int getHttp2ConnectionIdleTimeoutInSeconds() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.rest.server.http2.connection.idleTimeoutInSeconds", + DEFAULT_SERVER_CONNECTION_IDLE_TIMEOUT_SECOND) + .get(); + } + public static boolean getCompressed() { return DynamicPropertyFactory.getInstance() .getBooleanProperty("servicecomb.rest.server.compression", DEFAULT_SERVER_COMPRESSION_SUPPORT) @@ -163,16 +182,24 @@ public static int getMaxHeaderSize() { .get(); } + public static boolean enableLogActivity() { + return DynamicPropertyFactory.getInstance() + .getBooleanProperty("servicecomb.rest.server.enableLogActivity", + false) + .get(); + } + public static boolean isCorsEnabled() { return DynamicPropertyFactory.getInstance() .getBooleanProperty(SERVICECOMB_CORS_CONFIG_BASE + ".enabled", false) .get(); } - public static String getCorsAllowedOrigin() { - return DynamicPropertyFactory.getInstance() + public static Set getCorsAllowedOrigin() { + String allowedOrigin = DynamicPropertyFactory.getInstance() .getStringProperty(SERVICECOMB_CORS_CONFIG_BASE + ".origin", "*") .get(); + return convertToSet(allowedOrigin); } public static boolean isCorsAllowCredentials() { @@ -224,4 +251,46 @@ public static int getMaxInitialLineLength() { HttpServerOptions.DEFAULT_MAX_INITIAL_LINE_LENGTH) .get(); } + + public static int getMaxWebSocketFrameSize() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.websocket.server.maxFrameSize", + HttpServerOptions.DEFAULT_MAX_WEBSOCKET_FRAME_SIZE) + .get(); + } + + public static int getMaxWebSocketMessageSize() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.websocket.server.maxMessageSize", + HttpServerOptions.DEFAULT_MAX_WEBSOCKET_MESSAGE_SIZE) + .get(); + } + + public static int getWebSocketClosingTimeoutInSeconds() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.websocket.server.closingTimeoutInSeconds", + HttpServerOptions.DEFAULT_WEBSOCKET_CLOSING_TIMEOUT) + .get(); + } + + public static int getWebSocketCompressionLevel() { + return DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.websocket.server.compressionLevel", + HttpServerOptions.DEFAULT_WEBSOCKET_COMPRESSION_LEVEL) + .get(); + } + + public static boolean getPerFrameWebSocketCompressionSupported() { + return DynamicPropertyFactory.getInstance() + .getBooleanProperty("servicecomb.websocket.server.perFrameCompressionSupported", + DEFAULT_SERVER_COMPRESSION_SUPPORT) + .get(); + } + + public static boolean getPerMessageWebSocketCompressionSupported() { + return DynamicPropertyFactory.getInstance() + .getBooleanProperty("servicecomb.websocket.server.perMessageCompressionSupported", + DEFAULT_SERVER_COMPRESSION_SUPPORT) + .get(); + } } diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/VertxRestTransport.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/VertxRestTransport.java index 3267bf28a5d..267fd708f74 100644 --- a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/VertxRestTransport.java +++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/VertxRestTransport.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.List; +import java.util.Map; import org.apache.servicecomb.core.Const; import org.apache.servicecomb.core.Invocation; @@ -105,7 +106,13 @@ public boolean init() throws Exception { options.setWorkerPoolSize(VertxOptions.DEFAULT_WORKER_POOL_SIZE); prepareBlockResource(); - return VertxUtils.blockDeploy(transportVertx, TransportConfig.getRestServerVerticle(), options); + Map result = VertxUtils.blockDeploy(transportVertx, TransportConfig.getRestServerVerticle(), + options); + if ((boolean) result.get("code")) { + return true; + } else { + throw new IllegalStateException((String) result.get("message")); + } } private void prepareBlockResource() { diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/WebSocketTransport.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/WebSocketTransport.java new file mode 100644 index 00000000000..826e077f1e8 --- /dev/null +++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/WebSocketTransport.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.transport.rest.vertx; + +import org.apache.servicecomb.core.Const; +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.core.transport.AbstractTransport; +import org.apache.servicecomb.swagger.invocation.AsyncResponse; +import org.apache.servicecomb.transport.rest.client.ws.WebSocketTransportClient; +import org.apache.servicecomb.transport.rest.client.ws.WebSocketTransportClientManager; + +public class WebSocketTransport extends AbstractTransport { + + private WebSocketTransportClient webSocketTransportClient; + + @Override + public String getName() { + return Const.WEBSOCKET; + } + + @Override + public void send(Invocation invocation, AsyncResponse asyncResp) throws Exception { + webSocketTransportClient.send(invocation, asyncResp); + } + + @Override + public int getOrder() { + return -500; + } + + @Override + public boolean init() throws Exception { + webSocketTransportClient = WebSocketTransportClientManager.INSTANCE.getWebSocketTransportClient(); + return true; + } +} diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/ws/ServerWebSocketArgsFilter.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/ws/ServerWebSocketArgsFilter.java new file mode 100644 index 00000000000..1bd3a7f5167 --- /dev/null +++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/ws/ServerWebSocketArgsFilter.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.transport.rest.vertx.ws; + +import java.util.concurrent.CompletableFuture; + +import org.apache.servicecomb.common.rest.RestConst; +import org.apache.servicecomb.common.rest.filter.inner.ServerRestArgsFilter; +import org.apache.servicecomb.core.Const; +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx; +import org.apache.servicecomb.swagger.invocation.Response; +import org.apache.servicecomb.swagger.invocation.ws.ServerWebSocket; + +import com.netflix.config.DynamicPropertyFactory; + +public class ServerWebSocketArgsFilter extends ServerRestArgsFilter { + private static final boolean enabled = DynamicPropertyFactory.getInstance().getBooleanProperty + ("servicecomb.http.filter.server.serverWebSocketArgs.enabled", true).get(); + + @Override + public boolean enabled() { + return enabled; + } + + @Override + public boolean enabledForTransport(String transport) { + return Const.WEBSOCKET.equals(transport); + } + + @Override + public CompletableFuture beforeSendResponseAsync(Invocation invocation, HttpServletResponseEx responseEx) { + Response response = (Response) responseEx.getAttribute(RestConst.INVOCATION_HANDLER_RESPONSE); + if (response.getResult() instanceof ServerWebSocket) { + // don't do anything on WebSocket response. Let the WebSocketHandshakeServerFilter to handle WS handshaking. + return CompletableFuture.completedFuture(null); + } + // WS handshaking may fail and return an HTTP response, like 401 + return super.beforeSendResponseAsync(invocation, responseEx); + } +} diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/ws/VertxWebSocketAdaptor.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/ws/VertxWebSocketAdaptor.java new file mode 100644 index 00000000000..cdecfe2aded --- /dev/null +++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/ws/VertxWebSocketAdaptor.java @@ -0,0 +1,314 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.transport.rest.vertx.ws; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.core.event.WebSocketActionEvent; +import org.apache.servicecomb.core.executor.ReactiveExecutor; +import org.apache.servicecomb.core.tracing.BraveTraceIdGenerator; +import org.apache.servicecomb.core.tracing.TraceIdGenerator; +import org.apache.servicecomb.foundation.common.event.EventManager; +import org.apache.servicecomb.swagger.invocation.InvocationType; +import org.apache.servicecomb.swagger.invocation.ws.AbstractBaseWebSocket; +import org.apache.servicecomb.swagger.invocation.ws.BinaryBytesWebSocketMessage; +import org.apache.servicecomb.swagger.invocation.ws.SerialExecutorWrapper; +import org.apache.servicecomb.swagger.invocation.ws.TextWebSocketMessage; +import org.apache.servicecomb.swagger.invocation.ws.WebSocketActionType; +import org.apache.servicecomb.swagger.invocation.ws.WebSocketAdapter; +import org.apache.servicecomb.swagger.invocation.ws.WebSocketFrame; +import org.apache.servicecomb.swagger.invocation.ws.WebSocketMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.eventbus.EventBus; +import com.netflix.config.DynamicPropertyFactory; + +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.WebSocketBase; + +/** + * VertxWebSocketAdaptor + */ +public class VertxWebSocketAdaptor implements WebSocketAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(VertxWebSocketAdaptor.class); + + private static final TraceIdGenerator CONNECTION_ID_GENERATOR = new BraveTraceIdGenerator(); + + private static final int DEFAULT_ADAPTER_QUEUE_MAX_SIZE = 100; + + private static final int DEFAULT_ADAPTER_QUEUE_MAX_CONTINUE_TIMES = 10; + + private final Executor executor; + + /** + * The WebSocket type provided by user(from business logic) + */ + private final AbstractBaseWebSocket bizWebSocket; + + /** + * The underlying WebSocket type provided by Vert.x which represents the real WebSocket network connection. + */ + private final WebSocketBase vertxWebSocket; + + private final Object pauseLock = new Object(); + + private final Invocation invocation; + + private final long creationTimestamp; + + private final EventBus eventBus; + + private boolean inPauseStatus; + + private final String connectionId; + + private final InvocationType invocationType; + + public VertxWebSocketAdaptor( + Invocation invocation, + InvocationType invocationType, Executor workerPool, + AbstractBaseWebSocket bizWebSocket, + WebSocketBase vertxWebSocket) { + Objects.requireNonNull(invocation, "VertxWebSocketAdaptor invocation is null"); + Objects.requireNonNull(workerPool, "VertxWebSocketAdaptor workerPool is null"); + Objects.requireNonNull(bizWebSocket, "VertxWebSocketAdaptor bizWebSocket is null"); + Objects.requireNonNull(vertxWebSocket, "VertxWebSocketAdaptor vertxWebSocket is null"); + creationTimestamp = System.currentTimeMillis(); + this.invocation = invocation; + this.invocationType = invocationType; + this.connectionId = CONNECTION_ID_GENERATOR.generate(); + this.executor = workerPool instanceof ReactiveExecutor ? + workerPool // for reactive case, no need to wrap it into a serial queue model + : prepareSerialExecutorWrapper(workerPool); + this.bizWebSocket = bizWebSocket; + this.vertxWebSocket = vertxWebSocket; + eventBus = EventManager.getEventBus(); + inPauseStatus = true; + vertxWebSocket.pause(); // make sure the vert.x WebSocket pause status keep consistent with inPauseStatus flag + + // make sure the bi-direction message stream is established + linkVertxToBiz(); + linkBizToVertx(bizWebSocket); + // notify that the stream connection is ready to work + startWorking(); + } + + private void linkBizToVertx(AbstractBaseWebSocket bizWebSocket) { + bizWebSocket.setWebSocketAdapter(this); + } + + private SerialExecutorWrapper prepareSerialExecutorWrapper(Executor workerPool) { + final SerialExecutorWrapper wrapper = new SerialExecutorWrapper( + invocationType, + connectionId, + workerPool, + DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.websocket.adapter.queue.maxSize", + DEFAULT_ADAPTER_QUEUE_MAX_SIZE) + .get(), + DynamicPropertyFactory.getInstance() + .getIntProperty("servicecomb.websocket.adapter.queue.maxContinueTimes", + DEFAULT_ADAPTER_QUEUE_MAX_CONTINUE_TIMES) + .get()); + wrapper.subscribeQueueDrainEvent(this::resume); + wrapper.subscribeQueueFullEvent(this::pause); + return wrapper; + } + + private void linkVertxToBiz() { + linkVertxDrainHandler(); + linkVertxTextMessageHandler(); + linkVertxBinaryMessageHandler(); + linkVertxFrameHandler(); + linkVertxExceptionHandler(); + linkVertxCloseHandler(); + } + + private void linkVertxCloseHandler() { + vertxWebSocket.closeHandler(v -> + scheduleTask(createWebSocketActionEvent(WebSocketActionType.ON_CLOSE), + () -> bizWebSocket.onClose(vertxWebSocket.closeStatusCode(), vertxWebSocket.closeReason()))); + } + + private void linkVertxExceptionHandler() { + vertxWebSocket.exceptionHandler(t -> scheduleTask( + createWebSocketActionEvent(WebSocketActionType.ON_ERROR), + () -> bizWebSocket.onError(t))); + } + + private void linkVertxFrameHandler() { + // not support for now + // if this feature is added, should notice that the controlling frame may not be forwarded in EdgeService case + } + + private void linkVertxBinaryMessageHandler() { + vertxWebSocket.binaryMessageHandler(buffer -> { + final byte[] bytes = buffer.getBytes(); + scheduleTask(createWebSocketActionEvent(WebSocketActionType.ON_MESSAGE_BINARY) + .setDataSize(buffer.length()), + () -> bizWebSocket.onMessage(new BinaryBytesWebSocketMessage(bytes))); + }); + } + + private void linkVertxTextMessageHandler() { + vertxWebSocket.textMessageHandler(s -> + scheduleTask(createWebSocketActionEvent(WebSocketActionType.ON_MESSAGE_TEXT) + .setDataSize(s.length()), + () -> bizWebSocket.onMessage(new TextWebSocketMessage(s)))); + } + + private void linkVertxDrainHandler() { + vertxWebSocket.drainHandler(v -> + scheduleTask(createWebSocketActionEvent(WebSocketActionType.ON_SEND_QUEUE_DRAIN), + bizWebSocket::onWriteQueueDrain)); + } + + private void startWorking() { + WebSocketActionEvent event = createWebSocketActionEventInSyncMode(WebSocketActionType.CONNECTION_PREPARE); + scheduleTask( + createWebSocketActionEvent(WebSocketActionType.ON_OPEN), + bizWebSocket::startWorking); + eventBus.post(event.setActionEndTimestamp(System.currentTimeMillis())); + } + + private WebSocketActionEvent createWebSocketActionEvent(WebSocketActionType actionType) { + return new WebSocketActionEvent() + .setActionType(actionType) + .setConnectionStartTimestamp(creationTimestamp) + .setInvocationType(invocationType) + .setHandleThreadName(Thread.currentThread().getName()) + .setOperationMeta(invocation.getOperationMeta()) + .setConnectionId(connectionId) + .setTraceId(invocation.getTraceId()) + .setScheduleStartTimestamp(System.currentTimeMillis()); + } + + private WebSocketActionEvent createWebSocketActionEventInSyncMode(WebSocketActionType actionType) { + return createWebSocketActionEvent(actionType) + .setActionStartTimestamp(System.currentTimeMillis()) + .setHandleThreadName(Thread.currentThread().getName()); + } + + private void scheduleTask(WebSocketActionEvent event, Runnable task) { + try { + executor.execute(() -> { + event.setActionStartTimestamp(System.currentTimeMillis()) + .setHandleThreadName(Thread.currentThread().getName()); + try { + task.run(); + } catch (Throwable e) { + LOGGER.error("[{}]-[{}] error occurs while executing task, actionType is {}", + invocationType, connectionId, event.getActionType()); + } finally { + eventBus.post( + event.setActionEndTimestamp(System.currentTimeMillis()) + ); + } + }); + } catch (Throwable e) { + LOGGER.error("[{}]-[{}] error occurs in scheduleTask", invocationType, connectionId, e); + } + } + + @Override + public CompletableFuture sendMessage(WebSocketMessage message) { + if (message instanceof TextWebSocketMessage) { + String payload = ((TextWebSocketMessage) message).getPayload(); + return decorateSenderAction(WebSocketActionType.DO_SEND_TEXT, + payload.length(), + vertxWebSocket.writeTextMessage( + payload) + .toCompletionStage() + .toCompletableFuture()); + } + + if (message instanceof BinaryBytesWebSocketMessage) { + byte[] payload = ((BinaryBytesWebSocketMessage) message).getPayload(); + return decorateSenderAction(WebSocketActionType.DO_SEND_BINARY, + payload.length, + vertxWebSocket.writeBinaryMessage(Buffer.buffer( + payload)) + .toCompletionStage() + .toCompletableFuture()); + } + + throw new IllegalStateException("impossible case, unrecognized WebSocketMessage type!"); + } + + @Override + public CompletableFuture sendFrame(WebSocketFrame frame) { + // not supply this feature yet + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture close(short statusCode, String reason) { + return decorateSenderAction(WebSocketActionType.DO_CLOSE, 0, + vertxWebSocket.close(statusCode, reason) + .toCompletionStage() + .toCompletableFuture()); + } + + @Override + public void pause() { + WebSocketActionEvent event = createWebSocketActionEventInSyncMode(WebSocketActionType.DO_PAUSE); + synchronized (pauseLock) { + if (inPauseStatus) { + return; + } + vertxWebSocket.pause(); + inPauseStatus = true; + LOGGER.info("[{}]-[{}] pause websocket", invocationType, connectionId); + } + eventBus.post(event.setActionEndTimestamp(System.currentTimeMillis())); + } + + @Override + public void resume() { + WebSocketActionEvent event = createWebSocketActionEventInSyncMode(WebSocketActionType.DO_RESUME); + synchronized (pauseLock) { + if (!inPauseStatus) { + return; + } + vertxWebSocket.resume(); + inPauseStatus = false; + LOGGER.info("[{}]-[{}] resume websocket", invocationType, connectionId); + } + eventBus.post(event.setActionEndTimestamp(System.currentTimeMillis())); + } + + @Override + public boolean writeQueueFull() { + return vertxWebSocket.writeQueueFull(); + } + + private CompletableFuture decorateSenderAction(WebSocketActionType actionType, + int dataSize, + CompletableFuture actionFuture) { + WebSocketActionEvent event = createWebSocketActionEventInSyncMode(actionType).setDataSize(dataSize); + actionFuture + .whenComplete((v, t) -> + eventBus.post(event.setActionEndTimestamp(System.currentTimeMillis()))); + return actionFuture; + } +} diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/ws/WebSocketHandshakeServerFilter.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/ws/WebSocketHandshakeServerFilter.java new file mode 100644 index 00000000000..3c4bbcee0e3 --- /dev/null +++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/ws/WebSocketHandshakeServerFilter.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.transport.rest.vertx.ws; + +import java.util.concurrent.CompletableFuture; + +import org.apache.servicecomb.common.rest.RestConst; +import org.apache.servicecomb.common.rest.filter.HttpServerFilter; +import org.apache.servicecomb.core.Const; +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx; +import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx; +import org.apache.servicecomb.foundation.vertx.http.VertxServerRequestToHttpServletRequest; +import org.apache.servicecomb.swagger.invocation.InvocationType; +import org.apache.servicecomb.swagger.invocation.Response; +import org.apache.servicecomb.swagger.invocation.arguments.consumer.ClientWebSocketArgumentMapper; +import org.apache.servicecomb.swagger.invocation.ws.ServerWebSocket; +import org.apache.servicecomb.swagger.invocation.ws.WebSocketPipe; + +/** + * Switch HTTP to WebSocket protocol and complete WebSocket handshake. + */ +public class WebSocketHandshakeServerFilter implements HttpServerFilter { + public static final String WEBSOCKET_PIPE_CONTEXT_KEY = "x-scb-websocket-pipe"; + + @Override + public int getOrder() { + return 0; + } + + @Override + public boolean enabledForTransport(String transport) { + return Const.WEBSOCKET.equals(transport); + } + + @Override + public Response afterReceiveRequest(Invocation invocation, HttpServletRequestEx requestEx) { + if (invocation.isEdge() + && (invocation.getLocalContext(WEBSOCKET_PIPE_CONTEXT_KEY) == null)) { + // prepare pipe for edge forward websocket scene + final WebSocketPipe webSocketPipe = new WebSocketPipe(invocation.getTraceId()); + invocation.addLocalContext(WEBSOCKET_PIPE_CONTEXT_KEY, webSocketPipe); + invocation.addLocalContext(ClientWebSocketArgumentMapper.SCB_CLIENT_WEBSOCKET_LOCAL_CONTEXT_KEY, + webSocketPipe.getClientWebSocket()); + } + return null; + } + + @Override + public CompletableFuture beforeSendResponseAsync(Invocation invocation, HttpServletResponseEx responseEx) { + if (invocation == null) { // error case + return CompletableFuture.completedFuture(null); + } + if (Const.WEBSOCKET.equals(invocation.getProviderTransportName()) + && invocation.getRequestEx() instanceof VertxServerRequestToHttpServletRequest) { + Response response = (Response) responseEx.getAttribute(RestConst.INVOCATION_HANDLER_RESPONSE); + final Object result = response.getResult(); + if (result instanceof ServerWebSocket) { + ((VertxServerRequestToHttpServletRequest) invocation.getRequestEx()) + .toWebSocket() + .whenComplete((ws, t) -> + new VertxWebSocketAdaptor( + invocation, + InvocationType.PRODUCER, // must set it manually, or it's not correct in edge situation + invocation.getOperationMeta().getExecutor(), + (ServerWebSocket) result, + ws)); + } + // WebSocket operation may also return an HTTP response, for example, rejecting WebSocket handshake. + // Therefore, we don't throw Exception here, just let it pass and act like REST transport mode. + } + + return HttpServerFilter.super.beforeSendResponseAsync(invocation, responseEx); + } +} diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/ws/WebSocketResponseWrapClientFilter.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/ws/WebSocketResponseWrapClientFilter.java new file mode 100644 index 00000000000..92851307762 --- /dev/null +++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/ws/WebSocketResponseWrapClientFilter.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 org.apache.servicecomb.transport.rest.vertx.ws; + +import java.util.concurrent.Executor; + +import org.apache.servicecomb.common.rest.filter.HttpClientFilter; +import org.apache.servicecomb.core.Const; +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.core.exception.ExceptionCodes; +import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx; +import org.apache.servicecomb.foundation.vertx.ws.VertxClientWebSocketResponseToHttpServletResponse; +import org.apache.servicecomb.swagger.invocation.InvocationType; +import org.apache.servicecomb.swagger.invocation.Response; +import org.apache.servicecomb.swagger.invocation.arguments.consumer.ClientWebSocketArgumentMapper; +import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData; +import org.apache.servicecomb.swagger.invocation.exception.ExceptionFactory; +import org.apache.servicecomb.swagger.invocation.exception.InvocationException; +import org.apache.servicecomb.swagger.invocation.ws.ClientWebSocket; +import org.apache.servicecomb.swagger.invocation.ws.WebSocketPipe; + +/** + * WebSocketResponseWrapClientFilter + */ +public class WebSocketResponseWrapClientFilter implements HttpClientFilter { + @Override + public boolean enabled() { + return HttpClientFilter.super.enabled(); + } + + @Override + public boolean enabledForTransport(String transport) { + return Const.WEBSOCKET.equals(transport); + } + + @Override + public int getOrder() { + return 10010; + } + + @Override + public Response afterReceiveResponse(Invocation invocation, HttpServletResponseEx responseEx) { + if (!(responseEx instanceof VertxClientWebSocketResponseToHttpServletResponse)) { + throw new InvocationException(ExceptionFactory.CONSUMER_INNER_STATUS, "unexpected responseEx type[" + + responseEx.getClass() + + "], this should be a websocket scene"); + } + // only handshake success scenes run into this method. No need to consider handshake failure case. + final VertxClientWebSocketResponseToHttpServletResponse webSocketResponseEx = (VertxClientWebSocketResponseToHttpServletResponse) responseEx; + final ClientWebSocket clientWebSocket = invocation.getLocalContext( + ClientWebSocketArgumentMapper.SCB_CLIENT_WEBSOCKET_LOCAL_CONTEXT_KEY); + + final Executor executor = invocation + .getOperationMeta() + .getExecutor(); + final VertxWebSocketAdaptor webSocketAdaptor = new VertxWebSocketAdaptor( + invocation, + InvocationType.CONSUMER, // must set it manually, or it's not correct in edge situation + executor, + clientWebSocket, + webSocketResponseEx.getVertxClientWebSocket()); + invocation.addLocalContext("webSocketAdaptor", webSocketAdaptor); + + if (!invocation.isEdge()) { + return Response.success(null, + responseEx.getStatusType()); + } else { + final WebSocketPipe webSocketPipe = + invocation.getLocalContext(WebSocketHandshakeServerFilter.WEBSOCKET_PIPE_CONTEXT_KEY); + if (webSocketPipe == null) { + throw new InvocationException(ExceptionFactory.CONSUMER_INNER_STATUS_CODE, + ExceptionFactory.CONSUMER_INNER_REASON_PHRASE, + new CommonExceptionData(ExceptionCodes.GENERIC_CLIENT, "edge websocket lost pipe")); + } + return Response.success(webSocketPipe.getServerWebSocket(), responseEx.getStatusType()); + } + } +} diff --git a/transports/transport-rest/transport-rest-vertx/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpClientFilter b/transports/transport-rest/transport-rest-vertx/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpClientFilter new file mode 100644 index 00000000000..ac496f52443 --- /dev/null +++ b/transports/transport-rest/transport-rest-vertx/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpClientFilter @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. +# + +org.apache.servicecomb.transport.rest.vertx.ws.WebSocketResponseWrapClientFilter diff --git a/transports/transport-rest/transport-rest-vertx/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpServerFilter b/transports/transport-rest/transport-rest-vertx/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpServerFilter new file mode 100644 index 00000000000..41cd4403f56 --- /dev/null +++ b/transports/transport-rest/transport-rest-vertx/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpServerFilter @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. +# + +org.apache.servicecomb.transport.rest.vertx.ws.WebSocketHandshakeServerFilter +org.apache.servicecomb.transport.rest.vertx.ws.ServerWebSocketArgsFilter diff --git a/transports/transport-rest/transport-rest-vertx/src/main/resources/META-INF/services/org.apache.servicecomb.core.Transport b/transports/transport-rest/transport-rest-vertx/src/main/resources/META-INF/services/org.apache.servicecomb.core.Transport index be04a13de92..74ae694b980 100644 --- a/transports/transport-rest/transport-rest-vertx/src/main/resources/META-INF/services/org.apache.servicecomb.core.Transport +++ b/transports/transport-rest/transport-rest-vertx/src/main/resources/META-INF/services/org.apache.servicecomb.core.Transport @@ -16,3 +16,4 @@ # org.apache.servicecomb.transport.rest.vertx.VertxRestTransport +org.apache.servicecomb.transport.rest.vertx.WebSocketTransport diff --git a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/MockHttpServerResponse.java b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/MockHttpServerResponse.java index 03449c85a95..9d78aa6760c 100644 --- a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/MockHttpServerResponse.java +++ b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/MockHttpServerResponse.java @@ -26,6 +26,7 @@ import io.vertx.core.http.Cookie; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerResponse; +import io.vertx.core.net.HostAndPort; import java.util.HashMap; import java.util.Map; @@ -194,6 +195,11 @@ public HttpServerResponse endHandler(Handler handler) { return null; } + @Override + public Future writeHead() { + return Future.succeededFuture(); + } + @Override public Future write(String chunk, String enc) { return Future.succeededFuture(); @@ -218,6 +224,15 @@ public HttpServerResponse writeContinue() { return null; } + @Override + public Future writeEarlyHints(MultiMap headers) { + return Future.succeededFuture(); + } + + @Override + public void writeEarlyHints(MultiMap headers, Handler> handler) { + } + @Override public Future end(String chunk, String enc) { return Future.succeededFuture(); @@ -307,6 +322,11 @@ public HttpServerResponse push(HttpMethod method, String host, String path, Mult return null; } + @Override + public Future push(HttpMethod httpMethod, HostAndPort hostAndPort, String s, MultiMap multiMap) { + return null; + } + @Override public Future push(HttpMethod method, String host, String path, MultiMap headers) { return Future.succeededFuture(); diff --git a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestRestServerVerticle.java b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestRestServerVerticle.java index 57a6a2ac165..13fe1bf41c6 100644 --- a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestRestServerVerticle.java +++ b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestRestServerVerticle.java @@ -30,6 +30,7 @@ import org.apache.servicecomb.foundation.common.Holder; import org.apache.servicecomb.foundation.common.net.URIEndpointObject; import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; +import org.apache.servicecomb.serviceregistry.RegistryUtils; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.After; @@ -75,6 +76,7 @@ public void tearDown() { startPromise = null; SCBEngine.getInstance().destroy(); ArchaiusUtils.resetConfig(); + RegistryUtils.destroy(); } @Test @@ -240,8 +242,7 @@ CorsHandler maxAgeSeconds(int maxAgeSeconds) { new MockUp() { @Mock - CorsHandler getCorsHandler(String corsAllowedOrigin) { - Assertions.assertEquals("*", corsAllowedOrigin); + CorsHandler getCorsHandler() { return corsHandler; } }; diff --git a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestTransportConfig.java b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestTransportConfig.java index fd1e88d6348..898600146f7 100644 --- a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestTransportConfig.java +++ b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestTransportConfig.java @@ -20,14 +20,14 @@ import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; - -import mockit.Mock; -import mockit.MockUp; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import mockit.Mock; +import mockit.MockUp; + public class TestTransportConfig { @BeforeEach @@ -129,10 +129,11 @@ public void testIsCorsEnabled() { @Test public void testGetCorsAllowedOrigin() { - Assertions.assertEquals("*", TransportConfig.getCorsAllowedOrigin()); + Assertions.assertEquals(1, TransportConfig.getCorsAllowedOrigin().size()); + Assertions.assertEquals("*", TransportConfig.getCorsAllowedOrigin().iterator().next()); String origin = "http://localhost:8080"; ArchaiusUtils.setProperty("servicecomb.cors.origin", origin); - Assertions.assertEquals(origin, TransportConfig.getCorsAllowedOrigin()); + Assertions.assertEquals(1, TransportConfig.getCorsAllowedOrigin().size()); } @Test diff --git a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestVertxRestDispatcher.java b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestVertxRestDispatcher.java index 3a98723c541..0c6f85f17d2 100644 --- a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestVertxRestDispatcher.java +++ b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestVertxRestDispatcher.java @@ -42,6 +42,7 @@ import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx; import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx; +import org.apache.servicecomb.serviceregistry.RegistryUtils; import org.apache.servicecomb.swagger.invocation.exception.InvocationException; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; @@ -96,6 +97,7 @@ void invoke(Transport transport, HttpServletRequestEx requestEx, HttpServletResp public void teardown() { SCBEngine.getInstance().destroy(); ArchaiusUtils.resetConfig(); + RegistryUtils.destroy(); } @Test diff --git a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestVertxRestTransport.java b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestVertxRestTransport.java index dbe6cae4e2b..9a5f43f6feb 100644 --- a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestVertxRestTransport.java +++ b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestVertxRestTransport.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.net.ServerSocket; +import java.util.HashMap; +import java.util.Map; import org.apache.servicecomb.core.Endpoint; import org.apache.servicecomb.core.Invocation; @@ -62,9 +64,11 @@ public Vertx init(VertxOptions vertxOptions) { } @Mock - public boolean blockDeploy(Vertx vertx, Class cls, + public Map blockDeploy(Vertx vertx, Class cls, DeploymentOptions options) throws InterruptedException { - return true; + Map result = new HashMap<>(); + result.put("code", true); + return result; } }; instance.init();