Skip to content

Commit cf2ed78

Browse files
Shawyeoknobodyiam
authored andcommitted
feat: Provide a new configfiles API to return the raw content of configuration files directly
1 parent 30d1985 commit cf2ed78

File tree

5 files changed

+126
-9
lines changed

5 files changed

+126
-9
lines changed

CHANGES.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Release Notes.
55
Apollo 2.5.0
66

77
------------------
8-
*
8+
* [Feature: Provide a new configfiles API to return the raw content of configuration files directly](https://github.com/apolloconfig/apollo/pull/5336)
99

1010
------------------
1111
All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/16?closed=1)

apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/controller/ConfigFileController.java

+74-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.ctrip.framework.apollo.configservice.util.WatchKeysUtil;
2626
import com.ctrip.framework.apollo.core.ConfigConsts;
2727
import com.ctrip.framework.apollo.core.dto.ApolloConfig;
28+
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
2829
import com.ctrip.framework.apollo.core.utils.PropertiesUtil;
2930
import com.ctrip.framework.apollo.tracer.Tracer;
3031
import com.google.common.base.Joiner;
@@ -67,8 +68,10 @@ public class ConfigFileController implements ReleaseMessageListener {
6768
private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
6869
private static final long MAX_CACHE_SIZE = 50 * 1024 * 1024; // 50MB
6970
private static final long EXPIRE_AFTER_WRITE = 30;
70-
private final HttpHeaders propertiesResponseHeaders;
71+
private final HttpHeaders plainTextResponseHeaders;
7172
private final HttpHeaders jsonResponseHeaders;
73+
private final HttpHeaders yamlResponseHeaders;
74+
private final HttpHeaders xmlResponseHeaders;
7275
private final ResponseEntity<String> NOT_FOUND_RESPONSE;
7376
private Cache<String, String> localCache;
7477
private final Multimap<String, String>
@@ -106,10 +109,14 @@ public ConfigFileController(
106109
logger.debug("removed cache key: {}", cacheKey);
107110
})
108111
.build();
109-
propertiesResponseHeaders = new HttpHeaders();
110-
propertiesResponseHeaders.add("Content-Type", "text/plain;charset=UTF-8");
112+
plainTextResponseHeaders = new HttpHeaders();
113+
plainTextResponseHeaders.add("Content-Type", "text/plain;charset=UTF-8");
111114
jsonResponseHeaders = new HttpHeaders();
112115
jsonResponseHeaders.add("Content-Type", "application/json;charset=UTF-8");
116+
yamlResponseHeaders = new HttpHeaders();
117+
yamlResponseHeaders.add("Content-Type", "application/yaml;charset=UTF-8");
118+
xmlResponseHeaders = new HttpHeaders();
119+
xmlResponseHeaders.add("Content-Type", "application/xml;charset=UTF-8");
113120
NOT_FOUND_RESPONSE = new ResponseEntity<>(HttpStatus.NOT_FOUND);
114121
this.configController = configController;
115122
this.namespaceUtil = namespaceUtil;
@@ -136,7 +143,7 @@ public ResponseEntity<String> queryConfigAsProperties(@PathVariable String appId
136143
return NOT_FOUND_RESPONSE;
137144
}
138145

139-
return new ResponseEntity<>(result, propertiesResponseHeaders, HttpStatus.OK);
146+
return new ResponseEntity<>(result, plainTextResponseHeaders, HttpStatus.OK);
140147
}
141148

142149
@GetMapping(value = "/json/{appId}/{clusterName}/{namespace:.+}")
@@ -160,6 +167,44 @@ public ResponseEntity<String> queryConfigAsJson(@PathVariable String appId,
160167
return new ResponseEntity<>(result, jsonResponseHeaders, HttpStatus.OK);
161168
}
162169

