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