17
17
package org .apache .dubbo .remoting .transport .netty4 ;
18
18
19
19
import org .apache .dubbo .common .URL ;
20
+ import org .apache .dubbo .common .Version ;
21
+ import org .apache .dubbo .common .utils .NetUtils ;
20
22
import org .apache .dubbo .remoting .ChannelHandler ;
21
23
import org .apache .dubbo .remoting .Constants ;
22
24
import org .apache .dubbo .remoting .RemotingException ;
23
25
import org .apache .dubbo .remoting .api .WireProtocol ;
26
+ import org .apache .dubbo .remoting .transport .netty4 .http2 .Http2ClientSettingsHandler ;
24
27
import org .apache .dubbo .remoting .transport .netty4 .ssl .SslClientTlsHandler ;
25
28
import org .apache .dubbo .remoting .transport .netty4 .ssl .SslContexts ;
26
29
import org .apache .dubbo .remoting .utils .UrlUtils ;
27
30
31
+ import java .util .concurrent .TimeUnit ;
32
+ import java .util .concurrent .atomic .AtomicReference ;
33
+
28
34
import io .netty .bootstrap .Bootstrap ;
29
35
import io .netty .buffer .PooledByteBufAllocator ;
30
36
import io .netty .channel .ChannelFuture ;
37
+ import io .netty .channel .ChannelHandlerContext ;
31
38
import io .netty .channel .ChannelInitializer ;
32
39
import io .netty .channel .ChannelOption ;
33
40
import io .netty .channel .ChannelPipeline ;
34
41
import io .netty .channel .socket .SocketChannel ;
42
+ import io .netty .handler .codec .http2 .Http2FrameCodec ;
35
43
import io .netty .handler .ssl .SslContext ;
36
44
import io .netty .handler .timeout .IdleStateHandler ;
45
+ import io .netty .util .concurrent .DefaultPromise ;
46
+ import io .netty .util .concurrent .GlobalEventExecutor ;
47
+ import io .netty .util .concurrent .Promise ;
37
48
38
49
import static java .util .concurrent .TimeUnit .MILLISECONDS ;
50
+ import static org .apache .dubbo .common .constants .LoggerCodeConstants .TRANSPORT_CLIENT_CONNECT_TIMEOUT ;
39
51
import static org .apache .dubbo .remoting .transport .netty4 .NettyEventLoopFactory .socketChannelClass ;
40
52
41
53
public final class NettyConnectionClient extends AbstractNettyConnectionClient {
42
54
43
55
private Bootstrap bootstrap ;
44
56
57
+ private AtomicReference <Promise <Void >> connectionPrefaceReceivedPromiseRef ;
58
+
45
59
public NettyConnectionClient (URL url , ChannelHandler handler ) throws RemotingException {
46
60
super (url , handler );
47
61
}
@@ -87,6 +101,16 @@ protected void initChannel(SocketChannel ch) {
87
101
88
102
NettyConfigOperator operator = new NettyConfigOperator (nettyChannel , getChannelHandler ());
89
103
protocol .configClientPipeline (getUrl (), operator , nettySslContextOperator );
104
+
105
+ ChannelHandlerContext http2FrameCodecHandlerCtx = pipeline .context (Http2FrameCodec .class );
106
+ if (http2FrameCodecHandlerCtx != null ) {
107
+ connectionPrefaceReceivedPromiseRef = new AtomicReference <>();
108
+ pipeline .addAfter (
109
+ http2FrameCodecHandlerCtx .name (),
110
+ "client-connection-preface-handler" ,
111
+ new Http2ClientSettingsHandler (connectionPrefaceReceivedPromiseRef ));
112
+ }
113
+
90
114
// set null but do not close this client, it will be reconnecting in the future
91
115
ch .closeFuture ().addListener (channelFuture -> clearNettyChannel ());
92
116
// TODO support Socks5
@@ -97,6 +121,71 @@ protected void initChannel(SocketChannel ch) {
97
121
98
122
@ Override
99
123
protected ChannelFuture performConnect () {
124
+ if (connectionPrefaceReceivedPromiseRef != null ) {
125
+ connectionPrefaceReceivedPromiseRef .compareAndSet (null , new DefaultPromise <>(GlobalEventExecutor .INSTANCE ));
126
+ }
100
127
return bootstrap .connect ();
101
128
}
129
+
130
+ @ Override
131
+ protected void doConnect () throws RemotingException {
132
+ long start = System .currentTimeMillis ();
133
+ super .doConnect ();
134
+ waitConnectionPreface (start );
135
+ }
136
+
137
+ /**
138
+ * Wait connection preface
139
+ * <br>
140
+ * Http2 client should set max header list size of http2 encoder based on server connection preface before
141
+ * sending first data frame, otherwise the http2 server might send back GO_AWAY frame and disconnect the connection
142
+ * immediately if the size of client Headers frame is bigger than the MAX_HEADER_LIST_SIZE of server settings.<br>
143
+ * @see <a href="https://httpwg.org/specs/rfc7540.html#ConnectionHeader">HTTP/2 Connection Preface</a><br>
144
+ * In HTTP/2, each endpoint is required to send a connection preface as a final confirmation of the protocol
145
+ * in use and to establish the initial settings for the HTTP/2 connection. The client and server each send a
146
+ * different connection preface. The client connection preface starts with a sequence of 24 octets,
147
+ * which in hex notation is:<br>
148
+ * 0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a<br>
149
+ * That is, the connection preface starts with the string PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n<br>
150
+ * This sequence MUST be followed by a SETTINGS frame (Section 6.5), which MAY be empty.
151
+ * The server connection preface consists of a potentially empty SETTINGS frame (Section 6.5) that MUST be
152
+ * the first frame the server sends in the HTTP/2 connection.
153
+ *
154
+ * @param start start time of doConnect in milliseconds.
155
+ */
156
+ private void waitConnectionPreface (long start ) throws RemotingException {
157
+ if (connectionPrefaceReceivedPromiseRef == null ) {
158
+ return ;
159
+ }
160
+ Promise <Void > connectionPrefaceReceivedPromise = connectionPrefaceReceivedPromiseRef .get ();
161
+ if (connectionPrefaceReceivedPromise != null ) {
162
+ long retainedTimeout = getConnectTimeout () - System .currentTimeMillis () + start ;
163
+ boolean ret = connectionPrefaceReceivedPromise .awaitUninterruptibly (retainedTimeout , TimeUnit .MILLISECONDS );
164
+ // Only process once: destroy connectionPrefaceReceivedPromise after used
165
+ synchronized (this ) {
166
+ connectionPrefaceReceivedPromiseRef .set (null );
167
+ }
168
+ if (!ret || !connectionPrefaceReceivedPromise .isSuccess ()) {
169
+ // 6-2 Client-side connection preface timeout
170
+ RemotingException remotingException = new RemotingException (
171
+ this ,
172
+ "client(url: " + getUrl () + ") failed to connect to server " + getConnectAddress ()
173
+ + " client-side connection preface timeout " + getConnectTimeout ()
174
+ + "ms (elapsed: "
175
+ + (System .currentTimeMillis () - start ) + "ms) from netty client "
176
+ + NetUtils .getLocalHost ()
177
+ + " using dubbo version "
178
+ + Version .getVersion ());
179
+
180
+ logger .error (
181
+ TRANSPORT_CLIENT_CONNECT_TIMEOUT ,
182
+ "provider crash" ,
183
+ "" ,
184
+ "Client-side connection preface timeout" ,
185
+ remotingException );
186
+
187
+ throw remotingException ;
188
+ }
189
+ }
190
+ }
102
191
}
0 commit comments