Skip to content

Commit

Permalink
Fix support for secondary servers with custom SSL config (micronaut-p…
Browse files Browse the repository at this point in the history
  • Loading branch information
graemerocher authored and dstepanov committed Nov 22, 2021
1 parent c45eb62 commit c6710d5
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 30 deletions.
Expand Up @@ -32,6 +32,7 @@
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.io.ResourceResolver;
import io.micronaut.core.order.OrderUtil;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.http.codec.MediaTypeCodecRegistry;
Expand All @@ -44,10 +45,13 @@
import io.micronaut.http.server.RouteExecutor;
import io.micronaut.http.server.binding.RequestArgumentSatisfier;
import io.micronaut.http.server.netty.configuration.NettyHttpServerConfiguration;
import io.micronaut.http.server.netty.ssl.CertificateProvidedSslBuilder;
import io.micronaut.http.server.netty.ssl.SelfSignedSslBuilder;
import io.micronaut.http.server.netty.ssl.ServerSslBuilder;
import io.micronaut.http.server.netty.types.DefaultCustomizableResponseTypeHandlerRegistry;
import io.micronaut.http.server.netty.types.NettyCustomizableResponseTypeHandler;
import io.micronaut.http.server.netty.types.files.FileTypeHandler;
import io.micronaut.http.ssl.ServerSslConfiguration;
import io.micronaut.scheduling.executor.ExecutorSelector;
import io.micronaut.web.router.resource.StaticResourceResolver;
import io.micronaut.websocket.context.WebSocketBeanRegistry;
Expand Down Expand Up @@ -123,7 +127,13 @@ protected DefaultNettyEmbeddedServerFactory(ApplicationContext applicationContex
@Override
@NonNull
public NettyEmbeddedServer build(@NonNull NettyHttpServerConfiguration configuration) {
return buildInternal(configuration, false);
return buildInternal(configuration, false, null);
}

@Override
@NonNull
public NettyEmbeddedServer build(@NonNull NettyHttpServerConfiguration configuration, @Nullable ServerSslConfiguration sslConfiguration) {
return buildInternal(configuration, false, sslConfiguration);
}

/**
Expand All @@ -135,23 +145,68 @@ public NettyEmbeddedServer build(@NonNull NettyHttpServerConfiguration configura
@Primary
@NonNull
protected NettyEmbeddedServer buildDefaultServer(@NonNull NettyHttpServerConfiguration configuration) {
return buildInternal(configuration, true);
return buildInternal(configuration, true, null);
}

@NotNull
private NettyEmbeddedServer buildInternal(@NonNull NettyHttpServerConfiguration configuration,
boolean isDefaultServer) {
boolean isDefaultServer,
@Nullable ServerSslConfiguration sslConfiguration) {
Objects.requireNonNull(configuration, "Netty HTTP server configuration cannot be null");
List<NettyCustomizableResponseTypeHandler<?>> handlers = Arrays.asList(
new FileTypeHandler(configuration.getFileTypeHandlerConfiguration()),
new StreamTypeHandler()
);
return new NettyHttpServer(
configuration,
this,
new DefaultCustomizableResponseTypeHandlerRegistry(handlers.toArray(new NettyCustomizableResponseTypeHandler[0])),
isDefaultServer
);

if (isDefaultServer) {
return new NettyHttpServer(
configuration,
this,
new DefaultCustomizableResponseTypeHandlerRegistry(handlers.toArray(new NettyCustomizableResponseTypeHandler[0])),
true
);
} else {
NettyEmbeddedServices embeddedServices = resolveNettyEmbeddedServices(configuration, sslConfiguration);
return new NettyHttpServer(
configuration,
embeddedServices,
new DefaultCustomizableResponseTypeHandlerRegistry(handlers.toArray(new NettyCustomizableResponseTypeHandler[0])),
false
);
}
}

private NettyEmbeddedServices resolveNettyEmbeddedServices(@NonNull NettyHttpServerConfiguration configuration,
@Nullable ServerSslConfiguration sslConfiguration) {
if (sslConfiguration != null && sslConfiguration.isEnabled()) {
ServerSslBuilder serverSslBuilder;
final ResourceResolver resourceResolver = applicationContext.getBean(ResourceResolver.class);
if (sslConfiguration.buildSelfSigned()) {
serverSslBuilder = new SelfSignedSslBuilder(
configuration,
sslConfiguration,
resourceResolver
);
} else {
serverSslBuilder = new CertificateProvidedSslBuilder(
configuration,
sslConfiguration,
resourceResolver
);
}
return new DelegateNettyEmbeddedServices() {
@Override
public NettyEmbeddedServices getDelegate() {
return DefaultNettyEmbeddedServerFactory.this;
}

@Override
public ServerSslBuilder getServerSslBuilder() {
return serverSslBuilder;
}
};
}
return this;
}

@Override
Expand Down
@@ -0,0 +1,120 @@
/*
* Copyright 2017-2021 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.server.netty;

import java.util.List;
import java.util.concurrent.ExecutorService;

import io.micronaut.context.ApplicationContext;
import io.micronaut.context.event.ApplicationEventPublisher;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.http.codec.MediaTypeCodecRegistry;
import io.micronaut.http.netty.channel.EventLoopGroupConfiguration;
import io.micronaut.http.netty.channel.EventLoopGroupRegistry;
import io.micronaut.http.netty.channel.converters.ChannelOptionFactory;
import io.micronaut.http.server.RouteExecutor;
import io.micronaut.http.server.netty.ssl.ServerSslBuilder;
import io.micronaut.web.router.resource.StaticResourceResolver;
import io.micronaut.websocket.context.WebSocketBeanRegistry;
import io.netty.channel.ChannelOutboundHandler;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;

/**
* A delegating Netty embedded services instance.
*
* @since 3.1.4
* @author graemerocher
*/
@Internal
interface DelegateNettyEmbeddedServices extends NettyEmbeddedServices {
/**
* @return The instance to delegate to.
*/
@NonNull
NettyEmbeddedServices getDelegate();

@Override
default List<ChannelOutboundHandler> getOutboundHandlers() {
return getDelegate().getOutboundHandlers();
}

@Override
default ApplicationContext getApplicationContext() {
return getDelegate().getApplicationContext();
}

@Override
default RouteExecutor getRouteExecutor() {
return getDelegate().getRouteExecutor();
}

@Override
default MediaTypeCodecRegistry getMediaTypeCodecRegistry() {
return getDelegate().getMediaTypeCodecRegistry();
}

@Override
default StaticResourceResolver getStaticResourceResolver() {
return getDelegate().getStaticResourceResolver();
}

@Override
default ServerSslBuilder getServerSslBuilder() {
return getDelegate().getServerSslBuilder();
}

@Override
default ChannelOptionFactory getChannelOptionFactory() {
return getDelegate().getChannelOptionFactory();
}

@Override
default HttpCompressionStrategy getHttpCompressionStrategy() {
return getDelegate().getHttpCompressionStrategy();
}

@Override
default WebSocketBeanRegistry getWebSocketBeanRegistry() {
return getDelegate().getWebSocketBeanRegistry();
}

@Override
default EventLoopGroupRegistry getEventLoopGroupRegistry() {
return getDelegate().getEventLoopGroupRegistry();
}

@Override
default EventLoopGroup createEventLoopGroup(EventLoopGroupConfiguration config) {
return getDelegate().createEventLoopGroup(config);
}

@Override
default EventLoopGroup createEventLoopGroup(int numThreads, ExecutorService executorService, Integer ioRatio) {
return getDelegate().createEventLoopGroup(numThreads, executorService, ioRatio);
}

@Override
default ServerSocketChannel getServerSocketChannelInstance(EventLoopGroupConfiguration workerConfig) {
return getDelegate().getServerSocketChannelInstance(workerConfig);
}

@Override
default <E> ApplicationEventPublisher<E> getEventPublisher(Class<E> eventClass) {
return getDelegate().getEventPublisher(eventClass);
}
}
Expand Up @@ -16,7 +16,9 @@
package io.micronaut.http.server.netty;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.http.server.netty.configuration.NettyHttpServerConfiguration;
import io.micronaut.http.ssl.ServerSslConfiguration;

/**
* A factory / strategy interface for creating instances of {@link io.micronaut.http.server.netty.NettyEmbeddedServer}.
Expand All @@ -29,9 +31,26 @@ public interface NettyEmbeddedServerFactory {
/**
* Builds a {@link io.micronaut.http.server.netty.NettyEmbeddedServer} for the given configuration.
*
* <p>Note that the returned server instance should be closed gracefully by calling the {@link NettyEmbeddedServer#stop()} method.</p>
*
* @param configuration The configuration, never {@code null}
* @return A {@link io.micronaut.http.server.netty.NettyEmbeddedServer} instance
*/
@NonNull
NettyEmbeddedServer build(@NonNull NettyHttpServerConfiguration configuration);

/**
* Builds a {@link io.micronaut.http.server.netty.NettyEmbeddedServer} for the given configuration.
*
* <p>Note that the returned server instance should be closed gracefully by calling the {@link NettyEmbeddedServer#stop()} method.</p>
*
* @param configuration The configuration, never {@code null}
* @param sslConfiguration The SSL configuration, can be {@code null} if SSL is not required
* @return A {@link io.micronaut.http.server.netty.NettyEmbeddedServer} instance
* @since 3.1.4
*/
@NonNull
default NettyEmbeddedServer build(@NonNull NettyHttpServerConfiguration configuration, @Nullable ServerSslConfiguration sslConfiguration) {
return build(configuration, null);
}
}
@@ -1,3 +1,4 @@

Micronaut supports the programmatic creation of additional Netty servers through the api:http.server.netty.NettyEmbeddedServerFactory[] interface.

This is useful in cases where you, for example, need to expose distinct servers over different ports with potentially differing configurations (HTTPS, thread resources etc.).
Expand All @@ -10,10 +11,11 @@ snippet::io.micronaut.docs.http.server.secondary.SecondaryNettyServer[tags="impo
<2> Define a ann:annotation.annotation.Context[] scoped bean using the server name and including `preDestroy="close"` to ensure the server is shutdown when the context is closed
<3> Inject the api:http.server.netty.NettyEmbeddedServerFactory[] into a <<factories, Factory Bean>>
<4> Programmatically create the api:http.server.netty.configuration.NettyHttpServerConfiguration[]
<5> Use the `build` method to build the server instance
<6> Start the server with the `start` method
<7> Return the server instance as a managed bean
<8> Optionally define an instance of api:discovery.ServiceInstanceList[] if you wish to inject <<httpClient, HTTP Clients>> by the server name
<5> Optionally create the api:http.ssl.ServerSslConfiguration[]
<6> Use the `build` method to build the server instance
<7> Start the server with the `start` method
<8> Return the server instance as a managed bean
<9> Optionally define an instance of api:discovery.ServiceInstanceList[] if you wish to inject <<httpClient, HTTP Clients>> by the server name

With this class in place when the api:context.ApplicationContext[] starts the server will also be started with the appropriate configuration.

Expand Down
Expand Up @@ -6,15 +6,17 @@ import io.micronaut.context.annotation.Context
import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Requires
import io.micronaut.context.env.Environment
import io.micronaut.core.util.StringUtils
import io.micronaut.discovery.ServiceInstanceList
import io.micronaut.discovery.StaticServiceInstanceList
import io.micronaut.http.server.netty.NettyEmbeddedServer
import io.micronaut.http.server.netty.NettyEmbeddedServerFactory
import io.micronaut.http.server.netty.configuration.NettyHttpServerConfiguration
import io.micronaut.http.ssl.ServerSslConfiguration
import jakarta.inject.Named
// end::imports[]


@Requires(property = "secondary.enabled", value = StringUtils.TRUE)
// tag::class[]
@Factory
class SecondaryNettyServer {
Expand All @@ -27,8 +29,12 @@ class SecondaryNettyServer {
NettyEmbeddedServer nettyEmbeddedServer(NettyEmbeddedServerFactory serverFactory) { // <3>
def configuration =
new NettyHttpServerConfiguration() // <4>
def sslConfiguration = new ServerSslConfiguration() // <5>
sslConfiguration.setBuildSelfSigned(true)
sslConfiguration.enabled = true
sslConfiguration.port = -1 // random port
// configure server programmatically
final NettyEmbeddedServer embeddedServer = serverFactory.build(configuration) // <5>
final NettyEmbeddedServer embeddedServer = serverFactory.build(configuration, sslConfiguration) // <5>
embeddedServer.start() // <6>
return embeddedServer // <7>
}
Expand Down
@@ -1,5 +1,7 @@
package io.micronaut.docs.http.server.secondary

import io.micronaut.context.annotation.Property
import io.micronaut.core.util.StringUtils
import io.micronaut.http.HttpRequest
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
Expand All @@ -9,12 +11,10 @@ import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import jakarta.inject.Inject
import jakarta.inject.Named
import spock.lang.Ignore
import spock.lang.PendingFeature
import spock.lang.Specification

@MicronautTest
@Ignore
@Property(name = "secondary.enabled", value = StringUtils.TRUE)
class SecondaryServerTest extends Specification {
// tag::inject[]
@Client(path = "/", id = SecondaryNettyServer.SERVER_ID)
Expand All @@ -25,7 +25,6 @@ class SecondaryServerTest extends Specification {
EmbeddedServer embeddedServer // <2>
// end::inject[]

@PendingFeature(reason = "Named injection with Groovy constants broken")
void "test secondary server"() {
given:
final String result = httpClient.toBlocking().retrieve("/test/secondary/server")
Expand Down
Expand Up @@ -6,14 +6,17 @@ import io.micronaut.context.annotation.Context
import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Requires
import io.micronaut.context.env.Environment
import io.micronaut.core.util.StringUtils
import io.micronaut.discovery.ServiceInstanceList
import io.micronaut.discovery.StaticServiceInstanceList
import io.micronaut.http.server.netty.NettyEmbeddedServer
import io.micronaut.http.server.netty.NettyEmbeddedServerFactory
import io.micronaut.http.server.netty.configuration.NettyHttpServerConfiguration
import io.micronaut.http.ssl.ServerSslConfiguration
import jakarta.inject.Named
// end::imports[]

@Requires(property = "secondary.enabled", value = StringUtils.TRUE)
// tag::class[]
@Factory
class SecondaryNettyServer {
Expand All @@ -29,14 +32,20 @@ class SecondaryNettyServer {
serverFactory: NettyEmbeddedServerFactory // <3>
) : NettyEmbeddedServer {
val configuration = NettyHttpServerConfiguration() // <4>
val sslConfiguration = ServerSslConfiguration() // <5>

sslConfiguration.setBuildSelfSigned(true)
sslConfiguration.isEnabled = true
sslConfiguration.port = -1 // random port

// configure server programmatically
val embeddedServer = serverFactory.build(configuration) // <5>
embeddedServer.start() // <6>
return embeddedServer // <7>
val embeddedServer = serverFactory.build(configuration, sslConfiguration) // <6>
embeddedServer.start() // <7>
return embeddedServer // <8>
}

@Bean
fun serviceInstanceList( // <8>
fun serviceInstanceList( // <9>
@Named(SERVER_ID) nettyEmbeddedServer: NettyEmbeddedServer
): ServiceInstanceList {
return StaticServiceInstanceList(
Expand Down

0 comments on commit c6710d5

Please sign in to comment.