Skip to content

Commit 0033eb4

Browse files
committed
Polishing external contribution
- Removed duplicate Client in types names. - Removed buffering in favor of OutputStream to Flow.Publisher<ByteBuffer> bridge. - Made request and types package private. - Various other small improvements. Closes gh-30478
1 parent 2ca8dd2 commit 0033eb4

9 files changed

+848
-186
lines changed

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

-92
This file was deleted.

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

-55
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Copyright 2023-2023 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+
* https://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;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.UncheckedIOException;
22+
import java.net.URI;
23+
import java.net.http.HttpClient;
24+
import java.net.http.HttpRequest;
25+
import java.net.http.HttpResponse;
26+
import java.nio.ByteBuffer;
27+
import java.util.List;
28+
import java.util.concurrent.Executor;
29+
import java.util.concurrent.Flow;
30+
31+
import org.springframework.http.HttpHeaders;
32+
import org.springframework.http.HttpMethod;
33+
import org.springframework.lang.Nullable;
34+
import org.springframework.util.StreamUtils;
35+
36+
/**
37+
* {@link ClientHttpRequest} implementation based the Java {@link HttpClient}.
38+
* Created via the {@link JdkClientHttpRequestFactory}.
39+
*
40+
* @author Marten Deinum
41+
* @author Arjen Poutsma
42+
* @since 6.1
43+
*/
44+
class JdkClientHttpRequest extends AbstractStreamingClientHttpRequest {
45+
46+
/*
47+
* The JDK HttpRequest doesn't allow all headers to be set. The named headers are taken from the default
48+
* implementation for HttpRequest.
49+
*/
50+
private static final List<String> DISALLOWED_HEADERS =
51+
List.of("connection", "content-length", "expect", "host", "upgrade");
52+
53+
private final HttpClient httpClient;
54+
55+
private final HttpMethod method;
56+
57+
private final URI uri;
58+
59+
private final Executor executor;
60+
61+
62+
public JdkClientHttpRequest(HttpClient httpClient, URI uri, HttpMethod method, Executor executor) {
63+
this.httpClient = httpClient;
64+
this.uri = uri;
65+
this.method = method;
66+
this.executor = executor;
67+
}
68+
69+
@Override
70+
public HttpMethod getMethod() {
71+
return this.method;
72+
}
73+
74+
@Override
75+
public URI getURI() {
76+
return this.uri;
77+
}
78+
79+
80+
@Override
81+
protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException {
82+
try {
83+
HttpRequest request = buildRequest(headers, body);
84+
HttpResponse<InputStream> response = this.httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
85+
return new JdkClientHttpResponse(response);
86+
}
87+
catch (UncheckedIOException ex) {
88+
throw ex.getCause();
89+
}
90+
catch (InterruptedException ex) {
91+
Thread.currentThread().interrupt();
92+
throw new IOException("Could not send request: " + ex.getMessage(), ex);
93+
}
94+
}
95+
96+
97+
private HttpRequest buildRequest(HttpHeaders headers, @Nullable Body body) {
98+
HttpRequest.Builder builder = HttpRequest.newBuilder()
99+
.uri(this.uri);
100+
101+
headers.forEach((headerName, headerValues) -> {
102+
if (!headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) {
103+
if (!DISALLOWED_HEADERS.contains(headerName.toLowerCase())) {
104+
for (String headerValue : headerValues) {
105+
builder.header(headerName, headerValue);
106+
}
107+
}
108+
}
109+
});
110+
111+
builder.method(this.method.name(), bodyPublisher(headers, body));
112+
return builder.build();
113+
}
114+
115+
private HttpRequest.BodyPublisher bodyPublisher(HttpHeaders headers, @Nullable Body body) {
116+
if (body != null) {
117+
Flow.Publisher<ByteBuffer> outputStreamPublisher = OutputStreamPublisher.create(
118+
outputStream -> body.writeTo(StreamUtils.nonClosing(outputStream)),
119+
this.executor);
120+
121+
long contentLength = headers.getContentLength();
122+
if (contentLength != -1) {
123+
return HttpRequest.BodyPublishers.fromPublisher(outputStreamPublisher, contentLength);
124+
}
125+
else {
126+
return HttpRequest.BodyPublishers.fromPublisher(outputStreamPublisher);
127+
}
128+
}
129+
else {
130+
return HttpRequest.BodyPublishers.noBody();
131+
}
132+
}
133+
134+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2023-2023 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+
* https://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;
18+
19+
import java.io.IOException;
20+
import java.net.URI;
21+
import java.net.http.HttpClient;
22+
import java.util.concurrent.Executor;
23+
24+
import org.springframework.core.task.SimpleAsyncTaskExecutor;
25+
import org.springframework.http.HttpMethod;
26+
import org.springframework.util.Assert;
27+
28+
29+
/**
30+
* {@link ClientHttpRequestFactory} implementation based on the Java
31+
* {@link HttpClient}.
32+
*
33+
* @author Marten Deinum
34+
* @author Arjen Poutsma
35+
* @since 6.1
36+
*/
37+
public class JdkClientHttpRequestFactory implements ClientHttpRequestFactory {
38+
39+
private final HttpClient httpClient;
40+
41+
private final Executor executor;
42+
43+
44+
/**
45+
* Create a new instance of the {@code JdkClientHttpRequestFactory}
46+
* with a default {@link HttpClient}.
47+
*/
48+
public JdkClientHttpRequestFactory() {
49+
this(HttpClient.newHttpClient());
50+
}
51+
52+
/**
53+
* Create a new instance of the {@code JdkClientHttpRequestFactory} based on
54+
* the given {@link HttpClient}.
55+
* @param httpClient the client to base on
56+
*/
57+
public JdkClientHttpRequestFactory(HttpClient httpClient) {
58+
Assert.notNull(httpClient, "HttpClient is required");
59+
this.httpClient = httpClient;
60+
this.executor = httpClient.executor().orElseGet(SimpleAsyncTaskExecutor::new);
61+
}
62+
63+
/**
64+
* Create a new instance of the {@code JdkClientHttpRequestFactory} based on
65+
* the given {@link HttpClient} and {@link Executor}.
66+
* @param httpClient the client to base on
67+
* @param executor the executor to use for blocking write operations
68+
*/
69+
public JdkClientHttpRequestFactory(HttpClient httpClient, Executor executor) {
70+
Assert.notNull(httpClient, "HttpClient is required");
71+
Assert.notNull(executor, "Executor must not be null");
72+
this.httpClient = httpClient;
73+
this.executor = executor;
74+
}
75+
76+
77+
@Override
78+
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
79+
return new JdkClientHttpRequest(this.httpClient, uri, httpMethod, this.executor);
80+
}
81+
82+
}

0 commit comments

Comments
 (0)