[Pki-devel] [PATCH] 625 Fixed fail-over in HttpConnection.

Endi Sukma Dewata edewata at redhat.com
Thu Jul 2 16:10:14 UTC 2015


The HttpConnection class has been modified to support fail-over
and timeout more consistently. The targets are parsed into a list
during initialization. All direct calls to HttpClient.connect()
are replaced with a method that will connect to the first available
target. All connections are now created with a timeout (which by
default is 0). The timeout is handled internally by JSS instead of
by explicitly abandoning asynchronous connection thread.

https://fedorahosted.org/pki/ticket/891

-- 
Endi S. Dewata
-------------- next part --------------
From 5ec5fcda2a9064c4c493b31e6c8a83260d41af67 Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata at redhat.com>
Date: Wed, 1 Jul 2015 14:41:51 -0400
Subject: [PATCH] Fixed fail-over in HttpConnection.

The HttpConnection class has been modified to support fail-over
and timeout more consistently. The targets are parsed into a list
during initialization. All direct calls to HttpClient.connect()
are replaced with a method that will connect to the first available
target. All connections are now created with a timeout (which by
default is 0). The timeout is handled internally by JSS instead of
by explicitly abandoning asynchronous connection thread.

https://fedorahosted.org/pki/ticket/891
---
 base/ca/src/com/netscape/ca/CAService.java         |   5 +-
 .../netscape/cmscore/connector/HttpConnection.java | 246 +++++++++++----------
 .../com/netscape/cmsutil/http/ConnectAsync.java    |  46 ----
 .../src/com/netscape/cmsutil/http/HttpClient.java  |  40 +---
 .../netscape/cmsutil/http/JssSSLSocketFactory.java |  38 ++--
 .../com/netscape/cmsutil/net/ISocketFactory.java   |  10 +-
 6 files changed, 170 insertions(+), 215 deletions(-)
 delete mode 100644 base/util/src/com/netscape/cmsutil/http/ConnectAsync.java

