Build require yazpp 1.3.0
[metaproxy-moved-to-github.git] / src / filter_zoom.cpp
index 34b3084..617144c 100644 (file)
@@ -24,6 +24,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 #include <metaproxy/package.hpp>
 #include <metaproxy/util.hpp>
 #include <metaproxy/xmlutil.hpp>
+#include <yaz/comstack.h>
+#include <yaz/poll.h>
 #include "torus.hpp"
 
 #include <libxslt/xsltutils.h>
@@ -95,6 +97,7 @@ namespace metaproxy_1 {
             bool enable_cproxy;
             bool enable_explain;
             xmlDoc *explain_doc;
+            std::string m_proxy;
         public:
             Backend();
             ~Backend();
@@ -169,7 +172,12 @@ namespace metaproxy_1 {
                                            ODR odr, BackendPtr b,
                                            Odr_oid *preferredRecordSyntax,
                                            const char *element_set_name);
-
+            bool retry(mp::Package &package,
+                       mp::odr &odr,
+                       BackendPtr b, 
+                       int &error, char **addinfo,
+                       int &proxy_step, int &same_retries,
+                       int &proxy_retries);
             void log_diagnostic(mp::Package &package,
                                 int error, const char *addinfo);
         public:
@@ -186,6 +194,10 @@ namespace metaproxy_1 {
                            const char *path);
         private:
             void configure_local_records(const xmlNode * ptr, bool test_only);
+            bool check_proxy(const char *proxy);
+
+
+
             FrontendPtr get_frontend(mp::Package &package);
             void release_frontend(mp::Package &package);
             SearchablePtr parse_torus_record(const xmlNode *ptr);
@@ -211,6 +223,7 @@ namespace metaproxy_1 {
             xsltStylesheetPtr record_xsp;
             std::map<std::string,SearchablePtr> s_map;
             std::string zoom_timeout;
+            int proxy_timeout;
         };
     }
 }
