Skip to content

Commit 9137286

Browse files
authored
feat: resource charset support (#2440)
1 parent 77a88e5 commit 9137286

File tree

9 files changed

+95
-28
lines changed

9 files changed

+95
-28
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
**/src/test/resources/*-repo/** eol=lf
2+
**/foo_enc.properties text working-tree-encoding=UTF-8 eol=LF

docs/modules/ROOT/pages/client.adoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,19 @@ If you want to configure timeout thresholds:
154154
* Read timeouts can be configured by using the property `spring.cloud.config.request-read-timeout`.
155155
* Connection timeouts can be configured by using the property `spring.cloud.config.request-connect-timeout`.
156156

157+
[[configuring-charsets]]
158+
== Configuring Charset
159+
160+
If you want to configure a specific charset the resources should be delivered by the server, you need to apply it via charset.
161+
----
162+
spring:
163+
cloud:
164+
config:
165+
charset: UTF-8
166+
----
167+
168+
The charset configuration property is defined as `java.nio.charset.Charset`
169+
157170
[[security]]
158171
== Security
159172

spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientProperties.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.net.MalformedURLException;
2121
import java.net.URL;
2222
import java.net.URLDecoder;
23+
import java.nio.charset.Charset;
2324
import java.nio.charset.StandardCharsets;
2425
import java.util.Arrays;
2526
import java.util.HashMap;
@@ -133,6 +134,11 @@ public class ConfigClientProperties {
133134
*/
134135
private String mediaType = EnvironmentMediaType.V2_JSON;
135136

137+
/**
138+
* The charset to read the resource from the config server.
139+
*/
140+
private Charset charset = StandardCharsets.UTF_8;
141+
136142
/**
137143
* Discovery properties.
138144
*/
@@ -260,6 +266,14 @@ public void setMediaType(String mediaType) {
260266
this.mediaType = mediaType;
261267
}
262268

269+
public Charset getCharset() {
270+
return charset;
271+
}
272+
273+
public void setCharset(Charset charset) {
274+
this.charset = charset;
275+
}
276+
263277
public Discovery getDiscovery() {
264278
return this.discovery;
265279
}

spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLoader.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.cloud.config.client;
1818

19+
import java.nio.charset.Charset;
1920
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.Collections;
@@ -303,6 +304,7 @@ protected Environment getRemoteEnvironment(ConfigDataLoaderContext context, Conf
303304
}
304305
ResponseEntity<Environment> response = null;
305306
List<MediaType> acceptHeader = Collections.singletonList(MediaType.parseMediaType(properties.getMediaType()));
307+
List<Charset> acceptCharsetHeader = Collections.singletonList(properties.getCharset());
306308

307309
ConfigClientRequestTemplateFactory requestTemplateFactory = context.getBootstrapContext()
308310
.get(ConfigClientRequestTemplateFactory.class);
@@ -327,6 +329,7 @@ protected Environment getRemoteEnvironment(ConfigDataLoaderContext context, Conf
327329
try {
328330
HttpHeaders headers = new HttpHeaders();
329331
headers.setAccept(acceptHeader);
332+
headers.setAcceptCharset(acceptCharsetHeader);
330333
requestTemplateFactory.addAuthorizationToken(headers, username, password);
331334
if (StringUtils.hasText(token)) {
332335
headers.add(TOKEN_HEADER, token);

spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.cloud.config.client;
1818

19+
import java.nio.charset.Charset;
1920
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.Collection;
@@ -260,6 +261,7 @@ private Environment getRemoteEnvironment(ConfigClientRequestTemplateFactory requ
260261
}
261262
ResponseEntity<Environment> response = null;
262263
List<MediaType> acceptHeader = Collections.singletonList(MediaType.parseMediaType(properties.getMediaType()));
264+
List<Charset> acceptCharsetHeader = Collections.singletonList(properties.getCharset());
263265

264266
for (int i = 0; i < noOfUrls; i++) {
265267
Credentials credentials = properties.getCredentials(i);
@@ -272,6 +274,7 @@ private Environment getRemoteEnvironment(ConfigClientRequestTemplateFactory requ
272274
try {
273275
HttpHeaders headers = new HttpHeaders();
274276
headers.setAccept(acceptHeader);
277+
headers.setAcceptCharset(acceptCharsetHeader);
275278
requestTemplateFactory.addAuthorizationToken(headers, username, password);
276279
if (StringUtils.hasText(token)) {
277280
headers.add(TOKEN_HEADER, token);

spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/ResourceController.java

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.io.IOException;
2020
import java.io.InputStream;
2121
import java.nio.charset.Charset;
22+
import java.nio.charset.StandardCharsets;
23+
import java.nio.charset.UnsupportedCharsetException;
2224
import java.util.HashMap;
2325
import java.util.Map;
2426

@@ -29,11 +31,13 @@
2931
import org.springframework.cloud.config.server.encryption.ResourceEncryptor;
3032
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
3133
import org.springframework.core.io.Resource;
34+
import org.springframework.http.HttpHeaders;
3235
import org.springframework.http.MediaType;
3336
import org.springframework.util.StreamUtils;
3437
import org.springframework.util.StringUtils;
3538
import org.springframework.web.bind.annotation.GetMapping;
3639
import org.springframework.web.bind.annotation.PathVariable;
40+
import org.springframework.web.bind.annotation.RequestHeader;
3741
import org.springframework.web.bind.annotation.RequestMapping;
3842
import org.springframework.web.bind.annotation.RequestMethod;
3943
import org.springframework.web.bind.annotation.RequestParam;
@@ -98,17 +102,21 @@ public void setPlainTextEncryptEnabled(boolean plainTextEncryptEnabled) {
98102

99103
@GetMapping("/{name}/{profile}/{label}/**")
100104
public String retrieve(@PathVariable String name, @PathVariable String profile, @PathVariable String label,
101-
ServletWebRequest request, @RequestParam(defaultValue = "true") boolean resolvePlaceholders)
105+
ServletWebRequest request, @RequestParam(defaultValue = "true") boolean resolvePlaceholders,
106+
@RequestHeader(value = HttpHeaders.ACCEPT_CHARSET, required = false,
107+
defaultValue = "UTF-8") String acceptedCharset)
102108
throws IOException {
103109
String path = getFilePath(request, name, profile, label);
104-
return retrieve(request, name, profile, label, path, resolvePlaceholders);
110+
return retrieve(request, name, profile, label, path, resolvePlaceholders, acceptedCharset);
105111
}
106112

107113
@GetMapping(value = "/{name}/{profile}/{path:.*}", params = "useDefaultLabel")
108114
public String retrieveDefault(@PathVariable String name, @PathVariable String profile, @PathVariable String path,
109-
ServletWebRequest request, @RequestParam(defaultValue = "true") boolean resolvePlaceholders)
115+
ServletWebRequest request, @RequestParam(defaultValue = "true") boolean resolvePlaceholders,
116+
@RequestHeader(value = HttpHeaders.ACCEPT_CHARSET, required = false,
117+
defaultValue = "UTF-8") String acceptedCharset)
110118
throws IOException {
111-
return retrieve(request, name, profile, null, path, resolvePlaceholders);
119+
return retrieve(request, name, profile, null, path, resolvePlaceholders, acceptedCharset);
112120
}
113121

114122
private String getFilePath(ServletWebRequest request, String name, String profile, String label) {
@@ -130,7 +138,7 @@ private String getFilePath(ServletWebRequest request, String name, String profil
130138
* the files on disk.
131139
*/
132140
synchronized String retrieve(ServletWebRequest request, String name, String profile, String label, String path,
133-
boolean resolvePlaceholders) throws IOException {
141+
boolean resolvePlaceholders, String acceptedCharset) throws IOException {
134142
name = Environment.normalize(name);
135143
label = Environment.normalize(label);
136144
Resource resource = this.resourceRepository.findOne(name, profile, label, path);
@@ -140,7 +148,16 @@ synchronized String retrieve(ServletWebRequest request, String name, String prof
140148
}
141149
// ensure InputStream will be closed to prevent file locks on Windows
142150
try (InputStream is = resource.getInputStream()) {
143-
String text = StreamUtils.copyToString(is, Charset.forName("UTF-8"));
151+
152+
Charset charset = StandardCharsets.UTF_8;
153+
try {
154+
charset = Charset.forName(acceptedCharset);
155+
}
156+
catch (UnsupportedCharsetException e) {
157+
logger.warn("The accepted charset received from the client is not supported. Using UTF-8 instead.", e);
158+
}
159+
160+
String text = StreamUtils.copyToString(is, charset);
144161
String ext = StringUtils.getFilenameExtension(resource.getFilename());
145162
if (ext != null) {
146163
ext = ext.toLowerCase();
@@ -165,9 +182,9 @@ synchronized String retrieve(ServletWebRequest request, String name, String prof
165182
/*
166183
* Used only for unit tests.
167184
*/
168-
String retrieve(String name, String profile, String label, String path, boolean resolvePlaceholders)
169-
throws IOException {
170-
return retrieve(null, name, profile, label, path, resolvePlaceholders);
185+
String retrieve(String name, String profile, String label, String path, boolean resolvePlaceholders,
186+
String acceptedCharset) throws IOException {
187+
return retrieve(null, name, profile, label, path, resolvePlaceholders, acceptedCharset);
171188
}
172189

173190
@GetMapping(value = "/{name}/{profile}/{label}/**", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)

spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/ResourceControllerTests.java

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,15 @@ public void init() {
8383
@Disabled // FIXME: configdata
8484
public void templateReplacement() throws Exception {
8585
this.environmentRepository.setSearchLocations("classpath:/test");
86-
String resource = this.controller.retrieve("foo", "bar", "dev", "template.json", true);
86+
String resource = this.controller.retrieve("foo", "bar", "dev", "template.json", true, "UTF-8");
8787
assertThat(replaceNewLines(resource)).matches("\\{\\s*\"foo\": \"dev_bar\"\\s*\\}")
8888
.as("Wrong content: " + resource);
8989
}
9090

9191
@Test
9292
public void templateReplacementNotForResolvePlaceholdersFalse() throws Exception {
9393
this.environmentRepository.setSearchLocations("classpath:/test");
94-
String resource = this.controller.retrieve("foo", "bar", "dev", "template.json", false);
94+
String resource = this.controller.retrieve("foo", "bar", "dev", "template.json", false, "UTF-8");
9595
assertThat(replaceNewLines(resource)).matches("\\{\\s*\"foo\": \"\\$\\{foo\\}\"\\s*\\}")
9696
.as("Wrong content: " + resource);
9797
}
@@ -107,50 +107,64 @@ public void templateReplacementNotForBinary() throws Exception {
107107
@Test
108108
public void escapedPlaceholder() throws Exception {
109109
this.environmentRepository.setSearchLocations("classpath:/test");
110-
String resource = this.controller.retrieve("foo", "bar", "dev", "placeholder.txt", true);
110+
String resource = this.controller.retrieve("foo", "bar", "dev", "placeholder.txt", true, "UTF-8");
111111
assertThat(resource).isEqualToIgnoringNewLines("foo: ${foo}");
112112
}
113113

114+
@Test
115+
public void charsetWrongEncoding() throws Exception {
116+
this.environmentRepository.setSearchLocations("classpath:/test");
117+
String resource = this.controller.retrieve("foo_enc", "bar", "dev", "foo_enc", true, "ISO-8859-1");
118+
assertThat(resource).isEqualToIgnoringNewLines("foo: üäö");
119+
}
120+
121+
@Test
122+
public void charsetRightEncoding() throws Exception {
123+
this.environmentRepository.setSearchLocations("classpath:/test");
124+
String resource = this.controller.retrieve("foo_enc", "bar", "dev", "foo_enc", true, "UTF-8");
125+
assertThat(resource).isEqualToIgnoringNewLines("foo: üäö");
126+
}
127+
114128
@Test
115129
public void applicationAndLabelPlaceholdersWithoutSlash() throws Exception {
116130
this.environmentRepository.setSearchLocations("classpath:/test/{application}/{label}");
117-
String resource = this.controller.retrieve("dev", "bar", "spam", "foo.txt", true);
131+
String resource = this.controller.retrieve("dev", "bar", "spam", "foo.txt", true, "UTF-8");
118132
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar/spam");
119133
}
120134

121135
@Test
122136
public void applicationPlaceholderWithSlash() throws Exception {
123137
this.environmentRepository.setSearchLocations("classpath:/test/{application}");
124-
String resource = this.controller.retrieve("dev(_)spam", "bar", "", "foo.txt", true);
138+
String resource = this.controller.retrieve("dev(_)spam", "bar", "", "foo.txt", true, "UTF-8");
125139
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar/spam");
126140
}
127141

128142
@Test
129143
public void applicationPlaceholderWithSlashNullLabel() throws Exception {
130144
this.environmentRepository.setSearchLocations("classpath:/test/{application}");
131-
String resource = this.controller.retrieve("dev(_)spam", "bar", null, "foo.txt", true);
145+
String resource = this.controller.retrieve("dev(_)spam", "bar", null, "foo.txt", true, "UTF-8");
132146
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar/spam");
133147
}
134148

135149
@Test
136150
public void labelPlaceholderWithSlash() throws Exception {
137151
this.environmentRepository.setSearchLocations("classpath:/test/{label}");
138-
String resource = this.controller.retrieve("dev", "bar", "dev(_)spam", "foo.txt", true);
152+
String resource = this.controller.retrieve("dev", "bar", "dev(_)spam", "foo.txt", true, "UTF-8");
139153
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar/spam");
140154
}
141155

142156
@Test
143157
public void profilePlaceholderNullLabel() throws Exception {
144158
this.environmentRepository.setSearchLocations("classpath:/test/{profile}");
145-
String resource = this.controller.retrieve("bar", "dev", null, "spam/foo.txt", true);
159+
String resource = this.controller.retrieve("bar", "dev", null, "spam/foo.txt", true, "UTF-8");
146160
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar/spam");
147161
}
148162

149163
@Test
150164
public void nullNameAndLabel() throws Exception {
151165
this.environmentRepository.setSearchLocations("classpath:/test");
152166
try {
153-
this.controller.retrieve(null, "foo", "bar", "spam/foo.txt", true);
167+
this.controller.retrieve(null, "foo", "bar", "spam/foo.txt", true, "UTF-8");
154168
}
155169
catch (Exception e) {
156170
assertThat(e).isNotNull();
@@ -160,21 +174,21 @@ public void nullNameAndLabel() throws Exception {
160174
@Test
161175
public void labelWithSlash() throws Exception {
162176
this.environmentRepository.setSearchLocations("classpath:/test");
163-
String resource = this.controller.retrieve("foo", "bar", "dev(_)spam", "foo.txt", true);
177+
String resource = this.controller.retrieve("foo", "bar", "dev(_)spam", "foo.txt", true, "UTF-8");
164178
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar/spam");
165179
}
166180

167181
@Test
168182
public void resourceWithoutFileExtension() throws Exception {
169183
this.environmentRepository.setSearchLocations("classpath:/test");
170-
String resource = this.controller.retrieve("foo", "bar", "dev", "foo", true);
184+
String resource = this.controller.retrieve("foo", "bar", "dev", "foo", true, "UTF-8");
171185
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar");
172186
}
173187

174188
@Test
175189
public void resourceWithSlash() throws Exception {
176190
this.environmentRepository.setSearchLocations("classpath:/test");
177-
String resource = this.controller.retrieve("foo", "bar", "dev", "spam/foo.txt", true);
191+
String resource = this.controller.retrieve("foo", "bar", "dev", "spam/foo.txt", true, "UTF-8");
178192
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar/spam");
179193
}
180194

@@ -184,7 +198,7 @@ public void resourceWithSlashRequest() throws Exception {
184198
MockHttpServletRequest request = new MockHttpServletRequest();
185199
ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
186200
request.setRequestURI("/foo/bar/dev/" + "spam/foo.txt");
187-
String resource = this.controller.retrieve("foo", "bar", "dev", webRequest, true);
201+
String resource = this.controller.retrieve("foo", "bar", "dev", webRequest, true, "UTF-8");
188202
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar/spam");
189203
}
190204

@@ -195,21 +209,21 @@ public void resourceWithSlashRequestAndServletPath() throws Exception {
195209
ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
196210
request.setServletPath("/spring");
197211
request.setRequestURI("/foo/bar/dev/" + "spam/foo.txt");
198-
String resource = this.controller.retrieve("foo", "bar", "dev", webRequest, true);
212+
String resource = this.controller.retrieve("foo", "bar", "dev", webRequest, true, "UTF-8");
199213
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar/spam");
200214
}
201215

202216
@Test
203217
public void labelWithSlashForResolvePlaceholdersFalse() throws Exception {
204218
this.environmentRepository.setSearchLocations("classpath:/test");
205-
String resource = this.controller.retrieve("foo", "bar", "dev(_)spam", "foo.txt", false);
219+
String resource = this.controller.retrieve("foo", "bar", "dev(_)spam", "foo.txt", false, "UTF-8");
206220
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar/spam");
207221
}
208222

209223
@Test
210224
public void resourceWithSlashForResolvePlaceholdersFalse() throws Exception {
211225
this.environmentRepository.setSearchLocations("classpath:/test");
212-
String resource = this.controller.retrieve("foo", "bar", "dev", "spam/foo.txt", false);
226+
String resource = this.controller.retrieve("foo", "bar", "dev", "spam/foo.txt", false, "UTF-8");
213227
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar/spam");
214228
}
215229

@@ -219,7 +233,7 @@ public void resourceWithSlashForResolvePlaceholdersFalseRequest() throws Excepti
219233
MockHttpServletRequest request = new MockHttpServletRequest();
220234
ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
221235
request.setRequestURI("/foo/bar/dev/" + "spam/foo.txt");
222-
String resource = this.controller.retrieve("foo", "bar", "dev", webRequest, false);
236+
String resource = this.controller.retrieve("foo", "bar", "dev", webRequest, false, "UTF-8");
223237
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar/spam");
224238
}
225239

@@ -320,7 +334,7 @@ public void whenSupportedResourceWithDecrpyt_thenSuccess() throws Exception {
320334
this.controller.setPlainTextEncryptEnabled(true);
321335

322336
// when
323-
String resource = this.controller.retrieve("foo", "bar", "dev", "template.json", false);
337+
String resource = this.controller.retrieve("foo", "bar", "dev", "template.json", false, "UTF-8");
324338

325339
// then
326340
assertThat(resource).isEqualTo(decryptedStr);
@@ -339,7 +353,7 @@ public void whenUnkownResourceWithDecrpyt_thenNothingChanged() throws Exception
339353
this.controller.setPlainTextEncryptEnabled(true);
340354

341355
// when
342-
String resource = this.controller.retrieve("foo", "bar", "dev", "spam/foo.txt", false);
356+
String resource = this.controller.retrieve("foo", "bar", "dev", "spam/foo.txt", false, "UTF-8");
343357

344358
// then
345359
assertThat(resource).isEqualToIgnoringNewLines("foo: dev_bar/spam");
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
foo: üäö
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
foo:���

0 commit comments

Comments
 (0)