Skip to content

Commit 1eb06fc

Browse files
committed
Introduce JettyResourceFactory
JettyResourceFactory, similar to ReactorResourceFactory, allows to share resources (Executor, ByteBufferPool, Scheduler) between Jetty clients and servers. Issue: SPR-17179
1 parent 50b6f9d commit 1eb06fc

File tree

3 files changed

+223
-29
lines changed

3 files changed

+223
-29
lines changed

spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpConnector.java

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,19 @@
1717
package org.springframework.http.client.reactive;
1818

1919
import java.net.URI;
20+
import java.util.function.Consumer;
2021
import java.util.function.Function;
2122

2223
import org.eclipse.jetty.client.HttpClient;
2324
import org.eclipse.jetty.util.Callback;
2425
import reactor.core.publisher.Flux;
2526
import reactor.core.publisher.Mono;
2627

27-
import org.springframework.context.SmartLifecycle;
2828
import org.springframework.core.io.buffer.DataBuffer;
2929
import org.springframework.core.io.buffer.DataBufferFactory;
3030
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
3131
import org.springframework.http.HttpMethod;
32+
import org.springframework.lang.Nullable;
3233
import org.springframework.util.Assert;
3334

3435
/**
@@ -43,7 +44,7 @@
4344
* @since 5.1
4445
* @see <a href="https://github.com/jetty-project/jetty-reactive-httpclient">Jetty ReactiveStreams HttpClient</a>
4546
*/
46-
public class JettyClientHttpConnector implements ClientHttpConnector, SmartLifecycle {
47+
public class JettyClientHttpConnector implements ClientHttpConnector {
4748

4849
private final HttpClient httpClient;
4950

@@ -57,6 +58,22 @@ public JettyClientHttpConnector() {
5758
this(new HttpClient());
5859
}
5960

61+
/**
62+
* Constructor with an {@link JettyResourceFactory} that will manage shared resources.
63+
* @param resourceFactory the {@link JettyResourceFactory} to use
64+
* @param customizer the lambda used to customize the {@link HttpClient}
65+
*/
66+
public JettyClientHttpConnector(JettyResourceFactory resourceFactory, @Nullable Consumer<HttpClient> customizer) {
67+
HttpClient httpClient = new HttpClient();
68+
httpClient.setExecutor(resourceFactory.getExecutor());
69+
httpClient.setByteBufferPool(resourceFactory.getByteBufferPool());
70+
httpClient.setScheduler(resourceFactory.getScheduler());
71+
if (customizer != null) {
72+
customizer.accept(httpClient);
73+
}
74+
this.httpClient = httpClient;
75+
}
76+
6077
/**
6178
* Constructor with an initialized {@link HttpClient}.
6279
*/
@@ -71,33 +88,6 @@ public void setBufferFactory(DataBufferFactory bufferFactory) {
7188
}
7289

7390

74-
@Override
75-
public void start() {
76-
try {
77-
// HttpClient is internally synchronized and protected with state checks
78-
this.httpClient.start();
79-
}
80-
catch (Exception ex) {
81-
throw new RuntimeException(ex);
82-
}
83-
}
84-
85-
@Override
86-
public void stop() {
87-
try {
88-
this.httpClient.stop();
89-
}
90-
catch (Exception ex) {
91-
throw new RuntimeException(ex);
92-
}
93-
}
94-
95-
@Override
96-
public boolean isRunning() {
97-
return this.httpClient.isRunning();
98-
}
99-
100-
10191
@Override
10292
public Mono<ClientHttpResponse> connect(HttpMethod method, URI uri,
10393
Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.client.reactive;
18+
19+
20+
import java.nio.ByteBuffer;
21+
import java.util.concurrent.Executor;
22+
23+
import org.eclipse.jetty.io.ByteBufferPool;
24+
import org.eclipse.jetty.io.MappedByteBufferPool;
25+
import org.eclipse.jetty.util.ProcessorUtils;
26+
import org.eclipse.jetty.util.component.ContainerLifeCycle;
27+
import org.eclipse.jetty.util.thread.QueuedThreadPool;
28+
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
29+
import org.eclipse.jetty.util.thread.Scheduler;
30+
import org.eclipse.jetty.util.thread.ThreadPool;
31+
32+
import org.springframework.beans.factory.DisposableBean;
33+
import org.springframework.beans.factory.InitializingBean;
34+
import org.springframework.lang.Nullable;
35+
import org.springframework.util.Assert;
36+
37+
/**
38+
* Factory to manage Jetty resources, i.e. {@link Executor}, {@link ByteBufferPool} and
39+
* {@link Scheduler}, within the lifecycle of a Spring {@code ApplicationContext}.
40+
*
41+
* <p>This factory implements {@link InitializingBean} and {@link DisposableBean}
42+
* and is expected typically to be declared as a Spring-managed bean.
43+
*
44+
* @author Sebastien Deleuze
45+
* @since 5.1
46+
*/
47+
public class JettyResourceFactory implements InitializingBean, DisposableBean {
48+
49+
@Nullable
50+
private Executor executor;
51+
52+
@Nullable
53+
private ByteBufferPool byteBufferPool;
54+
55+
@Nullable
56+
private Scheduler scheduler;
57+
58+
private String threadPrefix = "jetty-http";
59+
60+
61+
/**
62+
* Configure the {@link Executor} to use.
63+
* <p>By default, initialized with a {@link QueuedThreadPool}.
64+
* @param executor the executor to use
65+
*/
66+
public void setExecutor(@Nullable Executor executor) {
67+
this.executor = executor;
68+
}
69+
70+
/**
71+
* Configure the {@link ByteBufferPool} to use.
72+
* <p>By default, initialized with a {@link MappedByteBufferPool}.
73+
* @param byteBufferPool the {@link ByteBuffer} pool to use
74+
*/
75+
public void setByteBufferPool(@Nullable ByteBufferPool byteBufferPool) {
76+
this.byteBufferPool = byteBufferPool;
77+
}
78+
79+
/**
80+
* Configure the {@link Scheduler} to use.
81+
* <p>By default, initialized with a {@link ScheduledExecutorScheduler}.
82+
* @param scheduler the {@link Scheduler} to use
83+
*/
84+
public void setScheduler(@Nullable Scheduler scheduler) {
85+
this.scheduler = scheduler;
86+
}
87+
88+
/**
89+
* Configure the thread prefix to initialize {@link QueuedThreadPool} executor with. This
90+
* is used only when a {@link Executor} instance isn't
91+
* {@link #setExecutor(Executor) provided}.
92+
* <p>By default set to "jetty-http".
93+
* @param threadPrefix the thread prefix to use
94+
*/
95+
public void setThreadPrefix(String threadPrefix) {
96+
Assert.notNull(threadPrefix, "Thread prefix is required");
97+
this.threadPrefix = threadPrefix;
98+
}
99+
100+
/**
101+
* Return the configured {@link Executor}.
102+
*/
103+
@Nullable
104+
public Executor getExecutor() {
105+
return this.executor;
106+
}
107+
108+
/**
109+
* Return the configured {@link ByteBufferPool}.
110+
*/
111+
@Nullable
112+
public ByteBufferPool getByteBufferPool() {
113+
return this.byteBufferPool;
114+
}
115+
116+
/**
117+
* Return the configured {@link Scheduler}.
118+
*/
119+
@Nullable
120+
public Scheduler getScheduler() {
121+
return this.scheduler;
122+
}
123+
124+
@Override
125+
public void afterPropertiesSet() throws Exception {
126+
String name = this.threadPrefix + "@" + Integer.toHexString(hashCode());
127+
if (this.executor == null) {
128+
QueuedThreadPool threadPool = new QueuedThreadPool();
129+
threadPool.setName(name);
130+
threadPool.start();
131+
this.executor = threadPool;
132+
}
133+
if (this.byteBufferPool == null) {
134+
this.byteBufferPool = new MappedByteBufferPool(2048,
135+
this.executor instanceof ThreadPool.SizedThreadPool
136+
? ((ThreadPool.SizedThreadPool) executor).getMaxThreads() / 2
137+
: ProcessorUtils.availableProcessors() * 2);
138+
}
139+
if (this.scheduler == null) {
140+
Scheduler scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false);
141+
scheduler.start();
142+
this.scheduler = scheduler;
143+
}
144+
}
145+
146+
@Override
147+
public void destroy() throws Exception {
148+
if (this.executor instanceof ContainerLifeCycle) {
149+
((ContainerLifeCycle)this.executor).stop();
150+
}
151+
if (this.scheduler != null) {
152+
this.scheduler.stop();
153+
}
154+
}
155+
156+
}

src/docs/asciidoc/web/webflux-webclient.adoc

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,54 @@ instances use shared resources:
138138
<3> Plug the connector into the `WebClient.Builder`.
139139

140140

141+
[[webflux-client-builder-jetty]]
142+
=== Jetty
143+
144+
To customize Jetty `HttpClient` settings:
145+
146+
[source,java,intent=0]
147+
[subs="verbatim,quotes"]
148+
----
149+
HttpClient httpClient = new HttpClient();
150+
httpClient.setCookieStore(...);
151+
ClientHttpConnector connector = new JettyClientHttpConnector(httpClient);
152+
153+
WebClient webClient = WebClient.builder().clientConnector(connector).build();
154+
----
155+
156+
By default `HttpClient` creates its own resources (`Executor`, `ByteBufferPool`, `Scheduler`)
157+
which remain active until the process exits or `stop()` is called.
158+
159+
You can share resources between multiple intances of Jetty client (and server) and ensure the
160+
resources are shut down when the Spring `ApplicationContext` is closed by declaring a
161+
Spring-managed bean of type `JettyResourceFactory`:
162+
163+
[source,java,intent=0]
164+
[subs="verbatim,quotes"]
165+
----
166+
@Bean
167+
public JettyResourceFactory resourceFactory() {
168+
return new JettyResourceFactory();
169+
}
170+
171+
@Bean
172+
public WebClient webClient() {
173+
174+
Consumer<HttpClient> customizer = client -> {
175+
// Further customizations...
176+
};
177+
178+
ClientHttpConnector connector =
179+
new JettyClientHttpConnector(resourceFactory(), customizer); // <2>
180+
181+
return WebClient.builder().clientConnector(connector).build(); // <3>
182+
}
183+
----
184+
185+
<1> Create shared resources.
186+
<2> Use `JettyClientHttpConnector` constructor with resource factory.
187+
<3> Plug the connector into the `WebClient.Builder`.
188+
141189

142190

143191
[[webflux-client-retrieve]]

0 commit comments

Comments
 (0)