nl.topicus.jdbc.shaded.io.netty.handler.ssl.SniHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spanner-jdbc Show documentation
Show all versions of spanner-jdbc Show documentation
JDBC Driver for Google Cloud Spanner
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in nl.topicus.jdbc.shaded.com.liance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.nl.topicus.jdbc.shaded.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 nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.handler.ssl;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.channel.ChannelHandlerContext;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.handler.codec.DecoderException;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.AsyncMapping;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.DomainNameMapping;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.Mapping;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.ReferenceCountUtil;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.concurrent.Future;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.concurrent.Promise;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.internal.ObjectUtil;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.internal.PlatformDependent;
/**
* Enables SNI
* (Server Name Indication) extension for server side SSL. For clients
* support SNI, the server could have multiple host name bound on a single IP.
* The client will send host name in the handshake data so server could decide
* which certificate to choose for the host name.
*/
public class SniHandler extends AbstractSniHandler {
private static final Selection EMPTY_SELECTION = new Selection(null, null);
protected final AsyncMapping mapping;
private volatile Selection selection = EMPTY_SELECTION;
/**
* Creates a SNI detection handler with configured {@link SslContext}
* maintained by {@link Mapping}
*
* @param mapping the mapping of domain name to {@link SslContext}
*/
public SniHandler(Mapping super String, ? extends SslContext> mapping) {
this(new AsyncMappingAdapter(mapping));
}
/**
* Creates a SNI detection handler with configured {@link SslContext}
* maintained by {@link DomainNameMapping}
*
* @param mapping the mapping of domain name to {@link SslContext}
*/
public SniHandler(DomainNameMapping extends SslContext> mapping) {
this((Mapping) mapping);
}
/**
* Creates a SNI detection handler with configured {@link SslContext}
* maintained by {@link AsyncMapping}
*
* @param mapping the mapping of domain name to {@link SslContext}
*/
@SuppressWarnings("unchecked")
public SniHandler(AsyncMapping super String, ? extends SslContext> mapping) {
this.mapping = (AsyncMapping) ObjectUtil.checkNotNull(mapping, "mapping");
}
/**
* @return the selected hostname
*/
public String hostname() {
return selection.hostname;
}
/**
* @return the selected {@link SslContext}
*/
public SslContext sslContext() {
return selection.context;
}
/**
* The default implementation will simply call {@link AsyncMapping#map(Object, Promise)} but
* users can override this method to implement custom behavior.
*
* @see AsyncMapping#map(Object, Promise)
*/
@Override
protected Future lookup(ChannelHandlerContext ctx, String hostname) throws Exception {
return mapping.map(hostname, ctx.executor().newPromise());
}
@Override
protected final void onLookupComplete(ChannelHandlerContext ctx,
String hostname, Future future) throws Exception {
if (!future.isSuccess()) {
throw new DecoderException("failed to get the SslContext for " + hostname, future.cause());
}
SslContext sslContext = future.getNow();
selection = new Selection(sslContext, hostname);
try {
replaceHandler(ctx, hostname, sslContext);
} catch (Throwable cause) {
selection = EMPTY_SELECTION;
PlatformDependent.throwException(cause);
}
}
/**
* The default implementation of this method will simply replace {@code this} {@link SniHandler}
* instance with a {@link SslHandler}. Users may override this method to implement custom behavior.
*
* Please be aware that this method may get called after a client has already disconnected and
* custom implementations must take it into consideration when overriding this method.
*
* It's also possible for the hostname argument to be {@code null}.
*/
protected void replaceHandler(ChannelHandlerContext ctx, String hostname, SslContext sslContext) throws Exception {
SslHandler sslHandler = null;
try {
sslHandler = sslContext.newHandler(ctx.alloc());
ctx.pipeline().replace(this, SslHandler.class.getName(), sslHandler);
sslHandler = null;
} finally {
// Since the SslHandler was not inserted into the pipeline the ownership of the SSLEngine was not
// transferred to the SslHandler.
// See https://github.nl.topicus.jdbc.shaded.com.nl.topicus.jdbc.shaded.net.y/nl.topicus.jdbc.shaded.net.y/issues/5678
if (sslHandler != null) {
ReferenceCountUtil.safeRelease(sslHandler.engine());
}
}
}
private static final class AsyncMappingAdapter implements AsyncMapping {
private final Mapping super String, ? extends SslContext> mapping;
private AsyncMappingAdapter(Mapping super String, ? extends SslContext> mapping) {
this.mapping = ObjectUtil.checkNotNull(mapping, "mapping");
}
@Override
public Future map(String input, Promise promise) {
final SslContext context;
try {
context = mapping.map(input);
} catch (Throwable cause) {
return promise.setFailure(cause);
}
return promise.setSuccess(context);
}
}
private static final class Selection {
final SslContext context;
final String hostname;
Selection(SslContext context, String hostname) {
this.context = context;
this.hostname = hostname;
}
}
}