io.servicetalk.http.netty.DeferredServerChannelBinder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of servicetalk-http-netty Show documentation
Show all versions of servicetalk-http-netty Show documentation
A networking framework that evolves with your application
The newest version!
/*
* Copyright © 2019-2023 Apple Inc. and the ServiceTalk project 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
*
* http://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.servicetalk.http.netty;
import io.servicetalk.concurrent.api.Single;
import io.servicetalk.http.api.HttpExecutionContext;
import io.servicetalk.http.api.HttpServerContext;
import io.servicetalk.http.api.StreamingHttpService;
import io.servicetalk.http.netty.NettyHttpServer.NettyHttpServerConnection;
import io.servicetalk.tcp.netty.internal.ReadOnlyTcpServerConfig;
import io.servicetalk.tcp.netty.internal.TcpServerBinder;
import io.servicetalk.tcp.netty.internal.TcpServerChannelInitializer;
import io.servicetalk.transport.api.ConnectionObserver;
import io.servicetalk.transport.api.EarlyConnectionAcceptor;
import io.servicetalk.transport.api.LateConnectionAcceptor;
import io.servicetalk.transport.netty.internal.InfluencerConnectionAcceptor;
import io.servicetalk.transport.netty.internal.NettyConnectionContext;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.SocketAddress;
import java.util.function.BiFunction;
import javax.annotation.Nullable;
import static io.servicetalk.concurrent.api.Single.failed;
import static io.servicetalk.http.netty.AlpnIds.HTTP_1_1;
import static io.servicetalk.http.netty.AlpnIds.HTTP_2;
final class DeferredServerChannelBinder {
private static final Logger LOGGER = LoggerFactory.getLogger(DeferredServerChannelBinder.class);
private DeferredServerChannelBinder() {
// No instances
}
static Single bind(final HttpExecutionContext executionContext,
final ReadOnlyHttpServerConfig config,
final SocketAddress listenAddress,
@Nullable final InfluencerConnectionAcceptor connectionAcceptor,
final StreamingHttpService service,
final boolean drainRequestPayloadBody,
final boolean sniOnly,
@Nullable final EarlyConnectionAcceptor earlyConnectionAcceptor,
@Nullable final LateConnectionAcceptor lateConnectionAcceptor) {
final ReadOnlyTcpServerConfig tcpConfig = config.tcpConfig();
assert tcpConfig.sslContext() != null;
final BiFunction> channelInit = sniOnly ?
(channel, connectionObserver) -> sniInitChannel(listenAddress, channel, config, executionContext,
service, drainRequestPayloadBody, connectionObserver) :
(channel, connectionObserver) -> alpnInitChannel(listenAddress, channel, config, executionContext,
service, drainRequestPayloadBody, connectionObserver);
return TcpServerBinder.bind(listenAddress, tcpConfig, executionContext, connectionAcceptor, channelInit,
serverConnection -> {
// Start processing requests on http/1.1 connection:
if (serverConnection instanceof NettyHttpServerConnection) {
((NettyHttpServerConnection) serverConnection).process(true);
}
// Nothing to do otherwise as h2 uses auto read on the parent channel
}, earlyConnectionAcceptor, lateConnectionAcceptor)
.map(delegate -> {
LOGGER.debug("Started HTTP server with ALPN for address {}", delegate.listenAddress());
// The ServerContext returned by TcpServerBinder takes care of closing the connectionAcceptor.
return new NettyHttpServer.NettyHttpServerContext(delegate, service, executionContext);
});
}
static Single alpnInitChannel(final SocketAddress listenAddress,
final Channel channel,
final ReadOnlyHttpServerConfig config,
final HttpExecutionContext httpExecutionContext,
final StreamingHttpService service,
final boolean drainRequestPayloadBody,
final ConnectionObserver observer) {
return new AlpnChannelSingle(channel,
new TcpServerChannelInitializer(config.tcpConfig(), observer, httpExecutionContext),
// Force a read to get the SSL handshake started. We initialize pipeline before
// SslHandshakeCompletionEvent will complete, therefore, no data will be propagated before we finish
// initialization.
ChannelHandlerContext::read).flatMap(protocol -> {
switch (protocol) {
case HTTP_1_1:
return NettyHttpServer.initChannel(channel, httpExecutionContext, config,
NoopChannelInitializer.INSTANCE, service, drainRequestPayloadBody, observer);
case HTTP_2:
return H2ServerParentConnectionContext.initChannel(listenAddress, channel, httpExecutionContext,
config, NoopChannelInitializer.INSTANCE, service, drainRequestPayloadBody, observer);
default:
return failed(new IllegalStateException("Unknown ALPN protocol negotiated: " + protocol));
}
});
}
static Single sniInitChannel(final SocketAddress listenAddress,
final Channel channel,
final ReadOnlyHttpServerConfig config,
final HttpExecutionContext httpExecutionContext,
final StreamingHttpService service,
final boolean drainRequestPayloadBody,
final ConnectionObserver observer) {
return new SniCompleteChannelSingle(channel,
new TcpServerChannelInitializer(config.tcpConfig(), observer, httpExecutionContext)).flatMap(sniEvt -> {
Throwable failureCause = sniEvt.cause();
if (failureCause != null) {
return failed(failureCause);
}
if (config.h2Config() != null) {
return H2ServerParentConnectionContext.initChannel(listenAddress, channel, httpExecutionContext, config,
NoopChannelInitializer.INSTANCE, service, drainRequestPayloadBody, observer);
}
if (config.h1Config() != null) {
return NettyHttpServer.initChannel(channel, httpExecutionContext, config,
NoopChannelInitializer.INSTANCE, service, drainRequestPayloadBody, observer);
}
return failed(new IllegalStateException(
"SSL handshake completed, but no protocols to initialize. Consider using ALPN to explicitly " +
"negotiate the protocol and/or configure protocols on the client/server builder."));
});
}
}