diff --git a/base/ca/src/com/netscape/ca/CAService.java b/base/ca/src/com/netscape/ca/CAService.java
index 6edaf2ade4ac09baa37f3d34bd270273adc36db1..36f0bd592e333a276da84662c1e64a2921c5e7d2 100644
--- a/base/ca/src/com/netscape/ca/CAService.java
+++ b/base/ca/src/com/netscape/ca/CAService.java
@@ -435,9 +435,8 @@ public class CAService implements ICAService, IService {
             // send request to KRA first
             if (type.equals(IRequest.ENROLLMENT_REQUEST) &&
                     isPKIArchiveOptionPresent(request) && mKRAConnector != null) {
-                if (Debug.ON) {
-                    Debug.trace("*** Sending enrollment request to KRA");
-                }
+
+                CMS.debug("CAService: Sending enrollment request to KRA");
                 boolean sendStatus = mKRAConnector.send(request);
 
                 if (mArchivalRequired == true) {
diff --git a/base/server/cmscore/src/com/netscape/cmscore/connector/HttpConnection.java b/base/server/cmscore/src/com/netscape/cmscore/connector/HttpConnection.java
index c179f4b3ed0664dc4b9466365ef5413d32f2d034..c4804784af837775e0817419859a1d8f1335f848 100644
--- a/base/server/cmscore/src/com/netscape/cmscore/connector/HttpConnection.java
+++ b/base/server/cmscore/src/com/netscape/cmscore/connector/HttpConnection.java
@@ -18,7 +18,9 @@
 package com.netscape.cmscore.connector;
 
 import java.io.IOException;
-import java.util.StringTokenizer;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
@@ -33,31 +35,113 @@ import com.netscape.cmsutil.http.HttpResponse;
 import com.netscape.cmsutil.net.ISocketFactory;
 
 public class HttpConnection implements IHttpConnection {
+
     protected IRemoteAuthority mDest = null;
     protected HttpRequest mHttpreq = new HttpRequest();
     protected IRequestEncoder mReqEncoder = null;
     protected HttpClient mHttpClient = null;
 
-    protected boolean Connect(String host, HttpClient client) {
-        StringTokenizer st = new StringTokenizer(host, " ");
-        while (st.hasMoreTokens()) {
-            String hp = st.nextToken(); // host:port
-            StringTokenizer st1 = new StringTokenizer(hp, ":");
-            try {
-                String h = st1.nextToken();
-                int p = Integer.parseInt(st1.nextToken());
-                client.connect(h, p);
-                return true;
-            } catch (Exception e) {
-                // may want to log the failure
+    int timeout = 0;
+    List<InetSocketAddress> targets;
+
+    public HttpConnection(IRemoteAuthority dest, ISocketFactory factory,
+            int timeout // seconds
+            ) {
+
+        CMS.debug("HttpConnection: Creating HttpConnection with timeout=" + timeout);
+
+        mDest = dest;
+        mReqEncoder = new HttpRequestEncoder();
+        mHttpClient = new HttpClient(factory);
+
+        this.timeout = timeout;
+
+        targets = parseTarget(dest.getHost(), dest.getPort());
+
+        try {
+            mHttpreq.setMethod("POST");
+
+            // in case of multi-uri, uri will be set right before send
+            //   by calling setRequestURI(uri)
+            if (mDest.getURI() != null)
+                mHttpreq.setURI(mDest.getURI());
+
+            String contentType = dest.getContentType();
+            if (contentType != null) {
+                CMS.debug("HttpConnection: setting Content-Type");
+                mHttpreq.setHeader("Content-Type", contentType );
             }
+
+            mHttpreq.setHeader("Connection", "Keep-Alive");
+
+            connect();
+
+        } catch (IOException e) {
+            // server's probably down. that's fine. try later.
+            CMS.debug("HttpConnection: Unable to create connection: " + e);
+        }
+    }
+
+    public HttpConnection(IRemoteAuthority dest, ISocketFactory factory) {
+        this(dest, factory, 0);
+    }
+
+    List<InetSocketAddress> parseTarget(String target, int port) {
+
+        List<InetSocketAddress> results = new ArrayList<InetSocketAddress>();
+
+        if (target == null || target.indexOf(' ') < 0) {
+            // target is a single hostname
+
+            // add hostname and the global port to the results
+            results.add(new InetSocketAddress(target, port));
+            return results;
+        }
+
+        // target is a list of hostname:port, for example:
+        // "server1.example.com:8443 server2.example.com:8443"
+
+        for (String hostnamePort : target.split(" ")) {
+
+            // parse hostname and port, and ignore the global port
+            String[] parts = hostnamePort.split(":");
+            String hostname = parts[0];
+            port = Integer.parseInt(parts[1]);
+
+            // add hostname and port to the results
+            results.add(new InetSocketAddress(hostname, port));
+        }
+
+        return results;
+    }
+
+    void connect() throws IOException {
+
+        IOException exception = null;
+
+        // try all targets
+        for (InetSocketAddress target : targets) {
+
+            String hostname = target.getHostString();
+            int port = target.getPort();
+
             try {
-                Thread.sleep(5000); // 5 seconds
-            } catch (Exception e) {
-            }
+                CMS.debug("HttpConnection: Connecting to " + hostname + ":" + port + " with timeout " + timeout + "s");
 
+                mHttpClient.connect(hostname, port, timeout * 1000);
+
+                CMS.debug("HttpConnection: Connected to " + hostname + ":" + port);
+                return;
+
+            } catch (IOException e) {
+                exception = e;
+                CMS.debug("HttpConnection: Unable to connect to " + hostname + ":" + port + ": " + e);
+                // try the next target immediately
+            }
         }
-        return false;
+
+        // throw the last exception
+        throw exception;
     }
 
     public void setRequestURI(String uri)
@@ -68,83 +152,6 @@ public class HttpConnection implements IHttpConnection {
     public String getRequestURI() {
         return mHttpreq.getURI();
     }
-
-    public HttpConnection(IRemoteAuthority dest, ISocketFactory factory) {
-        mDest = dest;
-        mReqEncoder = new HttpRequestEncoder();
-        mHttpClient = new HttpClient(factory);
-        if (Debug.ON)
-            Debug.trace("Created HttpClient");
-        try {
-            mHttpreq.setMethod("POST");
-            // in case of multi-uri, uri will be set right before send
-            //   by calling setRequestURI(uri)
-            if (mDest.getURI() != null)
-                mHttpreq.setURI(mDest.getURI());
-
-            String contentType = dest.getContentType();
-            if (contentType != null) {
-                CMS.debug("HttpConnection: setting Content-Type");
-                mHttpreq.setHeader("Content-Type", contentType );
-            }
-
-            mHttpreq.setHeader("Connection", "Keep-Alive");
-            CMS.debug("HttpConnection: connecting to " + dest.getHost() + ":" + dest.getPort());
-            String host = dest.getHost();
-            // we could have a list of host names in the host parameters
-            // the format is, for example,
-            // "directory.knowledge.com:1050 people.catalog.com 199.254.1.2"
-            if (host != null && host.indexOf(' ') != -1) {
-                // try to do client-side failover
-                boolean connected = false;
-                do {
-                    connected = Connect(host, mHttpClient);
-                } while (!connected);
-            } else {
-                mHttpClient.connect(host, dest.getPort());
-            }
-            CMS.debug("HttpConnection: connected to " + dest.getHost() + ":" + dest.getPort());
-        } catch (IOException e) {
-            // server's probably down. that's fine. try later.
-            //System.out.println(
-            //"Can't connect to server in connection creation");
-        }
-    }
-
-    /*
-     * @param op operation to determine the receiving servlet (multi-uri support)
-     */
-    public HttpConnection(IRemoteAuthority dest, ISocketFactory factory, int timeout) {
-        mDest = dest;
-        mReqEncoder = new HttpRequestEncoder();
-        mHttpClient = new HttpClient(factory);
-        CMS.debug("HttpConn:Created HttpConnection: factory " + factory + "client " + mHttpClient);
-        try {
-            mHttpreq.setMethod("POST");
-            // in case of multi-uri, uri will be set right before send
-            //   by calling setRequestURI(op)
-            if (mDest.getURI() != null)
-                mHttpreq.setURI(mDest.getURI());
-
-            String contentType = dest.getContentType();
-            if (contentType != null) {
-                CMS.debug("HttpConnection: setting Content-Type");
-                mHttpreq.setHeader("Content-Type", contentType );
-            }
-
-            mHttpreq.setHeader("Connection", "Keep-Alive");
-            CMS.debug("HttpConnection: connecting to " + dest.getHost() + ":" + dest.getPort() + " timeout:" + timeout);
-            mHttpClient.connect(dest.getHost(), dest.getPort(), timeout);
-            CMS.debug("HttpConnection: connected to " + dest.getHost() + ":" + dest.getPort() + " timeout:" + timeout);
-        } catch (IOException e) {
-            // server's probably down. that's fine. try later.
-            //System.out.println(
-            //"Can't connect to server in connection creation");
-            CMS.debug("CMSConn:IOException in creating HttpConnection " + e.toString());
-        }
-    }
-
-    // Insert end
     /**
      * sends a request to remote RA/CA, returning the result.
      *
@@ -207,16 +214,17 @@ public class HttpConnection implements IHttpConnection {
             CMS.debug("HttpConnection.send: with String content: null or empty");
             throw new EBaseException("HttpConnection.send: with String content: null or empty");
         }
-        // CMS.debug("HttpConnection.send: with String content: " + content);
+
+        CMS.debug("HttpConnection.send: with String content: " + content);
 
         resp = doSend(content);
         return resp;
     }
 
-    private HttpResponse doSend(String content)
-            throws EBaseException {
+    private HttpResponse doSend(String content) throws EBaseException {
+
         HttpResponse resp = null;
-        boolean reconnect = false;
+        boolean reconnected = false;
 
         if (getRequestURI() == null) {
             throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", "URI not set in HttpRequest"));
@@ -229,18 +237,21 @@ public class HttpConnection implements IHttpConnection {
 
         try {
             if (!mHttpClient.connected()) {
-                mHttpClient.connect(mDest.getHost(), mDest.getPort());
-                CMS.debug("HttpConnection.doSend: reconnected to " + mDest.getHost() + ":" + mDest.getPort());
-                reconnect = true;
+                connect();
+                reconnected = true;
             }
+
         } catch (IOException e) {
+
+            CMS.debug(e);
+
             if (e.getMessage().indexOf("Peer's certificate issuer has been marked as not trusted") != -1) {
                 throw new EBaseException(
                         CMS.getUserMessage(
                                 "CMS_BASE_CONN_FAILED",
                                 "(This local authority cannot connect to the remote authority. The local authority's signing certificate must chain to a CA certificate trusted for client authentication in the certificate database. Use the certificate manager, or command line tool such as certutil to verify that the trust permissions of the local authority's issuer cert have 'CT' setting in the SSL client auth field.)"));
             }
-            CMS.debug("HttpConn:Couldn't reconnect " + e);
+
             throw new EBaseException(CMS.getUserMessage("CMS_BASE_CONN_FAILED", "Couldn't reconnect " + e));
         }
 
@@ -249,28 +260,35 @@ public class HttpConnection implements IHttpConnection {
             try {
                 CMS.debug("HttpConnection.doSend: sending request");
                 resp = mHttpClient.send(mHttpreq);
+
             } catch (IOException e) {
-                CMS.debug("HttpConn: mHttpClient.send failed " + e.toString());
-                if (reconnect) {
-                    CMS.debug("HttpConnection.doSend:resend failed again. " + e);
-                    throw new EBaseException(CMS.getUserMessage("CMS_BASE_CONN_FAILED", "resend failed again. " + e));
+
+                CMS.debug(e);
+
+                if (reconnected) {
+                    CMS.debug("HttpConnection.doSend: resend failed again.");
+                    throw new EBaseException(
+                            CMS.getUserMessage("CMS_BASE_CONN_FAILED", "resend failed again: " + e), e);
                 }
+
                 try {
                     CMS.debug("HttpConnection.doSend: trying a reconnect ");
-                    mHttpClient.connect(mDest.getHost(), mDest.getPort());
+                    connect();
+
                 } catch (IOException ex) {
                     CMS.debug("HttpConnection.doSend: reconnect for resend failed. " + ex);
-                    throw new EBaseException(CMS.getUserMessage("CMS_BASE_CONN_FAILED", "reconnect for resend failed."
-                            + ex));
+                    throw new EBaseException(
+                            CMS.getUserMessage("CMS_BASE_CONN_FAILED", "reconnect for resend failed: " + ex), e);
                 }
-                reconnect = true;
+
+                reconnected = true;
             }
         } //while
 
         // got reply; check status
         String statusStr = resp.getStatusCode();
 
-        CMS.debug("HttpConnection.doSend:server returned status " + statusStr);
+        CMS.debug("HttpConnection.doSend: server returned status " + statusStr);
         int statuscode = -1;
 
         try {
@@ -287,16 +305,18 @@ public class HttpConnection implements IHttpConnection {
                 // XXX what to do here.
                 String msg = "request no good " + statuscode + " " + resp.getReasonPhrase();
 
-                CMS.debug(msg);
+                CMS.debug("HttpConnection: " + msg);
                 throw new EBaseException(CMS.getUserMessage("CMS_BASE_AUTHENTICATE_FAILED", msg));
+
             } else {
                 // XXX what to do here.
-                String msg = "HttpConn:request no good " + statuscode + " " + resp.getReasonPhrase();
+                String msg = "HttpConnection: request no good " + statuscode + " " + resp.getReasonPhrase();
 
                 CMS.debug(msg);
                 throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_ATTRIBUTE", msg));
             }
         }
+
         return resp;
     }
 }
diff --git a/base/util/src/com/netscape/cmsutil/http/ConnectAsync.java b/base/util/src/com/netscape/cmsutil/http/ConnectAsync.java
deleted file mode 100644
index ca230ca21bfafa025c36da4f1bf0308278cf5bae..0000000000000000000000000000000000000000
--- a/base/util/src/com/netscape/cmsutil/http/ConnectAsync.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// --- BEGIN COPYRIGHT BLOCK ---
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation; version 2 of the License.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License along
-// with this program; if not, write to the Free Software Foundation, Inc.,
-// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-//
-// (C) 2007 Red Hat, Inc.
-// All rights reserved.
-// --- END COPYRIGHT BLOCK ---
-package com.netscape.cmsutil.http;
-
-import java.net.SocketException;
-
-import com.netscape.cmsutil.net.ISocketFactory;
-
-public class ConnectAsync extends Thread {
-    String host = null;
-    int port = 0;
-    ISocketFactory obj = null;
-
-    public ConnectAsync(ISocketFactory sock, String host, int port) {
-        super();
-        this.host = host;
-        this.port = port;
-        this.obj = sock;
-        setName("ConnectAsync");
-    }
-
-    public void run() {
-        try {
-            obj.makeSocket(host, port);
-        } catch (SocketException e) {
-            // Stop throwing exception
-        } catch (Exception e) {
-            // Stop throwing exception
-        }
-    }
-}
diff --git a/base/util/src/com/netscape/cmsutil/http/HttpClient.java b/base/util/src/com/netscape/cmsutil/http/HttpClient.java
index 438c70c2333b8652996a3a27aab64e86aac33ae5..db042a781823186c54b80d9f85449b817d3b66f8 100644
--- a/base/util/src/com/netscape/cmsutil/http/HttpClient.java
+++ b/base/util/src/com/netscape/cmsutil/http/HttpClient.java
@@ -59,22 +59,24 @@ public class HttpClient {
         mCertApprovalCallback = certApprovalCallback;
     }
 
-    public void connect(String host, int port)
-            throws IOException {
+    public void connect(String host, int port,
+            int timeout // milliseconds
+            ) throws IOException {
+
         if (mFactory != null) {
             if (mCertApprovalCallback == null) {
-                mSocket = mFactory.makeSocket(host, port);
+                mSocket = mFactory.makeSocket(host, port, timeout);
             } else {
-                mSocket = mFactory.makeSocket(host, port, mCertApprovalCallback, null);
+                mSocket = mFactory.makeSocket(host, port, mCertApprovalCallback, null, timeout);
             }
+
         } else {
             mSocket = new Socket(host, port);
+            mSocket.setSoTimeout(timeout);
         }
 
         if (mSocket == null) {
-            IOException e = new IOException("Couldn't make connection");
-
-            throw e;
+            throw new IOException("Couldn't make connection");
         }
 
         mInputStream = mSocket.getInputStream();
@@ -85,30 +87,10 @@ public class HttpClient {
         mConnected = true;
     }
 
-    // Inserted by beomsuk
-    public void connect(String host, int port, int timeout)
-            throws IOException {
-        if (mFactory != null) {
-            mSocket = mFactory.makeSocket(host, port, timeout);
-        } else {
-            mSocket = new Socket(host, port);
-        }
-
-        if (mSocket == null) {
-            IOException e = new IOException("Couldn't make connection");
-
-            throw e;
-        }
-
-        mInputStream = mSocket.getInputStream();
-        mOutputStream = mSocket.getOutputStream();
-        mInputStreamReader = new InputStreamReader(mInputStream, "UTF8");
-        mBufferedReader = new BufferedReader(mInputStreamReader);
-        mOutputStreamWriter = new OutputStreamWriter(mOutputStream, "UTF8");
-        mConnected = true;
+    public void connect(String host, int port) throws IOException {
+        connect(host, port, 0);
     }
 
-    // Insert end
     public boolean connected() {
         return mConnected;
     }
diff --git a/base/util/src/com/netscape/cmsutil/http/JssSSLSocketFactory.java b/base/util/src/com/netscape/cmsutil/http/JssSSLSocketFactory.java
index 166479d5aad9eb4790c01cbaa92c9d56e69d6726..fc9e3221ec3bbc26bf1ff7f3f154fdccb264cb06 100644
--- a/base/util/src/com/netscape/cmsutil/http/JssSSLSocketFactory.java
+++ b/base/util/src/com/netscape/cmsutil/http/JssSSLSocketFactory.java
@@ -48,13 +48,14 @@ public class JssSSLSocketFactory implements ISocketFactory {
 
     public Socket makeSocket(String host, int port)
             throws IOException, UnknownHostException {
-        return makeSocket(host, port, null, null);
+        return makeSocket(host, port, null, null, 0);
     }
 
     public Socket makeSocket(String host, int port,
             SSLCertificateApprovalCallback certApprovalCallback,
-            SSLClientCertificateSelectionCallback clientCertCallback)
-            throws IOException, UnknownHostException {
+            SSLClientCertificateSelectionCallback clientCertCallback,
+            int timeout // milliseconds
+            ) throws IOException, UnknownHostException {
 
         try {
             /*
@@ -63,6 +64,7 @@ public class JssSSLSocketFactory implements ISocketFactory {
             s = new SSLSocket(host, port, null, 0, certApprovalCallback,
                     clientCertCallback);
             s.setUseClientMode(true);
+            s.setSoTimeout(timeout);
 
             SSLHandshakeCompletedListener listener = null;
 
@@ -79,34 +81,30 @@ public class JssSSLSocketFactory implements ISocketFactory {
                 s.setClientCertNickname(mClientAuthCertNickname);
             }
             s.forceHandshake();
+
         } catch (org.mozilla.jss.crypto.ObjectNotFoundException e) {
-            throw new IOException(e.toString());
+            throw new IOException(e.toString(), e);
+
         } catch (org.mozilla.jss.crypto.TokenException e) {
-            throw new IOException(e.toString());
+            throw new IOException(e.toString(), e);
+
         } catch (UnknownHostException e) {
             throw e;
+
         } catch (IOException e) {
             throw e;
+
         } catch (Exception e) {
-            throw new IOException(e.toString());
+            throw new IOException(e.toString(), e);
         }
+
         return s;
     }
 
-    public Socket makeSocket(String host, int port, int timeout)
-            throws IOException, UnknownHostException {
-        Thread t = new ConnectAsync(this, host, port);
-
-        t.start();
-        try {
-            t.join(1000 * timeout);
-        } catch (InterruptedException e) {
-        }
-
-        if (t.isAlive()) {
-        }
-
-        return s;
+    public Socket makeSocket(String host, int port,
+            int timeout // milliseconds
+            ) throws IOException, UnknownHostException {
+        return makeSocket(host, port, null, null, timeout);
     }
 
     public void log(int level, String msg) {
diff --git a/base/util/src/com/netscape/cmsutil/net/ISocketFactory.java b/base/util/src/com/netscape/cmsutil/net/ISocketFactory.java
index 18f6cac88f96dc22f0e3da1e0699479071699a63..0dd6963ce669457acb842c0692aa683eebd3e147 100644
--- a/base/util/src/com/netscape/cmsutil/net/ISocketFactory.java
+++ b/base/util/src/com/netscape/cmsutil/net/ISocketFactory.java
@@ -28,11 +28,13 @@ public interface ISocketFactory {
     Socket makeSocket(String host, int port)
             throws IOException, UnknownHostException;
 
-    Socket makeSocket(String host, int port, int timeout)
-            throws IOException, UnknownHostException;
+    Socket makeSocket(String host, int port,
+            int timeout // milliseconds
+            ) throws IOException, UnknownHostException;
 
     Socket makeSocket(String host, int port,
             SSLCertificateApprovalCallback certApprovalCallback,
-            SSLClientCertificateSelectionCallback clientCertCallback)
-            throws IOException, UnknownHostException;
+            SSLClientCertificateSelectionCallback clientCertCallback,
+            int timeout // milliseconds
+            ) throws IOException, UnknownHostException;
 }
-- 
1.9.3



More information about the Pki-devel mailing list