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.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.impl.nio.BufferedData;
38  import org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler;
39  import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexerFactory;
40  import org.apache.hc.core5.http2.HttpVersionPolicy;
41  import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
42  import org.apache.hc.core5.reactor.IOSession;
43  import org.apache.hc.core5.reactor.ProtocolIOSession;
44  import org.apache.hc.core5.reactor.ssl.TlsDetails;
45  import org.apache.hc.core5.util.Args;
46  
47  /**
48   * I/O event handler for events fired by {@link ProtocolIOSession} that implements
49   * client side of the HTTP/2 protocol negotiation handshake
50   * based on {@link HttpVersionPolicy} configuration.
51   *
52   * @since 5.0
53   */
54  @Internal
55  public class ClientHttpProtocolNegotiator extends ProtocolNegotiatorBase {
56  
57      // PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
58      final static byte[] PREFACE = new byte[] {
59              0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50,
60              0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d,
61              0x0d, 0x0a, 0x0d, 0x0a};
62  
63      private final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory;
64      private final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory;
65      private final HttpVersionPolicy versionPolicy;
66      private final AtomicBoolean initialized;
67  
68      private volatile ByteBuffer preface;
69      private volatile BufferedData inBuf;
70  
71      public ClientHttpProtocolNegotiator(
72              final ProtocolIOSession ioSession,
73              final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory,
74              final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
75              final HttpVersionPolicy versionPolicy) {
76          this(ioSession, http1StreamHandlerFactory, http2StreamHandlerFactory, versionPolicy, null);
77      }
78  
79      /**
80       * @since 5.1
81       */
82      public ClientHttpProtocolNegotiator(
83              final ProtocolIOSession ioSession,
84              final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory,
85              final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
86              final HttpVersionPolicy versionPolicy,
87              final FutureCallback<ProtocolIOSession> resultCallback) {
88          super(ioSession, resultCallback);
89          this.http1StreamHandlerFactory = Args.notNull(http1StreamHandlerFactory, "HTTP/1.1 stream handler factory");
90          this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory");
91          this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE;
92          this.initialized = new AtomicBoolean();
93      }
94  
95      private void startHttp1() throws IOException {
96          final ByteBuffer data = inBuf != null ? inBuf.data() : null;
97          startProtocol(new ClientHttp1IOEventHandler(http1StreamHandlerFactory.create(ioSession)), data);
98          if (inBuf != null) {
99              inBuf.clear();
100         }
101     }
102 
103     private void startHttp2() throws IOException {
104         final ByteBuffer data = inBuf != null ? inBuf.data() : null;
105         startProtocol(new ClientH2IOEventHandler(http2StreamHandlerFactory.create(ioSession)), data);
106         if (inBuf != null) {
107             inBuf.clear();
108         }
109     }
110 
111     private void initialize() throws IOException {
112         switch (versionPolicy) {
113             case NEGOTIATE:
114                 final TlsDetails tlsDetails = ioSession.getTlsDetails();
115                 if (tlsDetails != null) {
116                     if (ApplicationProtocol.HTTP_2.id.equals(tlsDetails.getApplicationProtocol())) {
117                         // Proceed with the H2 preface
118                         preface = ByteBuffer.wrap(PREFACE);
119                     }
120                 }
121                 break;
122             case FORCE_HTTP_2:
123                 preface = ByteBuffer.wrap(PREFACE);
124                 break;
125         }
126         if (preface == null) {
127             startHttp1();
128         } else {
129             ioSession.setEvent(SelectionKey.OP_WRITE);
130         }
131     }
132 
133     private void writeOutPreface(final IOSession session) throws IOException {
134         if (preface.hasRemaining()) {
135             session.write(preface);
136         }
137         if (!preface.hasRemaining()) {
138             session.clearEvent(SelectionKey.OP_WRITE);
139             startHttp2();
140             preface = null;
141         }
142     }
143 
144     @Override
145     public void connected(final IOSession session) throws IOException {
146         if (initialized.compareAndSet(false, true)) {
147             initialize();
148         }
149         if (preface != null) {
150             writeOutPreface(session);
151         }
152     }
153 
154     @Override
155     public void inputReady(final IOSession session, final ByteBuffer src) throws IOException  {
156         if (src != null) {
157             if (inBuf == null) {
158                 inBuf = BufferedData.allocate(src.remaining());
159             }
160             inBuf.put(src);
161         }
162         if (preface != null) {
163             writeOutPreface(session);
164         } else {
165             throw new ProtocolNegotiationException("Unexpected input");
166         }
167     }
168 
169     @Override
170     public void outputReady(final IOSession session) throws IOException {
171         if (initialized.compareAndSet(false, true)) {
172             initialize();
173         }
174         if (preface != null) {
175             writeOutPreface(session);
176         } else {
177             throw new ProtocolNegotiationException("Unexpected output");
178         }
179     }
180 
181     @Override
182     public String toString() {
183         return getClass().getName() + "/" + versionPolicy;
184     }
185 
186 }