Skip to content

Commit 83530f2

Browse files
Multi-address client: throw MalformedURLException instead of IllegalArgumentException (#1955)
Motivation: `MalformedURLException` is more suitable for a use-case when an URL is not in absolute-form. Modifications: - Throw `MalformedURLException` instead of `IllegalArgumentException`; - Try-catch `selectClient` operation and return failed `Single` in case of an error; Result: `StreamingUrlHttpClient` try-catches all exception during client selection and throws `MalformedURLException` when a non-absolute form URL is provided.
1 parent e9d69fa commit 83530f2

File tree

2 files changed

+29
-15
lines changed

2 files changed

+29
-15
lines changed

servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/DefaultMultiAddressUrlHttpClientBuilder.java

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import io.servicetalk.transport.api.IoExecutor;
4646

4747
import java.net.InetSocketAddress;
48+
import java.net.MalformedURLException;
4849
import java.util.concurrent.ConcurrentHashMap;
4950
import java.util.concurrent.ConcurrentMap;
5051
import java.util.function.Function;
@@ -63,6 +64,9 @@
6364
* A builder of {@link StreamingHttpClient} instances which have a capacity to call any server based on the parsed
6465
* absolute-form URL address information from each {@link StreamingHttpRequest}.
6566
* <p>
67+
* If {@link HttpRequestMetaData#requestTarget()} is not an absolute-form URL, a {@link MalformedURLException} will be
68+
* returned or thrown.
69+
* <p>
6670
* It also provides a good set of default settings and configurations, which could be used by most users as-is or
6771
* could be overridden to address specific use cases.
6872
*
@@ -118,23 +122,21 @@ public StreamingHttpClient buildStreaming() {
118122
/**
119123
* Returns a cached {@link UrlKey} or creates a new one based on {@link StreamingHttpRequest} information.
120124
*/
121-
private static final class CachingKeyFactory
122-
implements Function<HttpRequestMetaData, UrlKey>, AsyncCloseable {
125+
private static final class CachingKeyFactory implements AsyncCloseable {
123126

124127
private final ConcurrentMap<String, UrlKey> urlKeyCache = new ConcurrentHashMap<>();
125128

126-
@Override
127-
public UrlKey apply(final HttpRequestMetaData metaData) {
129+
public UrlKey get(final HttpRequestMetaData metaData) throws MalformedURLException {
128130
final String host = metaData.host();
129131
if (host == null) {
130-
throw new IllegalArgumentException(
132+
throw new MalformedURLException(
131133
"Request-target does not contain target host address: " + metaData.requestTarget() +
132134
", expected absolute-form URL");
133135
}
134136

135137
final String scheme = metaData.scheme();
136138
if (scheme == null) {
137-
throw new IllegalArgumentException("Request-target does not contains scheme: " +
139+
throw new MalformedURLException("Request-target does not contains scheme: " +
138140
metaData.requestTarget() + ", expected absolute-form URL");
139141
}
140142

@@ -262,21 +264,32 @@ private static final class StreamingUrlHttpClient implements FilterableStreaming
262264
this.executionContext = requireNonNull(executionContext);
263265
}
264266

265-
private FilterableStreamingHttpClient selectClient(
266-
HttpRequestMetaData metaData) {
267-
return group.get(keyFactory.apply(metaData));
267+
private FilterableStreamingHttpClient selectClient(HttpRequestMetaData metaData) throws MalformedURLException {
268+
return group.get(keyFactory.get(metaData));
268269
}
269270

270271
@Override
271272
public Single<? extends FilterableReservedStreamingHttpConnection> reserveConnection(
272273
final HttpExecutionStrategy strategy, final HttpRequestMetaData metaData) {
273-
return defer(() -> selectClient(metaData).reserveConnection(strategy, metaData).subscribeShareContext());
274+
return defer(() -> {
275+
try {
276+
return selectClient(metaData).reserveConnection(strategy, metaData).subscribeShareContext();
277+
} catch (Throwable t) {
278+
return Single.<FilterableReservedStreamingHttpConnection>failed(t).subscribeShareContext();
279+
}
280+
});
274281
}
275282

276283
@Override
277284
public Single<StreamingHttpResponse> request(final HttpExecutionStrategy strategy,
278285
final StreamingHttpRequest request) {
279-
return defer(() -> selectClient(request).request(strategy, request).subscribeShareContext());
286+
return defer(() -> {
287+
try {
288+
return selectClient(request).request(strategy, request).subscribeShareContext();
289+
} catch (Throwable t) {
290+
return Single.<StreamingHttpResponse>failed(t).subscribeShareContext();
291+
}
292+
});
280293
}
281294

282295
@Override

servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/MultiAddressUrlHttpClientTest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.junit.jupiter.api.Test;
3636

3737
import java.net.InetSocketAddress;
38+
import java.net.MalformedURLException;
3839
import java.net.UnknownHostException;
3940
import java.util.Collection;
4041
import java.util.List;
@@ -167,23 +168,23 @@ void requestWithRelativeFormRequestTarget() {
167168
StreamingHttpRequest request = client.get("/200?param=value");
168169
// no host header
169170
toSource(client.request(request)).subscribe(subscriber);
170-
assertThat(subscriber.awaitOnError(), is(instanceOf(IllegalArgumentException.class)));
171+
assertThat(subscriber.awaitOnError(), is(instanceOf(MalformedURLException.class)));
171172
}
172173

173174
@Test
174175
void requestWithRelativeFormRequestTargetAndHostHeader() {
175176
StreamingHttpRequest request = client.get("/200?param=value");
176177
request.headers().set(HOST, hostHeader);
177178
toSource(client.request(request)).subscribe(subscriber);
178-
assertThat(subscriber.awaitOnError(), is(instanceOf(IllegalArgumentException.class)));
179+
assertThat(subscriber.awaitOnError(), is(instanceOf(MalformedURLException.class)));
179180
}
180181

181182
@Test
182183
void requestWithRequestTargetWithoutScheme() {
183184
StreamingHttpRequest request = client.get(format("%s/200?param=value#tag", hostHeader));
184185
// no host header
185186
toSource(client.request(request)).subscribe(subscriber);
186-
assertThat(subscriber.awaitOnError(), is(instanceOf(IllegalArgumentException.class)));
187+
assertThat(subscriber.awaitOnError(), is(instanceOf(MalformedURLException.class)));
187188
}
188189

189190
@Test
@@ -240,7 +241,7 @@ void requestWithAsteriskFormRequestTargetWithHostHeader() {
240241
StreamingHttpRequest request = client.newRequest(OPTIONS, "*")
241242
.setHeader(HOST, hostHeader);
242243
toSource(client.request(request)).subscribe(subscriber);
243-
assertThat(subscriber.awaitOnError(), is(instanceOf(IllegalArgumentException.class)));
244+
assertThat(subscriber.awaitOnError(), is(instanceOf(MalformedURLException.class)));
244245
}
245246

246247
@Test

0 commit comments

Comments
 (0)