1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.core5.http2.impl.nio;
29
30 import java.io.IOException;
31 import java.net.SocketAddress;
32 import java.nio.ByteBuffer;
33 import java.nio.channels.ByteChannel;
34 import java.nio.channels.SelectionKey;
35 import java.util.concurrent.atomic.AtomicReference;
36
37 import javax.net.ssl.SSLSession;
38
39 import org.apache.hc.core5.annotation.Internal;
40 import org.apache.hc.core5.http.EndpointDetails;
41 import org.apache.hc.core5.http.ProtocolVersion;
42 import org.apache.hc.core5.http.impl.nio.BufferedData;
43 import org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler;
44 import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexer;
45 import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexerFactory;
46 import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler;
47 import org.apache.hc.core5.http.nio.command.CommandSupport;
48 import org.apache.hc.core5.http2.HttpVersionPolicy;
49 import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
50 import org.apache.hc.core5.io.CloseMode;
51 import org.apache.hc.core5.io.SocketTimeoutExceptionFactory;
52 import org.apache.hc.core5.reactor.IOSession;
53 import org.apache.hc.core5.reactor.ProtocolIOSession;
54 import org.apache.hc.core5.reactor.ssl.TlsDetails;
55 import org.apache.hc.core5.util.Args;
56 import org.apache.hc.core5.util.Timeout;
57
58
59
60
61
62
63
64
65 @Internal
66 public class ClientHttpProtocolNegotiator implements HttpConnectionEventHandler {
67
68
69 final static byte[] PREFACE = new byte[] {
70 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50,
71 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d,
72 0x0d, 0x0a, 0x0d, 0x0a};
73
74 private final ProtocolIOSession ioSession;
75 private final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory;
76 private final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory;
77 private final HttpVersionPolicy versionPolicy;
78 private final AtomicReference<HttpConnectionEventHandler> protocolHandlerRef;
79
80 private volatile ByteBuffer preface;
81 private volatile BufferedData inBuf;
82
83 public ClientHttpProtocolNegotiator(
84 final ProtocolIOSession ioSession,
85 final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory,
86 final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
87 final HttpVersionPolicy versionPolicy) {
88 this.ioSession = Args.notNull(ioSession, "I/O session");
89 this.http1StreamHandlerFactory = Args.notNull(http1StreamHandlerFactory, "HTTP/1.1 stream handler factory");
90 this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory");
91 this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE;
92 this.protocolHandlerRef = new AtomicReference<>(null);
93 }
94
95 private void startHttp1(final IOSession session) {
96 final ClientHttp1StreamDuplexer http1StreamHandler = http1StreamHandlerFactory.create(ioSession);
97 final HttpConnectionEventHandler protocolHandler = new ClientHttp1IOEventHandler(http1StreamHandler);
98 try {
99 ioSession.upgrade(protocolHandler);
100 protocolHandlerRef.set(protocolHandler);
101 protocolHandler.connected(session);
102 if (inBuf != null) {
103 protocolHandler.inputReady(session, inBuf.data());
104 inBuf.clear();
105 }
106 } catch (final Exception ex) {
107 protocolHandler.exception(session, ex);
108 session.close(CloseMode.IMMEDIATE);
109 }
110 }
111
112 private void startHttp2(final IOSession session) {
113 final ClientH2StreamMultiplexer streamMultiplexer = http2StreamHandlerFactory.create(ioSession);
114 final HttpConnectionEventHandler protocolHandler = new ClientH2IOEventHandler(streamMultiplexer);
115 try {
116 ioSession.upgrade(protocolHandler);
117 protocolHandlerRef.set(protocolHandler);
118 protocolHandler.connected(session);
119 if (inBuf != null) {
120 protocolHandler.inputReady(session, inBuf.data());
121 inBuf.clear();
122 }
123 } catch (final Exception ex) {
124 protocolHandler.exception(session, ex);
125 session.close(CloseMode.IMMEDIATE);
126 }
127 }
128
129 private void writeOutPreface(final IOSession session) throws IOException {
130 if (preface.hasRemaining()) {
131 final ByteChannel channel = session;
132 channel.write(preface);
133 }
134 if (!preface.hasRemaining()) {
135 session.clearEvent(SelectionKey.OP_WRITE);
136 startHttp2(session);
137 } else {
138 session.setEvent(SelectionKey.OP_WRITE);
139 }
140 }
141
142 @Override
143 public void connected(final IOSession session) throws IOException {
144 switch (versionPolicy) {
145 case NEGOTIATE:
146 final TlsDetails tlsDetails = ioSession.getTlsDetails();
147 if (tlsDetails != null) {
148 if (ApplicationProtocol.HTTP_2.id.equals(tlsDetails.getApplicationProtocol())) {
149
150 preface = ByteBuffer.wrap(PREFACE);
151 }
152 }
153 break;
154 case FORCE_HTTP_2:
155 preface = ByteBuffer.wrap(PREFACE);
156 break;
157 }
158 if (preface == null) {
159 startHttp1(session);
160 } else {
161 writeOutPreface(session);
162 }
163 }
164
165 @Override
166 public void inputReady(final IOSession session, final ByteBuffer src) throws IOException {
167 if (src != null) {
168 if (inBuf == null) {
169 inBuf = BufferedData.allocate(src.remaining());
170 }
171 inBuf.put(src);
172 }
173 outputReady(session);
174 }
175
176 @Override
177 public void outputReady(final IOSession session) throws IOException {
178 if (preface != null) {
179 writeOutPreface(session);
180 } else {
181 session.close(CloseMode.GRACEFUL);
182 }
183 }
184
185 @Override
186 public void timeout(final IOSession session, final Timeout timeout) {
187 exception(session, SocketTimeoutExceptionFactory.create(timeout));
188 }
189
190 @Override
191 public void exception(final IOSession session, final Exception cause) {
192 session.close(CloseMode.IMMEDIATE);
193 final HttpConnectionEventHandler protocolHandler = protocolHandlerRef.get();
194 if (protocolHandler != null) {
195 protocolHandler.exception(session, cause);
196 } else {
197 CommandSupport.failCommands(session, cause);
198 }
199 }
200
201 @Override
202 public void disconnected(final IOSession session) {
203 final HttpConnectionEventHandler protocolHandler = protocolHandlerRef.getAndSet(null);
204 if (protocolHandler != null) {
205 protocolHandler.disconnected(ioSession);
206 } else {
207 CommandSupport.cancelCommands(session);
208 }
209 }
210
211 @Override
212 public SSLSession getSSLSession() {
213 final TlsDetails tlsDetails = ioSession.getTlsDetails();
214 return tlsDetails != null ? tlsDetails.getSSLSession() : null;
215 }
216
217 @Override
218 public EndpointDetails getEndpointDetails() {
219 final HttpConnectionEventHandler protocolHandler = protocolHandlerRef.get();
220 return protocolHandler != null ? protocolHandler.getEndpointDetails() : null;
221 }
222
223 @Override
224 public void setSocketTimeout(final Timeout timeout) {
225 ioSession.setSocketTimeout(timeout);
226 }
227
228 @Override
229 public Timeout getSocketTimeout() {
230 return ioSession.getSocketTimeout();
231 }
232
233 @Override
234 public ProtocolVersion getProtocolVersion() {
235 final HttpConnectionEventHandler protocolHandler = protocolHandlerRef.get();
236 return protocolHandler != null ? protocolHandler.getProtocolVersion() : null;
237 }
238
239 @Override
240 public SocketAddress getRemoteAddress() {
241 return ioSession.getRemoteAddress();
242 }
243
244 @Override
245 public SocketAddress getLocalAddress() {
246 return ioSession.getLocalAddress();
247 }
248
249 @Override
250 public boolean isOpen() {
251 return ioSession.isOpen();
252 }
253
254 @Override
255 public void close() throws IOException {
256 ioSession.close();
257 }
258
259 @Override
260 public void close(final CloseMode closeMode) {
261 ioSession.close(closeMode);
262 }
263
264 @Override
265 public String toString() {
266 return "[" +
267 "versionPolicy=" + versionPolicy +
268 ']';
269 }
270
271 }