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.util.concurrent.atomic.AtomicBoolean;
33
34 import org.apache.hc.core5.annotation.Internal;
35 import org.apache.hc.core5.concurrent.FutureCallback;
36 import org.apache.hc.core5.http.ConnectionClosedException;
37 import org.apache.hc.core5.http.URIScheme;
38 import org.apache.hc.core5.http.impl.nio.BufferedData;
39 import org.apache.hc.core5.http.impl.nio.ServerHttp1IOEventHandler;
40 import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexer;
41 import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexerFactory;
42 import org.apache.hc.core5.http2.HttpVersionPolicy;
43 import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
44 import org.apache.hc.core5.reactor.IOSession;
45 import org.apache.hc.core5.reactor.ProtocolIOSession;
46 import org.apache.hc.core5.reactor.ssl.TlsDetails;
47 import org.apache.hc.core5.util.Args;
48
49
50
51
52
53
54
55
56 @Internal
57 public class ServerHttpProtocolNegotiator extends ProtocolNegotiatorBase {
58
59 final static byte[] PREFACE = ClientHttpProtocolNegotiator.PREFACE;
60
61 private final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory;
62 private final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory;
63 private final HttpVersionPolicy versionPolicy;
64 private final BufferedData inBuf;
65 private final AtomicBoolean initialized;
66
67 private volatile boolean expectValidH2Preface;
68
69 public ServerHttpProtocolNegotiator(
70 final ProtocolIOSession ioSession,
71 final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory,
72 final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory,
73 final HttpVersionPolicy versionPolicy) {
74 this(ioSession, http1StreamHandlerFactory, http2StreamHandlerFactory, versionPolicy, null);
75 }
76
77
78
79
80 public ServerHttpProtocolNegotiator(
81 final ProtocolIOSession ioSession,
82 final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory,
83 final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory,
84 final HttpVersionPolicy versionPolicy,
85 final FutureCallback<ProtocolIOSession> resultCallback) {
86 super(ioSession, resultCallback);
87 this.http1StreamHandlerFactory = Args.notNull(http1StreamHandlerFactory, "HTTP/1.1 stream handler factory");
88 this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory");
89 this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE;
90 this.inBuf = BufferedData.allocate(1024);
91 this.initialized = new AtomicBoolean();
92 }
93
94 private void startHttp1(final TlsDetails tlsDetails, final ByteBuffer data) throws IOException {
95 final ServerHttp1StreamDuplexer http1StreamHandler = http1StreamHandlerFactory.create(
96 tlsDetails != null ? URIScheme.HTTPS.id : URIScheme.HTTP.id,
97 ioSession);
98 startProtocol(new ServerHttp1IOEventHandler(http1StreamHandler), data);
99 }
100
101 private void startHttp2(final ByteBuffer data) throws IOException {
102 startProtocol(new ServerH2IOEventHandler(http2StreamHandlerFactory.create(ioSession)), data);
103 }
104
105 private void initialize() throws IOException {
106 final TlsDetails tlsDetails = ioSession.getTlsDetails();
107 switch (versionPolicy) {
108 case NEGOTIATE:
109 if (tlsDetails != null &&
110 ApplicationProtocol.HTTP_2.id.equals(tlsDetails.getApplicationProtocol())) {
111 expectValidH2Preface = true;
112 }
113 break;
114 case FORCE_HTTP_2:
115 if (tlsDetails == null ||
116 !ApplicationProtocol.HTTP_1_1.id.equals(tlsDetails.getApplicationProtocol())) {
117 expectValidH2Preface = true;
118 }
119 break;
120 case FORCE_HTTP_1:
121 startHttp1(tlsDetails, null);
122 break;
123 }
124 }
125
126 @Override
127 public void connected(final IOSession session) throws IOException {
128 if (initialized.compareAndSet(false, true)) {
129 initialize();
130 }
131 }
132
133 @Override
134 public void inputReady(final IOSession session, final ByteBuffer src) throws IOException {
135 if (src != null) {
136 inBuf.put(src);
137 }
138 boolean endOfStream = false;
139 if (inBuf.length() < PREFACE.length) {
140 final int bytesRead = inBuf.readFrom(session);
141 if (bytesRead == -1) {
142 endOfStream = true;
143 }
144 }
145 final ByteBuffer data = inBuf.data();
146 if (data.remaining() >= PREFACE.length) {
147 boolean validH2Preface = true;
148 for (int i = 0; i < PREFACE.length; i++) {
149 if (data.get() != PREFACE[i]) {
150 if (expectValidH2Preface) {
151 throw new ProtocolNegotiationException("Unexpected HTTP/2 preface");
152 }
153 validH2Preface = false;
154 }
155 }
156 if (validH2Preface) {
157 startHttp2(data.hasRemaining() ? data : null);
158 } else {
159 data.rewind();
160 startHttp1(ioSession.getTlsDetails(), data);
161 }
162 } else {
163 if (endOfStream) {
164 throw new ConnectionClosedException();
165 }
166 }
167 }
168
169 @Override
170 public void outputReady(final IOSession session) throws IOException {
171 if (initialized.compareAndSet(false, true)) {
172 initialize();
173 }
174 }
175
176 @Override
177 public String toString() {
178 return getClass().getName() + "/" + versionPolicy;
179 }
180
181 }