View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.http2.impl.nio;
29  
30  import java.io.IOException;
31  import java.net.SocketAddress;
32  import java.nio.ByteBuffer;
33  import java.nio.channels.ByteChannel;
34  
35  import javax.net.ssl.SSLSession;
36  
37  import org.apache.hc.core5.annotation.Internal;
38  import org.apache.hc.core5.http.EndpointDetails;
39  import org.apache.hc.core5.http.HttpException;
40  import org.apache.hc.core5.http.ProtocolVersion;
41  import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler;
42  import org.apache.hc.core5.http.nio.command.CommandSupport;
43  import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
44  import org.apache.hc.core5.io.CloseMode;
45  import org.apache.hc.core5.io.SocketTimeoutExceptionFactory;
46  import org.apache.hc.core5.reactor.IOEventHandler;
47  import org.apache.hc.core5.reactor.IOSession;
48  import org.apache.hc.core5.reactor.ProtocolIOSession;
49  import org.apache.hc.core5.reactor.ssl.TlsDetails;
50  import org.apache.hc.core5.util.Args;
51  import org.apache.hc.core5.util.TextUtils;
52  import org.apache.hc.core5.util.Timeout;
53  
54  /**
55   * I/O event handler for events fired by {@link ProtocolIOSession} that implements
56   * client side of the HTTP/2 protocol negotiation handshake always forcing the choice
57   * of HTTP/2.
58   *
59   * @since 5.0
60   */
61  @Internal
62  public class H2OnlyClientProtocolNegotiator implements HttpConnectionEventHandler {
63  
64      private final ProtocolIOSession ioSession;
65      private final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory;
66      private final boolean strictALPNHandshake;
67  
68      private final ByteBuffer preface;
69  
70      public H2OnlyClientProtocolNegotiator(
71              final ProtocolIOSession ioSession,
72              final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
73              final boolean strictALPNHandshake) {
74          this.ioSession = Args.notNull(ioSession, "I/O session");
75          this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory");
76          this.strictALPNHandshake = strictALPNHandshake;
77          this.preface = ByteBuffer.wrap(ClientHttpProtocolNegotiator.PREFACE);
78      }
79  
80      @Override
81      public void connected(final IOSession session) {
82          try {
83              final TlsDetails tlsDetails = ioSession.getTlsDetails();
84              if (tlsDetails != null) {
85                  final String applicationProtocol = tlsDetails.getApplicationProtocol();
86                  if (TextUtils.isEmpty(applicationProtocol)) {
87                      if (strictALPNHandshake) {
88                          throw new HttpException("ALPN: missing application protocol");
89                      }
90                  } else {
91                      if (!ApplicationProtocol.HTTP_2.id.equals(applicationProtocol)) {
92                          throw new HttpException("ALPN: unexpected application protocol '" + applicationProtocol + "'");
93                      }
94                  }
95              }
96              writePreface(session);
97          } catch (final Exception ex) {
98              session.close(CloseMode.IMMEDIATE);
99              exception(session, ex);
100         }
101     }
102 
103     private void writePreface(final IOSession session) throws IOException  {
104         if (preface.hasRemaining()) {
105             final ByteChannel channel = session;
106             channel.write(preface);
107         }
108         if (!preface.hasRemaining()) {
109             final ClientH2StreamMultiplexer streamMultiplexer = http2StreamHandlerFactory.create(ioSession);
110             final IOEventHandler newHandler = new ClientH2IOEventHandler(streamMultiplexer);
111             newHandler.connected(session);
112             ioSession.upgrade(newHandler);
113         }
114     }
115 
116     @Override
117     public void inputReady(final IOSession session, final ByteBuffer src) {
118         outputReady(session);
119     }
120 
121     @Override
122     public void outputReady(final IOSession session) {
123         try {
124             if (preface != null) {
125                 writePreface(session);
126             } else {
127                 session.close(CloseMode.GRACEFUL);
128             }
129         } catch (final IOException ex) {
130             session.close(CloseMode.IMMEDIATE);
131             exception(session, ex);
132         }
133     }
134 
135     @Override
136     public void timeout(final IOSession session, final Timeout timeout) {
137         exception(session, SocketTimeoutExceptionFactory.create(timeout));
138     }
139 
140     @Override
141     public void exception(final IOSession session, final Exception cause) {
142         try {
143             CommandSupport.failCommands(session, cause);
144         } finally {
145             session.close(CloseMode.IMMEDIATE);
146         }
147     }
148 
149     @Override
150     public void disconnected(final IOSession session) {
151         CommandSupport.cancelCommands(session);
152     }
153 
154     @Override
155     public SSLSession getSSLSession() {
156         final TlsDetails tlsDetails = ioSession.getTlsDetails();
157         return tlsDetails != null ? tlsDetails.getSSLSession() : null;
158     }
159 
160     @Override
161     public EndpointDetails getEndpointDetails() {
162         return null;
163     }
164 
165     @Override
166     public void setSocketTimeout(final Timeout timeout) {
167         ioSession.setSocketTimeout(timeout);
168     }
169 
170     @Override
171     public Timeout getSocketTimeout() {
172         return ioSession.getSocketTimeout();
173     }
174 
175     @Override
176     public ProtocolVersion getProtocolVersion() {
177         return null;
178     }
179 
180     @Override
181     public SocketAddress getRemoteAddress() {
182         return ioSession.getRemoteAddress();
183     }
184 
185     @Override
186     public SocketAddress getLocalAddress() {
187         return ioSession.getLocalAddress();
188     }
189 
190     @Override
191     public boolean isOpen() {
192         return ioSession.isOpen();
193     }
194 
195     @Override
196     public void close() throws IOException {
197         ioSession.close();
198     }
199 
200     @Override
201     public void close(final CloseMode closeMode) {
202         ioSession.close(closeMode);
203     }
204 
205 }