HttpClient based ClientHttpRequestFactory

As JDK17 is now the baseline it is possible to use the
HttpClient provided by Java.
This commit is contained in:
Marten Deinum 2023-05-11 19:57:18 +02:00 committed by Arjen Poutsma
parent c2e3fed8dc
commit 2ca8dd2faa
4 changed files with 285 additions and 0 deletions

View File

@ -0,0 +1,92 @@
/*
* Copyright 2023-2023 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.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
/**
* {@link ClientHttpRequest} implementation based on the Java {@code HttpClient}.
*
* @author Marten Deinum
* @since 6.1
*/
public class JdkClientClientHttpRequest extends AbstractBufferingClientHttpRequest {
/*
* The JDK HttpRequest doesn't allow all headers to be set. The named headers are taken from the default
* implementation for HttpRequest.
*/
private static final List<String> DISALLOWED_HEADERS =
List.of("connection", "content-length", "expect", "host", "upgrade");
private final HttpClient client;
private final URI uri;
private final HttpMethod method;
public JdkClientClientHttpRequest(HttpClient client, URI uri, HttpMethod method) {
this.client = client;
this.uri = uri;
this.method = method;
}
@Override
public HttpMethod getMethod() {
return this.method;
}
@Override
public URI getURI() {
return this.uri;
}
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] content) throws IOException {
HttpRequest.Builder builder = HttpRequest.newBuilder(this.uri)
.method(getMethod().name(), HttpRequest.BodyPublishers.ofByteArray(content));
addHeaders(headers, builder);
HttpRequest request = builder.build();
HttpResponse<InputStream> response;
try {
response = this.client.send(request, HttpResponse.BodyHandlers.ofInputStream());
} catch (InterruptedException ex)
{
Thread.currentThread().interrupt();
throw new IllegalStateException("Request interupted.", ex);
}
return new JdkClientClientHttpResponse(response);
}
private static void addHeaders(HttpHeaders headers, HttpRequest.Builder builder) {
headers.forEach((headerName, headerValues) -> {
if (!DISALLOWED_HEADERS.contains(headerName.toLowerCase())) {
for (String headerValue : headerValues) {
builder.header(headerName, headerValue);
}
}
});
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2023-2023 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.net.http.HttpClient;
import org.springframework.http.HttpMethod;
/**
* {@link ClientHttpRequestFactory} implementation that uses a
* <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.net.http/java/net/http/HttpClient.html">HttpClient</a> to create requests.
*
* @author Marten Deinum
* @since 6.1
*/
public class JdkClientClientHttpRequestFactory implements ClientHttpRequestFactory {
private HttpClient client;
private final boolean defaultClient;
public JdkClientClientHttpRequestFactory() {
this.client = HttpClient.newHttpClient();
this.defaultClient = true;
}
public JdkClientClientHttpRequestFactory(HttpClient client) {
this.client = client;
this.defaultClient = false;
}
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
return new JdkClientClientHttpRequest(this.client, uri, httpMethod);
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright 2023-2023 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.io.InputStream;
import java.net.http.HttpResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.lang.Nullable;
import org.springframework.util.StreamUtils;
/**
* {@link ClientHttpResponse} implementation based on the Java {@code HttpClient}.
*
* @author Marten Deinum
* @since 6.1
*/
public class JdkClientClientHttpResponse implements ClientHttpResponse {
private final HttpResponse<InputStream> response;
@Nullable
private volatile HttpHeaders headers;
public JdkClientClientHttpResponse(HttpResponse<InputStream> response) {
this.response = response;
}
@Override
public HttpStatusCode getStatusCode() throws IOException {
return HttpStatusCode.valueOf(this.response.statusCode());
}
@Override
@Deprecated
public int getRawStatusCode() {
return this.response.statusCode();
}
@Override
public String getStatusText() {
HttpStatus status = HttpStatus.resolve(this.response.statusCode());
return (status != null) ? status.getReasonPhrase() : "";
}
@Override
public InputStream getBody() throws IOException {
InputStream body = this.response.body();
return (body != null ? body : InputStream.nullInputStream());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = this.headers;
if (headers == null) {
headers = new HttpHeaders();
for (String headerName : this.response.headers().map().keySet()) {
for (String headerValue : this.response.headers().allValues(headerName)) {
headers.add(headerName, headerValue);
}
}
this.headers = headers;
}
return headers;
}
@Override
public void close() {
InputStream body = this.response.body();
try {
try {
StreamUtils.drain(body);
}
finally {
body.close();
}
}
catch (IOException ex) {
// Ignore exception on close...
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2023-2023 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 org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
/**
* @author Marten Deinum
*/
public class JdkClientClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTests {
@Override
protected ClientHttpRequestFactory createRequestFactory() {
return new JdkClientClientHttpRequestFactory();
}
@Override
@Test
public void httpMethods() throws Exception {
super.httpMethods();
assertHttpMethod("patch", HttpMethod.PATCH);
}
}