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.util.concurrent.atomic.AtomicBoolean;
34  import java.util.concurrent.atomic.AtomicReference;
35  
36  import javax.net.ssl.SSLSession;
37  
38  import org.apache.hc.core5.annotation.Internal;
39  import org.apache.hc.core5.concurrent.FutureCallback;
40  import org.apache.hc.core5.http.ConnectionClosedException;
41  import org.apache.hc.core5.http.EndpointDetails;
42  import org.apache.hc.core5.http.HttpVersion;
43  import org.apache.hc.core5.http.ProtocolVersion;
44  import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler;
45  import org.apache.hc.core5.http.nio.command.CommandSupport;
46  import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
47  import org.apache.hc.core5.io.CloseMode;
48  import org.apache.hc.core5.io.SocketTimeoutExceptionFactory;
49  import org.apache.hc.core5.reactor.IOSession;
50  import org.apache.hc.core5.reactor.ProtocolIOSession;
51  import org.apache.hc.core5.reactor.ssl.TlsDetails;
52  import org.apache.hc.core5.util.Args;
53  import org.apache.hc.core5.util.TextUtils;
54  import org.apache.hc.core5.util.Timeout;
55  
56  /**
57   * @since 5.2
58   */
59  @Internal
60  public class HttpProtocolNegotiator implements HttpConnectionEventHandler {
61  
62      private final ProtocolIOSession ioSession;
63      private final FutureCallback<ProtocolIOSession> resultCallback;
64      private final AtomicBoolean completed;
65      private final AtomicReference<ProtocolVersion> negotiatedProtocolRef;
66  
67      public HttpProtocolNegotiator(
68              final ProtocolIOSession ioSession,
69              final FutureCallback<ProtocolIOSession> resultCallback) {
70          this.ioSession = Args.notNull(ioSession, "I/O session");
71          this.resultCallback = resultCallback;
72          this.completed = new AtomicBoolean();
73          this.negotiatedProtocolRef = new AtomicReference<>();
74      }
75  
76      void startProtocol(final HttpVersion httpVersion) {
77          ioSession.switchProtocol(
78                  httpVersion == HttpVersion.HTTP_2 ? ApplicationProtocol.HTTP_2.id : ApplicationProtocol.HTTP_1_1.id,
79                  resultCallback);
80          negotiatedProtocolRef.set(httpVersion);
81      }
82  
83      @Override
84      public void connected(final IOSession session) throws IOException {
85          final HttpVersion httpVersion;
86          final TlsDetails tlsDetails = ioSession.getTlsDetails();
87          if (tlsDetails != null) {
88              final String appProtocol = tlsDetails.getApplicationProtocol();
89              if (TextUtils.isEmpty(appProtocol)) {
90                  httpVersion = HttpVersion.HTTP_1_1;
91              } else if (appProtocol.equals(ApplicationProtocol.HTTP_1_1.id)) {
92                  httpVersion = HttpVersion.HTTP_1_1;
93              } else if (appProtocol.equals(ApplicationProtocol.HTTP_2.id)) {
94                  httpVersion = HttpVersion.HTTP_2;
95              } else {
96                  throw new ProtocolNegotiationException("Unsupported application protocol: " + appProtocol);
97              }
98          } else {
99              httpVersion = HttpVersion.HTTP_1_1;
100         }
101         startProtocol(httpVersion);
102     }
103     @Override
104     public void inputReady(final IOSession session, final ByteBuffer src) throws IOException  {
105         throw new ProtocolNegotiationException("Unexpected input");
106     }
107 
108     @Override
109     public void outputReady(final IOSession session) throws IOException {
110         throw new ProtocolNegotiationException("Unexpected output");
111     }
112 
113     @Override
114     public void timeout(final IOSession session, final Timeout timeout) {
115         exception(session, SocketTimeoutExceptionFactory.create(timeout));
116     }
117 
118     @Override
119     public void exception(final IOSession session, final Exception cause) {
120         try {
121             session.close(CloseMode.IMMEDIATE);
122             CommandSupport.failCommands(session, cause);
123         } catch (final Exception ex) {
124             if (completed.compareAndSet(false, true) && resultCallback != null) {
125                 resultCallback.failed(ex);
126             }
127         }
128     }
129 
130     @Override
131     public void disconnected(final IOSession session) {
132         try {
133             CommandSupport.cancelCommands(session);
134         } finally {
135             if (completed.compareAndSet(false, true) && resultCallback != null) {
136                 resultCallback.failed(new ConnectionClosedException());
137             }
138         }
139     }
140 
141     @Override
142     public SSLSession getSSLSession() {
143         final TlsDetails tlsDetails = ioSession.getTlsDetails();
144         return tlsDetails != null ? tlsDetails.getSSLSession() : null;
145     }
146 
147     @Override
148     public EndpointDetails getEndpointDetails() {
149         return null;
150     }
151 
152     @Override
153     public void setSocketTimeout(final Timeout timeout) {
154         ioSession.setSocketTimeout(timeout);
155     }
156 
157     @Override
158     public Timeout getSocketTimeout() {
159         return ioSession.getSocketTimeout();
160     }
161 
162     @Override
163     public ProtocolVersion getProtocolVersion() {
164         return negotiatedProtocolRef.get();
165     }
166 
167     @Override
168     public SocketAddress getRemoteAddress() {
169         return ioSession.getRemoteAddress();
170     }
171 
172     @Override
173     public SocketAddress getLocalAddress() {
174         return ioSession.getLocalAddress();
175     }
176 
177     @Override
178     public boolean isOpen() {
179         return ioSession.isOpen();
180     }
181 
182     @Override
183     public void close() throws IOException {
184         ioSession.close();
185     }
186 
187     @Override
188     public void close(final CloseMode closeMode) {
189         ioSession.close(closeMode);
190     }
191 
192     @Override
193     public String toString() {
194         return getClass().getName();
195     }
196 
197 }