170+
@GetMapping(value = "/raw/{appId}/{clusterName}/{namespace:.+}")
171+
public ResponseEntity<String> queryConfigAsRaw(@PathVariable String appId,
172+
@PathVariable String clusterName,
173+
@PathVariable String namespace,
174+
@RequestParam(value = "dataCenter", required = false) String dataCenter,
175+
@RequestParam(value = "ip", required = false) String clientIp,
176+
@RequestParam(value = "label", required = false) String clientLabel,
177+
HttpServletRequest request,
178+
HttpServletResponse response) throws IOException {
179+
180+
String result =
181+
queryConfig(ConfigFileOutputFormat.RAW, appId, clusterName, namespace, dataCenter,
182+
clientIp, clientLabel, request, response);
183+
184+
if (result == null) {
185+
return NOT_FOUND_RESPONSE;
186+
}
187+
188+
ConfigFileFormat format = determineNamespaceFormat(namespace);
189+
HttpHeaders responseHeaders;
190+
switch (format) {
191+
case JSON:
192+
responseHeaders = jsonResponseHeaders;
193+
break;
194+
case YML:
195+
case YAML:
196+
responseHeaders = yamlResponseHeaders;
197+
break;
198+
case XML:
199+
responseHeaders = xmlResponseHeaders;
200+
break;
201+
default:
202+
responseHeaders = plainTextResponseHeaders;
203+
break;
204+
}
205+
return new ResponseEntity<>(result, responseHeaders, HttpStatus.OK);
206+
}
207+
163208
String queryConfig(ConfigFileOutputFormat outputFormat, String appId, String clusterName,
164209
String namespace, String dataCenter, String clientIp, String clientLabel,
165210
HttpServletRequest request,
@@ -247,11 +292,24 @@ private String loadConfig(ConfigFileOutputFormat outputFormat, String appId, Str
247292
case JSON:
248293
result = GSON.toJson(apolloConfig.getConfigurations());
249294
break;
295+
case RAW:
296+
result = getRawConfigContent(apolloConfig);
297+
break;
250298
}
251299

252300
return result;
253301
}
254302

