All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.http.client.ReactorNettyClientRequestFactory Maven / Gradle / Ivy

There is a newer version: 6.1.10
Show newest version
/*
 * Copyright 2002-2024 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.http.client;

import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.util.function.Function;

import io.netty.channel.ChannelOption;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.resources.LoopResources;

import org.springframework.context.SmartLifecycle;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * Reactor-Netty implementation of {@link ClientHttpRequestFactory}.
 *
 * 

This class implements {@link SmartLifecycle} and can be optionally declared * as a Spring-managed bean in order to support JVM Checkpoint Restore. * * @author Arjen Poutsma * @author Sebastien Deleuze * @since 6.1 */ public class ReactorNettyClientRequestFactory implements ClientHttpRequestFactory, SmartLifecycle { private static final Log logger = LogFactory.getLog(ReactorNettyClientRequestFactory.class); private static final Function defaultInitializer = client -> client.compress(true); private HttpClient httpClient; @Nullable private final ReactorResourceFactory resourceFactory; @Nullable private final Function mapper; private Duration exchangeTimeout = Duration.ofSeconds(5); private Duration readTimeout = Duration.ofSeconds(10); private volatile boolean running = true; private final Object lifecycleMonitor = new Object(); /** * Create a new instance of the {@code ReactorNettyClientRequestFactory} * with a default {@link HttpClient} that has compression enabled. */ public ReactorNettyClientRequestFactory() { this.httpClient = defaultInitializer.apply(HttpClient.create()); this.resourceFactory = null; this.mapper = null; } /** * Create a new instance of the {@code ReactorNettyClientRequestFactory} * based on the given {@link HttpClient}. * @param httpClient the client to base on */ public ReactorNettyClientRequestFactory(HttpClient httpClient) { Assert.notNull(httpClient, "HttpClient must not be null"); this.httpClient = httpClient; this.resourceFactory = null; this.mapper = null; } /** * Constructor with externally managed Reactor Netty resources, including * {@link LoopResources} for event loop threads, and {@link ConnectionProvider} * for the connection pool. *

This constructor should be used only when you don't want the client * to participate in the Reactor Netty global resources. By default the * client participates in the Reactor Netty global resources held in * {@link reactor.netty.http.HttpResources}, which is recommended since * fixed, shared resources are favored for event loop concurrency. However, * consider declaring a {@link ReactorResourceFactory} bean with * {@code globalResources=true} in order to ensure the Reactor Netty global * resources are shut down when the Spring ApplicationContext is stopped or closed * and restarted properly when the Spring ApplicationContext is * (with JVM Checkpoint Restore for example). * @param resourceFactory the resource factory to obtain the resources from * @param mapper a mapper for further initialization of the created client */ public ReactorNettyClientRequestFactory(ReactorResourceFactory resourceFactory, Function mapper) { this.httpClient = createHttpClient(resourceFactory, mapper); this.resourceFactory = resourceFactory; this.mapper = mapper; } private static HttpClient createHttpClient(ReactorResourceFactory resourceFactory, Function mapper) { ConnectionProvider provider = resourceFactory.getConnectionProvider(); Assert.notNull(provider, "No ConnectionProvider: is ReactorResourceFactory not initialized yet?"); return defaultInitializer.andThen(mapper).andThen(applyLoopResources(resourceFactory)) .apply(HttpClient.create(provider)); } private static Function applyLoopResources(ReactorResourceFactory factory) { return httpClient -> { LoopResources resources = factory.getLoopResources(); Assert.notNull(resources, "No LoopResources: is ReactorResourceFactory not initialized yet?"); return httpClient.runOn(resources); }; } /** * Set the underlying connect timeout in milliseconds. * A value of 0 specifies an infinite timeout. *

Default is 30 seconds. * @see HttpClient#option(ChannelOption, Object) * @see ChannelOption#CONNECT_TIMEOUT_MILLIS */ public void setConnectTimeout(int connectTimeout) { Assert.isTrue(connectTimeout >= 0, "Timeout must be a non-negative value"); this.httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout); } /** * Set the underlying connect timeout in milliseconds. * A value of 0 specifies an infinite timeout. *

Default is 30 seconds. * @see HttpClient#option(ChannelOption, Object) * @see ChannelOption#CONNECT_TIMEOUT_MILLIS */ public void setConnectTimeout(Duration connectTimeout) { Assert.notNull(connectTimeout, "ConnectTimeout must not be null"); Assert.isTrue(!connectTimeout.isNegative(), "Timeout must be a non-negative value"); this.httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int)connectTimeout.toMillis()); } /** * Set the underlying read timeout in milliseconds. *

Default is 10 seconds. */ public void setReadTimeout(long readTimeout) { Assert.isTrue(readTimeout > 0, "Timeout must be a positive value"); this.readTimeout = Duration.ofMillis(readTimeout); } /** * Set the underlying read timeout as {@code Duration}. *

Default is 10 seconds. */ public void setReadTimeout(Duration readTimeout) { Assert.notNull(readTimeout, "ReadTimeout must not be null"); Assert.isTrue(!readTimeout.isNegative(), "Timeout must be a non-negative value"); this.readTimeout = readTimeout; } /** * Set the timeout for the HTTP exchange in milliseconds. *

Default is 5 seconds. */ public void setExchangeTimeout(long exchangeTimeout) { Assert.isTrue(exchangeTimeout > 0, "Timeout must be a positive value"); this.exchangeTimeout = Duration.ofMillis(exchangeTimeout); } /** * Set the timeout for the HTTP exchange. *

Default is 5 seconds. */ public void setExchangeTimeout(Duration exchangeTimeout) { Assert.notNull(exchangeTimeout, "ExchangeTimeout must not be null"); Assert.isTrue(!exchangeTimeout.isNegative(), "Timeout must be a non-negative value"); this.exchangeTimeout = exchangeTimeout; } @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { return new ReactorNettyClientRequest(this.httpClient, uri, httpMethod, this.exchangeTimeout, this.readTimeout); } @Override public void start() { synchronized (this.lifecycleMonitor) { if (!isRunning()) { if (this.resourceFactory != null && this.mapper != null) { this.httpClient = createHttpClient(this.resourceFactory, this.mapper); } else { logger.warn("Restarting a ReactorNettyClientRequestFactory bean is only supported with externally managed Reactor Netty resources"); } this.running = true; } } } @Override public void stop() { synchronized (this.lifecycleMonitor) { if (isRunning()) { this.running = false; } } } @Override public final void stop(Runnable callback) { synchronized (this.lifecycleMonitor) { stop(); callback.run(); } } @Override public boolean isRunning() { return this.running; } @Override public boolean isAutoStartup() { return false; } @Override public int getPhase() { // Start after ReactorResourceFactory return 1; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy