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