Author: markt Date: Wed Apr 8 20:57:56 2015 New Revision: 1672177 URL: http://svn.apache.org/r1672177 Log: First pass at the plumbing to link from ALPN to creating a protocol specific processor to handle the connection. Some, but not all, of the plumbing that will be required for HTTP upgrade is also provided.
Added: tomcat/trunk/java/org/apache/coyote/UpgradeProtocol.java (with props) tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java (with props) tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (with props) Modified: tomcat/trunk/java/org/apache/coyote/AbstractProtocol.java tomcat/trunk/java/org/apache/coyote/ajp/AbstractAjpProtocol.java tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java tomcat/trunk/java/org/apache/coyote/spdy/SpdyProxyProtocol.java tomcat/trunk/java/org/apache/tomcat/util/net/AbstractEndpoint.java tomcat/trunk/java/org/apache/tomcat/util/net/AprEndpoint.java tomcat/trunk/java/org/apache/tomcat/util/net/LocalStrings.properties tomcat/trunk/java/org/apache/tomcat/util/net/SocketWrapperBase.java Modified: tomcat/trunk/java/org/apache/coyote/AbstractProtocol.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/AbstractProtocol.java?rev=1672177&r1=1672176&r2=1672177&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/AbstractProtocol.java (original) +++ tomcat/trunk/java/org/apache/coyote/AbstractProtocol.java Wed Apr 8 20:57:56 2015 @@ -365,6 +365,15 @@ public abstract class AbstractProtocol<S protected abstract String getProtocolName(); + /** + * @param name The name of the requested negotiated protocol. + * + * @return The instance where {@link UpgradeProtocol#getAlpnName()} matches + * the requested protocol + */ + protected abstract UpgradeProtocol getNegotiatedProtocol(String name); + + // ----------------------------------------------------- JMX related methods protected String domain; @@ -634,6 +643,16 @@ public abstract class AbstractProtocol<S try { if (processor == null) { + String negotiatedProtocol = wrapper.getNegotiatedProtocol(); + if (negotiatedProtocol != null) { + UpgradeProtocol upgradeProtocol = + getProtocol().getNegotiatedProtocol(negotiatedProtocol); + if (upgradeProtocol != null) { + processor = upgradeProtocol.getProcessor(wrapper); + } + } + } + if (processor == null) { processor = recycledProcessors.pop(); } if (processor == null) { Added: tomcat/trunk/java/org/apache/coyote/UpgradeProtocol.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/UpgradeProtocol.java?rev=1672177&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/coyote/UpgradeProtocol.java (added) +++ tomcat/trunk/java/org/apache/coyote/UpgradeProtocol.java Wed Apr 8 20:57:56 2015 @@ -0,0 +1,62 @@ +/* + * 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. + */ +package org.apache.coyote; + +import org.apache.tomcat.util.net.SocketWrapperBase; + +public interface UpgradeProtocol { + + /** + * @return The name that clients will use to request an upgrade to this + * protocol via an HTTP/1.1 upgrade request or <code>null</code> if + * upgrade via an HTTP/1.1 upgrade request is not supported. + */ + public String getHttpUpgradeName(); + + /** + * @return The byte sequence as listed in the IANA registry for this + * protocol or <code>null</code> if upgrade via ALPN is not + * supported. + */ + public byte[] getAlpnIdentifier(); + + /** + * @return The name of the protocol as listed in the IANA registry if and + * only if {@link #getAlpnIdentifier()} returns the UTF-8 encoding + * of this name. If {@link #getAlpnIdentifier()} returns some other + * byte sequence, then this method returns the empty string. If + * upgrade via ALPN is not supported then <code>null</code> is + * returned. + */ + /* + * Implementation note: If Tomcat ever supports ALPN for a protocol where + * the identifier is not the UTF-8 encoding of the name + * then some refactoring is going to be required. + * + * Implementation note: Tomcat assumes that the UTF-8 encoding of this name + * will not exceed 255 bytes. Tomcat's behaviour if + * longer names are used is undefined. + */ + public String getAlpnName(); + + /** + * + * @return A processor instance for processing a connection using this + * protocol. + */ + public Processor getProcessor(SocketWrapperBase<?> socketWrapper); +} Propchange: tomcat/trunk/java/org/apache/coyote/UpgradeProtocol.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: tomcat/trunk/java/org/apache/coyote/UpgradeProtocol.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: tomcat/trunk/java/org/apache/coyote/ajp/AbstractAjpProtocol.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/ajp/AbstractAjpProtocol.java?rev=1672177&r1=1672176&r2=1672177&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/ajp/AbstractAjpProtocol.java (original) +++ tomcat/trunk/java/org/apache/coyote/ajp/AbstractAjpProtocol.java Wed Apr 8 20:57:56 2015 @@ -22,6 +22,7 @@ import javax.servlet.http.HttpUpgradeHan import org.apache.coyote.AbstractProtocol; import org.apache.coyote.Processor; +import org.apache.coyote.UpgradeProtocol; import org.apache.tomcat.util.net.AbstractEndpoint; import org.apache.tomcat.util.net.SocketWrapperBase; import org.apache.tomcat.util.res.StringManager; @@ -67,6 +68,17 @@ public abstract class AbstractAjpProtoco } + /** + * {@inheritDoc} + * + * AJP does not support protocol negotiation so this always returns null. + */ + @Override + protected UpgradeProtocol getNegotiatedProtocol(String name) { + return null; + } + + // ------------------------------------------------- AJP specific properties // ------------------------------------------ managed in the ProtocolHandler Modified: tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java?rev=1672177&r1=1672176&r2=1672177&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java (original) +++ tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java Wed Apr 8 20:57:56 2015 @@ -20,9 +20,11 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -30,6 +32,7 @@ import javax.servlet.http.HttpUpgradeHan import org.apache.coyote.AbstractProtocol; import org.apache.coyote.Processor; +import org.apache.coyote.UpgradeProtocol; import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler; import org.apache.coyote.http11.upgrade.UpgradeProcessorExternal; import org.apache.coyote.http11.upgrade.UpgradeProcessorInternal; @@ -41,6 +44,13 @@ public abstract class AbstractHttp11Prot public AbstractHttp11Protocol(AbstractEndpoint<S> endpoint) { super(endpoint); setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT); + + // TODO: Make this configurable via nested UpgradeProtocol elements in + // the Connector. + // This is disabled by default otherwise it will break the + // APR/native connector with clients that support h2 with ALPN + // (because the Http2Protocol is only stubbed out) + //addUpgradeProtocol(new Http2Protocol()); } @@ -256,6 +266,27 @@ public abstract class AbstractHttp11Prot } + /** + * The protocols that are available via internal Tomcat support for access + * via HTTP upgrade. + */ + private final Map<String,UpgradeProtocol> httpUpgradeProtocols = new HashMap<>(); + /** + * The protocols that are available via internal Tomcat support for access + * via ALPN negotiation. + */ + private final Map<String,UpgradeProtocol> negotiatedProtocols = new HashMap<>(); + public void addUpgradeProtocol(UpgradeProtocol upgradeProtocol) { + httpUpgradeProtocols.put(upgradeProtocol.getHttpUpgradeName(), upgradeProtocol); + negotiatedProtocols.put(upgradeProtocol.getAlpnName(), upgradeProtocol); + getEndpoint().addNegotiatedProtocol(upgradeProtocol.getAlpnName()); + } + @Override + public UpgradeProtocol getNegotiatedProtocol(String negotiatedName) { + return negotiatedProtocols.get(negotiatedName); + } + + // ------------------------------------------------ HTTP specific properties // ------------------------------------------ passed through to the EndPoint Added: tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java?rev=1672177&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java (added) +++ tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java Wed Apr 8 20:57:56 2015 @@ -0,0 +1,53 @@ +/* + * 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. + */ +package org.apache.coyote.http2; + +import java.nio.charset.StandardCharsets; + +import org.apache.coyote.Processor; +import org.apache.coyote.UpgradeProtocol; +import org.apache.coyote.http11.upgrade.UpgradeProcessorInternal; +import org.apache.tomcat.util.net.SocketWrapperBase; + +public class Http2Protocol implements UpgradeProtocol { + + private static final String HTTP_UPGRADE_NAME = "h2c"; + private static final String ALPN_NAME = "h2"; + private static final byte[] ALPN_IDENTIFIER = ALPN_NAME.getBytes(StandardCharsets.UTF_8); + + @Override + public String getHttpUpgradeName() { + return HTTP_UPGRADE_NAME; + } + + @Override + public byte[] getAlpnIdentifier() { + return ALPN_IDENTIFIER; + } + + @Override + public String getAlpnName() { + return ALPN_NAME; + } + + @Override + public Processor getProcessor(SocketWrapperBase<?> socketWrapper) { + UpgradeProcessorInternal processor = + new UpgradeProcessorInternal(socketWrapper, null, new Http2UpgradeHandler()); + return processor; + } +} Propchange: tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java?rev=1672177&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (added) +++ tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Wed Apr 8 20:57:56 2015 @@ -0,0 +1,48 @@ +/* + * 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. + */ +package org.apache.coyote.http2; + +import javax.servlet.http.WebConnection; + +import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler; +import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState; +import org.apache.tomcat.util.net.SocketStatus; +import org.apache.tomcat.util.net.SocketWrapperBase; + +public class Http2UpgradeHandler implements InternalHttpUpgradeHandler { + + @Override + public void init(WebConnection connection) { + // TODO Auto-generated method stub + } + + @Override + public void setSocketWrapper(SocketWrapperBase<?> wrapper) { + // TODO Auto-generated method stub + } + + @Override + public SocketState upgradeDispatch(SocketStatus status) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void destroy() { + // TODO Auto-generated method stub + } +} Propchange: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: tomcat/trunk/java/org/apache/coyote/spdy/SpdyProxyProtocol.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/spdy/SpdyProxyProtocol.java?rev=1672177&r1=1672176&r2=1672177&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/spdy/SpdyProxyProtocol.java (original) +++ tomcat/trunk/java/org/apache/coyote/spdy/SpdyProxyProtocol.java Wed Apr 8 20:57:56 2015 @@ -20,6 +20,7 @@ import java.io.IOException; import java.nio.channels.SocketChannel; import org.apache.coyote.AbstractProtocol; +import org.apache.coyote.UpgradeProtocol; import org.apache.coyote.ajp.Constants; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -136,4 +137,10 @@ public class SpdyProxyProtocol extends A // TODO Auto-generated method stub } } + + @Override + protected UpgradeProtocol getNegotiatedProtocol(String name) { + // TODO Auto-generated method stub + return null; + } } Modified: tomcat/trunk/java/org/apache/tomcat/util/net/AbstractEndpoint.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/AbstractEndpoint.java?rev=1672177&r1=1672176&r2=1672177&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/util/net/AbstractEndpoint.java (original) +++ tomcat/trunk/java/org/apache/tomcat/util/net/AbstractEndpoint.java Wed Apr 8 20:57:56 2015 @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; @@ -496,6 +497,11 @@ public abstract class AbstractEndpoint<S protected abstract boolean getDeferAccept(); + protected final List<String> negotiableProtocols = new ArrayList<>(); + public void addNegotiatedProtocol(String negotiableProtocol) { + negotiableProtocols.add(negotiableProtocol); + } + /** * Attributes provide a way for configuration to be passed to sub-components * without the {@link org.apache.coyote.ProtocolHandler} being aware of the Modified: tomcat/trunk/java/org/apache/tomcat/util/net/AprEndpoint.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/AprEndpoint.java?rev=1672177&r1=1672176&r2=1672177&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/util/net/AprEndpoint.java (original) +++ tomcat/trunk/java/org/apache/tomcat/util/net/AprEndpoint.java Wed Apr 8 20:57:56 2015 @@ -20,8 +20,10 @@ import java.io.EOFException; import java.io.IOException; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; @@ -67,13 +69,17 @@ import org.apache.tomcat.util.net.Abstra */ public class AprEndpoint extends AbstractEndpoint<Long> { - // -------------------------------------------------------------- Constants - private static final Log log = LogFactory.getLog(AprEndpoint.class); + // http/1.1 with preceding length + private static final byte[] ALPN_DEFAULT = + new byte[] { 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31 }; + + // ----------------------------------------------------------------- Fields + /** * Root APR memory pool. */ @@ -628,9 +634,52 @@ public class AprEndpoint extends Abstrac log.warn(sm.getString("endpoint.apr.noSendfileWithSSL")); } } + + if (negotiableProtocols.size() > 0) { + byte[] protocols = buildAlpnConfig(negotiableProtocols); + if (SSLContext.setALPN(sslContext, protocols, protocols.length) != 0) { + log.warn(sm.getString("endpoint.alpn.fail", negotiableProtocols)); + } + } + } else if (negotiableProtocols.size() > 0) { + log.info(sm.getString("endpoint.noNegotiation", getName(), negotiableProtocols.toString())); + } + } + + + private byte[] buildAlpnConfig(List<String> protocols) { + /* + * The expected format is zero or more of the following: + * - Single byte for size + * - Sequence of size bytes for the identifier + */ + byte[][] protocolsBytes = new byte[protocols.size()][]; + int i = 0; + int size = 0; + for (String protocol : protocols) { + protocolsBytes[i] = protocol.getBytes(StandardCharsets.UTF_8); + size += protocolsBytes[i].length; + // And one byte to store the size + size++; + i++; + } + + size += ALPN_DEFAULT.length; + + byte[] result = new byte[size]; + int pos = 0; + for (byte[] protocolBytes : protocolsBytes) { + result[pos++] = (byte) (0xff & protocolBytes.length); + System.arraycopy(protocolBytes, 0, result, pos, protocolBytes.length); + pos += protocolBytes.length; } + + System.arraycopy(ALPN_DEFAULT, 0, result, pos, ALPN_DEFAULT.length); + + return result; } + public long getJniSslContext() { return sslContext; } @@ -857,6 +906,18 @@ public class AprEndpoint extends Abstrac wrapper.setSecure(isSSLEnabled()); wrapper.setReadTimeout(getSoTimeout()); wrapper.setWriteTimeout(getSoTimeout()); + if (isSSLEnabled() && negotiableProtocols.size() > 0) { + byte[] negotiated = new byte[256]; + int len = SSLSocket.getALPN(socket, negotiated); + String negotiatedProtocol = + new String(negotiated, 0, len, StandardCharsets.UTF_8); + if (negotiatedProtocol.length() > 0) { + wrapper.setNegotiatedProtocol(negotiatedProtocol); + if (log.isDebugEnabled()) { + log.debug(sm.getString("endpoint.alpn.negotiated", negotiatedProtocol)); + } + } + } connections.put(Long.valueOf(socket), wrapper); getExecutor().execute(new SocketWithOptionsProcessor(wrapper)); } Modified: tomcat/trunk/java/org/apache/tomcat/util/net/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/LocalStrings.properties?rev=1672177&r1=1672176&r2=1672177&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/util/net/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/tomcat/util/net/LocalStrings.properties Wed Apr 8 20:57:56 2015 @@ -41,12 +41,15 @@ endpoint.debug.socket=socket [{0}] endpoint.debug.socketCloseFail=Failed to close socket endpoint.debug.socketTimeout=Timing out [{0}] endpoint.debug.unlock=Caught exception trying to unlock accept on port {0} +endpoint.accept.fail=Socket accept failed +endpoint.alpn.fail=Failed to configure endpoint for ALPN using {0} +endpoint.alpn.negotiated=Negotiated [{0}] protocol using ALPN endpoint.executor.fail=Executor rejected socket [{0}] for processing +endpoint.getAttribute=[{0}] is [{1}] endpoint.init.bind=Socket bind failed: [{0}] {1} endpoint.init.listen=Socket listen failed: [{0}] {1} endpoint.init.notavail=APR not available -endpoint.accept.fail=Socket accept failed -endpoint.getAttribute=[{0}] is [{1}] +endpoint.noNegotiation=TLS was not configured for the [{0}] connector so negotiation via ALPN for {1} is not available endpoint.poll.limitedpollsize=Failed to create poller with specified size of {0} endpoint.poll.initfail=Poller creation failed endpoint.poll.fail=Critical poller failure (restarting poller): [{0}] {1} Modified: tomcat/trunk/java/org/apache/tomcat/util/net/SocketWrapperBase.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/SocketWrapperBase.java?rev=1672177&r1=1672176&r2=1672177&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/util/net/SocketWrapperBase.java (original) +++ tomcat/trunk/java/org/apache/tomcat/util/net/SocketWrapperBase.java Wed Apr 8 20:57:56 2015 @@ -52,6 +52,7 @@ public abstract class SocketWrapperBase< private boolean keptAlive = false; private volatile boolean upgraded = false; private boolean secure = false; + private String negotiatedProtocol = null; /* * Following cached for speed / reduced GC */ @@ -154,6 +155,10 @@ public abstract class SocketWrapperBase< public void setUpgraded(boolean upgraded) { this.upgraded = upgraded; } public boolean isSecure() { return secure; } public void setSecure(boolean secure) { this.secure = secure; } + public String getNegotiatedProtocol() { return negotiatedProtocol; } + public void setNegotiatedProtocol(String negotiatedProtocol) { + this.negotiatedProtocol = negotiatedProtocol; + } /** * Set the timeout for reading. Values of zero or less will be changed to] --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org