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.ProtocolVersion;
39 import org.apache.hc.core5.http.impl.nio.BufferedData;
40 import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
41 import org.apache.hc.core5.reactor.IOSession;
42 import org.apache.hc.core5.reactor.ProtocolIOSession;
43 import org.apache.hc.core5.reactor.ssl.TlsDetails;
44 import org.apache.hc.core5.util.Args;
45 import org.apache.hc.core5.util.TextUtils;
46
47
48
49
50
51
52
53
54 @Internal
55 public class H2OnlyClientProtocolNegotiator extends ProtocolNegotiatorBase {
56
57 private final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory;
58 private final boolean strictALPNHandshake;
59 private final AtomicBoolean initialized;
60
61 private volatile ByteBuffer preface;
62 private volatile BufferedData inBuf;
63
64 public H2OnlyClientProtocolNegotiator(
65 final ProtocolIOSession ioSession,
66 final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
67 final boolean strictALPNHandshake) {
68 this(ioSession, http2StreamHandlerFactory, strictALPNHandshake, null);
69 }
70
71
72
73
74 public H2OnlyClientProtocolNegotiator(
75 final ProtocolIOSession ioSession,
76 final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
77 final boolean strictALPNHandshake,
78 final FutureCallback<ProtocolIOSession> resultCallback) {
79 super(ioSession, resultCallback);
80 this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory");
81 this.strictALPNHandshake = strictALPNHandshake;
82 this.initialized = new AtomicBoolean();
83 }
84
85 private void initialize() throws IOException {
86 final TlsDetails tlsDetails = ioSession.getTlsDetails();
87 if (tlsDetails != null) {
88 final String applicationProtocol = tlsDetails.getApplicationProtocol();
89 if (TextUtils.isEmpty(applicationProtocol)) {
90 if (strictALPNHandshake) {
91 throw new ProtocolNegotiationException("ALPN: missing application protocol");
92 }
93 } else {
94 if (!ApplicationProtocol.HTTP_2.id.equals(applicationProtocol)) {
95 throw new ProtocolNegotiationException("ALPN: unexpected application protocol '" + applicationProtocol + "'");
96 }
97 }
98 }
99 this.preface = ByteBuffer.wrap(ClientHttpProtocolNegotiator.PREFACE);
100 ioSession.setEvent(SelectionKey.OP_WRITE);
101 }
102
103 private void writeOutPreface(final IOSession session) throws IOException {
104 if (preface.hasRemaining()) {
105 session.write(preface);
106 }
107 if (!preface.hasRemaining()) {
108 session.clearEvent(SelectionKey.OP_WRITE);
109 final ByteBuffer data = inBuf != null ? inBuf.data() : null;
110 startProtocol(HttpVersion.HTTP_2, new ClientH2IOEventHandler(http2StreamHandlerFactory.create(ioSession)), data);
111 preface = null;
112 }
113 }
114
115 @Override
116 public void connected(final IOSession session) throws IOException {
117 if (initialized.compareAndSet(false, true)) {
118 initialize();
119 }
120 }
121
122 @Override
123 public void outputReady(final IOSession session) throws IOException {
124 if (initialized.compareAndSet(false, true)) {
125 initialize();
126 }
127 if (preface != null) {
128 writeOutPreface(session);
129 } else {
130 throw new ProtocolNegotiationException("Unexpected output");
131 }
132 }
133
134 @Override
135 public void inputReady(final IOSession session, final ByteBuffer src) throws IOException {
136 if (src != null) {
137 if (inBuf == null) {
138 inBuf = BufferedData.allocate(src.remaining());
139 }
140 inBuf.put(src);
141 }
142 if (preface != null) {
143 writeOutPreface(session);
144 } else {
145 throw new ProtocolNegotiationException("Unexpected input");
146 }
147 }
148
149 @Override
150 public ProtocolVersion getProtocolVersion() {
151 final ProtocolVersion protocolVersion = super.getProtocolVersion();
152 return protocolVersion != null ? protocolVersion : HttpVersion.HTTP_2;
153 }
154
155 @Override
156 public String toString() {
157 return getClass().getName();
158 }
159
160 }