1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty5.handler.ssl;
17
18 import io.netty5.buffer.api.BufferAllocator;
19 import io.netty5.channel.ChannelHandlerContext;
20 import io.netty5.handler.codec.DecoderException;
21 import io.netty5.util.AsyncMapping;
22 import io.netty5.util.DomainNameMapping;
23 import io.netty5.util.Mapping;
24 import io.netty5.util.concurrent.Future;
25 import io.netty5.util.concurrent.Promise;
26 import io.netty5.util.internal.SilentDispose;
27 import io.netty5.util.internal.logging.InternalLogger;
28 import io.netty5.util.internal.logging.InternalLoggerFactory;
29
30 import static java.util.Objects.requireNonNull;
31
32
33
34
35
36
37
38
39 public class SniHandler extends AbstractSniHandler<SslContext> {
40 private static final InternalLogger logger = InternalLoggerFactory.getInstance(SniHandler.class);
41 private static final Selection EMPTY_SELECTION = new Selection(null, null);
42
43 protected final AsyncMapping<String, SslContext> mapping;
44
45 private volatile Selection selection = EMPTY_SELECTION;
46
47
48
49
50
51
52
53 public SniHandler(Mapping<? super String, ? extends SslContext> mapping) {
54 this(new AsyncMappingAdapter(mapping));
55 }
56
57
58
59
60
61
62
63 public SniHandler(DomainNameMapping<? extends SslContext> mapping) {
64 this((Mapping<String, ? extends SslContext>) mapping);
65 }
66
67
68
69
70
71
72
73 @SuppressWarnings("unchecked")
74 public SniHandler(AsyncMapping<? super String, ? extends SslContext> mapping) {
75 this.mapping = (AsyncMapping<String, SslContext>) requireNonNull(mapping, "mapping");
76 }
77
78
79
80
81 public String hostname() {
82 return selection.hostname;
83 }
84
85
86
87
88 public SslContext sslContext() {
89 return selection.context;
90 }
91
92
93
94
95
96
97
98 @Override
99 protected Future<SslContext> lookup(ChannelHandlerContext ctx, String hostname) throws Exception {
100 return mapping.map(hostname, ctx.executor().newPromise());
101 }
102
103 @Override
104 protected final void onLookupComplete(ChannelHandlerContext ctx,
105 String hostname, Future<? extends SslContext> future) throws Exception {
106 if (future.isFailed()) {
107 final Throwable cause = future.cause();
108 if (cause instanceof Error) {
109 throw (Error) cause;
110 }
111 throw new DecoderException("failed to get the SslContext for " + hostname, cause);
112 }
113
114 SslContext sslContext = future.getNow();
115 selection = new Selection(sslContext, hostname);
116 try {
117 replaceHandler(ctx, hostname, sslContext);
118 } catch (Throwable cause) {
119 selection = EMPTY_SELECTION;
120 throw cause;
121 }
122 }
123
124
125
126
127
128
129
130
131
132
133 protected void replaceHandler(ChannelHandlerContext ctx, String hostname, SslContext sslContext) throws Exception {
134 SslHandler sslHandler = null;
135 try {
136 sslHandler = newSslHandler(sslContext, ctx.bufferAllocator());
137 ctx.pipeline().replace(this, SslHandler.class.getName(), sslHandler);
138 sslHandler = null;
139 } finally {
140
141
142
143 if (sslHandler != null) {
144 SilentDispose.dispose(sslHandler.engine(), logger);
145 }
146 }
147 }
148
149
150
151
152
153 protected SslHandler newSslHandler(SslContext context, BufferAllocator allocator) {
154 return context.newHandler(allocator);
155 }
156
157 private static final class AsyncMappingAdapter implements AsyncMapping<String, SslContext> {
158 private final Mapping<? super String, ? extends SslContext> mapping;
159
160 private AsyncMappingAdapter(Mapping<? super String, ? extends SslContext> mapping) {
161 this.mapping = requireNonNull(mapping, "mapping");
162 }
163
164 @Override
165 public Future<SslContext> map(String input, Promise<SslContext> promise) {
166 final SslContext context;
167 try {
168 context = mapping.map(input);
169 } catch (Throwable cause) {
170 return promise.setFailure(cause).asFuture();
171 }
172 return promise.setSuccess(context).asFuture();
173 }
174 }
175
176 private static final class Selection {
177 final SslContext context;
178 final String hostname;
179
180 Selection(SslContext context, String hostname) {
181 this.context = context;
182 this.hostname = hostname;
183 }
184 }
185 }