All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.helidon.webclient.WebClientConfiguration Maven / Gradle / Ivy
/*
* Copyright (c) 2019, 2024 Oracle and/or its affiliates.
*
* 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 io.helidon.webclient;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.CookieStore;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import io.helidon.common.LazyValue;
import io.helidon.common.context.Context;
import io.helidon.config.Config;
import io.helidon.config.DeprecatedConfig;
import io.helidon.config.metadata.Configured;
import io.helidon.config.metadata.ConfiguredOption;
import io.helidon.media.common.MediaContext;
import io.helidon.media.common.MediaContextBuilder;
import io.helidon.media.common.MediaSupport;
import io.helidon.media.common.MessageBodyReader;
import io.helidon.media.common.MessageBodyReaderContext;
import io.helidon.media.common.MessageBodyStreamReader;
import io.helidon.media.common.MessageBodyStreamWriter;
import io.helidon.media.common.MessageBodyWriter;
import io.helidon.media.common.MessageBodyWriterContext;
import io.helidon.media.common.ParentingMediaContextBuilder;
import io.helidon.webclient.spi.WebClientService;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.IdentityCipherSuiteFilter;
import io.netty.handler.ssl.JdkSslContext;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
/**
* Configuration of the Helidon web client.
*/
class WebClientConfiguration {
private final WebClientRequestHeaders clientHeaders;
private final WebClientCookieManager cookieManager;
private final CookiePolicy cookiePolicy;
private final Config config;
private final Context context;
private final Duration connectTimeout;
private final boolean enableAutomaticCookieStore;
private final Duration readTimeout;
private final LazyValue userAgent;
private final List clientServices;
private final Proxy proxy;
private final boolean followRedirects;
private final boolean keepAlive;
private final int maxRedirects;
private final MessageBodyReaderContext readerContext;
private final MessageBodyWriterContext writerContext;
private final WebClientTls webClientTls;
private final URI uri;
private final boolean validateHeaders;
private final boolean relativeUris;
private final DnsResolverType dnsResolverType;
private final boolean mediaTypeParserRelaxed;
/**
* Creates a new instance of client configuration.
*
* @param builder configuration builder
*/
WebClientConfiguration(Builder, ?> builder) {
this.connectTimeout = builder.connectTimeout;
this.readTimeout = builder.readTimeout;
this.followRedirects = builder.followRedirects;
this.userAgent = builder.userAgent;
this.proxy = builder.proxy;
this.webClientTls = builder.webClientTls;
this.maxRedirects = builder.maxRedirects;
this.clientHeaders = builder.clientHeaders;
this.cookiePolicy = builder.cookiePolicy;
this.enableAutomaticCookieStore = builder.enableAutomaticCookieStore;
this.cookieManager = WebClientCookieManager.create(cookiePolicy,
builder.cookieStore,
builder.defaultCookies,
enableAutomaticCookieStore);
this.config = builder.config;
this.context = builder.context;
this.readerContext = builder.readerContext;
this.writerContext = builder.writerContext;
this.clientServices = List.copyOf(builder.clientServices);
this.uri = builder.uri;
this.keepAlive = builder.keepAlive;
this.validateHeaders = builder.validateHeaders;
this.relativeUris = builder.relativeUris;
this.dnsResolverType = builder.dnsResolverType;
this.mediaTypeParserRelaxed = builder.mediaTypeParserRelaxed;
}
/**
* Creates new builder to build a new instance of this class.
*
* @return a new builder instance
*/
static Builder, ?> builder() {
return new Builder<>();
}
/**
* Derives a new builder based on current instance of this class.
*
* @return a new builder instance
*/
Builder, ?> derive() {
return new Builder<>().update(this);
}
Optional sslContext() {
SslContext sslContext;
try {
if (webClientTls.sslContext().isPresent()) {
sslContext = nettySslFromJavaNet(webClientTls.sslContext().get());
} else {
SslContextBuilder sslContextBuilder = SslContextBuilder
.forClient()
.sslProvider(SslProvider.JDK);
if (webClientTls.certificates().size() > 0) {
sslContextBuilder.trustManager(webClientTls.certificates().toArray(new X509Certificate[0]));
}
if (webClientTls.clientPrivateKey().isPresent()) {
sslContextBuilder.keyManager(webClientTls.clientPrivateKey().get(),
webClientTls.clientCertificateChain().toArray(new X509Certificate[0]));
}
if (webClientTls.trustAll()) {
sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
}
if (!webClientTls.allowedCipherSuite().isEmpty()) {
sslContextBuilder.ciphers(webClientTls.allowedCipherSuite());
}
sslContext = sslContextBuilder.build();
}
} catch (SSLException e) {
throw new WebClientException("An error occurred while creating ssl context.", e);
}
return Optional.of(sslContext);
}
private SslContext nettySslFromJavaNet(SSLContext javaNetContext) {
Set allowedCipherSuite = webClientTls.allowedCipherSuite();
return new JdkSslContext(
javaNetContext, true, allowedCipherSuite.isEmpty() ? null : allowedCipherSuite,
IdentityCipherSuiteFilter.INSTANCE, null,
ClientAuth.OPTIONAL, null, false);
}
/**
* Connection timeout duration.
*
* @return connection timeout
*/
Duration connectTimeout() {
return connectTimeout;
}
/**
* Read timeout duration.
*
* @return read timeout
*/
Duration readTimout() {
return readTimeout;
}
/**
* Configured proxy.
*
* @return proxy
*/
Optional proxy() {
return Optional.ofNullable(proxy);
}
/**
* Returns true if client should follow redirection.
*
* @return follow redirection
*/
boolean followRedirects() {
return followRedirects;
}
/**
* Max number of followed redirections.
*
* @return max redirections
*/
int maxRedirects() {
return maxRedirects;
}
/**
* Default client headers.
*
* @return default headers
*/
WebClientRequestHeaders headers() {
return clientHeaders;
}
/**
* Instance of {@link CookieManager}.
*
* @return cookie manager
*/
CookieManager cookieManager() {
return cookieManager;
}
/**
* Returns user agent.
*
* @return user agent
*/
String userAgent() {
return userAgent.get();
}
WebClientTls tls() {
return webClientTls;
}
Optional context() {
return Optional.ofNullable(context);
}
Config config() {
return config;
}
List clientServices() {
return clientServices;
}
MessageBodyReaderContext readerContext() {
return readerContext;
}
MessageBodyWriterContext writerContext() {
return writerContext;
}
URI uri() {
return uri;
}
boolean keepAlive() {
return keepAlive;
}
boolean validateHeaders() {
return validateHeaders;
}
boolean relativeUris() {
return relativeUris;
}
DnsResolverType dnsResolverType() {
return dnsResolverType;
}
boolean mediaTypeParserRelaxed() {
return mediaTypeParserRelaxed;
}
/**
* A fluent API builder for {@link WebClientConfiguration}.
*/
@Configured(root = true, prefix = "client", description = "Configuration of the HTTP client")
static class Builder, T extends WebClientConfiguration>
implements io.helidon.common.Builder,
ParentingMediaContextBuilder,
MediaContextBuilder {
private final WebClientRequestHeaders clientHeaders;
private final Map defaultCookies;
private final List clientServices;
private Config config;
private Context context;
private CookieStore cookieStore;
private CookiePolicy cookiePolicy;
private int maxRedirects;
private Duration connectTimeout;
private Duration readTimeout;
private boolean followRedirects;
private LazyValue userAgent;
private Proxy proxy;
private boolean enableAutomaticCookieStore;
private boolean keepAlive;
private WebClientTls webClientTls;
private URI uri;
private MessageBodyReaderContext readerContext;
private MessageBodyWriterContext writerContext;
private boolean validateHeaders;
private boolean relativeUris;
private DnsResolverType dnsResolverType;
private boolean mediaTypeParserRelaxed;
@SuppressWarnings("unchecked")
private B me = (B) this;
/**
* Creates new instance of the builder.
*/
Builder() {
clientHeaders = new WebClientRequestHeadersImpl();
defaultCookies = new HashMap<>();
clientServices = new ArrayList<>();
}
@Override
@SuppressWarnings("unchecked")
public T build() {
return (T) new WebClientConfiguration(this);
}
/**
* Sets new connection timeout of the request.
*
* @param connectTimeout new connection timeout
* @return updated builder instance
*/
@ConfiguredOption(key = "connect-timeout-millis", type = Long.class, value = "60000")
public B connectTimeout(Duration connectTimeout) {
this.connectTimeout = connectTimeout;
return me;
}
/**
* Sets new read timeout of the response.
*
* @param readTimeout new read timeout
* @return updated builder instance
*/
@ConfiguredOption(key = "read-timeout-millis", type = Long.class, value = "600000")
public B readTimeout(Duration readTimeout) {
this.readTimeout = readTimeout;
return me;
}
/**
* Whether to follow any response redirections or not.
*
* @param followRedirects follow redirection
* @return updated builder instance
*/
@ConfiguredOption("false")
public B followRedirects(boolean followRedirects) {
this.followRedirects = followRedirects;
return me;
}
/**
* Name of the user agent which should be used.
*
* @param userAgent user agent
* @return updated builder instance
*/
@ConfiguredOption
public B userAgent(String userAgent) {
this.userAgent = LazyValue.create(() -> userAgent);
return me;
}
/**
* Sets new user agent wrapped by {@link LazyValue}.
*
* @param userAgent wrapped user agent
* @return updated builder instance
*/
public B userAgent(LazyValue userAgent) {
this.userAgent = userAgent;
return me;
}
/**
* Sets new request proxy.
*
* @param proxy request proxy
* @return updated builder instance
*/
@ConfiguredOption
public B proxy(Proxy proxy) {
this.proxy = proxy;
return me;
}
/**
* New TLS configuration.
*
* @param webClientTls tls configuration
* @return updated builder instance
*/
@ConfiguredOption
public B tls(WebClientTls webClientTls) {
this.webClientTls = webClientTls;
return me;
}
/**
* Sets max number of followed redirects.
*
* @param maxRedirects max redirects
* @return updated builder instance
*/
@ConfiguredOption("5")
public B maxRedirects(int maxRedirects) {
this.maxRedirects = maxRedirects;
return me;
}
/**
* Sets default client request headers.
*
* Overrides previously set default client headers.
*
* @param clientHeaders default request headers
* @return updated builder instance
*/
public B clientHeaders(WebClientRequestHeaders clientHeaders) {
this.clientHeaders.putAll(clientHeaders);
return me;
}
/**
* Sets new instance of {@link CookieStore} with default cookies.
*
* @param cookieStore cookie store
* @return updated builder instance
*/
public B cookieStore(CookieStore cookieStore) {
this.cookieStore = cookieStore;
return me;
}
/**
* Sets new {@link CookiePolicy}.
*
* @param cookiePolicy cookie policy
* @return updated builder instance
*/
@ConfiguredOption(key = "cookies.automatic-store-enabled",
type = Boolean.class,
description = "Whether to allow automatic cookie storing")
public B cookiePolicy(CookiePolicy cookiePolicy) {
this.cookiePolicy = cookiePolicy;
return me;
}
/**
* Adds default cookie to every request.
*
* @param key cookie name
* @param value cookie value
* @return updated builder instance
*/
@ConfiguredOption(key = "cookies.default-cookies",
type = Map.class,
description = "Default cookies to be used in each request. "
+ "Each list entry has to have \"name\" and \"value\" node")
public B defaultCookie(String key, String value) {
defaultCookies.put(key, value);
return me;
}
/**
* Adds default header to every request.
*
* @param key header name
* @param values header value
* @return updated builder instance
*/
@ConfiguredOption(key = "headers",
type = Map.class,
description = "Default headers to be used in each request. "
+ "Each list entry has to have \"name\" and \"value\" node")
public B defaultHeader(String key, List values) {
clientHeaders.put(key, values);
return me;
}
/**
* Can be set to {@code true} to force the use of relative URIs in all requests,
* regardless of the presence or absence of proxies or no-proxy lists.
*
* @param relativeUris relative URIs flag
* @return updated builder instance
*/
@ConfiguredOption("false")
public B relativeUris(boolean relativeUris) {
this.relativeUris = relativeUris;
return me;
}
@Override
@ConfiguredOption(key = "media-support")
public B mediaContext(MediaContext mediaContext) {
writerContextParent(mediaContext.writerContext());
readerContextParent(mediaContext.readerContext());
return me;
}
/**
* Sets specific context in which all the requests will be running.
*
* @return updated builder instance
*/
public B context(Context context) {
this.context = context;
return me;
}
/**
* Base uri for each request.
*
* @return updated builder instance
*/
@ConfiguredOption(type = String.class,
description = "Base URI for each request")
public B uri(URI uri) {
this.uri = uri;
return me;
}
/**
* Set which type of DNS resolver should be used.
*
* @param dnsResolverType dns resolver type to be used
* @return updated builder instance
*/
@ConfiguredOption
public B dnsResolverType(DnsResolverType dnsResolverType) {
this.dnsResolverType = dnsResolverType;
return me;
}
/**
* Whether to validate header names.
* Defaults to {@code true}.
*
* @param validate whether to validate the header name contains only allowed characters
* @return updated builder instance
*/
B validateHeaders(boolean validate) {
this.validateHeaders = validate;
return me;
}
@Override
public B addReader(MessageBodyReader> reader) {
this.readerContext.registerReader(reader);
return me;
}
@Override
public B addStreamReader(MessageBodyStreamReader> streamReader) {
this.readerContext.registerReader(streamReader);
return me;
}
@Override
public B addWriter(MessageBodyWriter> writer) {
this.writerContext.registerWriter(writer);
return me;
}
@Override
public B addStreamWriter(MessageBodyStreamWriter> streamWriter) {
this.writerContext.registerWriter(streamWriter);
return me;
}
@Override
public B addMediaSupport(MediaSupport mediaSupport) {
Objects.requireNonNull(mediaSupport);
mediaSupport.register(readerContext, writerContext);
return me;
}
B enableAutomaticCookieStore(Boolean enableAutomaticCookieStore) {
this.enableAutomaticCookieStore = enableAutomaticCookieStore;
return me;
}
B readerContextParent(MessageBodyReaderContext readerContext) {
this.readerContext = MessageBodyReaderContext.create(readerContext);
return me;
}
B writerContextParent(MessageBodyWriterContext writerContext) {
this.writerContext = MessageBodyWriterContext.create(writerContext);
return me;
}
B readerContext(MessageBodyReaderContext readerContext) {
this.readerContext = readerContext;
return me;
}
B writerContext(MessageBodyWriterContext writerContext) {
this.writerContext = writerContext;
return me;
}
B clientServices(List clientServices) {
this.clientServices.clear();
this.clientServices.addAll(clientServices);
return me;
}
B mediaTypeParserRelaxed(boolean relaxedMode) {
this.mediaTypeParserRelaxed = relaxedMode;
return me;
}
/**
* Enable keep alive option on the connection.
*
* @param keepAlive keep alive value
* @return updated builder instance
*/
@ConfiguredOption("true")
public B keepAlive(boolean keepAlive) {
this.keepAlive = keepAlive;
return me;
}
/**
* Configures this {@link WebClientConfiguration.Builder} from the supplied {@link Config}.
*
* Optional configuration parameters
*
* key
* description
*
*
* uri
* Basic uri for each client request
*
*
* connect-timeout-millis
* Request connection timeout
*
*
* read-timeout-millis
* Response read timeout
*
*
* follow-redirects
* Whether redirects should be followed or not
*
*
* max-redirects
* Max number of followed redirections
*
*
* user-agent
* Name of the user agent which should be used
*
*
* keep-alive
* Whether connection should be kept alive
*
*
* cookies
* Default cookies which should be used
*
*
* headers
* Default headers which should be used
*
*
* tls
* TLS configuration. See {@link WebClientTls.Builder#config(Config)}
*
*
* proxy
* Proxy configuration. See {@link Proxy.Builder#config(Config)}
*
*
* media-type-parser-relaxed
* Whether relaxed media type parsing mode should be used.
*
*
*
* @param config config
* @return updated builder instance
*/
public B config(Config config) {
this.config = config;
// now for other options
config.get("uri").asString().ifPresent(baseUri -> uri(URI.create(baseUri)));
config.get("connect-timeout-millis").asLong().ifPresent(timeout -> connectTimeout(Duration.ofMillis(timeout)));
config.get("read-timeout-millis").asLong().ifPresent(timeout -> readTimeout(Duration.ofMillis(timeout)));
config.get("follow-redirects").asBoolean().ifPresent(this::followRedirects);
config.get("max-redirects").asInt().ifPresent(this::maxRedirects);
config.get("user-agent").asString().ifPresent(this::userAgent);
config.get("keep-alive").asBoolean().ifPresent(this::keepAlive);
config.get("cookies").asNode().ifPresent(this::cookies);
config.get("headers").asNode().ifPresent(this::headers);
DeprecatedConfig.get(config, "tls", "ssl")
.as(WebClientTls.builder()::config)
.map(WebClientTls.Builder::build)
.ifPresent(this::tls);
config.get("proxy")
.as(Proxy.builder()::config)
.map(Proxy.Builder::build)
.ifPresent(this::proxy);
config.get("media-support").as(MediaContext::create).ifPresent(this::mediaContext);
config.get("relative-uris").asBoolean().ifPresent(this::relativeUris);
config.get("dns-resolver-type").asString()
.map(s -> DnsResolverType.valueOf(s.toUpperCase()))
.ifPresent(this::dnsResolverType);
config.get("media-type-parser-relaxed").asBoolean().ifPresent(this::mediaTypeParserRelaxed);
return me;
}
/**
* Updates builder existing client configuration.
*
* @param configuration client configuration
* @return updated builder instance
*/
public B update(WebClientConfiguration configuration) {
connectTimeout(configuration.connectTimeout);
readTimeout(configuration.readTimeout);
followRedirects(configuration.followRedirects);
userAgent(configuration.userAgent);
proxy(configuration.proxy);
tls(configuration.webClientTls);
maxRedirects(configuration.maxRedirects);
clientHeaders(configuration.clientHeaders);
enableAutomaticCookieStore(configuration.enableAutomaticCookieStore);
cookieStore(configuration.cookieManager.getCookieStore());
cookiePolicy(configuration.cookiePolicy);
clientServices(configuration.clientServices);
readerContextParent(configuration.readerContext);
writerContextParent(configuration.writerContext);
context(configuration.context);
keepAlive(configuration.keepAlive);
validateHeaders(configuration.validateHeaders);
dnsResolverType(configuration.dnsResolverType);
mediaTypeParserRelaxed(configuration.mediaTypeParserRelaxed);
configuration.cookieManager.defaultCookies().forEach(this::defaultCookie);
config = configuration.config;
return me;
}
private void headers(Config configHeaders) {
configHeaders.asNodeList()
.ifPresent(headers -> headers
.forEach(header -> defaultHeader(header.get("name").asString().get(),
header.get("value").asList(String.class).get())));
}
private void cookies(Config cookies) {
cookies.get("automatic-store-enabled").asBoolean().ifPresent(this::enableAutomaticCookieStore);
Config map = cookies.get("default-cookies");
map.asNodeList()
.ifPresent(headers -> headers
.forEach(header -> defaultCookie(header.get("name").asString().get(),
header.get("value").asString().get())));
}
}
}