Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/modules/ROOT/pages/client.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,25 @@ Label can also be provided as a comma-separated list.
This behavior can be useful when working on a feature branch.
For instance, you might want to align the config label with your branch but make it optional (in that case, use `spring.cloud.config.label=myfeature,develop`).

[[reading-plain-text-and-binary-files]]
== Reading Plain Text and Binary Files

The Config Server can also serve plain text and binary files from the configuration repository (see xref:server/serving-plain-text.adoc[Serving Plain Text] and xref:server/serving-binary-files.adoc[Serving Binary Files]).
When the configuration is imported with `spring.config.import=configserver:`, the client registers a `ConfigResourceClient` bean that reads such files with the same settings that loaded the configuration (URI, credentials, headers, TLS settings, timeouts and token), as the following example shows:

[source,java]
----
@Autowired
private ConfigResourceClient configResourceClient;

Resource nginx = configResourceClient.getPlainTextResource("nginx.conf");
Resource logo = configResourceClient.getBinaryResource("dev", "main", "images/logo.png");
----

Without a profile or label, the client uses `spring.cloud.config.profile` and `spring.cloud.config.label`; without a label, the server's default label is used.
Like the import, it tries each configured URI according to `spring.cloud.config.multiple-uri-strategy`, with the credentials of that URI, and tries a comma-separated label one label at a time.
A file is read into memory, so a file larger than `spring.cloud.config.resource-client.max-size` (1MB by default) is rejected; `0` disables the limit.

[[sharing-configuration-across-applications]]
== Sharing Configuration Across Applications

Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/partials/_configprops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
|spring.cloud.config.profile | `+++default+++` | The default profile to use when fetching remote configuration (comma-separated). Default is "default".
|spring.cloud.config.request-connect-timeout | | timeout on waiting to connect to the Config Server.
|spring.cloud.config.request-read-timeout | | timeout on waiting to read data from the Config Server.
|spring.cloud.config.resource-client.max-size | `+++1MB+++` | Maximum size of a file read from the config server; a larger file is rejected instead of being loaded into memory. Zero disables the limit.
|spring.cloud.config.retry.enabled | `+++false+++` | Whether to retry failed requests to the Config Server. Only has an effect when spring.cloud.config.fail-fast is enabled.
|spring.cloud.config.retry.initial-interval | `+++1000+++` | Initial retry interval in milliseconds.
|spring.cloud.config.retry.max-attempts | `+++6+++` | Maximum number of attempts.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2013-present the original author or authors.
*
* 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.config.client;

import org.springframework.core.io.Resource;
import org.springframework.web.client.HttpClientErrorException;

/**
* Provides access to the binary files served by a Spring Cloud Config Server.
*
* @author Craig Walls
* @author Anshul Mehra
* @author Sharang Gupta
* @since 5.1.0
* @see ConfigResourceClient
*/
public interface BinaryResourceConfigClient {

/**
* Retrieves a binary file using the configured profile and label.
* @param path the path of the file, relative to the application's folder
* @return the file served by the config server
* @throws IllegalArgumentException when the application name or the config server URI
* is undefined
* @throws HttpClientErrorException when the file is not found
*/
Resource getBinaryResource(String path);

/**
* Retrieves a binary file.
* @param profile the profile, or {@code null} for the configured one; may be a
* comma-separated list
* @param label the label, or {@code null} for the configured one
* @param path the path of the file, relative to the application's folder
* @return the file served by the config server
* @throws IllegalArgumentException when the application name or the config server URI
* is undefined
* @throws HttpClientErrorException when the file is not found
*/
Resource getBinaryResource(String profile, String label, String path);

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@
import org.springframework.cloud.config.environment.EnvironmentMediaType;
import org.springframework.cloud.configuration.TlsProperties;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.unit.DataSize;
import org.springframework.web.util.UriComponentsBuilder;

/**
Expand Down Expand Up @@ -143,6 +145,12 @@ public class ConfigClientProperties {
*/
private Discovery discovery = new Discovery();

/**
* Settings of the {@link ConfigResourceClient} that reads plain text and binary files
* from the config server.
*/
private ResourceClient resourceClient = new ResourceClient();

/**
* TLS properties.
*/
Expand Down Expand Up @@ -284,6 +292,14 @@ public Discovery getDiscovery() {
return this.discovery;
}

public ResourceClient getResourceClient() {
return this.resourceClient;
}

public void setResourceClient(ResourceClient resourceClient) {
this.resourceClient = resourceClient;
}

public void setDiscovery(Discovery discovery) {
this.discovery = discovery;
}
Expand Down Expand Up @@ -469,6 +485,28 @@ public String getUri() {

}

/**
* Settings of the {@link ConfigResourceClient}.
*/
public static class ResourceClient {

/**
* Maximum size of a file read from the config server; a larger file is rejected
* instead of being loaded into memory. Zero disables the limit.
*/
private DataSize maxSize = DataSize.ofMegabytes(1);

public DataSize getMaxSize() {
return this.maxSize;
}

public void setMaxSize(DataSize maxSize) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should validate this in some way. A negative number does not really make sense in this case. Also I wonder if setting it to 0 would be away to disable the limit?

Assert.isTrue(!maxSize.isNegative(), "maxSize must not be negative");
this.maxSize = maxSize;
}

}