303+
private String getRawConfigContent(ApolloConfig apolloConfig) throws IOException {
304+
ConfigFileFormat format = determineNamespaceFormat(apolloConfig.getNamespaceName());
305+
if (format == ConfigFileFormat.Properties) {
306+
Properties properties = new Properties();
307+
properties.putAll(apolloConfig.getConfigurations());
308+
return PropertiesUtil.toString(properties);
309+
}
310+
return apolloConfig.getConfigurations().get("content");
311+
}
312+
255313
String assembleCacheKey(ConfigFileOutputFormat outputFormat, String appId, String clusterName,
256314
String namespace,
257315
String dataCenter) {
@@ -263,6 +321,17 @@ String assembleCacheKey(ConfigFileOutputFormat outputFormat, String appId, Strin
263321
return STRING_JOINER.join(keyParts);
264322
}
265323

324+
ConfigFileFormat determineNamespaceFormat(String namespaceName) {
325+
String lowerCase = namespaceName.toLowerCase();
326+
for (ConfigFileFormat format : ConfigFileFormat.values()) {
327+
if (lowerCase.endsWith("." + format.getValue())) {
328+
return format;
329+
}
330+
}
331+
332+
return ConfigFileFormat.Properties;
333+
}
334+
266335
@Override
267336
public void handleMessage(ReleaseMessage message, String channel) {
268337
logger.info("message received - channel: {}, message: {}", channel, message);
@@ -286,7 +355,7 @@ public void handleMessage(ReleaseMessage message, String channel) {
286355
}
287356

288357
enum ConfigFileOutputFormat {
289-
PROPERTIES("properties"), JSON("json");
358+
PROPERTIES("properties"), JSON("json"), RAW("raw");
290359

291360
private String value;
292361

apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/controller/ConfigFileControllerTest.java

+31-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import static org.junit.Assert.assertEquals;
4747
import static org.junit.Assert.assertTrue;
4848
import static org.mockito.ArgumentMatchers.anyString;
49+
import static org.mockito.ArgumentMatchers.startsWith;
4950
import static org.mockito.Mockito.mock;
5051
import static org.mockito.Mockito.times;
5152
import static org.mockito.Mockito.verify;
@@ -93,7 +94,7 @@ public void setUp() throws Exception {
9394
someClientIp = "10.1.1.1";
9495
someClientLabel = "myLabel";
9596

96-
when(namespaceUtil.filterNamespaceName(someNamespace)).thenReturn(someNamespace);
97+
when(namespaceUtil.filterNamespaceName(startsWith(someNamespace))).thenReturn(someNamespace);
9798
when(namespaceUtil.normalizeNamespace(someAppId, someNamespace)).thenReturn(someNamespace);
9899
when(grayReleaseRulesHolder.hasGrayReleaseRule(anyString(), anyString(), anyString(),
99100
anyString())).thenReturn(false);
@@ -190,6 +191,35 @@ public void testQueryConfigAsJson() throws Exception {
190191
assertEquals(configurations, GSON.fromJson(response.getBody(), responseType));
191192
}
192193

194+
@Test
195+
public void testQueryConfigAsRaw() throws Exception {
196+
String someKey = "someKey";
197+
String someValue = "someValue";
198+
199+
String someWatchKey = "someWatchKey";
200+
Set<String> watchKeys = Sets.newHashSet(someWatchKey);
201+
202+
ApolloConfig someApolloConfig = mock(ApolloConfig.class);
203+
when(configController
204+
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp, someClientLabel,null,
205+
someRequest, someResponse)).thenReturn(someApolloConfig);
206+
when(someApolloConfig.getNamespaceName()).thenReturn(someNamespace + ".json");
207+
String jsonContent = GSON.toJson(ImmutableMap.of(someKey, someValue));
208+
when(someApolloConfig.getConfigurations()).thenReturn(ImmutableMap.of("content", jsonContent));
209+
when(watchKeysUtil
210+
.assembleAllWatchKeys(someAppId, someClusterName, someNamespace, someDataCenter))
211+
.thenReturn(watchKeys);
212+
213+
ResponseEntity<String> response =
214+
configFileController
215+
.queryConfigAsRaw(someAppId, someClusterName, someNamespace + ".json", someDataCenter,
216+
someClientIp, someClientLabel, someRequest, someResponse);
217+
218+
assertEquals(HttpStatus.OK, response.getStatusCode());
219+
assertEquals("application/json;charset=UTF-8", response.getHeaders().getContentType().toString());
220+
assertEquals(jsonContent, response.getBody());
221+
}
222+
193223
@Test
194224
public void testQueryConfigWithGrayRelease() throws Exception {
195225
String someKey = "someKey";

docs/en/client/other-language-client-user-guide.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Since the cache has a delay of at most one second, if you need to work with conf
3434

3535
The Http interface returns JSON format, UTF-8 encoding, which contains all the configuration items in the corresponding namespace.
3636

37-
Return content Sample is as follows.
37+
If the namespace is of type `properties`, the return content sample is as follows:
3838

3939
```json
4040
{
@@ -43,6 +43,15 @@ Return content Sample is as follows.
4343
}
4444
```
4545

46+
If the namespace is not of type `properties`, the return content sample is as follows:
47+
```json
48+
{
49+
"content": "{\"portal.elastic.document.type\":\"biz\",\"portal.elastic.cluster.name\":\"hermes-es-fws\"}"
50+
}
51+
```
52+
53+
> You can get the raw configuration content without escaping via `{config_server_url}/configfiles/raw/{appId}/{clusterName}/{namespaceName}?ip={clientIp}`.
54+
4655
> The configuration in the form of properties can be obtained via `{config_server_url}/configfiles/{appId}/{clusterName}/{namespaceName}?ip={clientIp}`
4756
4857
### 1.2.3 Testing

docs/zh/client/other-language-client-user-guide.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,23 @@
3131
### 1.2.2 Http接口返回格式
3232
该Http接口返回的是JSON格式、UTF-8编码,包含了对应namespace中所有的配置项。
3333

34-
返回内容Sample如下:
34+
若是properties类型的namespace,返回内容Sample如下:
3535
```json
3636
{
3737
"portal.elastic.document.type":"biz",
3838
"portal.elastic.cluster.name":"hermes-es-fws"
3939
}
4040
```
4141

42+
若不是properties类型的namespace,返回内容Sample如下(content是namespace的内容):
43+
```json
44+
{
45+
"content": "{\"portal.elastic.document.type\":\"biz\",\"portal.elastic.cluster.name\":\"hermes-es-fws\"}"
46+
}
47+
```
48+
49+
> 通过`{config_server_url}/configfiles/raw/{appId}/{clusterName}/{namespaceName}?ip={clientIp}`可以获取到原始的配置内容,不会进行转义。
50+
4251
> 通过`{config_server_url}/configfiles/{appId}/{clusterName}/{namespaceName}?ip={clientIp}`可以获取到properties形式的配置
4352
4453
### 1.2.3 测试

0 commit comments

Comments
 (0)