diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java index 9f29c0e6b0c..07754698852 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java @@ -28,6 +28,7 @@ import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.config.server.constant.ParametersField; import com.alibaba.nacos.config.server.controller.parameters.SameNamespaceCloneConfigBean; +import com.alibaba.nacos.config.server.enums.ApiVersionEnum; import com.alibaba.nacos.config.server.model.ConfigAdvanceInfo; import com.alibaba.nacos.config.server.model.ConfigAllInfo; import com.alibaba.nacos.config.server.model.ConfigInfo; @@ -244,7 +245,7 @@ public void getConfig(HttpServletRequest request, HttpServletResponse response, final String clientIp = RequestUtil.getRemoteIp(request); String isNotify = request.getHeader("notify"); - inner.doGetConfig(request, response, dataId, group, tenant, tag, isNotify, clientIp); + inner.doGetConfig(request, response, dataId, group, tenant, tag, isNotify, clientIp, ApiVersionEnum.V1); } /** diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigServletInner.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigServletInner.java index f5c94e66265..a78a454192f 100755 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigServletInner.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigServletInner.java @@ -24,39 +24,39 @@ import com.alibaba.nacos.common.utils.Pair; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.enums.ApiVersionEnum; import com.alibaba.nacos.config.server.enums.FileTypeEnum; -import com.alibaba.nacos.config.server.model.CacheItem; +import com.alibaba.nacos.config.server.exception.NacosConfigException; import com.alibaba.nacos.config.server.model.ConfigCacheGray; import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; import com.alibaba.nacos.config.server.model.gray.TagGrayRule; -import com.alibaba.nacos.config.server.service.ConfigCacheService; +import com.alibaba.nacos.config.server.service.query.ConfigChainRequestExtractorService; +import com.alibaba.nacos.config.server.service.query.ConfigQueryChainService; import com.alibaba.nacos.config.server.service.LongPollingService; -import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; +import com.alibaba.nacos.config.server.service.query.enums.ResponseCode; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; -import com.alibaba.nacos.config.server.utils.GroupKey2; -import com.alibaba.nacos.config.server.utils.LogUtil; import com.alibaba.nacos.config.server.utils.MD5Util; import com.alibaba.nacos.config.server.utils.Protocol; import com.alibaba.nacos.config.server.utils.RequestUtil; -import com.alibaba.nacos.config.server.utils.TimeUtils; import com.alibaba.nacos.plugin.encryption.handler.EncryptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.util.HashMap; import java.util.List; import java.util.Map; -import static com.alibaba.nacos.config.server.constant.Constants.ENCODE_UTF8; -import static com.alibaba.nacos.config.server.utils.LogUtil.PULL_LOG; +import static com.alibaba.nacos.api.common.Constants.CONFIG_TYPE; +import static com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG; +import static com.alibaba.nacos.config.server.constant.Constants.CONTENT_MD5; /** * ConfigServlet inner for aop. @@ -74,8 +74,11 @@ public class ConfigServletInner { private final LongPollingService longPollingService; - public ConfigServletInner(LongPollingService longPollingService) { + private final ConfigQueryChainService configQueryChainService; + + public ConfigServletInner(LongPollingService longPollingService, ConfigQueryChainService configQueryChainService) { this.longPollingService = longPollingService; + this.configQueryChainService = configQueryChainService; } /** @@ -119,177 +122,211 @@ public String doPollingConfig(HttpServletRequest request, HttpServletResponse re return HttpServletResponse.SC_OK + ""; } - /** - * Execute to get config [API V1]. - */ - public String doGetConfig(HttpServletRequest request, HttpServletResponse response, String dataId, String group, - String tenant, String tag, String isNotify, String clientIp) throws IOException, ServletException { - return doGetConfig(request, response, dataId, group, tenant, tag, isNotify, clientIp, false); - } - /** * Execute to get config [API V1] or [API V2]. */ public String doGetConfig(HttpServletRequest request, HttpServletResponse response, String dataId, String group, - String tenant, String tag, String isNotify, String clientIp, boolean isV2) throws IOException { + String tenant, String tag, String isNotify, String clientIp, ApiVersionEnum apiVersion) throws IOException { boolean notify = StringUtils.isNotBlank(isNotify) && Boolean.parseBoolean(isNotify); + String requestIpApp = RequestUtil.getAppName(request); - String acceptCharset = ENCODE_UTF8; + ConfigQueryChainRequest chainRequest = ConfigChainRequestExtractorService.getExtractor().extract(request); + ConfigQueryChainResponse chainResponse = configQueryChainService.handle(chainRequest); - if (isV2) { - response.setHeader(HttpHeaderConsts.CONTENT_TYPE, MediaType.APPLICATION_JSON); + if (ResponseCode.FAIL.getCode() == chainResponse.getResultCode()) { + throw new NacosConfigException(chainResponse.getMessage()); } - final String groupKey = GroupKey2.getKey(dataId, group, tenant); - String autoTag = request.getHeader(com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG); - - String requestIpApp = RequestUtil.getAppName(request); - int lockResult = ConfigCacheService.tryConfigReadLock(groupKey); - CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey); + logPullEvent(dataId, group, tenant, requestIpApp, chainResponse, clientIp, notify, tag); - final String requestIp = RequestUtil.getRemoteIp(request); - if (lockResult > 0 && cacheItem != null) { - try { - long lastModified; - - final String configType = - (null != cacheItem.getType()) ? cacheItem.getType() : FileTypeEnum.TEXT.getFileType(); - response.setHeader(com.alibaba.nacos.api.common.Constants.CONFIG_TYPE, configType); - FileTypeEnum fileTypeEnum = FileTypeEnum.getFileTypeEnumByFileExtensionOrFileType(configType); - String contentTypeHeader = fileTypeEnum.getContentType(); - response.setHeader(HttpHeaderConsts.CONTENT_TYPE, - isV2 ? MediaType.APPLICATION_JSON : contentTypeHeader); - - ConfigCacheGray matchedGray = null; - Map appLabels = new HashMap(4); - appLabels.put(BetaGrayRule.CLIENT_IP_LABEL, clientIp); - boolean specificTag = StringUtils.isNotBlank(tag); - - if (specificTag) { - appLabels.put(TagGrayRule.VIP_SERVER_TAG_LABEL, tag); - } else if (StringUtils.isNotBlank(autoTag)) { - appLabels.put(TagGrayRule.VIP_SERVER_TAG_LABEL, autoTag); - } - - if (cacheItem.getSortConfigGrays() != null && !cacheItem.getSortConfigGrays().isEmpty()) { - for (ConfigCacheGray configCacheGray : cacheItem.getSortConfigGrays()) { - if (configCacheGray.match(appLabels)) { - matchedGray = configCacheGray; - break; - } - } - } - - String pullEvent; - String content; - String md5; - String encryptedDataKey; - - if (matchedGray != null) { - md5 = matchedGray.getMd5(acceptCharset); - lastModified = matchedGray.getLastModifiedTs(); - encryptedDataKey = matchedGray.getEncryptedDataKey(); - content = ConfigDiskServiceFactory.getInstance() - .getGrayContent(dataId, group, tenant, matchedGray.getGrayName()); - pullEvent = ConfigTraceService.PULL_EVENT + "-" + matchedGray.getGrayName(); - if (BetaGrayRule.TYPE_BETA.equals(matchedGray.getGrayName())) { - response.setHeader("isBeta", "true"); - } - if (TagGrayRule.TYPE_TAG.equals(matchedGray.getGrayRule().getType())) { - response.setHeader(com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG, - URLEncoder.encode(matchedGray.getGrayRule().getRawGrayRuleExp(), - StandardCharsets.UTF_8.displayName())); - } - } else if (specificTag) { - //specific tag is not found - md5 = null; - lastModified = 0L; - encryptedDataKey = null; - content = null; - pullEvent = ConfigTraceService.PULL_EVENT + "-" + TagGrayRule.TYPE_TAG + "-" + tag; - response.setHeader(com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG, - URLEncoder.encode(tag, StandardCharsets.UTF_8.displayName())); - } else { - md5 = cacheItem.getConfigCache().getMd5(acceptCharset); - lastModified = cacheItem.getConfigCache().getLastModifiedTs(); - encryptedDataKey = cacheItem.getConfigCache().getEncryptedDataKey(); - content = ConfigDiskServiceFactory.getInstance().getContent(dataId, group, tenant); - pullEvent = ConfigTraceService.PULL_EVENT; - } - - if (content == null) { - ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, pullEvent, - ConfigTraceService.PULL_TYPE_NOTFOUND, -1, requestIp, notify, "http"); - return get404Result(response, isV2); - - } - response.setHeader(Constants.CONTENT_MD5, md5); - - // Disable cache. - response.setHeader("Pragma", "no-cache"); - response.setDateHeader("Expires", 0); - response.setHeader("Cache-Control", "no-cache,no-store"); - response.setDateHeader("Last-Modified", lastModified); - if (encryptedDataKey != null) { - response.setHeader("Encrypted-Data-Key", encryptedDataKey); - } - PrintWriter out; - Pair pair = EncryptionHandler.decryptHandler(dataId, encryptedDataKey, content); - String decryptContent = pair.getSecond(); - out = response.getWriter(); - if (isV2) { - out.print(JacksonUtils.toJson(Result.success(decryptContent))); - } else { - out.print(decryptContent); - } - - out.flush(); - out.close(); - - LogUtil.PULL_CHECK_LOG.warn("{}|{}|{}|{}", groupKey, requestIp, md5, TimeUtils.getCurrentTimeStr()); - - final long delayed = notify ? -1 : System.currentTimeMillis() - lastModified; - ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, lastModified, pullEvent, - ConfigTraceService.PULL_TYPE_OK, delayed, clientIp, notify, "http"); - } finally { - ConfigCacheService.releaseReadLock(groupKey); - } - } else if (lockResult == 0 || cacheItem == null) { - - ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, ConfigTraceService.PULL_EVENT, - ConfigTraceService.PULL_TYPE_NOTFOUND, -1, requestIp, notify, "http"); - return get404Result(response, isV2); - - } else { - - PULL_LOG.info("[client-get] clientIp={}, {}, get data during dump", clientIp, groupKey); - return get409Result(response, isV2); + switch (chainResponse.getStatus()) { + case CONFIG_NOT_FOUND: + return handlerConfigNotFound(response, apiVersion); + case CONFIG_QUERY_CONFLICT: + return handlerConfigConflict(response, apiVersion); + default: + return handleResponse(response, chainResponse, dataId, group, apiVersion); } - - return HttpServletResponse.SC_OK + ""; } - private String get404Result(HttpServletResponse response, boolean isV2) throws IOException { + private String handlerConfigNotFound(HttpServletResponse response, ApiVersionEnum apiVersion) throws IOException { response.setStatus(HttpServletResponse.SC_NOT_FOUND); - PrintWriter writer = response.getWriter(); - if (isV2) { - writer.println(JacksonUtils.toJson(Result.failure(ErrorCode.RESOURCE_NOT_FOUND, "config data not exist"))); + if (apiVersion == ApiVersionEnum.V1) { + return writeResponseForV1(response, Result.failure(ErrorCode.RESOURCE_NOT_FOUND, "config data not exist")); } else { - writer.println("config data not exist"); + return writeResponseForV2(response, Result.failure(ErrorCode.RESOURCE_NOT_FOUND, "config data not exist")); } - return HttpServletResponse.SC_NOT_FOUND + ""; } - private String get409Result(HttpServletResponse response, boolean isV2) throws IOException { + private String handlerConfigConflict(HttpServletResponse response, ApiVersionEnum apiVersion) throws IOException { response.setStatus(HttpServletResponse.SC_CONFLICT); + if (apiVersion == ApiVersionEnum.V1) { + return writeResponseForV1(response, Result.failure(ErrorCode.RESOURCE_CONFLICT, "requested file is being modified, please try later.")); + } else { + return writeResponseForV2(response, Result.failure(ErrorCode.RESOURCE_CONFLICT, "requested file is being modified, please try later.")); + } + } + + private String handleResponse(HttpServletResponse response, ConfigQueryChainResponse chainResponse, String dataId, + String group, ApiVersionEnum apiVersion) throws IOException { + if (apiVersion == ApiVersionEnum.V1) { + return handleResponseForV1(response, chainResponse, dataId, group); + } else { + return handleResponseForV2(response, chainResponse, dataId, group); + } + } + + private String handleResponseForV1(HttpServletResponse response, ConfigQueryChainResponse chainResponse, + String dataId, String tag) throws IOException { + if (chainResponse.getContent() == null) { + return handlerConfigNotFound(response, ApiVersionEnum.V1); + } + + setCommonResponseHead(response, chainResponse, tag); + setResponseHeadForV1(response, chainResponse); + writeContentForV1(response, chainResponse, dataId); + + return HttpServletResponse.SC_OK + ""; + } + + private String handleResponseForV2(HttpServletResponse response, ConfigQueryChainResponse chainResponse, + String dataId, String tag) throws IOException { + if (chainResponse.getContent() == null) { + return handlerConfigNotFound(response, ApiVersionEnum.V2); + } + + setCommonResponseHead(response, chainResponse, tag); + setResponseHeadForV2(response); + writeContentForV2(response, chainResponse, dataId); + + return HttpServletResponse.SC_OK + ""; + } + + private void setResponseHeadForV1(HttpServletResponse response, ConfigQueryChainResponse chainResponse) { + String contentType = chainResponse.getContentType() != null ? chainResponse.getContentType() : FileTypeEnum.TEXT.getFileType(); + FileTypeEnum fileTypeEnum = FileTypeEnum.getFileTypeEnumByFileExtensionOrFileType(contentType); + String contentTypeHeader = fileTypeEnum.getContentType(); + response.setHeader(HttpHeaderConsts.CONTENT_TYPE, contentTypeHeader); + } + + private void setResponseHeadForV2(HttpServletResponse response) { + response.setHeader(HttpHeaderConsts.CONTENT_TYPE, MediaType.APPLICATION_JSON); + } + + private void writeContentForV1(HttpServletResponse response, ConfigQueryChainResponse chainResponse, String dataId) + throws IOException { + PrintWriter out = response.getWriter(); + try { + String decryptContent = getDecryptContent(chainResponse, dataId); + out.print(decryptContent); + } finally { + out.flush(); + out.close(); + } + } + + private void writeContentForV2(HttpServletResponse response, ConfigQueryChainResponse chainResponse, String dataId) + throws IOException { + PrintWriter out = response.getWriter(); + try { + String decryptContent = getDecryptContent(chainResponse, dataId); + out.print(JacksonUtils.toJson(Result.success(decryptContent))); + } finally { + out.flush(); + out.close(); + } + } + + private static String getDecryptContent(ConfigQueryChainResponse chainResponse, String dataId) { + Pair pair = EncryptionHandler.decryptHandler(dataId, chainResponse.getEncryptedDataKey(), + chainResponse.getContent()); + return pair.getSecond(); + } + + private String writeResponseForV1(HttpServletResponse response, Result result) throws IOException { + PrintWriter writer = response.getWriter(); + writer.println(result.getData()); + return response.getStatus() + ""; + } + + private String writeResponseForV2(HttpServletResponse response, Result result) throws IOException { PrintWriter writer = response.getWriter(); - if (isV2) { - writer.println(JacksonUtils.toJson(Result.failure(ErrorCode.RESOURCE_CONFLICT, - "requested file is being modified, please try later."))); + writer.println(JacksonUtils.toJson(result)); + return response.getStatus() + ""; + } + + private String resolvePullEvent(ConfigQueryChainResponse chainResponse, String tag) { + switch (chainResponse.getStatus()) { + case CONFIG_FOUND_GRAY: + ConfigCacheGray matchedGray = chainResponse.getMatchedGray(); + if (matchedGray != null) { + return ConfigTraceService.PULL_EVENT + "-" + matchedGray.getGrayName(); + } else { + return ConfigTraceService.PULL_EVENT; + } + case SPECIAL_TAG_CONFIG_NOT_FOUND: + return ConfigTraceService.PULL_EVENT + "-" + TagGrayRule.TYPE_TAG + "-" + tag; + default: + return ConfigTraceService.PULL_EVENT; + } + } + + private void logPullEvent(String dataId, String group, String tenant, String requestIpApp, + ConfigQueryChainResponse chainResponse, String clientIp, boolean notify, String tag) { + + String pullEvent = resolvePullEvent(chainResponse, tag); + + ConfigQueryChainResponse.ConfigQueryStatus status = chainResponse.getStatus(); + + if (status == ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_QUERY_CONFLICT) { + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, pullEvent, + ConfigTraceService.PULL_TYPE_CONFLICT, -1, clientIp, notify, "http"); + } else if (status == ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_NOT_FOUND || chainResponse.getContent() == null) { + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, pullEvent, + ConfigTraceService.PULL_TYPE_NOTFOUND, -1, clientIp, notify, "http"); } else { - writer.println("requested file is being modified, please try later."); + long delayed = notify ? -1 : System.currentTimeMillis() - chainResponse.getLastModified(); + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, chainResponse.getLastModified(), pullEvent, + ConfigTraceService.PULL_TYPE_OK, delayed, clientIp, notify, "http"); + } + } + + private void setCommonResponseHead(HttpServletResponse response, ConfigQueryChainResponse chainResponse, String tag) { + String contentType = chainResponse.getContentType() != null ? chainResponse.getContentType() : FileTypeEnum.TEXT.getFileType(); + + response.setHeader(CONFIG_TYPE, contentType); + response.setHeader(CONTENT_MD5, chainResponse.getMd5()); + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + response.setHeader("Cache-Control", "no-cache,no-store"); + response.setDateHeader("Last-Modified", chainResponse.getLastModified()); + + if (chainResponse.getEncryptedDataKey() != null) { + response.setHeader("Encrypted-Data-Key", chainResponse.getEncryptedDataKey()); + } + + // Check if there is a matched gray rule + if (ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_FOUND_GRAY == chainResponse.getStatus()) { + if (BetaGrayRule.TYPE_BETA.equals(chainResponse.getMatchedGray().getGrayRule().getType())) { + response.setHeader("isBeta", "true"); + } else if (TagGrayRule.TYPE_TAG.equals(chainResponse.getMatchedGray().getGrayRule().getType())) { + try { + response.setHeader(TagGrayRule.TYPE_TAG, URLEncoder.encode(chainResponse.getMatchedGray().getGrayRule().getRawGrayRuleExp(), + StandardCharsets.UTF_8.displayName())); + } catch (Exception e) { + LOGGER.error("Error encoding tag", e); + } + } + } + + // Check if there is a special tag + if (ConfigQueryChainResponse.ConfigQueryStatus.SPECIAL_TAG_CONFIG_NOT_FOUND == chainResponse.getStatus()) { + try { + response.setHeader(VIPSERVER_TAG, URLEncoder.encode(tag, StandardCharsets.UTF_8.displayName())); + } catch (Exception e) { + LOGGER.error("Error encoding tag", e); + } } - return HttpServletResponse.SC_CONFLICT + ""; } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2.java index b7f4b9ffdd9..f6897265173 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2.java @@ -27,6 +27,7 @@ import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.config.server.controller.ConfigServletInner; +import com.alibaba.nacos.config.server.enums.ApiVersionEnum; import com.alibaba.nacos.config.server.model.ConfigInfo; import com.alibaba.nacos.config.server.model.ConfigRequestInfo; import com.alibaba.nacos.config.server.paramcheck.ConfigBlurSearchHttpParamExtractor; @@ -105,7 +106,7 @@ public void getConfig(HttpServletRequest request, HttpServletResponse response, ParamUtils.checkParamV2(tag); final String clientIp = RequestUtil.getRemoteIp(request); String isNotify = request.getHeader("notify"); - inner.doGetConfig(request, response, dataId, group, namespaceId, tag, isNotify, clientIp, true); + inner.doGetConfig(request, response, dataId, group, namespaceId, tag, isNotify, clientIp, ApiVersionEnum.V2); } /** diff --git a/config/src/main/java/com/alibaba/nacos/config/server/enums/ApiVersionEnum.java b/config/src/main/java/com/alibaba/nacos/config/server/enums/ApiVersionEnum.java new file mode 100644 index 00000000000..7235ace37b0 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/enums/ApiVersionEnum.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.enums; + +/** + * Config Api Version enum. + * @author Nacos + */ +public enum ApiVersionEnum { + + /** + * API version v1. + */ + V1("v1"), + + /** + * API version v2. + */ + V2("v2"); + + private final String version; + + ApiVersionEnum(String version) { + this.version = version; + } + + public String getVersion() { + return version; + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandler.java index 7b0d4d88f69..b49f24ed3ad 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandler.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandler.java @@ -22,17 +22,16 @@ import com.alibaba.nacos.api.remote.request.RequestMeta; import com.alibaba.nacos.api.remote.response.ResponseCode; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.common.utils.StringUtils; -import com.alibaba.nacos.config.server.model.CacheItem; import com.alibaba.nacos.config.server.model.ConfigCacheGray; import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; import com.alibaba.nacos.config.server.model.gray.TagGrayRule; -import com.alibaba.nacos.config.server.service.ConfigCacheService; -import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; +import com.alibaba.nacos.config.server.service.query.ConfigChainRequestExtractorService; +import com.alibaba.nacos.config.server.service.query.ConfigQueryChainService; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; import com.alibaba.nacos.config.server.utils.GroupKey2; import com.alibaba.nacos.config.server.utils.LogUtil; -import com.alibaba.nacos.config.server.utils.ParamUtils; import com.alibaba.nacos.config.server.utils.TimeUtils; import com.alibaba.nacos.core.control.TpsControl; import com.alibaba.nacos.core.paramcheck.ExtractorManager; @@ -40,13 +39,11 @@ import com.alibaba.nacos.core.remote.RequestHandler; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.alibaba.nacos.plugin.auth.constant.SignType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.net.URLEncoder; -import java.util.HashMap; -import java.util.Map; - -import static com.alibaba.nacos.api.common.Constants.CLIENT_IP; import static com.alibaba.nacos.config.server.constant.Constants.ENCODE_UTF8; import static com.alibaba.nacos.config.server.utils.LogUtil.PULL_LOG; import static com.alibaba.nacos.config.server.utils.RequestUtil.CLIENT_APPNAME_HEADER; @@ -60,7 +57,12 @@ @Component public class ConfigQueryRequestHandler extends RequestHandler { - public ConfigQueryRequestHandler() { + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigQueryRequestHandler.class); + + private final ConfigQueryChainService configQueryChainService; + + public ConfigQueryRequestHandler(ConfigQueryChainService configQueryChainService) { + this.configQueryChainService = configQueryChainService; } @Override @@ -68,126 +70,110 @@ public ConfigQueryRequestHandler() { @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) @ExtractorManager.Extractor(rpcExtractor = ConfigRequestParamExtractor.class) public ConfigQueryResponse handle(ConfigQueryRequest request, RequestMeta meta) throws NacosException { - try { - return getContext(request, meta, request.isNotify()); + String dataId = request.getDataId(); + String group = request.getGroup(); + String tenant = request.getTenant(); + String groupKey = GroupKey2.getKey(dataId, group, tenant); + boolean notify = request.isNotify(); + + String requestIpApp = meta.getLabels().get(CLIENT_APPNAME_HEADER); + String clientIp = meta.getClientIp(); + + ConfigQueryChainRequest chainRequest = ConfigChainRequestExtractorService.getExtractor().extract(request, meta); + ConfigQueryChainResponse chainResponse = configQueryChainService.handle(chainRequest); + + if (ResponseCode.FAIL.getCode() == chainResponse.getResultCode()) { + return ConfigQueryResponse.buildFailResponse(ResponseCode.FAIL.getCode(), chainResponse.getMessage()); + } + + if (chainResponse.getStatus() == ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_NOT_FOUND) { + return handlerConfigNotFound(request.getDataId(), request.getGroup(), request.getTenant(), requestIpApp, clientIp, notify); + } + + if (chainResponse.getStatus() == ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_QUERY_CONFLICT) { + return handlerConfigConflict(clientIp, groupKey); + } + + ConfigQueryResponse response = new ConfigQueryResponse(); + + // Check if there is a matched gray rule + if (chainResponse.getStatus() == ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_FOUND_GRAY) { + if (BetaGrayRule.TYPE_BETA.equals(chainResponse.getMatchedGray().getGrayRule().getType())) { + response.setBeta(true); + } else if (TagGrayRule.TYPE_TAG.equals(chainResponse.getMatchedGray().getGrayRule().getType())) { + response.setTag(URLEncoder.encode(chainResponse.getMatchedGray().getRawGrayRule(), ENCODE_UTF8)); + } + } + + // Check if there is a special tag + if (chainResponse.getStatus() == ConfigQueryChainResponse.ConfigQueryStatus.SPECIAL_TAG_CONFIG_NOT_FOUND) { + response.setTag(request.getTag()); + } + + response.setMd5(chainResponse.getMd5()); + response.setEncryptedDataKey(chainResponse.getEncryptedDataKey()); + response.setContent(chainResponse.getContent()); + response.setLastModified(chainResponse.getLastModified()); + + String pullType = ConfigTraceService.PULL_TYPE_OK; + if (chainResponse.getContent() == null) { + pullType = ConfigTraceService.PULL_TYPE_NOTFOUND; + response.setErrorInfo(ConfigQueryResponse.CONFIG_NOT_FOUND, "config data not exist"); + } else { + response.setResultCode(ResponseCode.SUCCESS.getCode()); + } + + String pullEvent = resolvePullEventType(chainResponse, request.getTag()); + LogUtil.PULL_CHECK_LOG.warn("{}|{}|{}|{}", groupKey, clientIp, response.getMd5(), TimeUtils.getCurrentTimeStr()); + final long delayed = notify ? -1 : System.currentTimeMillis() - response.getLastModified(); + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, response.getLastModified(), pullEvent, pullType, + delayed, clientIp, notify, "grpc"); + + return response; + } catch (Exception e) { + LOGGER.error("Failed to handle grpc configuration query", e); return ConfigQueryResponse.buildFailResponse(ResponseCode.FAIL.getCode(), e.getMessage()); } } - private ConfigQueryResponse getContext(ConfigQueryRequest configQueryRequest, RequestMeta meta, boolean notify) - throws Exception { - String dataId = configQueryRequest.getDataId(); - String group = configQueryRequest.getGroup(); - String tenant = configQueryRequest.getTenant(); - String clientIp = meta.getClientIp(); - String tag = configQueryRequest.getTag(); + private ConfigQueryResponse handlerConfigConflict(String clientIp, String groupKey) { + ConfigQueryResponse response = new ConfigQueryResponse(); - String groupKey = GroupKey2.getKey(configQueryRequest.getDataId(), configQueryRequest.getGroup(), - configQueryRequest.getTenant()); - String requestIpApp = meta.getLabels().get(CLIENT_APPNAME_HEADER); - String acceptCharset = ENCODE_UTF8; - ParamUtils.checkParam(tag); - int lockResult = ConfigCacheService.tryConfigReadLock(groupKey); - String pullEvent = ConfigTraceService.PULL_EVENT; - String pullType = ConfigTraceService.PULL_TYPE_OK; + PULL_LOG.info("[client-get] clientIp={}, {}, get data during dump", clientIp, groupKey); + response.setErrorInfo(ConfigQueryResponse.CONFIG_QUERY_CONFLICT, + "requested file is being modified, please try later."); + return response; + } + + private ConfigQueryResponse handlerConfigNotFound(String dataId, String group, String tenant, String requestIpApp, + String clientIp, boolean notify) { + //CacheItem No longer exists. It is impossible to simply calculate the push delayed. Here, simply record it as - 1. ConfigQueryResponse response = new ConfigQueryResponse(); - CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey); + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, ConfigTraceService.PULL_EVENT, + ConfigTraceService.PULL_TYPE_NOTFOUND, -1, clientIp, notify, "grpc"); + response.setErrorInfo(ConfigQueryResponse.CONFIG_NOT_FOUND, "config data not exist"); - if (lockResult > 0 && cacheItem != null) { - try { - long lastModified = 0L; - String configType = cacheItem.getType(); - response.setContentType((null != configType) ? configType : "text"); - - String content; - String md5; - String encryptedDataKey; - ConfigCacheGray matchedGray = null; - Map appLabels = null; - boolean specificTag = StringUtils.isNotBlank(tag); - if (specificTag) { - appLabels = new HashMap<>(4); - appLabels.put(TagGrayRule.VIP_SERVER_TAG_LABEL, tag); - appLabels.put(CLIENT_IP, clientIp); - } else { - appLabels = new HashMap(meta.getAppLabels()); - if (!appLabels.containsKey(CLIENT_IP)) { - appLabels.put(CLIENT_IP, clientIp); - } - } - - if (cacheItem.getSortConfigGrays() != null && !cacheItem.getSortConfigGrays().isEmpty()) { - for (ConfigCacheGray configCacheGray : cacheItem.getSortConfigGrays()) { - if (configCacheGray.match(appLabels)) { - matchedGray = configCacheGray; - break; - } - } - } + return response; + + } + + private String resolvePullEventType(ConfigQueryChainResponse chainResponse, String tag) { + switch (chainResponse.getStatus()) { + case CONFIG_FOUND_GRAY: + ConfigCacheGray matchedGray = chainResponse.getMatchedGray(); if (matchedGray != null) { - md5 = matchedGray.getMd5(acceptCharset); - lastModified = matchedGray.getLastModifiedTs(); - encryptedDataKey = matchedGray.getEncryptedDataKey(); - content = ConfigDiskServiceFactory.getInstance() - .getGrayContent(dataId, group, tenant, matchedGray.getGrayName()); - pullEvent = ConfigTraceService.PULL_EVENT + "-" + matchedGray.getGrayName(); - if (BetaGrayRule.TYPE_BETA.equals(matchedGray.getGrayName())) { - response.setBeta(true); - } - if (TagGrayRule.TYPE_TAG.equals(matchedGray.getGrayRule().getType())) { - response.setTag(URLEncoder.encode(matchedGray.getRawGrayRule(), ENCODE_UTF8)); - } - } else if (specificTag) { - //specific tag is not found - md5 = null; - lastModified = 0L; - encryptedDataKey = null; - content = null; - pullEvent = ConfigTraceService.PULL_EVENT + "-" + TagGrayRule.TYPE_TAG + "-" + tag; - response.setTag(tag); - } else { - md5 = cacheItem.getConfigCache().getMd5(acceptCharset); - lastModified = cacheItem.getConfigCache().getLastModifiedTs(); - encryptedDataKey = cacheItem.getConfigCache().getEncryptedDataKey(); - content = ConfigDiskServiceFactory.getInstance().getContent(dataId, group, tenant); - pullEvent = ConfigTraceService.PULL_EVENT; - } - - response.setMd5(md5); - response.setEncryptedDataKey(encryptedDataKey); - response.setContent(content); - response.setLastModified(lastModified); - if (content == null) { - pullType = ConfigTraceService.PULL_TYPE_NOTFOUND; - response.setErrorInfo(ConfigQueryResponse.CONFIG_NOT_FOUND, "config data not exist"); + return ConfigTraceService.PULL_EVENT + "-" + matchedGray.getGrayName(); } else { - response.setResultCode(ResponseCode.SUCCESS.getCode()); + return ConfigTraceService.PULL_EVENT; } - LogUtil.PULL_CHECK_LOG.warn("{}|{}|{}|{}", groupKey, clientIp, md5, TimeUtils.getCurrentTimeStr()); - - final long delayed = notify ? -1 : System.currentTimeMillis() - lastModified; - ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, lastModified, pullEvent, pullType, - delayed, clientIp, notify, "grpc"); - } finally { - ConfigCacheService.releaseReadLock(groupKey); - } - } else if (lockResult == 0 || cacheItem == null) { - - //CacheItem No longer exists. It is impossible to simply calculate the push delayed. Here, simply record it as - 1. - ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, pullEvent, - ConfigTraceService.PULL_TYPE_NOTFOUND, -1, clientIp, notify, "grpc"); - response.setErrorInfo(ConfigQueryResponse.CONFIG_NOT_FOUND, "config data not exist"); - - } else { - PULL_LOG.info("[client-get] clientIp={}, {}, get data during dump", clientIp, groupKey); - response.setErrorInfo(ConfigQueryResponse.CONFIG_QUERY_CONFLICT, - "requested file is being modified, please try later."); + case SPECIAL_TAG_CONFIG_NOT_FOUND: + return ConfigTraceService.PULL_EVENT + "-" + TagGrayRule.TYPE_TAG + "-" + tag; + default: + return ConfigTraceService.PULL_EVENT; } - return response; } - } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigChainRequestExtractorService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigChainRequestExtractorService.java new file mode 100644 index 00000000000..a847625863d --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigChainRequestExtractorService.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.config.server.exception.NacosConfigException; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.Optional; + +/** + * Service class for initializing and retrieving the configuration query request extractor. + * + * @author Nacos + */ +public class ConfigChainRequestExtractorService { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigChainRequestExtractorService.class); + + private static ConfigQueryChainRequestExtractor extractor; + + static { + String curExtractor = EnvUtil.getProperty("nacos.config.query.chain.request.extractor", "nacos"); + Optional optionalBuilder = NacosServiceLoader.load(ConfigQueryChainRequestExtractor.class) + .stream() + .filter(builder -> builder.getName().equals(curExtractor)) + .findFirst(); + if (optionalBuilder.isPresent()) { + extractor = optionalBuilder.get(); + LOGGER.info("ConfigQueryRequestExtractor has been initialized successfully with extractor: {}", curExtractor); + } else { + String errorMessage = "No suitable ConfigQueryRequestExtractor found for name: " + curExtractor; + LOGGER.error(errorMessage); + throw new NacosConfigException(errorMessage); + } + } + + public static ConfigQueryChainRequestExtractor getExtractor() { + return extractor; + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryChainRequestExtractor.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryChainRequestExtractor.java new file mode 100644 index 00000000000..ae229cb4709 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryChainRequestExtractor.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query; + +import com.alibaba.nacos.api.config.remote.request.ConfigQueryRequest; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; + +import javax.servlet.http.HttpServletRequest; + +/** + * Interface for extracting configuration query chain requests from different sources. + * + * @author Nacos + */ +public interface ConfigQueryChainRequestExtractor { + + /** + * Gets the name of the current implementation. + * + * @return the name of the current implementation + */ + String getName(); + + /** + * Extracts a configuration query chain request from an HTTP request. + * + * @param request the HTTP request object + * @return the extracted configuration query chain request + */ + ConfigQueryChainRequest extract(HttpServletRequest request); + + /** + * Extracts a configuration query chain request from a configuration query request object. + * + * @param request the configuration query request object + * @param requestMeta the request metadata + * @return the extracted configuration query chain request + */ + ConfigQueryChainRequest extract(ConfigQueryRequest request, RequestMeta requestMeta); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryChainService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryChainService.java new file mode 100644 index 00000000000..57c8d5659b6 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryChainService.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.config.server.exception.NacosConfigException; +import com.alibaba.nacos.config.server.service.query.enums.ResponseCode; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +/** + * Service class for initializing and retrieving the configuration query chain builder. + * + * @author Nacos + */ +@Service +public class ConfigQueryChainService { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigQueryChainService.class); + + private final ConfigQueryHandlerChain chain; + + public ConfigQueryChainService() { + String curChain = EnvUtil.getProperty("nacos.config.query.chain.builder", "nacos"); + Optional optionalBuilder = NacosServiceLoader.load(ConfigQueryHandlerChainBuilder.class) + .stream() + .filter(builder -> builder.getName().equals(curChain)) + .findFirst(); + if (optionalBuilder.isPresent()) { + chain = optionalBuilder.get().build(); + LOGGER.info("ConfigQueryHandlerChain has been initialized successfully with chain: {}", curChain); + } else { + String errorMessage = "No suitable ConfigQueryHandlerChainBuilder found for name: " + curChain; + LOGGER.error(errorMessage); + throw new NacosConfigException(errorMessage); + } + } + + /** + * Handles the configuration query request. + * + * @param request the configuration query request object + * @return the configuration query response object + */ + public ConfigQueryChainResponse handle(ConfigQueryChainRequest request) { + try { + return chain.handle(request); + } catch (Exception e) { + LOGGER.error("[Error] Fail to handle ConfigQueryChainRequest", e); + return ConfigQueryChainResponse.buildFailResponse(ResponseCode.FAIL.getCode(), e.getMessage()); + } + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryHandlerChain.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryHandlerChain.java new file mode 100644 index 00000000000..720fcb811b4 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryHandlerChain.java @@ -0,0 +1,70 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query; + +import com.alibaba.nacos.config.server.service.query.handler.ConfigQueryHandler; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Objects; + +/** + * ConfigQueryHandlerChain. + * @author Nacos + */ +public class ConfigQueryHandlerChain { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigQueryHandlerChain.class); + + private ConfigQueryHandler head; + + private ConfigQueryHandler tail; + + public ConfigQueryHandlerChain() { + } + + /** + * Adds a new configuration query handler to the chain. + * + * @param handler the configuration query handler to be added + * @return the current configuration query handler chain object, supporting method chaining + */ + public ConfigQueryHandlerChain addHandler(ConfigQueryHandler handler) { + if (Objects.isNull(handler)) { + LOGGER.warn("Attempted to add a null config query handler"); + return this; + } + + if (head == null) { + head = handler; + tail = handler; + } else { + tail.setNextHandler(handler); + tail = handler; + } + + return this; + } + + public ConfigQueryChainResponse handle(ConfigQueryChainRequest request) throws IOException { + return head.handle(request); + } + +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryHandlerChainBuilder.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryHandlerChainBuilder.java new file mode 100644 index 00000000000..e9f1b0d544c --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryHandlerChainBuilder.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query; + +/** + * ConfigQueryHandlerChainBuilder. + * + * @author Nacos + */ +public interface ConfigQueryHandlerChainBuilder { + + /** + * Builds the configuration query handler chain. + * + * @return the configuration query handler chain + */ + ConfigQueryHandlerChain build(); + + /** + * Gets the name of the builder. + * + * @return the name of the builder + */ + String getName(); +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/DefaultChainRequestExtractor.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/DefaultChainRequestExtractor.java new file mode 100644 index 00000000000..4e3ca0c7ea4 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/DefaultChainRequestExtractor.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query; + +import com.alibaba.nacos.api.config.remote.request.ConfigQueryRequest; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.model.gray.TagGrayRule; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.utils.RequestUtil; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + +import static com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG; + +/** + * DefaultChainRequestExtractor. + * + * @author Nacos + */ +public class DefaultChainRequestExtractor implements ConfigQueryChainRequestExtractor { + + @Override + public String getName() { + return "nacos"; + } + + @Override + public ConfigQueryChainRequest extract(HttpServletRequest request) { + final String dataId = request.getParameter("dataId"); + final String group = request.getParameter("group"); + String tenant = request.getParameter("tenant"); + if (StringUtils.isBlank(tenant)) { + tenant = StringUtils.EMPTY; + } + String tag = request.getParameter("tag"); + String autoTag = request.getHeader(VIPSERVER_TAG); + String clientIp = RequestUtil.getRemoteIp(request); + + Map appLabels = new HashMap<>(4); + appLabels.put(BetaGrayRule.CLIENT_IP_LABEL, clientIp); + if (StringUtils.isNotBlank(tag)) { + appLabels.put(TagGrayRule.VIP_SERVER_TAG_LABEL, tag); + } else if (StringUtils.isNotBlank(autoTag)) { + appLabels.put(TagGrayRule.VIP_SERVER_TAG_LABEL, autoTag); + } + + ConfigQueryChainRequest chainRequest = new ConfigQueryChainRequest(); + chainRequest.setDataId(dataId); + chainRequest.setGroup(group); + chainRequest.setTenant(tenant); + chainRequest.setTag(tag); + chainRequest.setAppLabels(appLabels); + + return chainRequest; + } + + @Override + public ConfigQueryChainRequest extract(ConfigQueryRequest request, RequestMeta requestMeta) { + ConfigQueryChainRequest chainRequest = new ConfigQueryChainRequest(); + + String tag = request.getTag(); + Map appLabels = new HashMap<>(4); + appLabels.put(BetaGrayRule.CLIENT_IP_LABEL, requestMeta.getClientIp()); + if (StringUtils.isNotBlank(tag)) { + appLabels.put(TagGrayRule.VIP_SERVER_TAG_LABEL, tag); + } else { + appLabels.putAll(requestMeta.getAppLabels()); + } + + chainRequest.setDataId(request.getDataId()); + chainRequest.setGroup(request.getGroup()); + chainRequest.setTenant(request.getTenant()); + chainRequest.setTag(request.getTag()); + chainRequest.setAppLabels(appLabels); + + return chainRequest; + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/DefaultConfigQueryHandlerChainBuilder.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/DefaultConfigQueryHandlerChainBuilder.java new file mode 100644 index 00000000000..464befe65e8 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/DefaultConfigQueryHandlerChainBuilder.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query; + +import com.alibaba.nacos.config.server.service.query.handler.ConfigChainEntryHandler; +import com.alibaba.nacos.config.server.service.query.handler.FormalHandler; +import com.alibaba.nacos.config.server.service.query.handler.GrayRuleMatchHandler; +import com.alibaba.nacos.config.server.service.query.handler.SpecialTagNotFoundHandler; + +/** + * DefaultConfigQueryHandlerChainBuilder. + * + * @author Nacos + */ +public class DefaultConfigQueryHandlerChainBuilder implements ConfigQueryHandlerChainBuilder { + + @Override + public ConfigQueryHandlerChain build() { + ConfigQueryHandlerChain chain = new ConfigQueryHandlerChain(); + chain.addHandler(new ConfigChainEntryHandler()) + .addHandler(new GrayRuleMatchHandler()) + .addHandler(new SpecialTagNotFoundHandler()) + .addHandler(new FormalHandler()); + return chain; + } + + @Override + public String getName() { + return "nacos"; + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/enums/ResponseCode.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/enums/ResponseCode.java new file mode 100644 index 00000000000..7dc0af9f9db --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/enums/ResponseCode.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query.enums; + +/** + * ResponseCode. + * + * @author Nacos + */ +public enum ResponseCode { + /** + * Request success. + */ + SUCCESS(200, "Response ok"), + + /** + * Request failed. + */ + FAIL(500, "Response fail"); + + int code; + + String desc; + + ResponseCode(int code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * Getter method for property code. + * + * @return property value of code + */ + public int getCode() { + return code; + } + + /** + * Getter method for property desc. + * + * @return property value of desc + */ + public String getDesc() { + return desc; + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/AbstractConfigQueryHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/AbstractConfigQueryHandler.java new file mode 100644 index 00000000000..90254ea819d --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/AbstractConfigQueryHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query.handler; + +/** + * AbstractConfigQueryHandler. + * This abstract class provides a base implementation for configuration query handlers. + * It implements the {@link ConfigQueryHandler} interface and handles the chaining of handlers. + * + * @author Nacos + */ +public abstract class AbstractConfigQueryHandler implements ConfigQueryHandler { + + public ConfigQueryHandler nextHandler; + + public void setNextHandler(ConfigQueryHandler nextHandler) { + this.nextHandler = nextHandler; + } + + public ConfigQueryHandler getNextHandler() { + return this.nextHandler; + } + +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/ConfigChainEntryHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/ConfigChainEntryHandler.java new file mode 100644 index 00000000000..a87e92a1482 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/ConfigChainEntryHandler.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query.handler; + +import com.alibaba.nacos.config.server.model.CacheItem; +import com.alibaba.nacos.config.server.service.ConfigCacheService; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; +import com.alibaba.nacos.config.server.utils.GroupKey2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +/** + * ConfigChainEntryHandler. + * The entry point handler for the responsibility chain, responsible for initializing the chain and handling configuration query requests. + * + * @author Nacos + */ +public class ConfigChainEntryHandler extends AbstractConfigQueryHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigChainEntryHandler.class); + + private static final String CHAIN_ENTRY_HANDLER = "chainEntryHandler"; + + private static final ThreadLocal CACHE_ITEM_THREAD_LOCAL = new ThreadLocal<>(); + + @Override + public String getName() { + return CHAIN_ENTRY_HANDLER; + } + + @Override + public ConfigQueryChainResponse handle(ConfigQueryChainRequest request) throws IOException { + String groupKey = GroupKey2.getKey(request.getDataId(), request.getGroup(), request.getTenant()); + int lockResult = ConfigCacheService.tryConfigReadLock(groupKey); + CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey); + + if (lockResult > 0 && cacheItem != null) { + try { + CACHE_ITEM_THREAD_LOCAL.set(cacheItem); + if (nextHandler != null) { + return nextHandler.handle(request); + } else { + LOGGER.warn("chainEntryHandler's next handler is null"); + return new ConfigQueryChainResponse(); + } + } finally { + CACHE_ITEM_THREAD_LOCAL.remove(); + ConfigCacheService.releaseReadLock(groupKey); + } + } else if (lockResult == 0 || cacheItem == null) { + ConfigQueryChainResponse response = new ConfigQueryChainResponse(); + response.setStatus(ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_NOT_FOUND); + return response; + } else { + ConfigQueryChainResponse response = new ConfigQueryChainResponse(); + response.setStatus(ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_QUERY_CONFLICT); + return response; + } + } + + public static CacheItem getThreadLocalCacheItem() { + return CACHE_ITEM_THREAD_LOCAL.get(); + } + + public static void removeThreadLocalCacheItem() { + CACHE_ITEM_THREAD_LOCAL.remove(); + } + +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/ConfigQueryHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/ConfigQueryHandler.java new file mode 100644 index 00000000000..a1a690b30f0 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/ConfigQueryHandler.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query.handler; + +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; + +import java.io.IOException; + +/** + * Configuration Query Handler Interface. + * This interface defines the standard methods for handling configuration query requests. + * + * @author Nacos + */ +public interface ConfigQueryHandler { + + /** + * Gets the name of the handler. + * @return The name of the handler. + */ + String getName(); + + /** + * Handles the configuration query request. + * If the current handler cannot process the request, it should throw an IOException. + * @param request The configuration query request. + * @return The response to the configuration query. + * @throws IOException If an I/O error occurs. + */ + ConfigQueryChainResponse handle(ConfigQueryChainRequest request) throws IOException; + + /** + * Sets the next handler in the chain. + * @param nextHandler The next handler to which the request can be passed if the current handler cannot process it. + */ + void setNextHandler(ConfigQueryHandler nextHandler); + + /** + * Gets the next handler in the chain. + * @return The next handler. + */ + ConfigQueryHandler getNextHandler(); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/FormalHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/FormalHandler.java new file mode 100644 index 00000000000..660c956f3ce --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/FormalHandler.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query.handler; + +import com.alibaba.nacos.config.server.model.CacheItem; +import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; + +import java.io.IOException; + +import static com.alibaba.nacos.config.server.constant.Constants.ENCODE_UTF8; + +/** + * Formal Handler. + * This class represents a formal handler in the configuration query processing chain. + * If the request has not been processed by previous handlers, it will be handled by this handler. + * @author Nacos + */ +public class FormalHandler extends AbstractConfigQueryHandler { + + private static final String FORMAL_HANDLER = "formalHandler"; + + @Override + public String getName() { + return FORMAL_HANDLER; + } + + @Override + public ConfigQueryChainResponse handle(ConfigQueryChainRequest request) throws IOException { + ConfigQueryChainResponse response = new ConfigQueryChainResponse(); + + String dataId = request.getDataId(); + String group = request.getGroup(); + String tenant = request.getTenant(); + + CacheItem cacheItem = ConfigChainEntryHandler.getThreadLocalCacheItem(); + String md5 = cacheItem.getConfigCache().getMd5(ENCODE_UTF8); + long lastModified = cacheItem.getConfigCache().getLastModifiedTs(); + String encryptedDataKey = cacheItem.getConfigCache().getEncryptedDataKey(); + String contentType = cacheItem.getType(); + String content = ConfigDiskServiceFactory.getInstance().getContent(dataId, group, tenant); + + response.setContent(content); + response.setMd5(md5); + response.setLastModified(lastModified); + response.setEncryptedDataKey(encryptedDataKey); + response.setContentType(contentType); + response.setStatus(ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_FOUND_FORMAL); + + return response; + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/GrayRuleMatchHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/GrayRuleMatchHandler.java new file mode 100644 index 00000000000..e95640539d9 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/GrayRuleMatchHandler.java @@ -0,0 +1,82 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query.handler; + +import com.alibaba.nacos.config.server.model.CacheItem; +import com.alibaba.nacos.config.server.model.ConfigCacheGray; +import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; + +import java.io.IOException; + +import static com.alibaba.nacos.config.server.constant.Constants.ENCODE_UTF8; + +/** + * GrayRuleMatchHandler. + * This class represents a gray rule handler in the configuration query processing chain. + * It checks if the request matches any gray rules and processes the request accordingly. + * + * @author Nacos + */ +public class GrayRuleMatchHandler extends AbstractConfigQueryHandler { + + private static final String GRAY_RULE_MATCH_HANDLER = "grayRuleMatchHandler"; + + @Override + public String getName() { + return GRAY_RULE_MATCH_HANDLER; + } + + @Override + public ConfigQueryChainResponse handle(ConfigQueryChainRequest request) throws IOException { + // Check if the request matches any gray rules + CacheItem cacheItem = ConfigChainEntryHandler.getThreadLocalCacheItem(); + ConfigCacheGray matchedGray = null; + if (cacheItem.getSortConfigGrays() != null && !cacheItem.getSortConfigGrays().isEmpty()) { + for (ConfigCacheGray configCacheGray : cacheItem.getSortConfigGrays()) { + if (configCacheGray.match(request.getAppLabels())) { + matchedGray = configCacheGray; + break; + } + } + } + + if (matchedGray != null) { + ConfigQueryChainResponse response = new ConfigQueryChainResponse(); + + long lastModified = matchedGray.getLastModifiedTs(); + String md5 = matchedGray.getMd5(ENCODE_UTF8); + String encryptedDataKey = matchedGray.getEncryptedDataKey(); + String content = ConfigDiskServiceFactory.getInstance() + .getGrayContent(request.getDataId(), request.getGroup(), request.getTenant(), + matchedGray.getGrayName()); + + response.setContent(content); + response.setMd5(md5); + response.setLastModified(lastModified); + response.setEncryptedDataKey(encryptedDataKey); + response.setMatchedGray(matchedGray); + response.setContentType(cacheItem.getType()); + response.setStatus(ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_FOUND_GRAY); + + return response; + } else { + return nextHandler.handle(request); + } + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/SpecialTagNotFoundHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/SpecialTagNotFoundHandler.java new file mode 100644 index 00000000000..08dee4c44ce --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/SpecialTagNotFoundHandler.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query.handler; + +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.model.CacheItem; +import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; + +import java.io.IOException; + +import static com.alibaba.nacos.config.server.constant.Constants.ENCODE_UTF8; + +/** + * SpecialTagNotFound Handler. + * This class represents special tag not found handler in the configuration query processing chain. + * + * @author Nacos + */ +public class SpecialTagNotFoundHandler extends AbstractConfigQueryHandler { + + private static final String SPECIAL_TAG_NOT_FOUND_HANDLER = "specialTagNotFoundHandler"; + + @Override + public String getName() { + return SPECIAL_TAG_NOT_FOUND_HANDLER; + } + + @Override + public ConfigQueryChainResponse handle(ConfigQueryChainRequest request) throws IOException { + if (StringUtils.isNotBlank(request.getTag())) { + ConfigQueryChainResponse response = new ConfigQueryChainResponse(); + + String dataId = request.getDataId(); + String group = request.getGroup(); + String tenant = request.getTenant(); + + CacheItem cacheItem = ConfigChainEntryHandler.getThreadLocalCacheItem(); + String md5 = cacheItem.getConfigCache().getMd5(ENCODE_UTF8); + long lastModified = cacheItem.getConfigCache().getLastModifiedTs(); + String encryptedDataKey = cacheItem.getConfigCache().getEncryptedDataKey(); + String contentType = cacheItem.getType(); + String content = ConfigDiskServiceFactory.getInstance().getContent(dataId, group, tenant); + + response.setContent(content); + response.setMd5(md5); + response.setLastModified(lastModified); + response.setEncryptedDataKey(encryptedDataKey); + response.setContentType(contentType); + response.setStatus(ConfigQueryChainResponse.ConfigQueryStatus.SPECIAL_TAG_CONFIG_NOT_FOUND); + + return response; + } else { + return nextHandler.handle(request); + } + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/model/ConfigQueryChainRequest.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/model/ConfigQueryChainRequest.java new file mode 100644 index 00000000000..7b20943e03c --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/model/ConfigQueryChainRequest.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query.model; + +import java.util.Map; +import java.util.Objects; + +/** + * ConfigQueryChainRequest. + * + * @author Nacos + */ +public class ConfigQueryChainRequest { + + private String dataId; + + private String group; + + private String tenant; + + private String tag; + + private Map appLabels; + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public Map getAppLabels() { + return appLabels; + } + + public void setAppLabels(Map appLabels) { + this.appLabels = appLabels; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ConfigQueryChainRequest that = (ConfigQueryChainRequest) o; + return Objects.equals(dataId, that.dataId) + && Objects.equals(group, that.group) + && Objects.equals(tenant, that.tenant) + && Objects.equals(tag, that.tag) + && Objects.equals(appLabels, that.appLabels); + } + + @Override + public int hashCode() { + return Objects.hash(dataId, group, tenant, tag, appLabels); + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/model/ConfigQueryChainResponse.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/model/ConfigQueryChainResponse.java new file mode 100644 index 00000000000..1028e35aa03 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/model/ConfigQueryChainResponse.java @@ -0,0 +1,190 @@ +/* + * Copyright 1999-$toady.year Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.alibaba.nacos.config.server.service.query.model; + +import com.alibaba.nacos.config.server.model.ConfigCacheGray; +import com.alibaba.nacos.config.server.service.query.enums.ResponseCode; + +import java.util.Objects; + +/** + * ConfigQueryChainResponse. + * + * @author Nacos + */ +public class ConfigQueryChainResponse { + + private String content; + + private String contentType; + + private String encryptedDataKey; + + private String md5; + + private long lastModified; + + private ConfigCacheGray matchedGray; + + private int resultCode; + + private String message; + + private ConfigQueryStatus status; + + public enum ConfigQueryStatus { + /** + * Indicates that the configuration was found and is formal. + */ + CONFIG_FOUND_FORMAL, + + /** + * Indicates that the configuration was found and is gray. + */ + CONFIG_FOUND_GRAY, + + /** + * Indicates that the configuration special tag was not found. + */ + SPECIAL_TAG_CONFIG_NOT_FOUND, + + /** + * Indicates that the configuration was not found. + */ + CONFIG_NOT_FOUND, + + /** + * Indicates a conflict in the configuration query. + */ + CONFIG_QUERY_CONFLICT, + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getEncryptedDataKey() { + return encryptedDataKey; + } + + public void setEncryptedDataKey(String encryptedDataKey) { + this.encryptedDataKey = encryptedDataKey; + } + + public String getMd5() { + return md5; + } + + public void setMd5(String md5) { + this.md5 = md5; + } + + public long getLastModified() { + return lastModified; + } + + public void setLastModified(long lastModified) { + this.lastModified = lastModified; + } + + public ConfigCacheGray getMatchedGray() { + return matchedGray; + } + + public void setMatchedGray(ConfigCacheGray matchedGray) { + this.matchedGray = matchedGray; + } + + public int getResultCode() { + return resultCode; + } + + public void setResultCode(int resultCode) { + this.resultCode = resultCode; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public ConfigQueryStatus getStatus() { + return status; + } + + public void setStatus(ConfigQueryStatus status) { + this.status = status; + } + + /** + * Build fail response. + * + * @param errorCode errorCode. + * @param message message. + * @return response. + */ + public static ConfigQueryChainResponse buildFailResponse(int errorCode, String message) { + ConfigQueryChainResponse response = new ConfigQueryChainResponse(); + response.setErrorInfo(errorCode, message); + return response; + } + + public void setErrorInfo(int errorCode, String errorMsg) { + this.resultCode = ResponseCode.FAIL.getCode(); + this.message = errorMsg; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ConfigQueryChainResponse that = (ConfigQueryChainResponse) o; + return lastModified == that.lastModified + && Objects.equals(content, that.content) + && Objects.equals(contentType, that.contentType) + && Objects.equals(encryptedDataKey, that.encryptedDataKey) + && Objects.equals(md5, that.md5) + && Objects.equals(matchedGray, that.matchedGray) + && Objects.equals(resultCode, that.resultCode) + && Objects.equals(message, that.message) + && status == that.status; + } + + @Override + public int hashCode() { + return Objects.hash(content, contentType, encryptedDataKey, md5, lastModified, matchedGray, resultCode, message, status); + } +} \ No newline at end of file diff --git a/config/src/main/resources/META-INF/services/com.alibaba.nacos.config.server.service.query.ConfigQueryChainRequestExtractor b/config/src/main/resources/META-INF/services/com.alibaba.nacos.config.server.service.query.ConfigQueryChainRequestExtractor new file mode 100644 index 00000000000..315a4ab2c4f --- /dev/null +++ b/config/src/main/resources/META-INF/services/com.alibaba.nacos.config.server.service.query.ConfigQueryChainRequestExtractor @@ -0,0 +1,17 @@ +# +# Copyright 1999-$toady.year Alibaba Group Holding Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. +# + +com.alibaba.nacos.config.server.service.query.DefaultChainRequestExtractor \ No newline at end of file diff --git a/config/src/main/resources/META-INF/services/com.alibaba.nacos.config.server.service.query.ConfigQueryHandlerChainBuilder b/config/src/main/resources/META-INF/services/com.alibaba.nacos.config.server.service.query.ConfigQueryHandlerChainBuilder new file mode 100644 index 00000000000..26df4b5395f --- /dev/null +++ b/config/src/main/resources/META-INF/services/com.alibaba.nacos.config.server.service.query.ConfigQueryHandlerChainBuilder @@ -0,0 +1,17 @@ +# +# Copyright 1999-$toady.year Alibaba Group Holding Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. +# + +com.alibaba.nacos.config.server.service.query.DefaultConfigQueryHandlerChainBuilder \ No newline at end of file diff --git a/config/src/test/java/com/alibaba/nacos/config/server/controller/ConfigServletInnerTest.java b/config/src/test/java/com/alibaba/nacos/config/server/controller/ConfigServletInnerTest.java index 651ca61fb84..a08073fd79d 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/controller/ConfigServletInnerTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/controller/ConfigServletInnerTest.java @@ -22,6 +22,7 @@ import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.MD5Utils; import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.enums.ApiVersionEnum; import com.alibaba.nacos.config.server.model.CacheItem; import com.alibaba.nacos.config.server.model.ConfigCacheGray; import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; @@ -32,6 +33,7 @@ import com.alibaba.nacos.config.server.service.LongPollingService; import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; import com.alibaba.nacos.config.server.service.dump.disk.ConfigRocksDbDiskService; +import com.alibaba.nacos.config.server.service.query.ConfigQueryChainService; import com.alibaba.nacos.config.server.utils.GroupKey; import com.alibaba.nacos.config.server.utils.GroupKey2; import com.alibaba.nacos.config.server.utils.MD5Util; @@ -93,6 +95,7 @@ class ConfigServletInnerTest { void setUp() { EnvUtil.setEnvironment(new StandardEnvironment()); ReflectionTestUtils.setField(configServletInner, "longPollingService", longPollingService); + ReflectionTestUtils.setField(configServletInner, "configQueryChainService", new ConfigQueryChainService()); configCacheServiceMockedStatic = Mockito.mockStatic(ConfigCacheService.class); propertyUtilMockedStatic = Mockito.mockStatic(PropertyUtil.class); propertyUtilMockedStatic.when(PropertyUtil::getMaxContent).thenReturn(1024 * 1000); @@ -160,14 +163,17 @@ void testDoGetConfigV1Beta() throws Exception { String mockBetaContent = "content3456543"; mockGray4Beta(cacheItem, mockBetaContent, "localhost", "betaKey1234567"); MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRemoteAddr("localhost:8080"); + request.setParameter("dataId", dataId); + request.setParameter("group", group); + request.setParameter("tenant", tenant); + request.setRemoteAddr("localhost"); request.addHeader(CLIENT_APPNAME_HEADER, "test"); MockHttpServletResponse response = new MockHttpServletResponse(); when(configRocksDbDiskService.getGrayContent(dataId, group, tenant, BetaGrayRule.TYPE_BETA)).thenReturn( mockBetaContent); String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, "", "true", - "localhost"); + "localhost", ApiVersionEnum.V1); assertEquals(HttpServletResponse.SC_OK + "", actualValue); assertEquals("true", response.getHeader("isBeta")); assertEquals(MD5Utils.md5Hex(mockBetaContent, ENCODE_UTF8), response.getHeader(CONTENT_MD5)); @@ -232,7 +238,10 @@ void testDoGetConfigV1Tag() throws Exception { //test auto tag. MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRemoteAddr("localhost:8080"); + request.setParameter("dataId", dataId); + request.setParameter("group", group); + request.setParameter("tenant", tenant); + request.setRemoteAddr("localhost"); request.addHeader(CLIENT_APPNAME_HEADER, "test"); request.addHeader(VIPSERVER_TAG, autoTag); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -241,7 +250,7 @@ void testDoGetConfigV1Tag() throws Exception { configRocksDbDiskService.getGrayContent(dataId, group, tenant, TagGrayRule.TYPE_TAG + "_" + autoTag)) .thenReturn(autoTagContent); String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, null, "true", - "localhost"); + "localhost", ApiVersionEnum.V1); assertEquals(HttpServletResponse.SC_OK + "", actualValue); assertEquals(autoTagContent, response.getContentAsString()); assertEquals(MD5Utils.md5Hex(autoTagContent, "UTF-8"), response.getHeader(CONTENT_MD5)); @@ -249,21 +258,23 @@ void testDoGetConfigV1Tag() throws Exception { //test for specific tag. has higher propority than auto tag. response = new MockHttpServletResponse(); + request.setParameter("tag", specificTag); when(configRocksDbDiskService.getGrayContent(dataId, group, tenant, TagGrayRule.TYPE_TAG + "_" + specificTag)).thenReturn(specificTagContent); actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, specificTag, "true", - "localhost"); + "localhost", ApiVersionEnum.V1); assertEquals(HttpServletResponse.SC_OK + "", actualValue); assertEquals(specificTagContent, response.getContentAsString()); assertEquals(MD5Utils.md5Hex(specificTagContent, "UTF-8"), response.getHeader(CONTENT_MD5)); assertEquals("specificTagkey", response.getHeader("Encrypted-Data-Key")); // test for specific tag ,not exist + request.setParameter("tag", "auto-tag-test-not-exist"); when(configRocksDbDiskService.getGrayContent(dataId, group, tenant, TagGrayRule.TYPE_TAG + "_" + "auto-tag-test-not-exist")).thenReturn(null); response = new MockHttpServletResponse(); actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, - "auto-tag-test-not-exist", "true", "localhost"); + "auto-tag-test-not-exist", "true", "localhost", ApiVersionEnum.V1); assertEquals(HttpServletResponse.SC_NOT_FOUND + "", actualValue); String expectedContent = "config data not exist"; String actualContent = response.getContentAsString(); @@ -283,7 +294,7 @@ void testDoGetConfigFormal() throws Exception { //mock cache item . CacheItem cacheItem = new CacheItem("test"); String md5 = "md5wertyui"; - String content = "content345678"; + final String content = "content345678"; cacheItem.getConfigCache().setMd5Utf8(md5); long ts = System.currentTimeMillis(); cacheItem.getConfigCache().setLastModifiedTs(ts); @@ -292,11 +303,14 @@ void testDoGetConfigFormal() throws Exception { () -> ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataId, group, tenant))) .thenReturn(cacheItem); MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("dataId", dataId); + request.setParameter("group", group); + request.setParameter("tenant", tenant); MockHttpServletResponse response = new MockHttpServletResponse(); when(configRocksDbDiskService.getContent(dataId, group, tenant)).thenReturn(content); String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, null, "true", - "localhost"); + "localhost", ApiVersionEnum.V1); assertEquals(content, response.getContentAsString()); assertEquals(HttpServletResponse.SC_OK + "", actualValue); assertEquals(md5, response.getHeader(CONTENT_MD5)); @@ -315,7 +329,7 @@ void testDoGetConfigFormalV2() throws Exception { //mock cache item . CacheItem cacheItem = new CacheItem("test"); String md5 = "md5wertyui"; - String content = "content345678"; + final String content = "content345678"; cacheItem.getConfigCache().setMd5Utf8(md5); long ts = System.currentTimeMillis(); cacheItem.getConfigCache().setLastModifiedTs(ts); @@ -324,11 +338,14 @@ void testDoGetConfigFormalV2() throws Exception { () -> ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataId, group, tenant))) .thenReturn(cacheItem); MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("dataId", dataId); + request.setParameter("group", group); + request.setParameter("tenant", tenant); MockHttpServletResponse response = new MockHttpServletResponse(); when(configRocksDbDiskService.getContent(dataId, group, tenant)).thenReturn(content); String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, null, "true", - "localhost", true); + "localhost", ApiVersionEnum.V2); assertEquals(JacksonUtils.toJson(Result.success(content)), response.getContentAsString()); assertEquals(HttpServletResponse.SC_OK + "", actualValue); assertEquals(md5, response.getHeader(CONTENT_MD5)); @@ -338,13 +355,21 @@ void testDoGetConfigFormalV2() throws Exception { @Test void testDoGetConfigNotExist() throws Exception { + String dataId = "test"; + String group = "test"; + final String tenant = "test"; + final String tag = "test"; // if lockResult equals 0,cache item not exist. configCacheServiceMockedStatic.when(() -> ConfigCacheService.tryConfigReadLock(anyString())).thenReturn(0); MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("dataId", dataId); + request.setParameter("group", group); + request.setParameter("tenant", tenant); + request.setParameter("tag", tag); MockHttpServletResponse response = new MockHttpServletResponse(); - String actualValue = configServletInner.doGetConfig(request, response, "test", "test", "test", "test", "true", - "localhost"); + String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, tag, "true", + "localhost", ApiVersionEnum.V1); assertEquals(HttpServletResponse.SC_NOT_FOUND + "", actualValue); configCacheServiceMockedStatic.when( @@ -353,7 +378,7 @@ void testDoGetConfigNotExist() throws Exception { // if lockResult less than 0 configCacheServiceMockedStatic.when(() -> ConfigCacheService.tryConfigReadLock(anyString())).thenReturn(-1); actualValue = configServletInner.doGetConfig(request, response, "test", "test", "test", "test", "true", - "localhost"); + "localhost", ApiVersionEnum.V1); assertEquals(HttpServletResponse.SC_CONFLICT + "", actualValue); } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2Test.java b/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2Test.java index 7eb34abfa8a..a94130b7f07 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2Test.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2Test.java @@ -22,6 +22,7 @@ import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.config.server.controller.ConfigServletInner; +import com.alibaba.nacos.config.server.enums.ApiVersionEnum; import com.alibaba.nacos.config.server.model.ConfigInfo; import com.alibaba.nacos.config.server.model.ConfigRequestInfo; import com.alibaba.nacos.config.server.model.form.ConfigForm; @@ -136,12 +137,12 @@ void testGetConfig() throws Exception { x.getArgument(1, HttpServletResponse.class).getWriter().print(JacksonUtils.toJson(stringResult)); return null; }).when(inner).doGetConfig(any(HttpServletRequest.class), any(HttpServletResponse.class), eq(TEST_DATA_ID), eq(TEST_GROUP), - eq(TEST_NAMESPACE_ID), eq(TEST_TAG), eq(null), anyString(), eq(true)); + eq(TEST_NAMESPACE_ID), eq(TEST_TAG), eq(null), anyString(), eq(ApiVersionEnum.V2)); configControllerV2.getConfig(request, response, TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, TEST_TAG); verify(inner).doGetConfig(eq(request), eq(response), eq(TEST_DATA_ID), eq(TEST_GROUP), eq(TEST_NAMESPACE_ID), eq(TEST_TAG), - eq(null), anyString(), eq(true)); + eq(null), anyString(), eq(ApiVersionEnum.V2)); JsonNode resNode = JacksonUtils.toObj(response.getContentAsString()); Integer errCode = JacksonUtils.toObj(resNode.get("code").toString(), Integer.class); String actContent = JacksonUtils.toObj(resNode.get("data").toString(), String.class); diff --git a/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandlerTest.java b/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandlerTest.java index 7762f6f56c0..4715b525ea3 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandlerTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandlerTest.java @@ -26,6 +26,7 @@ import com.alibaba.nacos.config.server.model.gray.ConfigGrayPersistInfo; import com.alibaba.nacos.config.server.model.gray.GrayRuleManager; import com.alibaba.nacos.config.server.model.gray.TagGrayRule; +import com.alibaba.nacos.config.server.service.query.ConfigQueryChainService; import com.alibaba.nacos.config.server.service.ConfigCacheService; import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; import com.alibaba.nacos.config.server.service.dump.disk.ConfigRocksDbDiskService; @@ -85,7 +86,7 @@ void setUp() throws IOException { configCacheServiceMockedStatic = Mockito.mockStatic(ConfigCacheService.class); propertyUtilMockedStatic = Mockito.mockStatic(PropertyUtil.class); configDiskServiceFactoryMockedStatic = Mockito.mockStatic(ConfigDiskServiceFactory.class); - configQueryRequestHandler = new ConfigQueryRequestHandler(); + configQueryRequestHandler = new ConfigQueryRequestHandler(new ConfigQueryChainService()); final String groupKey = GroupKey2.getKey(dataId, group, ""); when(ConfigCacheService.tryConfigReadLock(groupKey)).thenReturn(1); propertyUtilMockedStatic.when(PropertyUtil::getMaxContent).thenReturn(1024 * 1000); @@ -207,9 +208,9 @@ void testGetTagNotFound() throws Exception { //check content&md5 assertNull(response.getContent()); - assertNull(response.getMd5()); + assertEquals(MD5Utils.md5Hex(content, "UTF-8"), response.getMd5()); assertEquals(CONFIG_NOT_FOUND, response.getErrorCode()); - assertNull(response.getEncryptedDataKey()); + assertEquals("key_testGetTag_NotFound", response.getEncryptedDataKey()); //check flags. assertFalse(response.isBeta());