Skip to content

Commit

Permalink
JDK RawHttpClient (micronaut-projects#11259)
Browse files Browse the repository at this point in the history
* RawHttpClient
Draft because I still need to add tests

* tests

* httpClientFactory

* redirect and filter tests

* commit

* checkstyle

* review
  • Loading branch information
yawkat authored Nov 8, 2024
1 parent e4416f3 commit 11cb0f0
Show file tree
Hide file tree
Showing 41 changed files with 1,546 additions and 443 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ public interface RawHttpClientFactory {
* @return The client
*/
@NonNull
RawHttpClient createRawClient(@Nullable URI url);
default RawHttpClient createRawClient(@Nullable URI url) {
return createRawClient(url, new DefaultHttpClientConfiguration());
}

/**
* Create a new {@link RawHttpClient} with the specified configuration. Note that this method should only be used
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,25 @@ abstract class AbstractJdkHttpClient {
protected MediaTypeCodecRegistry mediaTypeCodecRegistry;
protected MessageBodyHandlerRegistry messageBodyHandlerRegistry;

protected AbstractJdkHttpClient(AbstractJdkHttpClient prototype) {
this.loadBalancer = prototype.loadBalancer;
this.httpVersion = prototype.httpVersion;
this.configuration = prototype.configuration;
this.contextPath = prototype.contextPath;
this.client = prototype.client;
this.cookieManager = prototype.cookieManager;
this.requestBinderRegistry = prototype.requestBinderRegistry;
this.clientId = prototype.clientId;
this.conversionService = prototype.conversionService;
this.sslBuilder = prototype.sslBuilder;
this.log = prototype.log;
this.filterResolver = prototype.filterResolver;
this.clientFilterEntries = prototype.clientFilterEntries;
this.cookieDecoder = prototype.cookieDecoder;
this.mediaTypeCodecRegistry = prototype.mediaTypeCodecRegistry;
this.messageBodyHandlerRegistry = prototype.messageBodyHandlerRegistry;
}

/**
* @param log the logger to use
* @param loadBalancer The {@link LoadBalancer} to use for selecting servers
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2017-2024 original 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 io.micronaut.http.client.jdk;

import io.micronaut.core.annotation.Internal;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.convert.value.MutableConvertibleValues;
import io.micronaut.core.convert.value.MutableConvertibleValuesMap;
import io.micronaut.http.HttpHeaders;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;

/**
* Base {@link HttpResponse} implementation for the JDK client, for streaming and buffered
* responses.
*
* @param <B> The JDK body type
* @param <O> The Micronaut HttpResponse body type
*/
@Internal
abstract class BaseHttpResponseAdapter<B, O> implements HttpResponse<O> {
final java.net.http.HttpResponse<B> httpResponse;
final ConversionService conversionService;
final MutableConvertibleValues<Object> attributes = new MutableConvertibleValuesMap<>();

BaseHttpResponseAdapter(java.net.http.HttpResponse<B> httpResponse,
ConversionService conversionService) {
this.httpResponse = httpResponse;
this.conversionService = conversionService;
}

@Override
public HttpStatus getStatus() {
return HttpStatus.valueOf(httpResponse.statusCode());
}

@Override
public int code() {
return httpResponse.statusCode();
}

@Override
public String reason() {
return getStatus().getReason();
}

@Override
public HttpHeaders getHeaders() {
return new HttpHeadersAdapter(httpResponse.headers(), conversionService);
}

@Override
public MutableConvertibleValues<Object> getAttributes() {
return attributes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.annotation.Order;
import io.micronaut.core.convert.ConversionService;
Expand All @@ -44,6 +45,8 @@
import io.micronaut.http.client.HttpVersionSelection;
import io.micronaut.http.client.LoadBalancer;
import io.micronaut.http.client.LoadBalancerResolver;
import io.micronaut.http.client.RawHttpClient;
import io.micronaut.http.client.RawHttpClientRegistry;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.client.exceptions.HttpClientException;
import io.micronaut.http.client.filter.ClientFilterResolutionContext;
Expand Down Expand Up @@ -81,7 +84,7 @@
@Order(2) // If both this and the netty client are present, netty is the default.
@Internal
@Experimental
public final class DefaultJdkHttpClientRegistry implements AutoCloseable, HttpClientRegistry<HttpClient> {
public final class DefaultJdkHttpClientRegistry implements AutoCloseable, HttpClientRegistry<HttpClient>, RawHttpClientRegistry {

private static final Logger LOG = LoggerFactory.getLogger(DefaultJdkHttpClientRegistry.class);

Expand Down Expand Up @@ -152,6 +155,28 @@ protected DefaultJdkHttpClient httpClient(
return resolveDefaultHttpClient(injectionPoint, loadBalancer, configuration, beanContext);
}

/**
* Creates a {@literal java.net.http.*} HTTP Client.
*
* @param injectionPoint
* @param loadBalancer
* @param configuration
* @param beanContext
* @return A {@literal java.net.http.*} HTTP Client
*/
@Bean
@BootstrapContextCompatible
@Primary
@Order(2) // If both this and the netty client are present, netty is the default.
RawHttpClient rawHttpClient(
@Nullable InjectionPoint<?> injectionPoint,
@Parameter @Nullable LoadBalancer loadBalancer,
@Parameter @Nullable HttpClientConfiguration configuration,
BeanContext beanContext
) {
return new JdkRawHttpClient(resolveDefaultHttpClient(injectionPoint, loadBalancer, configuration, beanContext));
}

private DefaultJdkHttpClient resolveDefaultHttpClient(
@Nullable InjectionPoint<?> injectionPoint,
@Nullable LoadBalancer loadBalancer,
Expand Down Expand Up @@ -327,7 +352,7 @@ private DefaultJdkHttpClient buildClient(
}

@Override
public HttpClient getClient(HttpVersionSelection httpVersion, String clientId, String path) {
public DefaultJdkHttpClient getClient(HttpVersionSelection httpVersion, String clientId, String path) {
final ClientKey key = new ClientKey(
httpVersion,
clientId,
Expand Down Expand Up @@ -367,6 +392,11 @@ public void close() throws Exception {
clients.clear();
}

@Override
public @NonNull RawHttpClient getRawClient(@NonNull HttpVersionSelection httpVersion, @NonNull String clientId, @Nullable String path) {
return new JdkRawHttpClient(getClient(httpVersion, clientId, path));
}

/**
* Client key.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,19 @@
import io.micronaut.http.client.HttpClientConfiguration;
import io.micronaut.http.codec.MediaTypeCodec;
import io.micronaut.http.codec.MediaTypeCodecRegistry;
import reactor.adapter.JdkFlowAdapter;
import reactor.core.publisher.Flux;

import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpRequest;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.concurrent.Flow;
import java.util.stream.Collectors;

/**
Expand Down Expand Up @@ -84,6 +89,16 @@ private static <I> HttpRequest.BodyPublisher publisherForRequest(
@Nullable MediaTypeCodecRegistry mediaTypeCodecRegistry,
@NonNull MessageBodyHandlerRegistry messageBodyHandlerRegistry
) {
if (request instanceof RawHttpRequestWrapper<?> raw) {
OptionalLong length = raw.byteBody().expectedLength();
Flow.Publisher<ByteBuffer> buffers = JdkFlowAdapter.publisherToFlowPublisher(
Flux.from(raw.byteBody().toByteArrayPublisher()).map(ByteBuffer::wrap));
if (length.isPresent()) {
return HttpRequest.BodyPublishers.fromPublisher(buffers, length.getAsLong());
} else {
return HttpRequest.BodyPublishers.fromPublisher(buffers);
}
}
if (!HttpMethod.permitsRequestBody(request.getMethod())) {
return HttpRequest.BodyPublishers.noBody();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,9 @@
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.convert.ConversionContext;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.convert.value.MutableConvertibleValues;
import io.micronaut.core.convert.value.MutableConvertibleValuesMap;
import io.micronaut.core.io.buffer.ByteArrayBufferFactory;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpHeaders;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.MediaType;
import io.micronaut.http.body.MessageBodyHandlerRegistry;
import io.micronaut.http.body.MessageBodyReader;
Expand All @@ -49,15 +45,12 @@
*/
@Internal
@Experimental
public class HttpResponseAdapter<O> implements HttpResponse<O> {
public class HttpResponseAdapter<O> extends BaseHttpResponseAdapter<byte[], O> {

private static final Logger LOG = LoggerFactory.getLogger(HttpResponseAdapter.class);

private final java.net.http.HttpResponse<byte[]> httpResponse;
@NonNull
private final Argument<O> bodyType;
private final ConversionService conversionService;
private final MutableConvertibleValues<Object> attributes = new MutableConvertibleValuesMap<>();

private final MediaTypeCodecRegistry mediaTypeCodecRegistry;
private final MessageBodyHandlerRegistry messageBodyHandlerRegistry;
Expand All @@ -67,38 +60,12 @@ public HttpResponseAdapter(java.net.http.HttpResponse<byte[]> httpResponse,
ConversionService conversionService,
MediaTypeCodecRegistry mediaTypeCodecRegistry,
MessageBodyHandlerRegistry messageBodyHandlerRegistry) {
this.httpResponse = httpResponse;
super(httpResponse, conversionService);
this.bodyType = bodyType;
this.conversionService = conversionService;
this.mediaTypeCodecRegistry = mediaTypeCodecRegistry;
this.messageBodyHandlerRegistry = messageBodyHandlerRegistry;
}

@Override
public HttpStatus getStatus() {
return HttpStatus.valueOf(httpResponse.statusCode());
}

@Override
public int code() {
return httpResponse.statusCode();
}

@Override
public String reason() {
return getStatus().getReason();
}

@Override
public HttpHeaders getHeaders() {
return new HttpHeadersAdapter(httpResponse.headers(), conversionService);
}

@Override
public MutableConvertibleValues<Object> getAttributes() {
return attributes;
}

@Override
public Optional<O> getBody() {
return getBody(bodyType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.io.buffer.ByteArrayBufferFactory;
import io.micronaut.http.MediaType;
Expand All @@ -25,6 +27,8 @@
import io.micronaut.http.body.WritableBodyWriter;
import io.micronaut.http.client.AbstractHttpClientFactory;
import io.micronaut.http.client.HttpClientConfiguration;
import io.micronaut.http.client.RawHttpClient;
import io.micronaut.http.client.RawHttpClientFactory;
import io.micronaut.json.JsonMapper;
import io.micronaut.json.body.JsonMessageHandler;
import io.micronaut.runtime.ApplicationConfiguration;
Expand All @@ -38,7 +42,7 @@
*/
@Internal
@Experimental
public class JdkHttpClientFactory extends AbstractHttpClientFactory<DefaultJdkHttpClient> {
public class JdkHttpClientFactory extends AbstractHttpClientFactory<DefaultJdkHttpClient> implements RawHttpClientFactory {

public JdkHttpClientFactory() {
super(null, createDefaultMessageBodyHandlerRegistry(), ConversionService.SHARED);
Expand Down Expand Up @@ -66,4 +70,9 @@ public static MessageBodyHandlerRegistry createDefaultMessageBodyHandlerRegistry
registry.add(MediaType.APPLICATION_JSON_STREAM_TYPE, new JsonMessageHandler<>(mapper));
return registry;
}

@Override
public @NonNull RawHttpClient createRawClient(@Nullable URI url, @NonNull HttpClientConfiguration configuration) {
return new JdkRawHttpClient(createHttpClient(url, configuration));
}
}
Loading

0 comments on commit 11cb0f0

Please sign in to comment.