io.helidon.webclient.api.LoomClient Maven / Gradle / Ivy
/*
* Copyright (c) 2022, 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.api;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import io.helidon.common.HelidonServiceLoader;
import io.helidon.common.LazyValue;
import io.helidon.common.configurable.LruCache;
import io.helidon.common.tls.Tls;
import io.helidon.http.Method;
import io.helidon.webclient.spi.ClientProtocolProvider;
import io.helidon.webclient.spi.HttpClientSpi;
import io.helidon.webclient.spi.HttpClientSpiProvider;
import io.helidon.webclient.spi.Protocol;
import io.helidon.webclient.spi.ProtocolConfig;
/**
* Base class for HTTP implementations of {@link WebClient}.
*/
@SuppressWarnings("rawtypes")
class LoomClient implements WebClient {
static final LazyValue EXECUTOR =
LazyValue.create(() -> Executors.newThreadPerTaskExecutor(Thread.ofVirtual()
.name("helidon-client-", 0)
.factory()));
private static final List PROVIDERS =
HelidonServiceLoader.create(ServiceLoader.load(HttpClientSpiProvider.class))
.asList();
private static final Map HTTP_PROVIDERS_BY_PROTOCOL;
static {
Map providerMap = new HashMap<>();
PROVIDERS.forEach(it -> providerMap.put(it.protocolId(), it));
HTTP_PROVIDERS_BY_PROTOCOL = Map.copyOf(providerMap);
}
private final WebClientConfig config;
// a map of protocol ids to the client SPI implementing them
private final Map clientSpiByProtocol;
private final Map clientsByProtocol = new ConcurrentHashMap<>();
private final List protocols;
private final List tcpProtocols;
private final ProtocolConfigs protocolConfigs;
private final List tcpProtocolIds;
private final WebClientCookieManager cookieManager;
private final LruCache clientSpiLruCache = LruCache.create();
/**
* Construct this instance from a subclass of builder.
*
* @param config builder the subclass is built from
*/
@SuppressWarnings({"rawtypes", "unchecked"})
protected LoomClient(WebClientConfig config) {
this.config = config;
this.protocolConfigs = ProtocolConfigs.create(config.protocolConfigs());
this.cookieManager = config.cookieManager().orElseGet(() -> WebClientCookieManager.builder().build());
List providers;
List protocolPreference = config.protocolPreference();
if (protocolPreference.isEmpty()) {
// use the discovered ones
providers = new ArrayList<>(PROVIDERS);
} else {
providers = new ArrayList<>();
for (String protocol : protocolPreference) {
HttpClientSpiProvider spi = HTTP_PROVIDERS_BY_PROTOCOL.get(protocol);
if (spi == null) {
throw new IllegalStateException("Requested protocol \"" + protocol + "\" is not available on classpath");
}
providers.add(spi);
}
}
if (providers.isEmpty()) {
throw new IllegalStateException("WebClient requires at least one protocol provider to be present on classpath,"
+ " or configured through protocolPreference (such as http1)");
}
Map clients = new HashMap<>();
List protocols = new ArrayList<>();
List tcpProtocols = new ArrayList<>();
for (HttpClientSpiProvider provider : providers) {
Object protocolConfig = protocolConfigs.config(provider.protocolId(),
provider.configType(),
() -> (ProtocolConfig) provider.defaultConfig());
HttpClientSpi clientSpi = (HttpClientSpi) provider.protocol(this, protocolConfig);
String protocolId = provider.protocolId();
ProtocolSpi spi = new ProtocolSpi(protocolId, clientSpi);
clients.putIfAbsent(protocolId, spi);
protocols.add(spi);
if (clientSpi.isTcp()) {
tcpProtocols.add(spi);
}
}
this.clientSpiByProtocol = clients;
this.protocols = protocols;
this.tcpProtocols = tcpProtocols;
this.tcpProtocolIds = tcpProtocols.stream()
.map(ProtocolSpi::id)
.toList();
}
@Override
public WebClientCookieManager cookieManager() {
return cookieManager;
}
@Override
public HttpClientRequest method(Method method) {
ClientUri clientUri = prototype().baseUri()
.map(ClientUri::create) // create from base config
.orElseGet(ClientUri::create); // create as empty
prototype().baseQuery().ifPresent(clientUri.writeableQuery()::from);
prototype().baseFragment().ifPresent(clientUri::fragment);
return new HttpClientRequest(this,
this.prototype(),
method,
clientUri,
clientSpiByProtocol,
protocols,
tcpProtocols,
tcpProtocolIds,
clientSpiLruCache);
}
@Override
public void closeResource() {
for (ProtocolSpi o : List.copyOf(clientSpiByProtocol.values())) {
o.spi().releaseResource();
}
}
@Override
public T client(Protocol protocol, C protocolConfig) {
return protocol.provider().protocol(this, protocolConfig);
}
@SuppressWarnings("unchecked")
@Override
public T client(Protocol protocol) {
ClientProtocolProvider provider = protocol.provider();
return (T) clientsByProtocol.computeIfAbsent(provider.protocolId(),
protocolId -> {
C config = protocolConfigs.config(provider.protocolId(),
provider.configType(),
provider::defaultConfig);
return protocol.provider().protocol(this, config);
});
}
@Override
public WebClientConfig prototype() {
return config;
}
@Override
public ExecutorService executor() {
return EXECUTOR.get();
}
record ProtocolSpi(String id, HttpClientSpi spi) {
}
record EndpointKey(String scheme, // http/https
String authority, // myserver:80
Tls tlsConfig, // TLS configuration (may be disabled, never null)
Proxy proxy) { // proxy, never null
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy