Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config property to proxy managed requests through a domain socket #974

Draft
wants to merge 2 commits into
base: client-builder
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.oraclecloud.httpclient.netty;

import io.micronaut.context.annotation.Requires;
import io.micronaut.context.event.BeanCreatedEvent;
import io.micronaut.context.event.BeanCreatedEventListener;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.http.client.netty.NettyClientCustomizer;
import io.netty.bootstrap.Bootstrap;
import jakarta.inject.Singleton;

import java.net.UnixDomainSocketAddress;

/**
* Customizer that replaces the remote address for proxying through a domain socket.
*
* @author Jonas Konrad
* @since 4.3.0
*/
@Singleton
@Requires(property = OciNettyConfiguration.PREFIX + ".proxy-domain-socket")
final class DomainSocketCustomizer implements BeanCreatedEventListener<NettyClientCustomizer.Registry> {
private final OciNettyConfiguration configuration;

DomainSocketCustomizer(OciNettyConfiguration configuration) {
this.configuration = configuration;
}

@Override
public NettyClientCustomizer.Registry onCreated(@NonNull BeanCreatedEvent<NettyClientCustomizer.Registry> event) {
UnixDomainSocketAddress address = UnixDomainSocketAddress.of(configuration.proxyDomainSocket());
event.getBean().register(new NettyClientCustomizer() {
@Override
public @NonNull NettyClientCustomizer specializeForBootstrap(@NonNull Bootstrap bootstrap) {
bootstrap.remoteAddress(address);
return this;
}
});
return event.getBean();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,24 @@ public class ManagedNettyHttpProvider implements HttpProvider {
@Nullable
final ExecutorService ioExecutor;
final JsonMapper jsonMapper;
final OciNettyConfiguration configuration;

@Inject
public ManagedNettyHttpProvider(
ManagedNettyHttpProvider(
HttpClientRegistry<?> mnHttpClientRegistry,
@Named(TaskExecutors.BLOCKING) @Nullable ExecutorService ioExecutor,
ObjectMapper jsonMapper,
OciSerdeConfiguration ociSerdeConfiguration,
OciSerializationConfiguration ociSerializationConfiguration,
@Nullable List<OciNettyClientFilter<?>> nettyClientFilters
@Nullable List<OciNettyClientFilter<?>> nettyClientFilters,
OciNettyConfiguration configuration
) {
this.mnHttpClientRegistry = mnHttpClientRegistry;
this.mnHttpClient = null;
this.ioExecutor = ioExecutor;
this.jsonMapper = jsonMapper.cloneWithConfiguration(ociSerdeConfiguration, ociSerializationConfiguration, null);
this.nettyClientFilters = nettyClientFilters == null ? Collections.emptyList() : nettyClientFilters;
this.configuration = configuration;
}

// for OKE
Expand All @@ -88,6 +91,7 @@ public ManagedNettyHttpProvider(
this.ioExecutor = ioExecutor;
this.jsonMapper = OciSdkMicronautSerializer.getDefaultObjectMapper();
this.nettyClientFilters = nettyClientFilters == null ? Collections.emptyList() : nettyClientFilters;
this.configuration = new OciNettyConfiguration(null);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.Collections;
import java.util.Comparator;
Expand All @@ -59,6 +60,7 @@ final class NettyHttpClient implements HttpClient {

final boolean hasContext;
final boolean ownsThreadPool;
final boolean proxyDomainSocket;
final URI baseUri;
final List<RequestInterceptor> requestInterceptors;
final List<OciNettyClientFilter<?>> nettyClientFilter;
Expand All @@ -83,6 +85,7 @@ final class NettyHttpClient implements HttpClient {
if (builder.managedProvider == null) {
hasContext = false;
ownsThreadPool = true;
proxyDomainSocket = false;
DefaultHttpClientConfiguration cfg = new DefaultHttpClientConfiguration();
if (builder.properties.containsKey(StandardClientProperties.CONNECT_TIMEOUT)) {
cfg.setConnectTimeout((Duration) builder.properties.get(StandardClientProperties.CONNECT_TIMEOUT));
Expand Down Expand Up @@ -117,10 +120,11 @@ final class NettyHttpClient implements HttpClient {
blockingIoExecutor = builder.managedProvider.ioExecutor;
}
jsonMapper = builder.managedProvider.jsonMapper;
proxyDomainSocket = builder.managedProvider.configuration.proxyDomainSocket() != null;
}
upstreamHttpClient = mnClient;
connectionManager = mnClient.connectionManager();
baseUri = Objects.requireNonNull(builder.baseUri, "baseUri");
this.baseUri = Objects.requireNonNull(builder.baseUri, "baseUri");
requestInterceptors = builder.requestInterceptors.stream()
.sorted(Comparator.comparingInt(p -> p.priority))
.map(p -> p.value)
Expand All @@ -132,7 +136,16 @@ final class NettyHttpClient implements HttpClient {
nettyClientFilter = Collections.emptyList();
}

requestKey = new DefaultHttpClient.RequestKey(mnClient, this.baseUri);
URI baseUriForRequestKey = builder.baseUri;
if (proxyDomainSocket && baseUriForRequestKey.getScheme().equals("https")) {
// use a normal HTTP connection to the domain socket proxy.
try {
baseUriForRequestKey = new URI("http", baseUriForRequestKey.getSchemeSpecificPart(), baseUriForRequestKey.getFragment());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
requestKey = new DefaultHttpClient.RequestKey(mnClient, baseUriForRequestKey);
this.buffered = builder.buffered;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ public CompletionStage<HttpResponse> execute() {
}
}, result::completeExceptionally);

return result;
return last;
}

private void bufferBody() {
Expand Down Expand Up @@ -342,9 +342,15 @@ private io.netty.handler.codec.http.HttpRequest buildNettyRequest(ConnectionMana
}
}

String pathAndQuery = uri.getRawPath();
if (uri.getRawQuery() != null) {
pathAndQuery = pathAndQuery + "?" + uri.getRawQuery();
String pathAndQuery;
if (client.proxyDomainSocket) {
// when proxying, we need to include the full uri
pathAndQuery = uri.toASCIIString();
} else {
pathAndQuery = uri.getRawPath();
if (uri.getRawQuery() != null) {
pathAndQuery = pathAndQuery + "?" + uri.getRawQuery();
}
}

boolean hasTransferHeader = headers.contains(HttpHeaderNames.CONTENT_LENGTH) ||
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.oraclecloud.httpclient.netty;

import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.annotation.Nullable;

import java.nio.file.Path;

/**
* Configuration properties specific to the managed client.
*
* @param proxyDomainSocket A domain socket to send all requests through. The requests will be sent
* as HTTP (no TLS) and with absolute-form URIs.
* @author Jonas Konrad
* @since 4.3.0
*/
@ConfigurationProperties(OciNettyConfiguration.PREFIX)
record OciNettyConfiguration(
@Experimental
@Nullable
Path proxyDomainSocket
) {
static final String PREFIX = "oci.netty";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.oraclecloud.httpclient.netty;

import io.micronaut.context.annotation.Primary;
import io.micronaut.context.annotation.Replaces;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.http.netty.channel.DefaultEventLoopGroupFactory;
import io.micronaut.http.netty.channel.EventLoopGroupConfiguration;
import io.micronaut.http.netty.channel.EventLoopGroupFactory;
import io.micronaut.http.netty.channel.NettyChannelType;
import io.micronaut.http.netty.channel.NioEventLoopGroupFactory;
import io.micronaut.http.netty.configuration.NettyGlobalConfiguration;
import io.netty.channel.Channel;
import jakarta.inject.Singleton;

/**
* {@link EventLoopGroupFactory} that makes the HTTP client use domain socket channels instead of
* normal nio channels.
*
* @author Jonas Konrad
* @since 4.3.0
*/
@Primary
@Singleton
@Replaces(DefaultEventLoopGroupFactory.class)
@Requires(property = OciNettyConfiguration.PREFIX + ".proxy-domain-socket")
final class UnixGroupFactory extends DefaultEventLoopGroupFactory {
public UnixGroupFactory(NioEventLoopGroupFactory nioEventLoopGroupFactory, @Nullable EventLoopGroupFactory nativeFactory, @Nullable NettyGlobalConfiguration nettyGlobalConfiguration) {
super(nioEventLoopGroupFactory, nativeFactory, nettyGlobalConfiguration);
}

@Override
public Channel channelInstance(NettyChannelType type, @Nullable EventLoopGroupConfiguration configuration) {
if (type == NettyChannelType.CLIENT_SOCKET) {
type = NettyChannelType.DOMAIN_SOCKET;
}
return super.channelInstance(type, configuration);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.micronaut.oraclecloud.factory;

import com.oracle.bmc.objectstorage.ObjectStorageAsyncClient
import com.oracle.bmc.objectstorage.ObjectStorageClient
import io.micronaut.context.annotation.Requires
import io.micronaut.context.event.BeanCreatedEvent
import io.micronaut.context.event.BeanCreatedEventListener
import io.micronaut.core.annotation.NonNull
Expand Down Expand Up @@ -42,6 +43,7 @@ class ObjectStorageFactorySpec extends Specification {
}

@Singleton
@Requires(property = "spec.name", notEquals = "DomainSocketProxyTest")
static class DatabaseClientBuilderListener
implements BeanCreatedEventListener<ObjectStorageClient.Builder> {
@Override
Expand All @@ -55,6 +57,7 @@ class ObjectStorageFactorySpec extends Specification {
}

@Singleton
@Requires(property = "spec.name", notEquals = "DomainSocketProxyTest")
static class DatabaseAsyncClientBuilderListener
implements BeanCreatedEventListener<ObjectStorageAsyncClient.Builder> {
@Override
Expand Down
Loading