/**
* Discovery properties.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2013-present the original author or authors.
*
* 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.config.client;

/**
* Provides access to the plain text and binary files served by a Spring Cloud Config
* Server. When the configuration is imported with {@code spring.config.import}, a
* {@link ConfigResourceClient} that uses the same connection settings as the import is
* available as a bean.
*
* @author Daniel Lavoie
* @author Sharang Gupta
* @since 5.1.0
* @see ConfigResourceClientAutoConfiguration
*/
public interface ConfigResourceClient extends PlainTextConfigClient, BinaryResourceConfigClient {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2013-present the original author or authors.
*
* 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.config.client;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;

/**
* Auto-configuration for a {@link ConfigResourceClient} that reads plain text and binary
* files from the config server with the connection that loaded the configuration, when it
* was imported with {@code spring.config.import}.
*
* @author Daniel Lavoie
* @author Roy Clarkson
* @author Dylan Roberts
* @author Sharang Gupta
* @since 5.1.0
*/
@AutoConfiguration(after = ConfigClientAutoConfiguration.class)
@ConditionalOnProperty(name = ConfigClientProperties.PREFIX + ".enabled", matchIfMissing = true)
public class ConfigResourceClientAutoConfiguration {

@Bean
@ConditionalOnMissingBean(ConfigResourceClient.class)
@ConditionalOnBean(ConfigServerRestClientHolder.class)
public ConfigResourceClient configResourceClient(ConfigServerRestClientHolder restClientHolder,
@Qualifier(ConfigServerConfigDataLocationResolver.PROPERTIES_BEAN_NAME) ObjectProvider<ConfigClientProperties> importedProperties,
ObjectProvider<ConfigClientProperties> properties) {
// The properties resolved by the config server import describe the server the
// RestClient talks to; a context may also hold the ones bound by
// ConfigClientAutoConfiguration.
return new DefaultConfigResourceClient(restClientHolder.getRestClient(),
importedProperties.getIfAvailable(properties::getObject));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

import org.apache.commons.logging.Log;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.bootstrap.BootstrapContextClosedEvent;
import org.springframework.boot.bootstrap.BootstrapRegistry;
import org.springframework.boot.bootstrap.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.bootstrap.ConfigurableBootstrapContext;
Expand Down Expand Up @@ -51,6 +53,7 @@
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestTemplate;

import static org.springframework.cloud.config.client.ConfigClientProperties.CONFIG_DISCOVERY_ENABLED;
Expand All @@ -62,6 +65,13 @@
* Prefix for Config Server imports.
*/
public static final String PREFIX = "configserver:";

/**
* Name of the {@link ConfigClientProperties} bean resolved by the config server
* import.
*/
public static final String PROPERTIES_BEAN_NAME = "configDataConfigClientProperties";

static final boolean RSA_IS_PRESENT = ClassUtils
.isPresent("org.springframework.security.crypto.encrypt.RsaSecretEncryptor", null);

Expand Down Expand Up @@ -241,8 +251,7 @@
InstanceSupplier.of(properties).withScope(BootstrapRegistry.Scope.PROTOTYPE));
bootstrapContext.addCloseListener(event -> event.getApplicationContext()
.getBeanFactory()
.registerSingleton("configDataConfigClientProperties",
event.getBootstrapContext().get(ConfigClientProperties.class)));
.registerSingleton(PROPERTIES_BEAN_NAME, event.getBootstrapContext().get(ConfigClientProperties.class)));

bootstrapContext.registerIfAbsent(ConfigClientRequestTemplateFactory.class, context -> {
ConfigClientRequestTemplateFactory factory = new ConfigClientRequestTemplateFactory(log,
Expand All @@ -252,10 +261,11 @@
return factory;
});

bootstrapContext.registerIfAbsent(RestTemplate.class, context -> {

Check warning on line 264 in spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java

View workflow job for this annotation

GitHub Actions / build / pr

org.springframework.web.client.RestTemplate in org.springframework.web.client has been deprecated and marked for removal
ConfigClientRequestTemplateFactory factory = context.get(ConfigClientRequestTemplateFactory.class);
return factory.create();
});
bootstrapContext.addCloseListener(this::registerRestClientHolder);

bootstrapContext.registerIfAbsent(PropertyResolver.class,
context -> new PropertyResolver(resolverContext.getBinder(), getBindHandler(resolverContext)));
Expand Down Expand Up @@ -320,10 +330,36 @@

List<ConfigServerConfigDataResource> locations = new ArrayList<>();
locations.add(resource);

return locations;
}

/**
* Keep the {@link RestTemplate} that loaded the configuration, which carries the URI,
* credentials, headers, TLS settings, timeouts and interceptors of the client,
* available as a {@link RestClient} to the {@link ConfigResourceClient}, without
* exposing it as a {@link RestClient} bean.
*/
private void registerRestClientHolder(BootstrapContextClosedEvent event) {
ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory();
// The listener is added on every resolution (one per import location and
// profile pass) and runs that many times when the bootstrap context closes,
// while a singleton can only be registered once.
if (beanFactory.containsBean(ConfigServerRestClientHolder.BEAN_NAME)
|| !event.getBootstrapContext().isRegistered(RestTemplate.class)) {
return;
}
try {
RestClient restClient = RestClient.create(event.getBootstrapContext().get(RestTemplate.class));
beanFactory.registerSingleton(ConfigServerRestClientHolder.BEAN_NAME,
new ConfigServerRestClientHolder(restClient));
}
catch (RuntimeException ex) {
// An optional component must not prevent the application from starting
this.log.warn("Could not build a RestClient from the config client RestTemplate; "
+ "the ConfigResourceClient will not be available", ex);
}
}

public static class PropertyResolver {

private final Binder binder;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2013-present the original author or authors.
*
* 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.config.client;

import org.springframework.util.Assert;
import org.springframework.web.client.RestClient;

/**
* Holds the {@link RestClient} built from the {@code RestTemplate} that loaded the
* configuration from the config server, which carries the client's headers, credentials,
* TLS settings, timeouts and interceptors. It is registered as a bean of its own type,
* not as a {@link RestClient}, so that it is neither ambiguous with the application's own
* {@link RestClient} nor injected into it by accident.
*
* @author Sharang Gupta
* @since 5.1.0
*/
final class ConfigServerRestClientHolder {

/**
* Name of the bean registered when the configuration is imported from the config
* server.
*/
static final String BEAN_NAME = "configServerRestClientHolder";

private final RestClient restClient;

ConfigServerRestClientHolder(RestClient restClient) {
Assert.notNull(restClient, "RestClient must not be null");
this.restClient = restClient;
}

RestClient getRestClient() {
return this.restClient;
}

}
Loading
Loading