• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.http.nio.netty.internal;
17 
18 import static software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKey.CHANNEL_DIAGNOSTICS;
19 import static software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKey.HTTP2_CONNECTION;
20 import static software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKey.HTTP2_INITIAL_WINDOW_SIZE;
21 import static software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKey.PROTOCOL_FUTURE;
22 import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.HTTP2_CONNECTION_PING_TIMEOUT_SECONDS;
23 import static software.amazon.awssdk.http.nio.netty.internal.utils.NettyUtils.newSslHandler;
24 import static software.amazon.awssdk.utils.NumericUtils.saturatedCast;
25 import static software.amazon.awssdk.utils.StringUtils.lowerCase;
26 
27 import io.netty.buffer.UnpooledByteBufAllocator;
28 import io.netty.channel.Channel;
29 import io.netty.channel.ChannelInitializer;
30 import io.netty.channel.ChannelOption;
31 import io.netty.channel.ChannelPipeline;
32 import io.netty.channel.pool.AbstractChannelPoolHandler;
33 import io.netty.channel.pool.ChannelPool;
34 import io.netty.handler.codec.http.HttpClientCodec;
35 import io.netty.handler.codec.http2.Http2FrameCodec;
36 import io.netty.handler.codec.http2.Http2FrameCodecBuilder;
37 import io.netty.handler.codec.http2.Http2FrameLogger;
38 import io.netty.handler.codec.http2.Http2MultiplexHandler;
39 import io.netty.handler.codec.http2.Http2Settings;
40 import io.netty.handler.logging.LogLevel;
41 import io.netty.handler.logging.LoggingHandler;
42 import io.netty.handler.ssl.SslContext;
43 import io.netty.handler.ssl.SslHandler;
44 import io.netty.handler.ssl.SslProvider;
45 import java.net.URI;
46 import java.time.Duration;
47 import java.util.concurrent.CompletableFuture;
48 import java.util.concurrent.atomic.AtomicReference;
49 import software.amazon.awssdk.annotations.SdkInternalApi;
50 import software.amazon.awssdk.http.Protocol;
51 import software.amazon.awssdk.http.nio.netty.internal.http2.Http2GoAwayEventListener;
52 import software.amazon.awssdk.http.nio.netty.internal.http2.Http2PingHandler;
53 import software.amazon.awssdk.http.nio.netty.internal.http2.Http2SettingsFrameHandler;
54 
55 /**
56  * ChannelPoolHandler to configure the client pipeline.
57  */
58 @SdkInternalApi
59 public final class ChannelPipelineInitializer extends AbstractChannelPoolHandler {
60     private final Protocol protocol;
61     private final SslContext sslCtx;
62     private final SslProvider sslProvider;
63     private final long clientMaxStreams;
64     private final int clientInitialWindowSize;
65     private final Duration healthCheckPingPeriod;
66     private final AtomicReference<ChannelPool> channelPoolRef;
67     private final NettyConfiguration configuration;
68     private final URI poolKey;
69 
ChannelPipelineInitializer(Protocol protocol, SslContext sslCtx, SslProvider sslProvider, long clientMaxStreams, int clientInitialWindowSize, Duration healthCheckPingPeriod, AtomicReference<ChannelPool> channelPoolRef, NettyConfiguration configuration, URI poolKey)70     public ChannelPipelineInitializer(Protocol protocol,
71                                       SslContext sslCtx,
72                                       SslProvider sslProvider,
73                                       long clientMaxStreams,
74                                       int clientInitialWindowSize,
75                                       Duration healthCheckPingPeriod,
76                                       AtomicReference<ChannelPool> channelPoolRef,
77                                       NettyConfiguration configuration,
78                                       URI poolKey) {
79         this.protocol = protocol;
80         this.sslCtx = sslCtx;
81         this.sslProvider = sslProvider;
82         this.clientMaxStreams = clientMaxStreams;
83         this.clientInitialWindowSize = clientInitialWindowSize;
84         this.healthCheckPingPeriod = healthCheckPingPeriod;
85         this.channelPoolRef = channelPoolRef;
86         this.configuration = configuration;
87         this.poolKey = poolKey;
88     }
89 
90     @Override
channelCreated(Channel ch)91     public void channelCreated(Channel ch) {
92         ch.attr(CHANNEL_DIAGNOSTICS).set(new ChannelDiagnostics(ch));
93         ch.attr(PROTOCOL_FUTURE).set(new CompletableFuture<>());
94         ChannelPipeline pipeline = ch.pipeline();
95         if (sslCtx != null) {
96 
97             SslHandler sslHandler = newSslHandler(sslCtx, ch.alloc(), poolKey.getHost(), poolKey.getPort(),
98                                                   configuration.tlsHandshakeTimeout());
99 
100             pipeline.addLast(sslHandler);
101             pipeline.addLast(SslCloseCompletionEventHandler.getInstance());
102 
103             // Use unpooled allocator to avoid increased heap memory usage from Netty 4.1.43.
104             // See https://github.com/netty/netty/issues/9768
105             if (sslProvider == SslProvider.JDK) {
106                 ch.config().setOption(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT);
107             }
108         }
109 
110         if (protocol == Protocol.HTTP2) {
111             configureHttp2(ch, pipeline);
112         } else {
113             configureHttp11(ch, pipeline);
114         }
115 
116         if (configuration.reapIdleConnections()) {
117             pipeline.addLast(new IdleConnectionReaperHandler(configuration.idleTimeoutMillis()));
118         }
119 
120         if (configuration.connectionTtlMillis() > 0) {
121             pipeline.addLast(new OldConnectionReaperHandler(configuration.connectionTtlMillis()));
122         }
123 
124         pipeline.addLast(FutureCancelHandler.getInstance());
125 
126         // Only add it for h1 channel because it does not apply to
127         // h2 connection channel. It will be attached
128         // to stream channels when they are created.
129         if (protocol == Protocol.HTTP1_1) {
130             pipeline.addLast(UnusedChannelExceptionHandler.getInstance());
131         }
132 
133         pipeline.addLast(new LoggingHandler(LogLevel.DEBUG));
134     }
135 
configureHttp2(Channel ch, ChannelPipeline pipeline)136     private void configureHttp2(Channel ch, ChannelPipeline pipeline) {
137         // Using Http2FrameCodecBuilder and Http2MultiplexHandler based on 4.1.37 release notes
138         // https://netty.io/news/2019/06/28/4-1-37-Final.html
139         Http2FrameCodec codec =
140             Http2FrameCodecBuilder.forClient()
141                                   .headerSensitivityDetector((name, value) -> lowerCase(name.toString()).equals("authorization"))
142                                   .initialSettings(Http2Settings.defaultSettings().initialWindowSize(clientInitialWindowSize))
143                                   .frameLogger(new Http2FrameLogger(LogLevel.DEBUG))
144                                   .build();
145 
146         // Connection listeners have higher priority than handlers, in the eyes of the Http2FrameCodec. The Http2FrameCodec will
147         // close any connections when a GOAWAY is received, but we'd like to send a "GOAWAY happened" exception instead of just
148         // closing the connection. Because of this, we use a go-away listener instead of a handler, so that we can send the
149         // exception before the Http2FrameCodec closes the connection itself.
150         codec.connection().addListener(new Http2GoAwayEventListener(ch));
151 
152         pipeline.addLast(codec);
153         ch.attr(HTTP2_CONNECTION).set(codec.connection());
154 
155         ch.attr(HTTP2_INITIAL_WINDOW_SIZE).set(clientInitialWindowSize);
156         pipeline.addLast(new Http2MultiplexHandler(new NoOpChannelInitializer()));
157         pipeline.addLast(new Http2SettingsFrameHandler(ch, clientMaxStreams, channelPoolRef));
158         if (healthCheckPingPeriod == null) {
159             pipeline.addLast(new Http2PingHandler(HTTP2_CONNECTION_PING_TIMEOUT_SECONDS * 1_000));
160         } else if (healthCheckPingPeriod.toMillis() > 0) {
161             pipeline.addLast(new Http2PingHandler(saturatedCast(healthCheckPingPeriod.toMillis())));
162         }
163     }
164 
configureHttp11(Channel ch, ChannelPipeline pipeline)165     private void configureHttp11(Channel ch, ChannelPipeline pipeline) {
166         pipeline.addLast(new HttpClientCodec());
167         ch.attr(PROTOCOL_FUTURE).get().complete(Protocol.HTTP1_1);
168     }
169 
170     private static class NoOpChannelInitializer extends ChannelInitializer<Channel> {
171         @Override
initChannel(Channel ch)172         protected void initChannel(Channel ch) {
173         }
174     }
175 }
176 
177 
178