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.nio.ByteBuffer;
32 import java.nio.channels.SelectionKey;
33 import java.util.concurrent.atomic.AtomicBoolean;
34
35 import org.apache.hc.core5.annotation.Internal;
36 import org.apache.hc.core5.concurrent.FutureCallback;
37 import org.apache.hc.core5.http.impl.nio.BufferedData;
38 import org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler;
39 import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexerFactory;
40 import org.apache.hc.core5.http2.HttpVersionPolicy;
41 import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
42 import org.apache.hc.core5.reactor.IOSession;
43 import org.apache.hc.core5.reactor.ProtocolIOSession;
44 import org.apache.hc.core5.reactor.ssl.TlsDetails;
45 import org.apache.hc.core5.util.Args;
46
47
48
49
50
51
52
53
54 @Internal
55 public class ClientHttpProtocolNegotiator extends ProtocolNegotiatorBase {
56
57
58 final static byte[] PREFACE = new byte[] {
59 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50,
60 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d,
61 0x0d, 0x0a, 0x0d, 0x0a};
62
63 private final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory;
64 private final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory;
65 private final HttpVersionPolicy versionPolicy;
66 private final AtomicBoolean initialized;
67
68 private volatile ByteBuffer preface;
69 private volatile BufferedData inBuf;
70
71 public ClientHttpProtocolNegotiator(
72 final ProtocolIOSession ioSession,
73 final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory,
74 final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
75 final HttpVersionPolicy versionPolicy) {
76 this(ioSession, http1StreamHandlerFactory, http2StreamHandlerFactory, versionPolicy, null);
77 }
78
79
80
81
82 public ClientHttpProtocolNegotiator(
83 final ProtocolIOSession ioSession,
84 final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory,
85 final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
86 final HttpVersionPolicy versionPolicy,
87 final FutureCallback<ProtocolIOSession> resultCallback) {
88 super(ioSession, resultCallback);
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.initialized = new AtomicBoolean();
93 }
94
95 private void startHttp1() throws IOException {
96 final ByteBuffer data = inBuf != null ? inBuf.data() : null;
97 startProtocol(new ClientHttp1IOEventHandler(http1StreamHandlerFactory.create(ioSession)), data);
98 if (inBuf != null) {
99 inBuf.clear();
100 }
101 }
102
103 private void startHttp2() throws IOException {
104 final ByteBuffer data = inBuf != null ? inBuf.data() : null;
105 startProtocol(new ClientH2IOEventHandler(http2StreamHandlerFactory.create(ioSession)), data);
106 if (inBuf != null) {
107 inBuf.clear();
108 }
109 }
110
111 private void initialize() throws IOException {
112 switch (versionPolicy) {
113 case NEGOTIATE:
114 final TlsDetails tlsDetails = ioSession.getTlsDetails();
115 if (tlsDetails != null) {
116 if (ApplicationProtocol.HTTP_2.id.equals(tlsDetails.getApplicationProtocol())) {
117
118 preface = ByteBuffer.wrap(PREFACE);
119 }
120 }
121 break;
122 case FORCE_HTTP_2:
123 preface = ByteBuffer.wrap(PREFACE);
124 break;
125 }
126 if (preface == null) {
127 startHttp1();
128 } else {
129 ioSession.setEvent(SelectionKey.OP_WRITE);
130 }
131 }
132
133 private void writeOutPreface(final IOSession session) throws IOException {
134 if (preface.hasRemaining()) {
135 session.write(preface);
136 }
137 if (!preface.hasRemaining()) {
138 session.clearEvent(SelectionKey.OP_WRITE);
139 startHttp2();
140 preface = null;
141 }
142 }
143
144 @Override
145 public void connected(final IOSession session) throws IOException {
146 if (initialized.compareAndSet(false, true)) {
147 initialize();
148 }
149 if (preface != null) {
150 writeOutPreface(session);
151 }
152 }
153
154 @Override
155 public void inputReady(final IOSession session, final ByteBuffer src) throws IOException {
156 if (src != null) {
157 if (inBuf == null) {
158 inBuf = BufferedData.allocate(src.remaining());
159 }
160 inBuf.put(src);
161 }
162 if (preface != null) {
163 writeOutPreface(session);
164 } else {
165 throw new ProtocolNegotiationException("Unexpected input");
166 }
167 }
168
169 @Override
170 public void outputReady(final IOSession session) throws IOException {
171 if (initialized.compareAndSet(false, true)) {
172 initialize();
173 }
174 if (preface != null) {
175 writeOutPreface(session);
176 } else {
177 throw new ProtocolNegotiationException("Unexpected output");
178 }
179 }
180
181 @Override
182 public String toString() {
183 return getClass().getName() + "/" + versionPolicy;
184 }
185
186 }