You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by co...@apache.org on 2012/03/13 06:36:17 UTC
svn commit: r1299980 - in /tomcat/trunk/java/org/apache/tomcat/jni:
SSLExt.java socket/ socket/AprSocket.java socket/AprSocketContext.java
socket/HostInfo.java
Author: costin
Date: Tue Mar 13 05:36:17 2012
New Revision: 1299980
URL: http://svn.apache.org/viewvc?rev=1299980&view=rev
Log:
Based on AprEndpoint, an implmentation of non-blocking SSL sockets which is thread safe for read/write.
It can be used for both client and server mode sockets, and it's independent of the rest of tomcat.
It is in a separate package because it's a bit higher level than the raw jni interfaces. Many settings
are missing ( could be added if needed - most are related to setup, but they are not used in tomcat
spdy implementation ).
Added:
tomcat/trunk/java/org/apache/tomcat/jni/socket/
tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java
tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocketContext.java
tomcat/trunk/java/org/apache/tomcat/jni/socket/HostInfo.java
Modified:
tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java
Modified: tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java?rev=1299980&r1=1299979&r2=1299980&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java Tue Mar 13 05:36:17 2012
@@ -88,15 +88,22 @@ public final class SSLExt {
*/
public static native int setSNI(long tcsock, byte[] data, int len);
+ /**
+ * Return the last openssl error
+ */
+ public static native String sslErrReasonErrorString();
+
+ public static native long sslCtxSetMode(long ctx, long mode);
+
/* Allow SSL_write(..., n) to return r with 0 < r < n (i.e. report success
* when just a single record has been written): */
- static final int SSL_MODE_ENABLE_PARTIAL_WRITE = 0x1;
+ public static final int SSL_MODE_ENABLE_PARTIAL_WRITE = 0x1;
/* Make it possible to retry SSL_write() with changed buffer location
* (buffer contents must stay the same!); this is not the default to avoid
* the misconception that non-blocking SSL_write() behaves like
* non-blocking write(): */
- static final int SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER = 0x2;
+ public static final int SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER = 0x2;
/* Don't attempt to automatically build certificate chain */
static final int SSL_MODE_NO_AUTO_CHAIN = 0x8;
@@ -104,7 +111,7 @@ public final class SSLExt {
/* Save RAM by releasing read and write buffers when they're empty. (SSL3 and
* TLS only.) "Released" buffers are put onto a free-list in the context
* or just freed (depending on the context's setting for freelist_max_len). */
- static final int SSL_MODE_SMALL_BUFFERS = 0x10;
+ public static final int SSL_MODE_RELEASE_BUFFERS = 0x10;
// 1.1
//static final int SSL_MODE_HANDSHAKE_CUTTHROUGH = ..;
@@ -112,7 +119,7 @@ public final class SSLExt {
/**
* SSL_set_mode
*/
- public static native int sslSetMode(long tcsock, int mode);
+ public static native long sslSetMode(long tcsock, long mode);
public static int setNPN(long sslContext, byte[] spdyNPN) {
try {
Added: tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java?rev=1299980&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java Tue Mar 13 05:36:17 2012
@@ -0,0 +1,926 @@
+/*
+ * 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.tomcat.jni.socket;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.jni.Address;
+import org.apache.tomcat.jni.Error;
+import org.apache.tomcat.jni.Poll;
+import org.apache.tomcat.jni.SSL;
+import org.apache.tomcat.jni.SSLExt;
+import org.apache.tomcat.jni.SSLSocket;
+import org.apache.tomcat.jni.Sockaddr;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
+import org.apache.tomcat.jni.socket.AprSocketContext.AprPoller;
+import org.apache.tomcat.jni.socket.AprSocketContext.BlockingPollHandler;
+
+/**
+ * Native socket, using JNI + APR + openssl.
+ *
+ * The socket is non-blocking - you can register either a blocking or non
+ * blocking callback.
+ *
+ * There is no explicit method to register/unregister poll interest -
+ * it is done automatically, when read/write methods return 0.
+ *
+ * To keep the socket polling you must read all the available data, until
+ * read() returns 0. If you want to pause - don't read all input. To resume -
+ * read again until it returns 0.
+ *
+ * Same for write - when write() returns 0 the socket is registered for
+ * write interest.
+ *
+ * You can also use the blocking read/write methods.
+ */
+public class AprSocket implements Runnable {
+
+ static final byte[][] NO_CERTS = new byte[0][];
+
+ static Logger log = Logger.getLogger("AprSocket");
+
+ static int CONNECTING = 1;
+ static int CONNECTED = 0x2;
+
+ // Current ( real ) poll status
+ static int POLLIN_ACTIVE = 0x4;
+ static int POLLOUT_ACTIVE = 0x8;
+
+ static int POLL = 0x10;
+
+ static int SSL_ATTACHED = 0x40;
+
+ // Requested poll status. Set by read/write when needed.
+ // Cleared when polled
+ static int POLLIN = 0x80;
+ static int POLLOUT = 0x100;
+
+ static int ACCEPTED = 0x200;
+ static int ERROR = 0x400;
+ static int CLOSED = 0x800;
+
+ static int READING = 0x1000;
+ static int WRITING = 0x2000;
+
+ // Not null
+ private AprSocketContext context;
+
+ // only one - to save per/socket memory - context has similar callbacks.
+ BlockingPollHandler handler;
+
+ // Set while it's associated with a poller - it'll stay associated after
+ // connect until close. Destroy will happen in the poller.
+ // POLL bit indicates if the socket is actually polling.
+ AprPoller poller;
+
+ // Bit field indicating the status and socket should only be accessed with
+ // socketLock protection
+ private int status;
+
+ long socket;
+
+ //long to = 10000;
+
+ // Persistent info about the peer ( SSL, etc )
+ private HostInfo hostInfo;
+
+ AprSocket(AprSocketContext context) {
+ this.context = context;
+ }
+
+ public void recycle() {
+ status = 0;
+ hostInfo = null;
+ handler = null;
+ socket = 0;
+ poller = null;
+ }
+
+ public String toString() {
+ return (context.isServer() ? "AprSrv-" : "AprCli-") +
+ Long.toHexString(socket) + " " + Integer.toHexString(status);
+ }
+
+ public void setHandler(BlockingPollHandler l) {
+ handler = l;
+ }
+
+ private void setNonBlocking() {
+ if (socket != 0 && context.running) {
+ Socket.optSet(socket, Socket.APR_SO_NONBLOCK, 1);
+ Socket.timeoutSet(socket, 0);
+ }
+ }
+
+ /**
+ * Check if the socket is currently registered with a poller.
+ */
+ public boolean isPolling() {
+ synchronized (this) {
+ return (status & POLL) != 0;
+ }
+ }
+
+ public BlockingPollHandler getHandler() {
+ return handler;
+ }
+
+ public AprSocketContext getContext() {
+ return context;
+ }
+
+ AprSocket setHost(HostInfo hi) {
+ hostInfo = hi;
+ return this;
+ }
+
+ /**
+ */
+ public void connect() throws IOException {
+ if (isBlocking()) {
+ // will call handleConnected() at the end.
+ context.connectBlocking(this);
+ } else {
+ synchronized(this) {
+ if ((status & CONNECTING) != 0) {
+ return;
+ }
+ status |= CONNECTING;
+ }
+ context.connectExecutor.execute(this);
+ }
+ }
+
+
+ // after connection is done, called from a thread pool ( not IO thread )
+ // may block for handshake.
+ void afterConnect() throws IOException {
+ if (hostInfo.secure) {
+ blockingStartTLS();
+ }
+
+ setNonBlocking(); // call again, to set the bits ( connect was blocking )
+
+ setStatus(CONNECTED);
+ clearStatus(CONNECTING);
+
+ notifyConnected(false);
+ }
+
+ public HostInfo getHost() {
+ return hostInfo;
+ }
+
+ /**
+ * Write.
+ *
+ * For both blocking and non-blocking, it'll return the number of bytes
+ * written - it'll attempt to send as much as possible, but if the buffers
+ * are full it'll return 0.
+ *
+ * @param data
+ * @param off
+ * @param len
+ * @return
+ * @throws IOException
+ */
+ public int write(byte[] data, int off, int len, long to) throws IOException {
+ long max = System.currentTimeMillis() + to;
+
+ while (true) {
+ int rc = writeInternal(data, off, len);
+ if (rc < 0) {
+ throw new IOException("Write error " + rc);
+ } else if (rc == 0) {
+ // need poll out - do we need to update polling ?
+ context.findPollerAndAdd(this);
+ } else {
+ return rc;
+ }
+
+ try {
+ long waitTime = max - System.currentTimeMillis();
+ if (waitTime <= 0) {
+ return 0;
+ }
+ wait(waitTime);
+ } catch (InterruptedException e) {
+ return 0;
+ }
+ }
+ }
+
+ public int write(byte[] data, int off, int len) throws IOException {
+ // In SSL mode, read/write can't be called at the same time.
+ int rc = writeInternal(data, off, len);
+ if (rc < 0) {
+ throw new IOException("Write error " + rc);
+ } else if (rc == 0) {
+ // need poll out - do we need to update polling ?
+ synchronized (this) {
+ context.findPollerAndAdd(this);
+ }
+ }
+ return rc;
+ }
+
+ private int writeInternal(byte[] data, int off, int len) throws IOException {
+ int rt = 0;
+ int sent = 0;
+ synchronized(this) {
+ if ((status & CLOSED) != 0
+ || socket == 0
+ || !context.running) {
+ throw new IOException("Closed");
+ }
+ if ((status & WRITING) != 0) {
+ throw new IOException("Write from 2 threads not allowed");
+ }
+ status |= WRITING;
+
+ while (len > 0) {
+ sent = Socket.send(socket, data, off, len);
+ if (sent <= 0) {
+ break;
+ }
+ off += sent;
+ len -= sent;
+ }
+
+ status &= ~WRITING;
+ }
+
+ if (context.rawDataHandler != null) {
+ context.rawData(this, false, data, off, sent, len, false);
+ }
+
+ if (sent <= 0) {
+ if (sent == -Status.TIMEUP || sent == -Status.EAGAIN || sent == 0) {
+ setStatus(POLLOUT);
+ updatePolling();
+ return rt;
+ }
+ if (context.debug) {
+ log.warning("apr.send(): Failed to send, closing " + sent);
+ }
+ reset();
+ throw new IOException("Error sending " + sent + " " + Error.strerror((int) -sent));
+ } else {
+ off += sent;
+ len -= sent;
+ rt += sent;
+ return sent;
+ }
+ }
+
+ public int read(byte[] data, int off, int len, long to) throws IOException {
+ int rd = readNB(data, off, len);
+ if (rd == 0) {
+ synchronized(this) {
+ try {
+ wait(to);
+ } catch (InterruptedException e) {
+ return 0;
+ }
+ }
+ rd = readNB(data, off, len);
+ }
+ return processReadResult(data, off, len, rd);
+ }
+
+ public int read(byte[] data, int off, int len) throws IOException {
+ return readNB(data, off, len);
+ }
+
+ private int processReadResult(byte[] data, int off, int len, int read)
+ throws IOException {
+ if (context.rawDataHandler != null) {
+ context.rawData(this, true, data, off, read, len, false);
+ }
+
+ if (read > 0) {
+ return read;
+ }
+
+ if (read == 0 || read == -Status.TIMEUP || read == -Status.ETIMEDOUT
+ || read == -Status.EAGAIN) {
+ read = 0;
+ setStatus(POLLIN);
+ updatePolling();
+ return 0;
+ }
+
+ if (read == -Status.APR_EOF || read == -1) {
+ close();
+ return -1;
+ }
+ // abrupt close
+ reset();
+ throw new IOException("apr.read(): " + read + " " + Error.strerror((int) -read));
+ }
+
+ public int readNB(byte[] data, int off, int len) throws IOException {
+ int read;
+ synchronized(this) {
+ if ((status & CLOSED) != 0
+ || socket == 0
+ || !context.running) {
+ return -1;
+ }
+ if ((status & READING) != 0) {
+ throw new IOException("Read from 2 threads not allowed");
+ }
+ status |= READING;
+
+ read = Socket.recv(socket, data, off, len);
+ status &= ~READING;
+ }
+ return processReadResult(data, off, len, read);
+ }
+
+ /*
+ No support for shutdownOutput: SSL is quite tricky.
+ Use close() instead - no read/write will be allowed after.
+
+ */
+
+ public void close() throws IOException {
+ synchronized (this) {
+ if ((status & CLOSED) != 0 || socket == 0) {
+ return;
+ }
+ status |= CLOSED;
+ status &= ~POLLIN;
+ status &= ~POLLOUT;
+ }
+ if (context.rawDataHandler != null) {
+ context.rawDataHandler.rawData(this, false, null, 0, 0, 0, true);
+ }
+ Socket.close(socket);
+ if (poller == null) {
+ maybeDestroy();
+ } else {
+ try {
+ poller.requestUpdate(this);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ void maybeDestroy() {
+ synchronized(this) {
+ if (socket == 0 ||
+ (status & CONNECTING) != 0 || !context.running) {
+ // closed or operation in progress
+ // if context stopped, pool will be destroyed and close
+ // all sockets automatically.
+ return;
+ }
+ if ((status & CLOSED) == 0) {
+ return; // not closed
+ }
+ if ((status & (WRITING | READING)) != 0) {
+ return; // not closed
+ }
+
+ if (context.rawDataHandler != null) {
+ context.rawDataHandler.rawData(this, false, null, -1, -1, -1, true);
+ }
+ if (context.debug) {
+ log.info("closing: context.open=" + context.open.get() + " " + this);
+ }
+
+ context.open.decrementAndGet();
+
+ if (socket != 0 && (status & CLOSED) == 0) {
+ Socket.close(socket);
+ status |= CLOSED;
+ }
+
+ if (handler != null) {
+ if (isBlocking()) {
+ context.getExecutor().execute(this);
+ } else {
+ handler.closed(this);
+ }
+ }
+
+ context.destroySocket(this);
+ }
+ }
+
+
+
+ /**
+ * Close input and output, potentially sending RST, than close the socket.
+ *
+ * The proper way to close when gracefully done is by calling writeEnd() and
+ * reading all remaining input until -1 (EOF) is received.
+ *
+ * If EOF is received, the proper way to close is send whatever is remaining and
+ * call writeEnd();
+ */
+ public void reset() {
+ setStatus(ERROR);
+ try {
+ close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ /**
+ */
+ public boolean isClosed() {
+ synchronized(this) {
+ if ((status & CLOSED) != 0 || socket == 0 || !context.running) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public long getIOTimeout() throws IOException {
+ if (socket != 0 && context.running) {
+ try {
+ return Socket.timeoutGet(socket) / 1000;
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+ } else {
+ throw new IOException("Socket is closed");
+ }
+ }
+
+ // Cert is in DER format
+ // Called after handshake
+ public byte[][] getPeerCert(boolean check) throws IOException {
+ getHost();
+ if (hostInfo.certs != null && hostInfo.certs != NO_CERTS && !check) {
+ return hostInfo.certs;
+ }
+ if (!checkBitAndSocket(SSL_ATTACHED)) {
+ return NO_CERTS;
+ }
+ try {
+ int certLength = SSLSocket.getInfoI(socket,
+ SSL.SSL_INFO_CLIENT_CERT_CHAIN);
+ // TODO: if resumed, old certs are good.
+ // If not - warn if certs changed, remember first cert, etc.
+ if (certLength <= 0) {
+ // Can also happen on session resume - keep the old
+ if (hostInfo.certs == null) {
+ hostInfo.certs = NO_CERTS;
+ }
+ return hostInfo.certs;
+ }
+ hostInfo.certs = new byte[certLength + 1][];
+
+ hostInfo.certs[0] = SSLSocket.getInfoB(socket,
+ SSL.SSL_INFO_CLIENT_CERT);
+ for (int i = 0; i < certLength; i++) {
+ hostInfo.certs[i + 1] = SSLSocket.getInfoB(socket,
+ SSL.SSL_INFO_CLIENT_CERT_CHAIN + i);
+ }
+ return hostInfo.certs;
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+ }
+
+ public X509Certificate[] getPeerX509Cert() throws IOException {
+ byte[][] certs = getPeerCert(false);
+ X509Certificate[] xcerts = new X509Certificate[certs.length];
+ if (certs.length == 0) {
+ return xcerts;
+ }
+ try {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ for (int i = 0; i < certs.length; i++) {
+ if (certs[i] != null) {
+ ByteArrayInputStream bis = new ByteArrayInputStream(
+ certs[i]);
+ xcerts[i] = (X509Certificate) cf.generateCertificate(bis);
+ bis.close();
+ }
+ }
+ } catch (CertificateException ex) {
+ throw new IOException(ex);
+ }
+ return xcerts;
+ }
+
+ public String getCipherSuite() throws IOException {
+ if (checkBitAndSocket(SSL_ATTACHED)) {
+ return null;
+ }
+ try {
+ return SSLSocket.getInfoS(socket, SSL.SSL_INFO_CIPHER);
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+ }
+
+ public int getKeySize() throws IOException {
+ if (checkBitAndSocket(SSL_ATTACHED)) {
+ return -1;
+ }
+ try {
+ return SSLSocket.getInfoI(socket, SSL.SSL_INFO_CIPHER_USEKEYSIZE);
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+ }
+
+ public int getRemotePort() throws IOException {
+ if (socket != 0 && context.running) {
+ try {
+ long sa = Address.get(Socket.APR_REMOTE, socket);
+ Sockaddr addr = Address.getInfo(sa);
+ return addr.port;
+ } catch (Exception ex) {
+ throw new IOException(ex);
+ }
+ }
+ throw new IOException("Socket closed");
+ }
+
+ public String getRemoteAddress() throws IOException {
+ if (socket != 0 && context.running) {
+ try {
+ long sa = Address.get(Socket.APR_REMOTE, socket);
+ return Address.getip(sa);
+ } catch (Exception ex) {
+ throw new IOException(ex);
+ }
+ }
+ throw new IOException("Socket closed");
+ }
+
+ public String getRemoteHostname() throws IOException {
+ if (socket != 0 && context.running) {
+ try {
+ long sa = Address.get(Socket.APR_REMOTE, socket);
+ String remoteHost = Address.getnameinfo(sa, 0);
+ if (remoteHost == null) {
+ remoteHost = Address.getip(sa);
+ }
+ return remoteHost;
+ } catch (Exception ex) {
+ throw new IOException(ex);
+ }
+ }
+ throw new IOException("Socket closed");
+ }
+
+ public int getLocalPort() throws IOException {
+ if (socket != 0 && context.running) {
+ try {
+ long sa = Address.get(Socket.APR_LOCAL, socket);
+ Sockaddr addr = Address.getInfo(sa);
+ } catch (Exception ex) {
+ throw new IOException(ex);
+ }
+ }
+ throw new IOException("Socket closed");
+ }
+
+ public String getLocalAddress() throws IOException {
+ if (socket != 0 && context.running) {
+ try {
+ long sa = Address.get(Socket.APR_LOCAL, socket);
+ return Address.getip(sa);
+ } catch (Exception ex) {
+ throw new IOException(ex);
+ }
+ }
+ throw new IOException("Socket closed");
+ }
+
+ public String getLocalHostname() throws IOException {
+ if (socket != 0 && context.running) {
+ try {
+ long sa = Address.get(Socket.APR_LOCAL, socket);
+ return Address.getnameinfo(sa, 0);
+ } catch (Exception ex) {
+ throw new IOException(ex);
+ }
+ }
+ throw new IOException("Socket closed");
+ }
+
+ public boolean isBlocking() {
+ return ! (handler instanceof AprSocketContext.NonBlockingPollHandler);
+ }
+
+ public boolean isError() {
+ return checkPreConnect(ERROR);
+ }
+
+ void notifyError(Throwable err, boolean needsThread) {
+ if (handler instanceof AprSocketContext.NonBlockingPollHandler) {
+ if (err != null) {
+ ((AprSocketContext.NonBlockingPollHandler) handler).error(this, err);
+ }
+ } else {
+ // poller destroyed, etc
+ if (needsThread) {
+ context.getExecutor().execute(this);
+ } else {
+ try {
+ notifyIO();
+ } catch (IOException e) {
+ log.log(Level.SEVERE, this + " error ", e);
+ }
+ }
+ }
+ }
+
+ // Called after connect and from poller.
+ void notifyIO() throws IOException {
+ long t0 = System.currentTimeMillis();
+ try {
+ if (handler != null) {
+ handler.process(this, true, false, false);
+ }
+ } catch (Throwable t) {
+ throw new IOException(t);
+ } finally {
+ long t1 = System.currentTimeMillis();
+ t1 -= t0;
+ if (t1 > context.maxHandlerTime.get()) {
+ context.maxHandlerTime.set(t1);
+ }
+ context.totalHandlerTime.addAndGet(t1);
+ context.handlerCount.incrementAndGet();
+ }
+ }
+
+ private void notifyConnected(boolean server) throws IOException {
+ // Will set the handler on the channel for accepted
+ context.onSocket(this);
+
+ if (handler instanceof AprSocketContext.NonBlockingPollHandler) {
+ ((AprSocketContext.NonBlockingPollHandler) handler).connected(this);
+
+ ((AprSocketContext.NonBlockingPollHandler) handler).process(this, true, true, false);
+ // Now register for polling - unless process() set suspendRead and
+ // doesn't need out notifications
+ updatePolling();
+ } else {
+ if (server) {
+ // client will block in connect().
+ // Server: call process();
+ notifyIO();
+ }
+ }
+ }
+
+ private void updatePolling() throws IOException {
+ synchronized (this) {
+ if ((status & CLOSED) != 0) {
+ maybeDestroy();
+ return;
+ }
+ }
+ context.findPollerAndAdd(this);
+ }
+
+ @Override
+ public void run() {
+ if (!context.running) {
+ return;
+ }
+ if (checkPreConnect(CLOSED)) {
+ if (handler != null) {
+ handler.closed(this);
+ }
+ return;
+ }
+ if (!checkPreConnect(CONNECTED)) {
+ if (checkBitAndSocket(ACCEPTED)) {
+ try {
+ context.open.incrementAndGet();
+
+ if (context.debug) {
+ log.info("Accept: " + context.open.get() + " " + this + " " +
+ getRemotePort());
+ }
+ if (context.tcpNoDelay) {
+ Socket.optSet(socket, Socket.APR_TCP_NODELAY, 1);
+ }
+
+ setStatus(CONNECTED);
+ if (context.sslMode) {
+ Socket.timeoutSet(socket, context.connectTimeout * 1000);
+ blockingStartTLS();
+ }
+ setNonBlocking(); // call again, to set the bits ( connect was blocking )
+
+ notifyConnected(true);
+ return;
+ } catch (Throwable t) {
+ t.printStackTrace(); // no error handler yet
+ reset();
+ notifyError(t, false);
+ return;
+ }
+ }
+ if (checkPreConnect(CONNECTING)) {
+ // Non-blocking connect - will call 'afterConnection' at the end.
+ try {
+ context.connectBlocking(this);
+ } catch (IOException t) {
+ reset(); // also sets status ERROR
+ if (handler instanceof AprSocketContext.NonBlockingPollHandler) {
+ ((AprSocketContext.NonBlockingPollHandler) handler).process(this, false, false, true);
+ }
+ notifyError(t, false);
+ }
+ }
+ } else {
+ if (handler != null) {
+ try {
+ notifyIO();
+ } catch (Throwable e) {
+ log.log(Level.SEVERE, this + " error ", e);
+ reset();
+ // no notifyIO - just did it.
+ }
+ }
+ }
+ }
+
+ /**
+ * This is a blocking call ! ( can be made non-blocking, but too complex )
+ *
+ * Will be called automatically after connect() or accept if 'secure' is
+ * true.
+ *
+ * Can be called manually to upgrade the channel
+ * @throws IOException
+ */
+ public void blockingStartTLS() throws IOException {
+ synchronized(this) {
+ if (socket == 0 || !context.running) {
+ return;
+ }
+ if ((status & SSL_ATTACHED) != 0) {
+ return;
+ }
+ status |= SSL_ATTACHED;
+ }
+
+ try {
+ if (context.debug) {
+ log.info(this + " StartSSL");
+ }
+
+ AprSocketContext aprCon = (AprSocketContext) context;
+ SSLSocket.attach(aprCon.getSslCtx(), socket);
+
+ if (context.debugSSL) {
+ SSLExt.debug(socket);
+ }
+ if (!((AprSocketContext) getContext()).isServer()) {
+ if (context.USE_TICKETS && hostInfo.ticketLen > 0) {
+ SSLExt.setTicket(socket, hostInfo.ticket,
+ hostInfo.ticketLen);
+ } else if (hostInfo.sessDer != null) {
+ SSLExt.setSessionData(socket, hostInfo.sessDer,
+ hostInfo.sessDer.length);
+ }
+ }
+ SSLExt.sslSetMode(socket, SSLExt.SSL_MODE_ENABLE_PARTIAL_WRITE |
+ SSLExt.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+ int rc = SSLSocket.handshake(socket);
+
+ // At this point we have the session ID, remote certs, etc
+ // we can lookup host info
+ if (hostInfo == null) {
+ hostInfo = new HostInfo();
+ }
+
+ if (rc != Status.APR_SUCCESS) {
+ throw new IOException(this + " Handshake failed " + rc + " "
+ + Error.strerror(rc) + " SSLL "
+ + SSL.getLastError());
+ } else { // SUCCESS
+ handshakeDone();
+ }
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+ }
+
+ private void handshakeDone() throws IOException {
+ getHost();
+ if (socket == 0 || !context.running) {
+ throw new IOException("Socket closed");
+ }
+ if (context.USE_TICKETS && ! context.isServer()) {
+ if (hostInfo.ticket == null) {
+ hostInfo.ticket = new byte[2048];
+ }
+ int ticketLen = SSLExt.getTicket(socket, hostInfo.ticket);
+ if (ticketLen > 0) {
+ hostInfo.ticketLen = ticketLen;
+ if (context.debug) {
+ log.info("Received ticket: " + ticketLen);
+ }
+ }
+ }
+
+ // TODO: if the ticket, session id or session changed - callback to
+ // save the session again
+ try {
+ hostInfo.sessDer = SSLExt.getSessionData(socket);
+ getPeerCert(true);
+ hostInfo.sessionId = SSLSocket.getInfoS(socket,
+ SSL.SSL_INFO_SESSION_ID);
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+
+ hostInfo.npn = new byte[32];
+ hostInfo.npnLen = SSLExt.getNPN(socket, hostInfo.npn);
+
+ // If custom verification is used - should check the certificates
+ if (context.tlsCertVerifier != null) {
+ context.tlsCertVerifier.handshakeDone(this);
+ }
+ }
+
+ int requestedPolling() {
+ synchronized(this) {
+ if (socket == 0 || ((status & CLOSED) != 0)) {
+ return 0;
+ }
+ // Implicit:
+ //Poll.APR_POLLNVAL | Poll.APR_POLLHUP | Poll.APR_POLLERR |
+ int res = 0;
+ if ((status & POLLIN) != 0) {
+ res = Poll.APR_POLLIN;
+ }
+ if ((status & POLLOUT) != 0) {
+ res |= Poll.APR_POLLOUT;
+ }
+ return res;
+ }
+ }
+
+ boolean checkBitAndSocket(int bit) {
+ synchronized (this) {
+ return ((status & bit) != 0 && socket != 0 &&
+ (status & CLOSED) == 0 && context.running);
+ }
+ }
+
+ boolean checkPreConnect(int bit) {
+ synchronized (this) {
+ return ((status & bit) != 0);
+ }
+ }
+
+ void clearStatus(int bit) {
+ synchronized (this) {
+ status &= ~bit;
+ }
+ }
+
+ boolean setStatus(int bit) {
+ synchronized (this) {
+ int old = status & bit;
+ status |= bit;
+ return old != 0;
+ }
+ }
+
+
+}
\ No newline at end of file
Added: tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocketContext.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocketContext.java?rev=1299980&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocketContext.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocketContext.java Tue Mar 13 05:36:17 2012
@@ -0,0 +1,1367 @@
+/*
+ * 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.tomcat.jni.socket;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.jni.Address;
+import org.apache.tomcat.jni.Error;
+import org.apache.tomcat.jni.Library;
+import org.apache.tomcat.jni.OS;
+import org.apache.tomcat.jni.Poll;
+import org.apache.tomcat.jni.Pool;
+import org.apache.tomcat.jni.SSL;
+import org.apache.tomcat.jni.SSLContext;
+import org.apache.tomcat.jni.SSLExt;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
+
+public class AprSocketContext {
+ /**
+ * Called when a chunk of data is sent or received. This is very low
+ * level, used mostly for debugging or stats.
+ */
+ public static interface RawDataHandler {
+ public void rawData(AprSocket ch, boolean input, byte[] data, int pos,
+ int len, int requested, boolean closed);
+ }
+
+ /**
+ * Called in SSL mode after the handshake is completed.
+ *
+ * @see AprSocketContext.customVerification()
+ */
+ public static interface TlsCertVerifier {
+ public void handshakeDone(AprSocket ch);
+ }
+
+ /**
+ * Delegates loading of persistent info about a host - public certs,
+ * tickets, config, persistent info etc.
+ */
+ public static interface HostInfoLoader {
+ public HostInfo getHostInfo(String name, int port, boolean ssl);
+ }
+
+ /**
+ * Reads/writes of this size or lower are using Get/SetByteArrayRegion.
+ * Larger reads use Get/ReelaseByteArrayElements.
+ * Larger writes use malloc/free + GetByteArrayRagion.
+ */
+ static final int TCN_BUFFER_SZ = 8192;
+
+ static Logger log = Logger.getLogger("AprSocketCtx");
+
+ // If interrupt() or thread-safe poll update are not supported - the
+ // poll updates will happen after the poll() timeout.
+ // The poll timeout with interrupt/thread safe updates can be much higher/
+ static int FALLBACK_POLL_TIME = 2000;
+ static int MAX_POLL_SIZE = 60;
+
+ // It seems to send the ticket, get server helo / ChangeCipherSpec, but than
+ // SSL3_GET_RECORD:decryption failed or bad record mac in s3_pkt.c:480:
+ // Either bug in openssl, or some combination of ciphers - needs more debugging.
+ // ( this can save a roundtrip and CPU on TLS handshake )
+ boolean USE_TICKETS = false;
+
+ boolean useFinalizer = true;
+
+ final AprSocket END = new AprSocket(this);
+
+ static AtomicInteger contextNumber = new AtomicInteger();
+ int contextId;
+
+ AtomicInteger threadNumber = new AtomicInteger();
+
+ /**
+ * For now - single acceptor thread per connector.
+ */
+ AcceptorThread acceptor;
+ AcceptorDispatchThread acceptorDispatch;
+
+ // APR/JNI is thread safe
+ boolean threadSafe = true;
+
+ /**
+ * Pollers.
+ */
+ List<AprPoller> pollers = new ArrayList<AprPoller>();
+ static int pollerCnt = 0;
+
+ // Set on all accepted or connected sockets.
+ // TODO: add the other properties
+ boolean tcpNoDelay = true;
+
+ protected boolean running = true;
+
+ protected boolean sslMode;
+
+ // onSocket() will be called in accept thread.
+ // If false: use executor ( but that may choke the acceptor thread )
+ protected boolean nonBlockingAccept = false;
+
+ BlockingQueue<AprSocket> acceptedQueue = new LinkedBlockingQueue<AprSocket>();
+
+ /**
+ * Root APR memory pool.
+ */
+ private long rootPool = 0;
+
+ /**
+ * SSL context.
+ */
+ private long sslCtx = 0;
+
+ TlsCertVerifier tlsCertVerifier;
+
+ //
+ int connectTimeout = 20000;
+ int defaultTimeout = 100000;
+
+ int keepAliveTimeout = 20000;
+
+ AtomicInteger open = new AtomicInteger();
+
+ /**
+ * Poll interval, in microseconds. If the platform doesn't support
+ * poll interrupt - it'll take this time to stop the poller.
+ *
+ */
+ protected int pollTime = 5 * 1000000;
+
+ HostInfoLoader hostInfoLoader;
+
+ RawDataHandler rawDataHandler = null;
+
+ // TODO: do we need this here ?
+ protected Map<String, HostInfo> hosts = new HashMap<String, HostInfo>();
+
+ String[] enabledCiphers;
+
+ String certFile;
+ String keyFile;
+
+ byte[] spdyNPN;
+
+ byte[] ticketKey;
+
+ // For resolving DNS ( i.e. connect ), callbacks
+ private ExecutorService threadPool;
+
+ // Separate executor for connect/handshakes
+ ExecutorService connectExecutor;
+
+ boolean debug = false;
+ boolean debugSSL = false;
+ boolean debugPoll = false;
+
+ protected boolean deferAccept = false;
+
+ protected int backlog = 100;
+
+ protected boolean useSendfile;
+
+ int sslProtocol = SSL.SSL_PROTOCOL_TLSV1 | SSL.SSL_PROTOCOL_SSLV3;
+
+ /**
+ * Max time spent in a callback ( will be longer for blocking )
+ */
+ AtomicLong maxHandlerTime = new AtomicLong();
+ AtomicLong totalHandlerTime = new AtomicLong();
+ AtomicLong handlerCount = new AtomicLong();
+
+ /**
+ * Total connections handled ( accepted or connected ).
+ */
+ AtomicInteger connectionsCount = new AtomicInteger();
+
+
+ public AprSocketContext() {
+ connectExecutor =new ThreadPoolExecutor(0, 64, 5, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<Runnable>(), new RejectedExecutionHandler() {
+ @Override
+ public void rejectedExecution(Runnable r,
+ java.util.concurrent.ThreadPoolExecutor executor) {
+ AprSocket s = (AprSocket) r;
+ log.severe("Rejecting " + s);
+ s.reset();
+ }
+ });
+ contextId = contextNumber.incrementAndGet();
+ }
+
+ /**
+ * Poller thread count.
+ */
+ protected int pollerThreadCount = 4;
+ public void setPollerThreadCount(int pollerThreadCount) { this.pollerThreadCount = pollerThreadCount; }
+ public int getPollerThreadCount() { return pollerThreadCount; }
+
+ // to test the limits - default should be lower
+ int maxConnections = 64 * 1024;
+ public void setMaxconnections(int maxCon) {
+ this.maxConnections = maxCon;
+ }
+
+ public void setBacklog(int backlog) { if (backlog > 0) this.backlog = backlog; }
+ public int getBacklog() { return backlog; }
+
+ /**
+ * Defer accept.
+ */
+ public void setDeferAccept(boolean deferAccept) { this.deferAccept = deferAccept; }
+ public boolean getDeferAccept() { return deferAccept; }
+
+ /**
+ * For client:
+ * - ClientHello will include the npn extension ( the ID == 0x3374)
+ * - if ServerHello includes a list of protocols - select one
+ * - send it after ChangeCipherSpec and before Finish
+ *
+ * For server:
+ * - if ClientHello includes the npn extension
+ * -- will send this string as list of supported protocols in ServerHello
+ * - read the selection before Finish.
+ * @param npn
+ */
+ public void setNpn(String npn) {
+ byte[] data = npn.getBytes();
+ byte[] npnB = new byte[data.length + 2];
+
+ System.arraycopy(data, 0, npnB, 1, data.length);
+ npnB[0] = (byte) data.length;
+ npnB[npnB.length - 1] = 0;
+ spdyNPN = npnB;
+
+ }
+
+ public void setNpn(byte[] data) {
+ spdyNPN = data;
+ }
+
+ public void setHostLoader(HostInfoLoader handler) {
+ this.hostInfoLoader = handler;
+ }
+
+ public boolean isServer() {
+ return acceptor != null;
+ }
+
+ protected Executor getExecutor() {
+ if (threadPool == null) {
+ threadPool = Executors.newCachedThreadPool(new ThreadFactory( ) {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r, "AprThread-" + contextId + "-" +
+ threadNumber.incrementAndGet());
+ t.setDaemon(true);
+ return t;
+ }
+ });
+ }
+ return threadPool;
+ }
+
+ /**
+ * All accepted/connected sockets will start handshake automatically.
+ */
+ public AprSocketContext setTls() {
+ this.sslMode = true;
+ return this;
+ }
+
+ public void setTcpNoDelay(boolean b) {
+ tcpNoDelay = b;
+ }
+
+ public void setSslProtocol(String protocol) {
+ protocol = protocol.trim();
+ if ("SSLv2".equalsIgnoreCase(protocol)) {
+ sslProtocol = SSL.SSL_PROTOCOL_SSLV2;
+ } else if ("SSLv3".equalsIgnoreCase(protocol)) {
+ sslProtocol = SSL.SSL_PROTOCOL_SSLV3;
+ } else if ("TLSv1".equalsIgnoreCase(protocol)) {
+ sslProtocol = SSL.SSL_PROTOCOL_TLSV1;
+ } else if ("all".equalsIgnoreCase(protocol)) {
+ sslProtocol = SSL.SSL_PROTOCOL_ALL;
+ }
+ }
+
+ public void setTicketKey(byte[] key48Bytes) {
+ if(key48Bytes.length != 48) {
+ throw new RuntimeException("Key must be 48 bytes");
+ }
+ this.ticketKey = key48Bytes;
+ }
+
+ public void customVerification(TlsCertVerifier verifier) {
+ tlsCertVerifier = verifier;
+ }
+
+ public void setEnabledCiphers(String[] enabled) {
+ enabledCiphers = enabled;
+ }
+
+ // TODO: should have a separate method for switching to tls later.
+ /**
+ * Set certificate, will also enable TLS mode.
+ */
+ public AprSocketContext setKeys(String certPemFile, String keyDerFile)
+ throws IOException {
+ this.sslMode = true;
+ setTls();
+ certFile = certPemFile;
+ keyFile = keyDerFile;
+ return this;
+ }
+
+ /**
+ * SSL cipher suite.
+ */
+ protected String SSLCipherSuite = "ALL";
+ public String getSSLCipherSuite() { return SSLCipherSuite; }
+ public void setSSLCipherSuite(String SSLCipherSuite) { this.SSLCipherSuite = SSLCipherSuite; }
+
+ /**
+ * Override or use hostInfoLoader to implement persistent/memcache storage.
+ */
+ public HostInfo getHostInfo(String host, int port, boolean ssl) {
+ if (hostInfoLoader != null) {
+ return hostInfoLoader.getHostInfo(host, port, ssl);
+ }
+ // Use local cache
+ String key = host + ":" + port;
+ HostInfo pi = hosts.get(key);
+ if (pi != null) {
+ return pi;
+ }
+ pi = new HostInfo(host, port, ssl);
+ hosts.put(key, pi);
+ return pi;
+ }
+
+ protected void rawData(AprSocket ch, boolean inp, byte[] data, int pos,
+ int len, int requested, boolean closed) {
+ if (rawDataHandler != null) {
+ rawDataHandler.rawData(ch, inp, data, pos, len, requested, closed);
+ }
+ }
+
+ public void listen(final int port) throws IOException {
+ if (acceptor != null) {
+ throw new IOException("Already accepting on " + acceptor.port);
+ }
+ if (sslMode && certFile == null) {
+ throw new IOException("Missing certificates for server");
+ }
+ if (sslMode || !nonBlockingAccept) {
+ acceptorDispatch = new AcceptorDispatchThread(port);
+ acceptorDispatch.setName("AprAcceptorDispatch-" + port);
+ acceptorDispatch.start();
+ }
+
+ acceptor = new AcceptorThread(port);
+ acceptor.prepare();
+ acceptor.setName("AprAcceptor-" + port);
+ acceptor.start();
+
+
+ }
+
+ /**
+ * Get a socket for connectiong to host:port.
+ */
+ public AprSocket socket(String host, int port, boolean ssl) throws IOException {
+ HostInfo hi = getHostInfo(host, port, ssl);
+ return socket(hi);
+ }
+
+ public AprSocket socket(HostInfo hi) throws IOException {
+ AprSocket sock = newSocket(this);
+ sock.setHost(hi);
+ return sock;
+ }
+
+ public AprSocket socket(long socket) throws IOException {
+ AprSocket sock = newSocket(this);
+ sock.setStatus(AprSocket.ACCEPTED);
+ sock.socket = socket;
+ return sock;
+ }
+
+
+ void destroySocket(AprSocket socket) {
+ // TODO: does it need to be done in io thread ?
+ synchronized (socket) {
+ if (socket.socket != 0) {
+ long s = socket.socket;
+ socket.socket = 0;
+ System.err.println("DESTROY: " + Long.toHexString(s));
+ Socket.destroy(s);
+ }
+ }
+ }
+
+ protected void connectBlocking(AprSocket apr) throws IOException {
+ try {
+ if (!running) {
+ throw new IOException("Stopped");
+ }
+ HostInfo hi = apr.getHost();
+
+ long clientSockP;
+ synchronized (pollers) {
+ long socketpool = Pool.create(getRootPool());
+
+ int family = Socket.APR_INET;
+
+ clientSockP = Socket.create(family,
+ Socket.SOCK_STREAM,
+ Socket.APR_PROTO_TCP, socketpool); // or rootPool ?
+ }
+ Socket.timeoutSet(clientSockP, connectTimeout * 1000);
+ if (OS.IS_UNIX) {
+ Socket.optSet(clientSockP, Socket.APR_SO_REUSEADDR, 1);
+ }
+
+ Socket.optSet(clientSockP, Socket.APR_SO_KEEPALIVE, 1);
+
+ // Blocking
+ // TODO: use socket pool
+ // TODO: cache it ( and TTL ) in hi
+ long inetAddress = Address.info(hi.host, Socket.APR_INET,
+ hi.port, 0, rootPool);
+ // this may take a long time - stop/destroy must wait
+ // at least connect timeout
+ int rc = Socket.connect(clientSockP, inetAddress);
+
+ if (rc != 0) {
+ synchronized (pollers) {
+ Socket.close(clientSockP);
+ Socket.destroy(clientSockP);
+ }
+ /////Pool.destroy(socketpool);
+ throw new IOException("Socket.connect(): " + rc + " " + Error.strerror(rc) + " " + connectTimeout);
+ }
+ if (!running) {
+ throw new IOException("Stopped");
+ }
+
+ connectionsCount.incrementAndGet();
+ if (tcpNoDelay) {
+ Socket.optSet(clientSockP, Socket.APR_TCP_NODELAY, 1);
+ }
+
+ Socket.timeoutSet(clientSockP, defaultTimeout * 1000);
+
+ apr.socket = clientSockP;
+
+ apr.afterConnect();
+ } catch (IOException e) {
+ apr.reset();
+ throw e;
+ } catch (Throwable e) {
+ apr.reset();
+ e.printStackTrace();
+ throw new IOException(e);
+ }
+ }
+
+ AprSocket newSocket(AprSocketContext context) throws IOException {
+ return new AprSocket(context);
+ }
+
+ /**
+ * To clean the pools - we could track if all channels are
+ * closed, but this seems simpler and safer.
+ */
+ protected void finalize() {
+ if (rootPool != 0) {
+ log.warning(this + " GC without stop()");
+ try {
+ stop();
+ } catch (Exception e) {
+ //TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+ public void stop() throws IOException {
+ synchronized (pollers) {
+ if (!running) {
+ return;
+ }
+ running = false;
+ }
+
+ if (rootPool != 0) {
+ if (acceptor != null) {
+ try {
+ acceptor.unblock();
+ acceptor.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ if (acceptorDispatch != null) {
+ acceptedQueue.add(END);
+ try {
+ acceptorDispatch.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ if (threadPool != null) {
+ threadPool.shutdownNow();
+ }
+
+ log.info("Stopping pollers " + contextId);
+
+ while (true) {
+ AprPoller a;
+ synchronized (pollers) {
+ if (pollers.size() == 0) {
+ break;
+ }
+ a = pollers.remove(0);
+ }
+ a.interruptPoll();
+ try {
+ a.join();
+ log.info("Poller " + a.id + " done ");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+
+ // Called when the last poller has been destroyed.
+ void destroy() {
+ synchronized (pollers) {
+ if (pollers.size() != 0) {
+ return;
+ }
+
+ if (rootPool == 0) {
+ return;
+ }
+ System.err.println("DESTROY " + rootPool);
+ //Pool.destroy(rootPool);
+ //rootPool = 0;
+ }
+ }
+
+ static IOException noApr;
+ static {
+
+ try {
+ Library.initialize(null);
+ SSL.initialize(null);
+ } catch (Exception e) {
+ noApr = new IOException("APR not present", e);
+ }
+
+ }
+
+ private long getRootPool() throws IOException {
+ if (rootPool == 0) {
+ if (noApr != null) {
+ throw noApr;
+ }
+ // Create the root APR memory pool
+ rootPool = Pool.create(0);
+
+ // Adjust poller sizes
+ if ((OS.IS_WIN32 || OS.IS_WIN64) && (maxConnections > 1024)) {
+ // The maximum per poller to get reasonable performance is 1024
+ pollerThreadCount = maxConnections / 1024;
+ // Adjust poller size so that it won't reach the limit
+ maxConnections = maxConnections - (maxConnections % 1024);
+ }
+ }
+ return rootPool;
+ }
+
+ long getSslCtx() throws Exception {
+ if (sslCtx == 0) {
+ synchronized (AprSocketContext.class) {
+
+ boolean serverMode = acceptor != null;
+ sslCtx = SSLContext.make(getRootPool(),
+ sslProtocol,
+ serverMode ? SSL.SSL_MODE_SERVER : SSL.SSL_MODE_CLIENT);
+
+
+ // SSL.SSL_OP_NO_SSLv3
+ int opts = SSL.SSL_OP_NO_SSLv2 |
+ SSL.SSL_OP_SINGLE_DH_USE;
+
+ if (!USE_TICKETS || serverMode && ticketKey == null) {
+ opts |= SSL.SSL_OP_NO_TICKET;
+ }
+
+ SSLContext.setOptions(sslCtx, opts);
+ // Set revocation
+ // SSLContext.setCARevocation(sslContext, SSLCARevocationFile, SSLCARevocationPath);
+
+ // Client certificate verification - maybe make it option
+ try {
+ SSLContext.setCipherSuite(sslCtx, SSLCipherSuite);
+
+
+ if (serverMode) {
+ if (ticketKey != null) {
+ //SSLExt.setTicketKeys(sslCtx, ticketKey, ticketKey.length);
+ }
+ if (certFile != null) {
+ boolean rc = SSLContext.setCertificate(sslCtx,
+ certFile,
+ keyFile, null, SSL.SSL_AIDX_DSA);
+ if (!rc) {
+ throw new IOException("Can't set keys");
+ }
+ }
+ SSLContext.setVerify(sslCtx, SSL.SSL_CVERIFY_NONE, 10);
+
+ if (spdyNPN != null) {
+ SSLExt.setNPN(sslCtx, spdyNPN, spdyNPN.length);
+ }
+ } else {
+ if (tlsCertVerifier != null) {
+ // NONE ?
+ SSLContext.setVerify(sslCtx,
+ SSL.SSL_CVERIFY_NONE, 10);
+ } else {
+ SSLContext.setCACertificate(sslCtx,
+ "/etc/ssl/certs/ca-certificates.crt",
+ "/etc/ssl/certs");
+ SSLContext.setVerify(sslCtx,
+ SSL.SSL_CVERIFY_REQUIRE, 10);
+ }
+
+ if (spdyNPN != null) {
+ SSLExt.setNPN(sslCtx, spdyNPN, spdyNPN.length);
+ }
+ }
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+
+ long mode =
+ SSLExt.sslCtxSetMode(sslCtx, SSLExt.SSL_MODE_ENABLE_PARTIAL_WRITE |
+ SSLExt.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+ // TODO: try release buffers
+ }
+ }
+ return sslCtx;
+ }
+
+ void findPollerAndAdd(AprSocket ch) throws IOException {
+ if (ch.poller != null) {
+ ch.poller.requestUpdate(ch);
+ return;
+ }
+ assignPoller(ch);
+ }
+
+ void assignPoller(AprSocket ch) throws IOException {
+ AprPoller target = null;
+ synchronized (pollers) {
+ // Make sure we have min number of pollers
+ int needPollers = pollerThreadCount - pollers.size();
+ if (needPollers > 0) {
+ for (int i = needPollers; i > 0; i--) {
+ pollers.add(allocatePoller());
+ }
+ }
+ int max = 0;
+ for (AprPoller poller: pollers) {
+ int rem = poller.remaining();
+ if (rem > max) {
+ target = poller;
+ max = rem;
+ }
+ }
+ }
+ if (target != null && target.add(ch)) {
+ return;
+ }
+
+ // can't be added - add a new poller
+ synchronized (pollers) {
+ AprPoller poller = allocatePoller();
+ poller.add(ch);
+ pollers.add(poller);
+ }
+ }
+
+ /**
+ * Called on each accepted socket ( for servers ) or after connection (client)
+ * after handshake.
+ */
+ protected void onSocket(AprSocket s) throws IOException {
+
+ }
+
+ class AcceptorThread extends Thread {
+ int port;
+ long serverSockPool = 0;
+ long serverSock = 0;
+
+ final String addressStr = null;
+
+ long inetAddress;
+
+ AcceptorThread(int port) {
+ this.port = port;
+ setDaemon(true);
+ }
+
+ void prepare() throws IOException {
+ try {
+ // Create the pool for the server socket
+ serverSockPool = Pool.create(getRootPool());
+
+ int family = Socket.APR_INET;
+ inetAddress = Address.info(addressStr, family,
+ port, 0, serverSockPool);
+
+ // Create the APR server socket
+ serverSock = Socket.create(family,
+ Socket.SOCK_STREAM,
+ Socket.APR_PROTO_TCP, serverSockPool);
+
+ if (OS.IS_UNIX) {
+ Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
+ }
+ // Deal with the firewalls that tend to drop the inactive sockets
+ Socket.optSet(serverSock, Socket.APR_SO_KEEPALIVE, 1);
+ // Bind the server socket
+ int ret = Socket.bind(serverSock, inetAddress);
+ if (ret != 0) {
+ throw new IOException("Socket.bind " + ret + " " +
+ Error.strerror(ret) + " port=" + port);
+ }
+ // Start listening on the server socket
+ ret = Socket.listen(serverSock, backlog );
+ if (ret != 0) {
+ throw new IOException("endpoint.init.listen"
+ + ret + " " + Error.strerror(ret));
+ }
+ if (OS.IS_WIN32 || OS.IS_WIN64) {
+ // On Windows set the reuseaddr flag after the bind/listen
+ Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
+ }
+
+ // Sendfile usage on systems which don't support it cause major problems
+ if (useSendfile && !Library.APR_HAS_SENDFILE) {
+ useSendfile = false;
+ }
+
+ // Delay accepting of new connections until data is available
+ // Only Linux kernels 2.4 + have that implemented
+ // on other platforms this call is noop and will return APR_ENOTIMPL.
+ if (deferAccept) {
+ if (Socket.optSet(serverSock, Socket.APR_TCP_DEFER_ACCEPT, 1) == Status.APR_ENOTIMPL) {
+ deferAccept = false;
+ }
+ }
+ } catch (Throwable t) {
+ throw new IOException(t);
+ }
+ }
+
+ void unblock() {
+ try {
+ // Easiest ( maybe safest ) way to interrupt accept
+ // we could have it in non-blocking mode, etc
+ java.net.Socket sock = new java.net.Socket();
+ sock.connect(new InetSocketAddress("127.0.0.1", port));
+ } catch (Exception ex) {
+ // ignore - the acceptor may have shut down by itself.
+ }
+ }
+
+ @Override
+ public void run() {
+ while (running) {
+ try {
+ // each socket has a pool.
+ final AprSocket ch = newSocket(AprSocketContext.this);
+ ch.setStatus(AprSocket.ACCEPTED);
+
+ ch.socket = Socket.accept(serverSock);
+ if (!running) {
+ break;
+ }
+ connectionsCount.incrementAndGet();
+ if (connectionsCount.get() % 1000 == 0) {
+ System.err.println("Accepted: " + connectionsCount.get());
+ }
+
+ if (nonBlockingAccept && !sslMode) {
+ ch.setStatus(AprSocket.CONNECTED);
+ // TODO: SSL really needs a thread.
+ onSocket(ch);
+ } else {
+ acceptedQueue.add(ch);
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+ Socket.close(serverSock);
+ }
+ }
+
+ class AcceptorDispatchThread extends Thread {
+
+ AcceptorDispatchThread(int port) {
+ setDaemon(true);
+ }
+
+ public void run() {
+ while(running) {
+ try {
+ AprSocket ch = acceptedQueue.take();
+ if (ch == END) {
+ return;
+ }
+ connectExecutor.execute(ch);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Create the poller. With some versions of APR, the maximum poller size will
+ * be 62 (recompiling APR is necessary to remove this limitation).
+ * @throws IOException
+ */
+ AprPoller allocatePoller() throws IOException {
+ long pool = Pool.create(getRootPool());
+ int size = maxConnections / pollerThreadCount;
+ int timeout = keepAliveTimeout;
+
+ long serverPollset = allocatePoller(size, pool, timeout);
+
+ if (serverPollset == 0 && size > 1024) {
+ log.severe("Falling back to 1024-sized poll, won't scale");
+ size = 1024;
+ serverPollset = allocatePoller(size, pool, timeout);
+ }
+ if (serverPollset == 0) {
+ log.severe("Falling back to 62-sized poll, won't scale");
+ size = 62;
+ serverPollset = allocatePoller(size, pool, timeout);
+ }
+
+ AprPoller res = new AprPoller();
+ res.pool = pool;
+ res.serverPollset = serverPollset;
+ res.desc = new long[size * 2];
+ res.size = size;
+ res.id = contextId++;
+ res.setDaemon(true);
+ res.setName("AprPoller-" + res.id);
+ res.start();
+ if (debugPoll && !sizeLogged) {
+ sizeLogged = true;
+ log.info("Poller size " + (res.desc.length / 2));
+ }
+ return res;
+ }
+
+ // Removed the 'thread safe' updates for now, to simplify the code
+ // last test shows a small improvement, can switch later.
+ static boolean sizeLogged = false;
+
+ protected long allocatePoller(int size, long pool, int timeout) {
+ int flag = threadSafe ? Poll.APR_POLLSET_THREADSAFE: 0;
+ for (int i = 0; i < 2; i++) {
+ try {
+ // timeout must be -1 - or ttl will take effect, strange results.
+ return Poll.create(size, pool, flag, -1); // timeout * 1000);
+ } catch (Error e) {
+ e.printStackTrace();
+ if (Status.APR_STATUS_IS_EINVAL(e.getError())) {
+ log.info(" endpoint.poll.limitedpollsize " + size);
+ return 0;
+ } else if (Status.APR_STATUS_IS_ENOTIMPL(e.getError())) {
+ // thread safe not supported
+ log.severe("THREAD SAFE NOT SUPPORTED" + e);
+ threadSafe = false;
+ // try again without the flags
+ continue;
+ } else {
+ log.severe("endpoint.poll.initfail" + e);
+ return 0;
+ }
+ }
+ }
+ log.severe("Unexpected ENOTIMPL with flag==0");
+ return 0;
+ }
+
+ class AprPoller extends Thread {
+
+ public int id;
+ public int size;
+ protected long serverPollset = 0;
+ protected long pool = 0;
+ protected long[] desc;
+
+ long lastPoll;
+ long lastPollTime;
+ long lastCallbackTime;
+ AtomicBoolean inPoll = new AtomicBoolean(false);
+
+ // Should be replaced with socket data.
+ // used only to lookup by socket
+ Map<Long, AprSocket> channels = new HashMap<Long, AprSocket>();
+
+ // Active + pending, must be < desc.length / 2
+ // The channel will also have poller=this when active or pending
+ // How many sockets have poller == this
+ protected AtomicInteger keepAliveCount = new AtomicInteger();
+ // Tracks desc, how many sockets are actively polled
+ protected AtomicInteger polledCount = new AtomicInteger();
+
+ protected AtomicInteger pollCount = new AtomicInteger();
+
+ private List<AprSocket> updates = new ArrayList<AprSocket>();
+
+ public void run() {
+ if (!running) {
+ return;
+ }
+ if (debugPoll) {
+ log.info("Starting poller " + id + " " + (isServer() ? "SRV ": "CLI "));
+ }
+ long t0 = System.currentTimeMillis();
+ while (running) {
+ try {
+ updates();
+
+
+ lastCallbackTime = t0 - lastPoll;
+
+ // Pool for the specified interval. Remove signaled sockets
+ synchronized (this) {
+ inPoll.set(true);
+ }
+ // if updates are added after updates and poll - interrupt will have still
+ // work
+
+ int rv = Poll.poll(serverPollset, pollTime, desc, true);
+ synchronized (this) {
+ inPoll.set(false);
+ if (!running) {
+ break;
+ }
+ }
+
+ pollCount.incrementAndGet();
+ lastPoll = System.currentTimeMillis();
+ lastPollTime = lastPoll - t0;
+
+ if (rv > 0) {
+ if (debugPoll) {
+ log.info(" Poll() id=" + id + " rv=" + rv + " keepAliveCount=" + keepAliveCount +
+ " polled = " + polledCount.get()
+ + " time=" + lastPollTime);
+ }
+ polledCount.addAndGet(-rv);
+ for (int pollIdx = 0; pollIdx < rv; pollIdx++) {
+ long sock = desc[pollIdx * 2 + 1];
+ AprSocket ch;
+ boolean blocking = false;
+
+ synchronized (channels) {
+ ch = channels.get(sock);
+ if (ch != null) {
+ blocking = ch.isBlocking();
+ } else {
+ log.severe("Polled socket not found !!!!!" + Long.toHexString(sock));
+ // TODO: destroy/close the raw socket
+ continue;
+ }
+ }
+ // was removed from polling
+ ch.clearStatus(AprSocket.POLL);
+
+ // We just removed it ( see last param to poll()).
+ // Check for failed sockets and hand this socket off to a worker
+ long mask = desc[pollIdx * 2];
+
+ boolean hup = ((mask & Poll.APR_POLLHUP) == Poll.APR_POLLHUP);
+ boolean err = ((mask & Poll.APR_POLLERR) == Poll.APR_POLLERR);
+ boolean nval = ((mask & Poll.APR_POLLNVAL) != 0);
+ if (err || nval) {
+ System.err.println("ERR " + err + " NVAL " + nval);
+ }
+
+ boolean out = (mask & Poll.APR_POLLOUT) == Poll.APR_POLLOUT;
+ boolean in = (mask & Poll.APR_POLLIN) == Poll.APR_POLLIN;
+ if (debugPoll) {
+ log.info(" Poll channel: " + Long.toHexString(mask) +
+ (out ? " OUT" :"") +
+ (in ? " IN": "") +
+ (err ? " ERR" : "") +
+ " Ch: " + ch);
+ }
+
+ // will be set again in process(), if all read/write is done
+ ch.clearStatus(AprSocket.POLLOUT);
+ ch.clearStatus(AprSocket.POLLIN);
+
+ // try to send if needed
+ if (blocking) {
+ synchronized (ch) {
+ ch.notifyAll();
+ }
+ getExecutor().execute(ch);
+ } else {
+ ((AprSocketContext.NonBlockingPollHandler) ch.handler).process(ch, in, out, false);
+
+ // Update polling for the channel (in IO thread, safe)
+ updateIOThread(ch);
+ }
+ }
+ } else if (rv < 0) {
+ int errn = -rv;
+ if (errn == Status.TIMEUP) {
+ // to or interrupt
+// if (debugPoll) {
+// log.info(" Poll() timeup" + " keepAliveCount=" + keepAliveCount +
+// " polled = " + polledCount.get()
+// + " time=" + lastPollTime);
+// }
+ } else if (errn == Status.EINTR) {
+ // interrupt - no need to log
+ } else {
+ if (debugPoll) {
+ log.info(" Poll() rv=" + rv + " keepAliveCount=" + keepAliveCount +
+ " polled = " + polledCount.get()
+ + " time=" + lastPollTime);
+ }
+ /* Any non timeup or interrupted error is critical */
+ if (errn > Status.APR_OS_START_USERERR) {
+ errn -= Status.APR_OS_START_USERERR;
+ }
+ log.severe("endpoint.poll.fail " + errn + " " + Error.strerror(errn));
+ // Handle poll critical failure
+ synchronized (this) {
+ destroyPoller(); // will close all sockets
+ }
+ continue;
+ }
+ }
+ // TODO: timeouts
+ } catch (Throwable t) {
+ log.log(Level.SEVERE, "endpoint.poll.error", t);
+ }
+
+ }
+ if (!running) {
+ destroyPoller();
+ }
+ }
+
+ /**
+ * Destroy the poller.
+ */
+ protected void destroyPoller() {
+ synchronized (pollers) {
+ pollers.remove(this);
+ }
+ log.info("Poller stopped after cnt=" +
+ pollCount.get() +
+ " sockets=" + channels.size() +
+ " lastPoll=" + lastPoll);
+
+ // Close all sockets
+ synchronized (this) {
+ if (serverPollset == 0) {
+ return;
+ }
+
+// for (AprSocket ch: channels.values()) {
+// ch.poller = null;
+// ch.reset();
+// }
+ keepAliveCount.set(0);
+ log.warning("Destroy pollset");
+ //serverPollset = 0;
+ }
+ Pool.destroy(pool);
+ pool = 0;
+ synchronized (pollers) {
+ // Now we can destroy the root pool
+ if (pollers.size() == 0 && !running) {
+ log.info("Destroy server context");
+// AprSocketContext.this.destroy();
+ }
+ }
+ }
+
+ /**
+ * Called only in poller thread, only used if not thread safe
+ * @throws IOException
+ */
+ protected void updates() throws IOException {
+ synchronized (this) {
+ for (AprSocket up: updates) {
+ updateIOThread(up);
+ }
+ updates.clear();
+ }
+ }
+
+ void interruptPoll() {
+ try {
+ int rc = Status.APR_SUCCESS;
+ synchronized (this) {
+ if (serverPollset != 0) {
+ rc = Poll.interrupt(serverPollset);
+ } else {
+ log.severe("Interrupt with closed pollset");
+ }
+ }
+ if (rc != Status.APR_SUCCESS) {
+ log.severe("Failed interrupt and not thread safe");
+ }
+ } catch (Throwable t) {
+ t.printStackTrace();
+ if (pollTime > FALLBACK_POLL_TIME) {
+ pollTime = FALLBACK_POLL_TIME;
+ }
+ }
+ }
+
+
+ int remaining() {
+ synchronized (channels) {
+ return (desc.length - channels.size() * 2);
+ }
+ }
+
+
+
+ /**
+ * Called from any thread, return true if we could add it
+ * to pending.
+ */
+ boolean add(AprSocket ch) throws IOException {
+ synchronized (this) {
+ if (!running) {
+ return false;
+ }
+ if (keepAliveCount.get() >= size) {
+ return false;
+ }
+ keepAliveCount.incrementAndGet();
+ ch.poller = this;
+ }
+
+ requestUpdate(ch);
+
+ return true;
+ }
+
+ /**
+ * May be called outside of IOThread.
+ */
+ protected void requestUpdate(AprSocket ch) throws IOException {
+ synchronized (this) {
+ if (!running) {
+ return;
+ }
+ }
+ if (isPollerThread()) {
+ updateIOThread(ch);
+ } else {
+ synchronized (this) {
+ updates.add(ch);
+ interruptPoll();
+ }
+ if (debugPoll) {
+ log.info("Poll: requestUpdate " + id + " " + ch);
+ }
+ }
+ }
+
+ private void updateIOThread(AprSocket ch) throws IOException {
+ if (!running || ch.socket == 0) {
+ return;
+ }
+ // called from IO thread, either in 'updates' or after
+ // poll.
+ //synchronized (ch)
+ boolean polling = ch.checkPreConnect(AprSocket.POLL);
+
+ int requested = ch.requestedPolling();
+ if (requested == 0) {
+ if (polling) {
+ removeSafe(ch);
+ }
+ if (ch.isClosed()) {
+ synchronized (channels) {
+ ch.poller = null;
+ channels.remove(ch.socket);
+ }
+ keepAliveCount.decrementAndGet();
+ ch.reset();
+ }
+ } else {
+ if (polling) {
+ removeSafe(ch);
+ }
+ // will close if error
+ pollAdd(ch, requested);
+ }
+ if (debugPoll) {
+ log.info("Poll: updated=" + id + " " + ch);
+ }
+ }
+
+ /**
+ * Called only from IO thread
+ */
+ private void pollAdd(AprSocket up, int req) throws IOException {
+ boolean failed = false;
+ int rv;
+ synchronized (channels) {
+ rv = Poll.add(serverPollset, up.socket, req);
+ if (rv != Status.APR_SUCCESS) {
+ up.poller = null;
+ keepAliveCount.decrementAndGet();
+ failed = true;
+ } else {
+ polledCount.incrementAndGet();
+ channels.put(up.socket, up);
+ up.setStatus(AprSocket.POLL);
+ }
+ }
+ if (failed) {
+ up.reset();
+ throw new IOException("poll add error " + rv + " " + up + " " + Error.strerror((int)rv));
+ }
+ }
+
+ /**
+ * Called only from IO thread. Remove from Poll and channels,
+ * set POLL bit to false.
+ */
+ private void removeSafe(AprSocket up) throws IOException {
+ int rv = Status.APR_EGENERAL;
+ if (running && serverPollset != 0 && up.socket != 0
+ && !up.isClosed()) {
+ rv = Poll.remove(serverPollset, up.socket);
+ }
+ up.clearStatus(AprSocket.POLL);
+
+ if (rv != Status.APR_SUCCESS) {
+ log.severe("poll remove error " + Error.strerror((int)rv) + " " + up);
+ } else {
+ polledCount.decrementAndGet();
+ }
+ }
+
+
+ public boolean isPollerThread() {
+ return Thread.currentThread() == this;
+ }
+
+ }
+
+ /**
+ * Callback for poll events, will be invoked in a thread pool.
+ *
+ */
+ public static interface BlockingPollHandler {
+
+ /**
+ * Called when the socket has been polled for in, out or closed.
+ *
+ *
+ */
+ public void process(AprSocket ch, boolean in, boolean out, boolean close);
+
+
+ /**
+ * Called just before the socket is destroyed
+ */
+ public void closed(AprSocket ch);
+ }
+
+ /**
+ * Additional callbacks for non-blocking.
+ * This can be much faster - but it's harder to code, should be used only
+ * for low-level protocol implementation, proxies, etc.
+ *
+ * The model is restricted in many ways to avoid complexity and bugs:
+ *
+ * - read can only happen in the IO thread associated with the poller
+ * - user doesn't control poll interest - it is set automatically based
+ * on read/write results
+ * - it is only possible to suspend read, for TCP flow control - also
+ * only from the IO thread. Resume can happen from any thread.
+ * - it is also possible to call write() from any thread
+ */
+ public static interface NonBlockingPollHandler extends BlockingPollHandler {
+
+ /**
+ * Called after connection is established, in a thread pool.
+ * Process will be called next.
+ */
+ public void connected(AprSocket ch);
+
+ /**
+ * Before close, if an exception happens.
+ */
+ public void error(AprSocket ch, Throwable t);
+ }
+
+}
Added: tomcat/trunk/java/org/apache/tomcat/jni/socket/HostInfo.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/socket/HostInfo.java?rev=1299980&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/jni/socket/HostInfo.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/jni/socket/HostInfo.java Tue Mar 13 05:36:17 2012
@@ -0,0 +1,83 @@
+/*
+ * 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.tomcat.jni.socket;
+
+import java.io.Serializable;
+
+/**
+ * Information about the remote host. Persisting this in memcache or similar
+ * storage can improve performance on future TLS connections by skipping roundtrips
+ * and reducing CPU use in handshake.
+ *
+ * This class is used in both server and client mode.
+ *
+ * AprSocketContextLitener.getPeer(name) can be used to read from an external storage.
+ *
+ * TODO: also save the SPDY persistent settings here.
+ * TODO: fix tickets, don't seem to work anymore.
+ */
+public class HostInfo implements Serializable {
+
+ public String host;
+
+ public int port;
+
+ public boolean secure;
+
+ /**
+ * Raw cert data (x.509 format).
+ * This is retrieved when a full handshake happens - if session reuse or tickets
+ * are used you'll not receive the certs again.
+ */
+ public byte[][] certs;
+
+ public byte[] ticket;
+ public int ticketLen;
+
+ public String sessionId;
+
+ /**
+ * DER-encoded session data.
+ */
+ public byte[] sessDer;
+
+ /**
+ * Negotiated NPN.
+ */
+ byte[] npn;
+ int npnLen;
+
+ public HostInfo() {
+ }
+
+ public HostInfo(String host, int port, boolean secure) {
+ this.host = host;
+ this.port = port;
+ this.secure = secure;
+ }
+
+ public String getNpn() {
+ return new String(npn, 0, npnLen);
+ }
+
+ public void setNpn(String npn) {
+ if (npn == null) {
+ npnLen = 0;
+ npn = null;
+ }
+ }
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org