blob: 41d3800ca60388d915836ca00334c0e691818eae [file] [log] [blame]
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.core5.http2.impl.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.impl.nio.BufferedData;
import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
import org.apache.hc.core5.reactor.IOSession;
import org.apache.hc.core5.reactor.ProtocolIOSession;
import org.apache.hc.core5.reactor.ssl.TlsDetails;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.TextUtils;
/**
* I/O event handler for events fired by {@link ProtocolIOSession} that implements
* client side of the HTTP/2 protocol negotiation handshake always forcing the choice
* of HTTP/2.
*
* @since 5.2
*/
@Internal
public class ClientH2PrefaceHandler extends PrefaceHandlerBase {
// PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
final static byte[] PREFACE = new byte[] {
0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50,
0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d,
0x0d, 0x0a, 0x0d, 0x0a};
private final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory;
private final boolean strictALPNHandshake;
private final AtomicBoolean initialized;
private volatile ByteBuffer preface;
private volatile BufferedData inBuf;
public ClientH2PrefaceHandler(
final ProtocolIOSession ioSession,
final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
final boolean strictALPNHandshake) {
this(ioSession, http2StreamHandlerFactory, strictALPNHandshake, null);
}
/**
* @since 5.1
*/
public ClientH2PrefaceHandler(
final ProtocolIOSession ioSession,
final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory,
final boolean strictALPNHandshake,
final FutureCallback<ProtocolIOSession> resultCallback) {
super(ioSession, resultCallback);
this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory");
this.strictALPNHandshake = strictALPNHandshake;
this.initialized = new AtomicBoolean();
}
private void initialize() throws IOException {
final TlsDetails tlsDetails = ioSession.getTlsDetails();
if (tlsDetails != null) {
final String applicationProtocol = tlsDetails.getApplicationProtocol();
if (TextUtils.isEmpty(applicationProtocol)) {
if (strictALPNHandshake) {
throw new ProtocolNegotiationException("ALPN: missing application protocol");
}
} else {
if (!ApplicationProtocol.HTTP_2.id.equals(applicationProtocol)) {
throw new ProtocolNegotiationException("ALPN: unexpected application protocol '" + applicationProtocol + "'");
}
}
}
this.preface = ByteBuffer.wrap(PREFACE);
ioSession.setEvent(SelectionKey.OP_WRITE);
}
/**
* @return true if the entire preface has been written out
*/
private boolean writeOutPreface(final IOSession session, final ByteBuffer preface) throws IOException {
if (preface.hasRemaining()) {
session.write(preface);
}
if (!preface.hasRemaining()) {
session.clearEvent(SelectionKey.OP_WRITE);
final ByteBuffer data = inBuf != null ? inBuf.data() : null;
startProtocol(new ClientH2IOEventHandler(http2StreamHandlerFactory.create(ioSession)), data);
if (inBuf != null) {
inBuf.clear();
}
return true;
}
return false;
}
@Override
public void connected(final IOSession session) throws IOException {
if (initialized.compareAndSet(false, true)) {
initialize();
}
}
@Override
public void outputReady(final IOSession session) throws IOException {
if (initialized.compareAndSet(false, true)) {
initialize();
}
final ByteBuffer preface = this.preface;
if (preface != null) {
if (writeOutPreface(session, preface)) {
this.preface = null;
}
} else {
throw new ProtocolNegotiationException("Unexpected output");
}
}
@Override
public void inputReady(final IOSession session, final ByteBuffer src) throws IOException {
if (src != null) {
if (inBuf == null) {
inBuf = BufferedData.allocate(src.remaining());
}
inBuf.put(src);
}
final ByteBuffer preface = this.preface;
if (preface != null) {
if (writeOutPreface(session, preface)) {
this.preface = null;
}
} else {
throw new ProtocolNegotiationException("Unexpected input");
}
}
@Override
public String toString() {
return getClass().getName();
}
}