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