@@ -301,6 +314,21 @@ void yf::Zoom::Backend::get_zoom_error(int *error, char **addinfo,
             *error = yaz_diag_srw_to_bib1(error0);
         else if (!strcmp(dset, "Bib-1"))
             *error = error0;
+        else if (!strcmp(dset, "ZOOM"))
+        {
+            *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;                
+            if (error0 == ZOOM_ERROR_INIT)
+                *error = YAZ_BIB1_INIT_AC_AUTHENTICATION_SYSTEM_ERROR;
+            else if (error0 == ZOOM_ERROR_DECODE)
+            {
+                if (zoom_addinfo)
+                {
+                    if (strstr(zoom_addinfo, "Authentication") ||
+                        strstr(zoom_addinfo, "authentication"))
+                        *error = YAZ_BIB1_INIT_AC_AUTHENTICATION_SYSTEM_ERROR;
+                }
+            }
+        }
         else
             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
         
@@ -431,7 +459,7 @@ void yf::Zoom::Impl::release_frontend(mp::Package &package)
 
 yf::Zoom::Impl::Impl() :
     apdu_log(false), element_transform("pz2") , element_raw("raw"),
-    zoom_timeout("40")
+    zoom_timeout("40"), proxy_timeout(1)
 {
     bibset = ccl_qual_mk();
 
@@ -734,6 +762,8 @@ void yf::Zoom::Impl::configure(const xmlNode *ptr, bool test_only,
             {
                 if (!strcmp((const char *) attr->name, "timeout"))
                     zoom_timeout = mp::xml::get_text(attr->children);
+                else if (!strcmp((const char *) attr->name, "proxy_timeout"))
+                    proxy_timeout = mp::xml::get_int(attr->children, 1);
                 else
                     throw mp::filter::FilterException(
                         "Bad attribute " + std::string((const char *)
@@ -957,12 +987,15 @@ yf::Zoom::BackendPtr yf::Zoom::Frontend::get_backend_from_databases(
     std::string &database, int *error, char **addinfo, mp::odr &odr,
     int *proxy_step)
 {
+    bool connection_reuse = false;
+    std::string proxy;
+
     std::list<BackendPtr>::const_iterator map_it;
     if (m_backend && !m_backend->enable_explain && 
         m_backend->m_frontend_database == database)
     {
-        m_backend->connect("", error, addinfo, odr);
-        return m_backend;
+        connection_reuse = true;
+        proxy = m_backend->m_proxy;
     }
 
     std::string input_args;
@@ -978,7 +1011,6 @@ yf::Zoom::BackendPtr yf::Zoom::Frontend::get_backend_from_databases(
 
     std::string authentication;
     std::string content_authentication;
-    std::string proxy;
     std::string content_proxy;
     std::string realm = m_p->default_realm;
 
@@ -1027,15 +1059,31 @@ yf::Zoom::BackendPtr yf::Zoom::Frontend::get_backend_from_databases(
             char **dstr;
             int dnum = 0;
             nmem_strsplit(((ODR) odr)->mem, ",", value, &dstr, &dnum);
-            if (*proxy_step >= dnum)
-                *proxy_step = 0;
+            if (connection_reuse)
+            {
+                // find the step after our current proxy
+                int i;
+                for (i = 0; i < dnum; i++)
+                    if (!strcmp(proxy.c_str(), dstr[i]))
+                        break;
+                if (i >= dnum - 1)
+                    *proxy_step = 0;
+                else
+                    *proxy_step = i + 1;
+            }
             else
             {
-                proxy = dstr[*proxy_step];
-                
-                (*proxy_step)++;
-                if (*proxy_step == dnum)
+                // step is known.. Guess our proxy from it
+                if (*proxy_step >= dnum)
                     *proxy_step = 0;
+                else
+                {
+                    proxy = dstr[*proxy_step];
+                    
+                    (*proxy_step)++;
+                    if (*proxy_step == dnum)
+                        *proxy_step = 0;
+                }
             }
         }
         else if (!strcmp(name, "cproxysession"))
@@ -1060,7 +1108,16 @@ yf::Zoom::BackendPtr yf::Zoom::Frontend::get_backend_from_databases(
             *addinfo = msg;
             return notfound;
         }
+    }    
+    if (proxy.length())
+        package.log("zoom", YLOG_LOG, "proxy: %s", proxy.c_str());
+
+    if (connection_reuse)
+    {
+        m_backend->connect("", error, addinfo, odr);
+        return m_backend;
     }
+
     if (param_user)
     {
         authentication = std::string(param_user);
@@ -1203,6 +1260,7 @@ yf::Zoom::BackendPtr yf::Zoom::Frontend::get_backend_from_databases(
 
     BackendPtr b(new Backend);
 
+    b->m_proxy = proxy;
     b->sptr = sptr;
     b->xsp = xsp;
     b->m_frontend_database = database;
@@ -1282,9 +1340,6 @@ yf::Zoom::BackendPtr yf::Zoom::Frontend::get_backend_from_databases(
         if (proxy.length())
             b->set_option("proxy", proxy);
     }
-    if (proxy.length())
-        package.log("zoom", YLOG_LOG, "proxy: %s", proxy.c_str());
-                
     std::string url;
     if (sptr->sru.length())
     {
@@ -1816,6 +1871,95 @@ yf::Zoom::BackendPtr yf::Zoom::Frontend::explain_search(mp::Package &package,
     }
 }
 
+static bool wait_conn(COMSTACK cs, int secs)
+{
+    struct yaz_poll_fd pfd;
+
+    yaz_poll_add(pfd.input_mask, yaz_poll_except);
+    if (cs->io_pending && CS_WANT_WRITE)
+        yaz_poll_add(pfd.input_mask, yaz_poll_write);
+    if (cs->io_pending & CS_WANT_READ)
+        yaz_poll_add(pfd.input_mask, yaz_poll_read);
+
+    pfd.fd = cs_fileno(cs);
+    pfd.client_data = 0;
+    
+    int ret = yaz_poll(&pfd, 1, secs, 0);
+    return ret > 0;
+}
+
+bool yf::Zoom::Impl::check_proxy(const char *proxy)
+{
+    COMSTACK conn = 0;
+    const char *uri = "http://localhost/";
+    void *add;
+    mp::odr odr;
+    bool outcome = false;
+    conn = cs_create_host_proxy(uri, 0, &add, proxy);
+
+    if (!conn)
+        return false;
+
+    Z_GDU *gdu = z_get_HTTP_Request_uri(odr, uri, 0, 1);
+    gdu->u.HTTP_Request->method = odr_strdup(odr, "GET");
+    
+    if (z_GDU(odr, &gdu, 0, 0))
+    {
+        int len;
+        char *buf = odr_getbuf(odr, &len, 0);
+        
+        int ret = cs_connect(conn, add);
+        if (ret > 0 || (ret == 0 && wait_conn(conn, 1)))
+        {
+            while (1)
+            {
+                ret = cs_put(conn, buf, len);
+                if (ret != 1)
+                    break;
+                if (!wait_conn(conn, proxy_timeout))
+                    break;
+            }
+            if (ret == 0)
+                outcome = true;
+        }
+    }
+    cs_close(conn);
+    return outcome;
+}
+
+bool yf::Zoom::Frontend::retry(mp::Package &package,
+                               mp::odr &odr,
+                               BackendPtr b, 
+                               int &error, char **addinfo,
+                               int &proxy_step, int &same_retries,
+                               int &proxy_retries)
+{
+    if (b && b->m_proxy.length() && !m_p->check_proxy(b->m_proxy.c_str()))
+    {
+        log_diagnostic(package, error, *addinfo);
+        package.log("zoom", YLOG_LOG, "proxy %s fails", b->m_proxy.c_str());
+        m_backend.reset();
+        if (proxy_step) // there is a failover
+        {
+            proxy_retries++;
+            package.log("zoom", YLOG_WARN, "search failed: trying next proxy");
+            return true;
+        }
+        error = YAZ_BIB1_PROXY_FAILURE;
+        *addinfo = odr_strdup(odr, b->m_proxy.c_str());
+    }
+    else if (same_retries == 0 && proxy_retries == 0)
+    {
+        log_diagnostic(package, error, *addinfo);
+        same_retries++;
+        package.log("zoom", YLOG_WARN, "search failed: retry");
+        m_backend.reset();
+        proxy_step = 0;
+        return true;
+    }
+    return false;
+}
+
 void yf::Zoom::Frontend::handle_search(mp::Package &package)
 {
     Z_GDU *gdu = package.request().get();
@@ -1832,6 +1976,8 @@ void yf::Zoom::Frontend::handle_search(mp::Package &package)
         return;
     }
     int proxy_step = 0;
+    int same_retries = 0;
+    int proxy_retries = 0;
 
 next_proxy:
 
@@ -1841,11 +1987,11 @@ next_proxy:
 
     BackendPtr b = get_backend_from_databases(package, db, &error,
                                               &addinfo, odr, &proxy_step);
-    if (error && proxy_step)
+    if (error)
     {
-        package.log("zoom", YLOG_WARN,
-                    "create backend failed: trying next proxy");
-        goto next_proxy;
+        if (retry(package, odr, b, error, &addinfo, proxy_step,
+                  same_retries, proxy_retries))
+            goto next_proxy;
     }
     if (error)
     {
@@ -2089,12 +2235,11 @@ next_proxy:
         ZOOM_query_destroy(q);
     }
 
-    if (error && proxy_step)
+    if (error)
     {
-        // reset below prevent reuse in get_backend_from_databases
-        m_backend.reset();
-        package.log("zoom", YLOG_WARN, "search failed: trying next proxy");
-        goto next_proxy;
+        if (retry(package, odr, b, error, &addinfo, proxy_step,
+                  same_retries, proxy_retries))
+            goto next_proxy;
     }
 
     const char *element_set_name = 0;