proxy.sh start/stop script
[yazpp-moved-to-github.git] / src / yaz-proxy.cpp
1 /*
2  * Copyright (c) 1998-2003, Index Data.
3  * See the file LICENSE for details.
4  * 
5  * $Id: yaz-proxy.cpp,v 1.66 2003-10-23 09:10:12 adam Exp $
6  */
7
8 #include <assert.h>
9 #include <time.h>
10
11 #include <yaz/marcdisp.h>
12 #include <yaz/yaz-iconv.h>
13 #include <yaz/log.h>
14 #include <yaz/diagbib1.h>
15 #include <yaz++/proxy.h>
16
17 static const char *apdu_name(Z_APDU *apdu)
18 {
19     switch (apdu->which)
20     {
21     case Z_APDU_initRequest:
22         return "initRequest";
23     case Z_APDU_initResponse:
24         return "initResponse";
25     case Z_APDU_searchRequest:
26         return "searchRequest";
27     case Z_APDU_searchResponse:
28         return "searchResponse";
29     case Z_APDU_presentRequest:
30         return "presentRequest";
31     case Z_APDU_presentResponse:
32         return "presentResponse";
33     case Z_APDU_deleteResultSetRequest:
34         return "deleteResultSetRequest";
35     case Z_APDU_deleteResultSetResponse:
36         return "deleteResultSetResponse";
37     case Z_APDU_scanRequest:
38         return "scanRequest";
39     case Z_APDU_scanResponse:
40         return "scanResponse";
41     case Z_APDU_sortRequest:
42         return "sortRequest";
43     case Z_APDU_sortResponse:
44         return "sortResponse";
45     case Z_APDU_extendedServicesRequest:
46         return "extendedServicesRequest";
47     case Z_APDU_extendedServicesResponse:
48         return "extendedServicesResponse";
49     case Z_APDU_close:
50         return "close";
51     }
52     return "other";
53 }
54
55 Yaz_Proxy::Yaz_Proxy(IYaz_PDU_Observable *the_PDU_Observable,
56                      Yaz_Proxy *parent) :
57     Yaz_Z_Assoc(the_PDU_Observable), m_bw_stat(60), m_pdu_stat(60)
58 {
59     m_PDU_Observable = the_PDU_Observable;
60     m_client = 0;
61     m_parent = parent;
62     m_clientPool = 0;
63     m_seqno = 1;
64     m_keepalive_limit_bw = 500000;
65     m_keepalive_limit_pdu = 1000;
66     m_proxyTarget = 0;
67     m_default_target = 0;
68     m_proxy_authentication = 0;
69     m_max_clients = 150;
70     m_log_mask = 0;
71     m_seed = time(0);
72     m_client_idletime = 600;
73     m_target_idletime = 600;
74     m_optimize = xstrdup ("1");
75     strcpy(m_session_str, "0");
76     m_session_no=0;
77     m_bytes_sent = m_bytes_recv = 0;
78     m_bw_hold_PDU = 0;
79     m_bw_max = 0;
80     m_pdu_max = 0;
81     m_max_record_retrieve = 0;
82     m_reconfig_flag = 0;
83     m_config_fname = 0;
84     m_request_no = 0;
85     m_invalid_session = 0;
86     m_config = 0;
87     m_marcxml_flag = 0;
88 }
89
90 Yaz_Proxy::~Yaz_Proxy()
91 {
92     yaz_log(LOG_LOG, "%sClosed %d/%d sent/recv bytes total", m_session_str,
93             m_bytes_sent, m_bytes_recv);
94     xfree (m_proxyTarget);
95     xfree (m_default_target);
96     xfree (m_proxy_authentication);
97     xfree (m_optimize);
98     delete m_config;
99 }
100
101 int Yaz_Proxy::set_config(const char *config)
102 {
103     delete m_config;
104     m_config = new Yaz_ProxyConfig();
105     xfree(m_config_fname);
106     m_config_fname = xstrdup(config);
107     int r = m_config->read_xml(config);
108     if (!r)
109         m_config->get_generic_info(&m_log_mask, &m_max_clients);
110     return r;
111 }
112
113 void Yaz_Proxy::set_default_target(const char *target)
114 {
115     xfree (m_default_target);
116     m_default_target = 0;
117     if (target)
118         m_default_target = (char *) xstrdup (target);
119 }
120
121 void Yaz_Proxy::set_proxy_authentication (const char *auth)
122 {
123     xfree (m_proxy_authentication);
124     m_proxy_authentication = 0;
125     if (auth)
126         m_proxy_authentication = (char *) xstrdup (auth);
127 }
128
129 Yaz_ProxyConfig *Yaz_Proxy::check_reconfigure()
130 {
131     if (m_parent)
132         return m_parent->check_reconfigure();
133
134     Yaz_ProxyConfig *cfg = m_config;
135     if (m_reconfig_flag)
136     {
137         yaz_log(LOG_LOG, "reconfigure");
138         yaz_log_reopen();
139         if (m_config_fname && cfg)
140         {
141             yaz_log(LOG_LOG, "reconfigure config %s", m_config_fname);
142             int r = cfg->read_xml(m_config_fname);
143             if (r)
144                 yaz_log(LOG_WARN, "reconfigure failed");
145             else
146             {
147                 m_log_mask = 0;
148                 cfg->get_generic_info(&m_log_mask, &m_max_clients);
149             }
150         }
151         else
152             yaz_log(LOG_LOG, "reconfigure");
153         m_reconfig_flag = 0;
154     }
155     return cfg;
156 }
157
158 IYaz_PDU_Observer *Yaz_Proxy::sessionNotify(IYaz_PDU_Observable
159                                             *the_PDU_Observable, int fd)
160 {
161     check_reconfigure();
162     Yaz_Proxy *new_proxy = new Yaz_Proxy(the_PDU_Observable, this);
163     new_proxy->m_config = 0;
164     new_proxy->m_config_fname = 0;
165     new_proxy->timeout(m_client_idletime);
166     new_proxy->m_target_idletime = m_target_idletime;
167     new_proxy->set_default_target(m_default_target);
168     new_proxy->m_max_clients = m_max_clients;
169     new_proxy->m_log_mask = m_log_mask;
170     new_proxy->set_APDU_log(get_APDU_log());
171     if (m_log_mask & PROXY_LOG_APDU_CLIENT)
172         new_proxy->set_APDU_yazlog(1);
173     else
174         new_proxy->set_APDU_yazlog(0);
175     new_proxy->set_proxy_authentication(m_proxy_authentication);
176     sprintf(new_proxy->m_session_str, "%ld:%d ", (long) time(0), m_session_no);
177     m_session_no++;
178     yaz_log (LOG_LOG, "%sNew session %s", new_proxy->m_session_str,
179              the_PDU_Observable->getpeername());
180     return new_proxy;
181 }
182
183 char *Yaz_Proxy::get_cookie(Z_OtherInformation **otherInfo)
184 {
185     int oid[OID_SIZE];
186     Z_OtherInformationUnit *oi;
187     struct oident ent;
188     ent.proto = PROTO_Z3950;
189     ent.oclass = CLASS_USERINFO;
190     ent.value = (oid_value) VAL_COOKIE;
191     assert (oid_ent_to_oid (&ent, oid));
192
193     if (oid_ent_to_oid (&ent, oid) && 
194         (oi = update_otherInformation(otherInfo, 0, oid, 1, 1)) &&
195         oi->which == Z_OtherInfo_characterInfo)
196         return oi->information.characterInfo;
197     return 0;
198 }
199
200 char *Yaz_Proxy::get_proxy(Z_OtherInformation **otherInfo)
201 {
202     int oid[OID_SIZE];
203     Z_OtherInformationUnit *oi;
204     struct oident ent;
205     ent.proto = PROTO_Z3950;
206     ent.oclass = CLASS_USERINFO;
207     ent.value = (oid_value) VAL_PROXY;
208     if (oid_ent_to_oid (&ent, oid) &&
209         (oi = update_otherInformation(otherInfo, 0, oid, 1, 1)) &&
210         oi->which == Z_OtherInfo_characterInfo)
211         return oi->information.characterInfo;
212     return 0;
213 }
214
215 const char *Yaz_Proxy::load_balance(const char **url)
216 {
217     int zurl_in_use[MAX_ZURL_PLEX];
218     Yaz_ProxyClient *c;
219     int i;
220
221     for (i = 0; i<MAX_ZURL_PLEX; i++)
222         zurl_in_use[i] = 0;
223     for (c = m_parent->m_clientPool; c; c = c->m_next)
224     {
225         for (i = 0; url[i]; i++)
226             if (!strcmp(url[i], c->get_hostname()))
227                 zurl_in_use[i]++;
228     }
229     int min = 100000;
230     const char *ret = 0;
231     for (i = 0; url[i]; i++)
232     {
233         yaz_log(LOG_DEBUG, "%szurl=%s use=%d",
234                 m_session_str, url[i], zurl_in_use[i]);
235         if (min > zurl_in_use[i])
236         {
237             ret = url[i];
238             min = zurl_in_use[i];
239         }
240     }
241     return ret;
242 }
243
244 Yaz_ProxyClient *Yaz_Proxy::get_client(Z_APDU *apdu)
245 {
246     assert (m_parent);
247     Yaz_Proxy *parent = m_parent;
248     Z_OtherInformation **oi;
249     Yaz_ProxyClient *c = m_client;
250     
251     get_otherInfoAPDU(apdu, &oi);
252     char *cookie = get_cookie(oi);
253
254     if (!m_proxyTarget)
255     {
256         const char *url[MAX_ZURL_PLEX];
257         const char *proxy_host = get_proxy(oi);
258         Yaz_ProxyConfig *cfg = check_reconfigure();
259         if (proxy_host)
260         {
261 #if 0
262 /* only to be enabled for debugging... */
263             if (!strcmp(proxy_host, "stop"))
264                 exit(0);
265 #endif
266             xfree(m_default_target);
267             m_default_target = xstrdup(proxy_host);
268             proxy_host = m_default_target;
269         }
270         int client_idletime = -1;
271         url[0] = m_default_target;
272         url[1] = 0;
273         if (cfg)
274         {
275             int pre_init = 0;
276             cfg->get_target_info(proxy_host, url, &m_bw_max,
277                                  &m_pdu_max, &m_max_record_retrieve,
278                                  &m_target_idletime, &client_idletime,
279                                  &parent->m_max_clients,
280                                  &m_keepalive_limit_bw,
281                                  &m_keepalive_limit_pdu,
282                                  &pre_init);
283         }
284         if (client_idletime != -1)
285         {
286             m_client_idletime = client_idletime;
287             timeout(m_client_idletime);
288         }
289         if (!url[0])
290         {
291             yaz_log(LOG_LOG, "%sNo default target", m_session_str);
292             return 0;
293         }
294         // we don't handle multiplexing for cookie session, so we just
295         // pick the first one in this case (anonymous users will be able
296         // to use any backend)
297         if (cookie && *cookie)
298             m_proxyTarget = (char*) xstrdup(url[0]);
299         else
300             m_proxyTarget = (char*) xstrdup(load_balance(url));
301     }
302     if (cookie && *cookie)
303     {   // search in sessions with a cookie
304         for (c = parent->m_clientPool; c; c = c->m_next)
305         {
306             assert (c->m_prev);
307             assert (*c->m_prev == c);
308             if (c->m_cookie && !strcmp(cookie,c->m_cookie) &&
309                 !strcmp(m_proxyTarget, c->get_hostname()))
310             {
311                 // Found it in cache
312                 // The following handles "cancel"
313                 // If connection is busy (waiting for PDU) and
314                 // we have an initRequest we can safely do re-open
315                 if (c->m_waiting && apdu->which == Z_APDU_initRequest)
316                 {
317                     yaz_log (LOG_LOG, "%s REOPEN target=%s", m_session_str,
318                              c->get_hostname());
319                     c->close();
320                     c->m_init_flag = 0;
321                     
322                     c->m_last_ok = 0;
323                     c->m_cache.clear();
324                     c->m_last_resultCount = 0;
325                     c->m_sr_transform = 0;
326                     c->m_waiting = 0;
327                     c->m_resultSetStartPoint = 0;
328                     c->m_target_idletime = m_target_idletime;
329                     if (c->client(m_proxyTarget))
330                     {
331                         delete c;
332                         return 0;
333                     }
334                     c->timeout(30); 
335                 }
336                 c->m_seqno = parent->m_seqno;
337                 if (c->m_server && c->m_server != this)
338                     c->m_server->m_client = 0;
339                 c->m_server = this;
340                 (parent->m_seqno)++;
341                 yaz_log (LOG_DEBUG, "get_client 1 %p %p", this, c);
342                 return c;
343             }
344         }
345     }
346     else if (!c)
347     {
348         // don't have a client session yet. Search in session w/o cookie
349         for (c = parent->m_clientPool; c; c = c->m_next)
350         {
351             assert (c->m_prev);
352             assert (*c->m_prev == c);
353             if (c->m_server == 0 && c->m_cookie == 0 && 
354                 c->m_waiting == 0 &&
355                 !strcmp(m_proxyTarget, c->get_hostname()))
356             {
357                 // found it in cache
358                 yaz_log (LOG_LOG, "%sREUSE %s",
359                          m_session_str, c->get_hostname());
360                 
361                 c->m_seqno = parent->m_seqno;
362                 assert(c->m_server == 0);
363                 c->m_server = this;
364
365                 if (parent->m_log_mask & PROXY_LOG_APDU_SERVER)
366                     c->set_APDU_yazlog(1);
367                 else
368                     c->set_APDU_yazlog(0);
369
370                 (parent->m_seqno)++;
371                 
372                 parent->pre_init();
373                 
374                 return c;
375             }
376         }
377     }
378     if (!m_client)
379     {
380         if (apdu->which != Z_APDU_initRequest)
381         {
382             yaz_log (LOG_LOG, "no first INIT!");
383             return 0;
384         }
385         Z_InitRequest *initRequest = apdu->u.initRequest;
386
387         if (!initRequest->idAuthentication)
388         {
389             if (m_proxy_authentication)
390             {
391                 initRequest->idAuthentication =
392                     (Z_IdAuthentication *)
393                     odr_malloc (odr_encode(),
394                                 sizeof(*initRequest->idAuthentication));
395                 initRequest->idAuthentication->which =
396                     Z_IdAuthentication_open;
397                 initRequest->idAuthentication->u.open =
398                     odr_strdup (odr_encode(), m_proxy_authentication);
399             }
400         }
401
402         // go through list of clients - and find the lowest/oldest one.
403         Yaz_ProxyClient *c_min = 0;
404         int min_seq = -1;
405         int no_of_clients = 0;
406         if (parent->m_clientPool)
407             yaz_log (LOG_DEBUG, "Existing sessions");
408         for (c = parent->m_clientPool; c; c = c->m_next)
409         {
410             yaz_log (LOG_DEBUG, " Session %-3d wait=%d %s cookie=%s", c->m_seqno,
411                                c->m_waiting, c->get_hostname(),
412                                c->m_cookie ? c->m_cookie : "");
413             no_of_clients++;
414             if (min_seq < 0 || c->m_seqno < min_seq)
415             {
416                 min_seq = c->m_seqno;
417                 c_min = c;
418             }
419         }
420         if (no_of_clients >= parent->m_max_clients)
421         {
422             c = c_min;
423             if (c->m_waiting || strcmp(m_proxyTarget, c->get_hostname()))
424             {
425                 yaz_log (LOG_LOG, "%sMAXCLIENTS Destroy %d",
426                          m_session_str, c->m_seqno);
427                 if (c->m_server && c->m_server != this)
428                     delete c->m_server;
429                 c->m_server = 0;
430             }
431             else
432             {
433                 yaz_log (LOG_LOG, "%sMAXCLIENTS Reuse %d %d %s",
434                          m_session_str,
435                          c->m_seqno, parent->m_seqno, c->get_hostname());
436                 xfree (c->m_cookie);
437                 c->m_cookie = 0;
438                 if (cookie)
439                     c->m_cookie = xstrdup(cookie);
440                 c->m_seqno = parent->m_seqno;
441                 if (c->m_server && c->m_server != this)
442                 {
443                     c->m_server->m_client = 0;
444                     delete c->m_server;
445                 }
446                 (parent->m_seqno)++;
447                 c->m_target_idletime = m_target_idletime;
448                 c->timeout(m_target_idletime);
449                 
450                 if (parent->m_log_mask & PROXY_LOG_APDU_SERVER)
451                     c->set_APDU_yazlog(1);
452                 else
453                     c->set_APDU_yazlog(0);
454
455                 return c;
456             }
457         }
458         else
459         {
460             yaz_log (LOG_LOG, "%sNEW %d %s",
461                      m_session_str, parent->m_seqno, m_proxyTarget);
462             c = new Yaz_ProxyClient(m_PDU_Observable->clone(), parent);
463             c->m_next = parent->m_clientPool;
464             if (c->m_next)
465                 c->m_next->m_prev = &c->m_next;
466             parent->m_clientPool = c;
467             c->m_prev = &parent->m_clientPool;
468         }
469
470         xfree (c->m_cookie);
471         c->m_cookie = 0;
472         if (cookie)
473             c->m_cookie = xstrdup(cookie);
474
475         c->m_seqno = parent->m_seqno;
476         c->m_init_flag = 0;
477         c->m_last_resultCount = 0;
478         c->m_last_ok = 0;
479         c->m_cache.clear();
480         c->m_sr_transform = 0;
481         c->m_waiting = 0;
482         c->m_resultSetStartPoint = 0;
483         (parent->m_seqno)++;
484         if (c->client(m_proxyTarget))
485         {
486             delete c;
487             return 0;
488         }
489         c->m_target_idletime = m_target_idletime;
490         c->timeout(30);
491
492         if (parent->m_log_mask & PROXY_LOG_APDU_SERVER)
493             c->set_APDU_yazlog(1);
494         else
495             c->set_APDU_yazlog(0);
496     }
497     yaz_log (LOG_DEBUG, "get_client 3 %p %p", this, c);
498     return c;
499 }
500
501 void Yaz_Proxy::display_diagrecs(Z_DiagRec **pp, int num)
502 {
503     int i;
504     for (i = 0; i<num; i++)
505     {
506         oident *ent;
507         Z_DefaultDiagFormat *r;
508         Z_DiagRec *p = pp[i];
509         if (p->which != Z_DiagRec_defaultFormat)
510         {
511             yaz_log(LOG_LOG, "%sError no diagnostics", m_session_str);
512             return;
513         }
514         else
515             r = p->u.defaultFormat;
516         if (!(ent = oid_getentbyoid(r->diagnosticSetId)) ||
517             ent->oclass != CLASS_DIAGSET || ent->value != VAL_BIB1)
518             yaz_log(LOG_LOG, "%sError unknown diagnostic set", m_session_str);
519         switch (r->which)
520         {
521         case Z_DefaultDiagFormat_v2Addinfo:
522             yaz_log(LOG_LOG, "%sError %d %s:%s",
523                     m_session_str,
524                     *r->condition, diagbib1_str(*r->condition),
525                     r->u.v2Addinfo);
526             break;
527         case Z_DefaultDiagFormat_v3Addinfo:
528             yaz_log(LOG_LOG, "%sError %d %s:%s",
529                     m_session_str,
530                     *r->condition, diagbib1_str(*r->condition),
531                     r->u.v3Addinfo);
532             break;
533         }
534     }
535 }
536
537 void Yaz_Proxy::convert_to_marcxml(Z_NamePlusRecordList *p)
538 {
539     int i;
540
541     yaz_marc_t mt = yaz_marc_create();
542     yaz_marc_xml(mt, YAZ_MARC_MARCXML);
543     for (i = 0; i < p->num_records; i++)
544     {
545         Z_NamePlusRecord *npr = p->records[i];
546         if (npr->which == Z_NamePlusRecord_databaseRecord)
547         {
548             Z_External *r = npr->u.databaseRecord;
549             if (r->which == Z_External_octet)
550             {
551                 int rlen;
552                 char *result;
553                 if (yaz_marc_decode_buf(mt, (char*) r->u.octet_aligned->buf,
554                                         r->u.octet_aligned->len,
555                                         &result, &rlen))
556                 {
557                     yaz_iconv_t cd = yaz_iconv_open("UTF-8", "MARC-8");
558                     WRBUF wrbuf = wrbuf_alloc();
559                     
560                     char outbuf[120];
561                     size_t inbytesleft = rlen;
562                     const char *inp = result;
563                     while (cd && inbytesleft)
564                     {
565                         size_t outbytesleft = sizeof(outbuf);
566                         char *outp = outbuf;
567                         size_t r;
568                         
569                         r = yaz_iconv (cd, (char**) &inp,
570                                        &inbytesleft,
571                                        &outp, &outbytesleft);
572                         if (r == (size_t) (-1))
573                         {
574                             int e = yaz_iconv_error(cd);
575                             if (e != YAZ_ICONV_E2BIG)
576                             {
577                                 yaz_log(LOG_WARN, "conversion failure");
578                                 break;
579                             }
580                         }
581                         wrbuf_write(wrbuf, outbuf, outp - outbuf);
582                     }
583                     if (cd)
584                         yaz_iconv_close(cd);
585
586                     npr->u.databaseRecord = z_ext_record(odr_encode(),
587                                                          VAL_TEXT_XML,
588                                                          wrbuf_buf(wrbuf),
589                                                          wrbuf_len(wrbuf));
590                     wrbuf_free(wrbuf, 1);
591                 }
592             }
593         }
594     }
595     yaz_marc_destroy(mt);
596 }
597
598 int Yaz_Proxy::send_to_client(Z_APDU *apdu)
599 {
600     int len = 0;
601     if (apdu->which == Z_APDU_searchResponse)
602     {
603         Z_SearchResponse *sr = apdu->u.searchResponse;
604         Z_Records *p = sr->records;
605         if (p && p->which == Z_Records_NSD)
606         {
607             Z_DiagRec dr, *dr_p = &dr;
608             dr.which = Z_DiagRec_defaultFormat;
609             dr.u.defaultFormat = p->u.nonSurrogateDiagnostic;
610
611             display_diagrecs(&dr_p, 1);
612         }
613         else
614         {
615             if (m_marcxml_flag && p && p->which == Z_Records_DBOSD)
616                 convert_to_marcxml(p->u.databaseOrSurDiagnostics);
617             if (sr->resultCount)
618             {
619                 yaz_log(LOG_LOG, "%s%d hits", m_session_str,
620                         *sr->resultCount);
621                 if (*sr->resultCount < 0)
622                     m_invalid_session = 1;
623             }
624         }
625     }
626     else if (apdu->which == Z_APDU_presentResponse)
627     {
628         Z_PresentResponse *sr = apdu->u.presentResponse;
629         Z_Records *p = sr->records;
630         if (p && p->which == Z_Records_NSD)
631         {
632             Z_DiagRec dr, *dr_p = &dr;
633             dr.which = Z_DiagRec_defaultFormat;
634             dr.u.defaultFormat = p->u.nonSurrogateDiagnostic;
635
636             display_diagrecs(&dr_p, 1);
637         }
638         if (m_marcxml_flag && p && p->which == Z_Records_DBOSD)
639             convert_to_marcxml(p->u.databaseOrSurDiagnostics);
640     }
641     int r = send_Z_PDU(apdu, &len);
642     if (m_log_mask & PROXY_LOG_APDU_CLIENT)
643         yaz_log (LOG_DEBUG, "%sSending %s to client %d bytes", m_session_str,
644                  apdu_name(apdu), len);
645     m_bytes_sent += len;
646     m_bw_stat.add_bytes(len);
647     return r;
648 }
649
650 int Yaz_ProxyClient::send_to_target(Z_APDU *apdu)
651 {
652     int len = 0;
653     int r = send_Z_PDU(apdu, &len);
654     if (m_root->get_log_mask() & PROXY_LOG_REQ_SERVER)
655         yaz_log (LOG_LOG, "%sSending %s to %s %d bytes",
656                  get_session_str(),
657                  apdu_name(apdu), get_hostname(), len);
658     m_bytes_sent += len;
659     return r;
660 }
661
662 Z_APDU *Yaz_Proxy::result_set_optimize(Z_APDU *apdu)
663 {
664     if (apdu->which == Z_APDU_presentRequest)
665     {
666         Z_PresentRequest *pr = apdu->u.presentRequest;
667         int toget = *pr->numberOfRecordsRequested;
668         int start = *pr->resultSetStartPoint;
669
670         yaz_log(LOG_LOG, "%sPresent %s %d+%d", m_session_str,
671                 pr->resultSetId, start, toget);
672
673         if (*m_parent->m_optimize == '0')
674             return apdu;
675
676         if (!m_client->m_last_resultSetId)
677         {
678             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
679             new_apdu->u.presentResponse->records =
680                 create_nonSurrogateDiagnostics(odr_encode(), 30,
681                                                pr->resultSetId);
682             send_to_client(new_apdu);
683             return 0;
684         }
685 #if 0
686         if (!strcmp(m_client->m_last_resultSetId, pr->resultSetId))
687         {
688             if (start+toget-1 > m_client->m_last_resultCount)
689             {
690                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
691                 new_apdu->u.presentResponse->records =
692                     create_nonSurrogateDiagnostics(odr_encode(), 13, 0);
693                 send_to_client(new_apdu);
694                 return 0;
695             }
696             if (m_client->m_cache.lookup (odr_encode(), &npr, start, toget,
697                                           pr->preferredRecordSyntax,
698                                           pr->recordComposition))
699             {
700                 yaz_log (LOG_LOG, "%sReturned cached records for present request", 
701                          m_session_str);
702                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
703                 new_apdu->u.presentResponse->referenceId = pr->referenceId;
704                 
705                 new_apdu->u.presentResponse->numberOfRecordsReturned
706                     = odr_intdup(odr_encode(), toget);
707                                                                  
708                 new_apdu->u.presentResponse->records = (Z_Records*)
709                     odr_malloc(odr_encode(), sizeof(Z_Records));
710                 new_apdu->u.presentResponse->records->which = Z_Records_DBOSD;
711                 new_apdu->u.presentResponse->records->u.databaseOrSurDiagnostics = npr;
712                 new_apdu->u.presentResponse->nextResultSetPosition =
713                     odr_intdup(odr_encode(), start+toget);
714
715                 send_to_client(new_apdu);
716                 return 0;
717             }
718         }
719 #endif
720     }
721
722     if (apdu->which != Z_APDU_searchRequest)
723         return apdu;
724     Z_SearchRequest *sr = apdu->u.searchRequest;
725     Yaz_Z_Query *this_query = new Yaz_Z_Query;
726     Yaz_Z_Databases this_databases;
727
728     this_databases.set(sr->num_databaseNames, (const char **)
729                        sr->databaseNames);
730     
731     this_query->set_Z_Query(sr->query);
732
733     char query_str[120];
734     this_query->print(query_str, sizeof(query_str)-1);
735     yaz_log(LOG_LOG, "%sSearch %s", m_session_str, query_str);
736
737     if (*m_parent->m_optimize != '0' &&
738         m_client->m_last_ok && m_client->m_last_query &&
739         m_client->m_last_query->match(this_query) &&
740         !strcmp(m_client->m_last_resultSetId, sr->resultSetName) &&
741         m_client->m_last_databases.match(this_databases))
742     {
743         delete this_query;
744         if (m_client->m_last_resultCount > *sr->smallSetUpperBound &&
745             m_client->m_last_resultCount < *sr->largeSetLowerBound)
746         {
747             Z_NamePlusRecordList *npr;
748             int toget = *sr->mediumSetPresentNumber;
749             Z_RecordComposition *comp = 0;
750
751             if (toget > m_client->m_last_resultCount)
752                 toget = m_client->m_last_resultCount;
753             
754             if (sr->mediumSetElementSetNames)
755             {
756                 comp = (Z_RecordComposition *)
757                     odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
758                 comp->which = Z_RecordComp_simple;
759                 comp->u.simple = sr->mediumSetElementSetNames;
760             }
761  
762             if (m_client->m_cache.lookup (odr_encode(), &npr, 1, toget,
763                                           sr->preferredRecordSyntax, comp))
764             {
765                 yaz_log (LOG_LOG, "%sReturned cached records for medium set",
766                          m_session_str);
767                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
768                 new_apdu->u.searchResponse->referenceId = sr->referenceId;
769                 new_apdu->u.searchResponse->resultCount =
770                     &m_client->m_last_resultCount;
771                 
772                 new_apdu->u.searchResponse->numberOfRecordsReturned
773                     = odr_intdup(odr_encode(), toget);
774                                                         
775                 new_apdu->u.searchResponse->presentStatus =
776                     odr_intdup(odr_encode(), Z_PresentStatus_success);
777                 new_apdu->u.searchResponse->records = (Z_Records*)
778                     odr_malloc(odr_encode(), sizeof(Z_Records));
779                 new_apdu->u.searchResponse->records->which = Z_Records_DBOSD;
780                 new_apdu->u.searchResponse->records->u.databaseOrSurDiagnostics = npr;
781                 new_apdu->u.searchResponse->nextResultSetPosition =
782                     odr_intdup(odr_encode(), toget+1);
783                 send_to_client(new_apdu);
784                 return 0;
785             }
786             else
787             {
788                 // medium Set
789                 // send present request (medium size)
790                 yaz_log (LOG_LOG, "%sOptimizing search for medium set",
791                          m_session_str);
792
793                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
794                 Z_PresentRequest *pr = new_apdu->u.presentRequest;
795                 pr->referenceId = sr->referenceId;
796                 pr->resultSetId = sr->resultSetName;
797                 pr->preferredRecordSyntax = sr->preferredRecordSyntax;
798                 *pr->numberOfRecordsRequested = toget;
799                 pr->recordComposition = comp;
800                 m_client->m_sr_transform = 1;
801                 return new_apdu;
802             }
803         }
804         else if (m_client->m_last_resultCount >= *sr->largeSetLowerBound ||
805             m_client->m_last_resultCount <= 0)
806         {
807             // large set. Return pseudo-search response immediately
808             yaz_log (LOG_LOG, "%sOptimizing search for large set",
809                      m_session_str);
810             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
811             new_apdu->u.searchResponse->referenceId = sr->referenceId;
812             new_apdu->u.searchResponse->resultCount =
813                 &m_client->m_last_resultCount;
814             send_to_client(new_apdu);
815             return 0;
816         }
817         else
818         {
819             Z_NamePlusRecordList *npr;
820             int toget = m_client->m_last_resultCount;
821             Z_RecordComposition *comp = 0;
822             // small set
823             // send a present request (small set)
824             
825             if (sr->smallSetElementSetNames)
826             {
827                 comp = (Z_RecordComposition *)
828                     odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
829                 comp->which = Z_RecordComp_simple;
830                 comp->u.simple = sr->smallSetElementSetNames;
831             }
832
833             if (m_client->m_cache.lookup (odr_encode(), &npr, 1, toget,
834                                           sr->preferredRecordSyntax, comp))
835             {
836                 yaz_log (LOG_LOG, "%sReturned cached records for small set",
837                          m_session_str);
838                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
839                 new_apdu->u.searchResponse->referenceId = sr->referenceId;
840                 new_apdu->u.searchResponse->resultCount =
841                     &m_client->m_last_resultCount;
842                 
843                 new_apdu->u.searchResponse->numberOfRecordsReturned
844                     = odr_intdup(odr_encode(), toget);
845                                                                  
846                 new_apdu->u.searchResponse->presentStatus =
847                     odr_intdup(odr_encode(), Z_PresentStatus_success);
848                 new_apdu->u.searchResponse->records = (Z_Records*)
849                     odr_malloc(odr_encode(), sizeof(Z_Records));
850                 new_apdu->u.searchResponse->records->which = Z_Records_DBOSD;
851                 new_apdu->u.searchResponse->records->u.databaseOrSurDiagnostics = npr;
852                 new_apdu->u.searchResponse->nextResultSetPosition =
853                     odr_intdup(odr_encode(), toget+1);
854                 send_to_client(new_apdu);
855                 return 0;
856             }
857             else
858             {
859                 yaz_log (LOG_LOG, "%sOptimizing search for small set",
860                          m_session_str);
861                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
862                 Z_PresentRequest *pr = new_apdu->u.presentRequest;
863                 pr->referenceId = sr->referenceId;
864                 pr->resultSetId = sr->resultSetName;
865                 pr->preferredRecordSyntax = sr->preferredRecordSyntax;
866                 *pr->numberOfRecordsRequested = toget;
867                 pr->recordComposition = comp;
868                 m_client->m_sr_transform = 1;
869                 return new_apdu;
870             }
871         }
872     }
873     else  // query doesn't match
874     {
875         delete m_client->m_last_query;
876         m_client->m_last_query = this_query;
877         m_client->m_last_ok = 0;
878         m_client->m_cache.clear();
879         m_client->m_resultSetStartPoint = 0;
880
881         xfree (m_client->m_last_resultSetId);
882         m_client->m_last_resultSetId = xstrdup (sr->resultSetName);
883
884         m_client->m_last_databases.set(sr->num_databaseNames,
885                                        (const char **) sr->databaseNames);
886     }
887     return apdu;
888 }
889
890
891 void Yaz_Proxy::recv_Z_PDU(Z_APDU *apdu, int len)
892 {
893     char *cp = strchr(m_session_str, ' ');
894     m_request_no++;
895     if (cp)
896         sprintf(cp+1, "%d ", m_request_no);
897
898     int reduce = 0;
899     m_bytes_recv += len;
900     
901     if (m_log_mask & PROXY_LOG_APDU_CLIENT)
902         yaz_log (LOG_DEBUG, "%sReceiving %s from client %d bytes",
903                  m_session_str, apdu_name(apdu), len);
904
905     if (m_bw_hold_PDU)     // double incoming PDU. shutdown now.
906         shutdown();
907
908     m_bw_stat.add_bytes(len);
909     m_pdu_stat.add_bytes(1);
910
911     int bw_total = m_bw_stat.get_total();
912     int pdu_total = m_pdu_stat.get_total();
913
914     yaz_log(LOG_LOG, "%sstat bw=%d pdu=%d limit-bw=%d limit-pdu=%d",
915             m_session_str, bw_total, pdu_total, m_bw_max, m_pdu_max);
916     if (m_bw_max)
917     {
918         if (bw_total > m_bw_max)
919         {
920             reduce = (bw_total/m_bw_max);
921         }
922     }
923     if (m_pdu_max)
924     {
925         if (pdu_total > m_pdu_max)
926         {
927             int nreduce = (60/m_pdu_max);
928             reduce = (reduce > nreduce) ? reduce : nreduce;
929         }
930     }
931     if (reduce)  
932     {
933         yaz_log(LOG_LOG, "%sLimit delay=%d", m_session_str, reduce);
934         m_bw_hold_PDU = apdu;  // save PDU and signal "on hold"
935         timeout(reduce);       // call us reduce seconds later
936     }
937     else
938         recv_Z_PDU_0(apdu);    // all fine. Proceed receive PDU as usual
939 }
940
941 void Yaz_Proxy::handle_max_record_retrieve(Z_APDU *apdu)
942 {
943     if (m_max_record_retrieve)
944     {
945         if (apdu->which == Z_APDU_presentRequest)
946         {
947             Z_PresentRequest *pr = apdu->u.presentRequest;
948             if (pr->numberOfRecordsRequested && 
949                 *pr->numberOfRecordsRequested > m_max_record_retrieve)
950                 *pr->numberOfRecordsRequested = m_max_record_retrieve;
951         }
952     }
953 }
954
955 Z_Records *Yaz_Proxy::create_nonSurrogateDiagnostics(ODR odr,
956                                                      int error,
957                                                      const char *addinfo)
958 {
959     Z_Records *rec = (Z_Records *)
960         odr_malloc (odr, sizeof(*rec));
961     int *err = (int *)
962         odr_malloc (odr, sizeof(*err));
963     Z_DiagRec *drec = (Z_DiagRec *)
964         odr_malloc (odr, sizeof(*drec));
965     Z_DefaultDiagFormat *dr = (Z_DefaultDiagFormat *)
966         odr_malloc (odr, sizeof(*dr));
967     *err = error;
968     rec->which = Z_Records_NSD;
969     rec->u.nonSurrogateDiagnostic = dr;
970     dr->diagnosticSetId =
971         yaz_oidval_to_z3950oid (odr, CLASS_DIAGSET, VAL_BIB1);
972     dr->condition = err;
973     dr->which = Z_DefaultDiagFormat_v2Addinfo;
974     dr->u.v2Addinfo = odr_strdup (odr, addinfo ? addinfo : "");
975     return rec;
976 }
977
978 Z_APDU *Yaz_Proxy::handle_query_validation(Z_APDU *apdu)
979 {
980     if (apdu->which == Z_APDU_searchRequest)
981     {
982         Z_SearchRequest *sr = apdu->u.searchRequest;
983         int err = 0;
984         char *addinfo = 0;
985
986         Yaz_ProxyConfig *cfg = check_reconfigure();
987         if (cfg)
988             err = cfg->check_query(odr_encode(), m_default_target,
989                                    sr->query, &addinfo);
990         if (err)
991         {
992             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
993
994             new_apdu->u.searchResponse->referenceId = sr->referenceId;
995             new_apdu->u.searchResponse->records =
996                 create_nonSurrogateDiagnostics(odr_encode(), err, addinfo);
997             *new_apdu->u.searchResponse->searchStatus = 0;
998
999             send_to_client(new_apdu);
1000
1001             return 0;
1002         }
1003     }
1004     return apdu;
1005 }
1006
1007 Z_APDU *Yaz_Proxy::handle_syntax_validation(Z_APDU *apdu)
1008 {
1009     m_marcxml_flag = 0;
1010     if (apdu->which == Z_APDU_searchRequest)
1011     {
1012         Z_SearchRequest *sr = apdu->u.searchRequest;
1013         int err = 0;
1014         char *addinfo = 0;
1015         Yaz_ProxyConfig *cfg = check_reconfigure();
1016         
1017         if (cfg)
1018             err = cfg->check_syntax(odr_encode(),
1019                                     m_default_target,
1020                                     sr->preferredRecordSyntax,
1021                                     &addinfo);
1022         if (err == -1)
1023         {
1024             sr->preferredRecordSyntax =
1025                 yaz_oidval_to_z3950oid(odr_decode(), CLASS_RECSYN,
1026                                        VAL_USMARC);
1027             m_marcxml_flag = 1;
1028         }
1029         else if (err)
1030         {
1031             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
1032             
1033             new_apdu->u.searchResponse->referenceId = sr->referenceId;
1034             new_apdu->u.searchResponse->records =
1035                 create_nonSurrogateDiagnostics(odr_encode(), err, addinfo);
1036             *new_apdu->u.searchResponse->searchStatus = 0;
1037             
1038             send_to_client(new_apdu);
1039             
1040             return 0;
1041         }
1042     }
1043     else if (apdu->which == Z_APDU_presentRequest)
1044     {
1045         Z_PresentRequest *pr = apdu->u.presentRequest;
1046         int err = 0;
1047         char *addinfo = 0;
1048         Yaz_ProxyConfig *cfg = check_reconfigure();
1049
1050         if (cfg)
1051             err = cfg->check_syntax(odr_encode(), m_default_target,
1052                                     pr->preferredRecordSyntax,
1053                                     &addinfo);
1054         if (err == -1)
1055         {
1056             pr->preferredRecordSyntax =
1057                 yaz_oidval_to_z3950oid(odr_decode(), CLASS_RECSYN,
1058                                        VAL_USMARC);
1059             m_marcxml_flag = 1;
1060         }
1061         else if (err)
1062         {
1063             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
1064             
1065             new_apdu->u.presentResponse->referenceId = pr->referenceId;
1066             new_apdu->u.presentResponse->records =
1067                 create_nonSurrogateDiagnostics(odr_encode(), err, addinfo);
1068             *new_apdu->u.presentResponse->presentStatus =
1069                 Z_PresentStatus_failure;
1070             
1071             send_to_client(new_apdu);
1072             
1073             return 0;
1074         }
1075     }
1076     return apdu;
1077 }
1078
1079 void Yaz_Proxy::recv_Z_PDU_0(Z_APDU *apdu)
1080 {
1081     // Determine our client.
1082     m_client = get_client(apdu);
1083     if (!m_client)
1084     {
1085         delete this;
1086         return;
1087     }
1088     m_client->m_server = this;
1089
1090     if (apdu->which == Z_APDU_initRequest)
1091     {
1092         if (apdu->u.initRequest->implementationId)
1093             yaz_log(LOG_LOG, "%simplementationId: %s",
1094                     m_session_str, apdu->u.initRequest->implementationId);
1095         if (apdu->u.initRequest->implementationName)
1096             yaz_log(LOG_LOG, "%simplementationName: %s",
1097                     m_session_str, apdu->u.initRequest->implementationName);
1098         if (apdu->u.initRequest->implementationVersion)
1099             yaz_log(LOG_LOG, "%simplementationVersion: %s",
1100                     m_session_str, apdu->u.initRequest->implementationVersion);
1101         if (m_client->m_init_flag)
1102         {
1103             Z_APDU *apdu = m_client->m_initResponse;
1104             apdu->u.initResponse->otherInfo = 0;
1105             if (m_client->m_cookie && *m_client->m_cookie)
1106                 set_otherInformationString(apdu, VAL_COOKIE, 1,
1107                                            m_client->m_cookie);
1108             send_to_client(apdu);
1109             return;
1110         }
1111         m_client->m_init_flag = 1;
1112     }
1113     handle_max_record_retrieve(apdu);
1114
1115     if (apdu)
1116         apdu = handle_syntax_validation(apdu);
1117
1118     if (apdu)
1119         apdu = handle_query_validation(apdu);
1120
1121     if (apdu)
1122         apdu = result_set_optimize(apdu);
1123     if (!apdu)
1124     {
1125         m_client->timeout(m_target_idletime);  // mark it active even 
1126         // though we didn't use it
1127         return;
1128     }
1129
1130     // delete other info part from PDU before sending to target
1131     Z_OtherInformation **oi;
1132     get_otherInfoAPDU(apdu, &oi);
1133     if (oi)
1134         *oi = 0;
1135
1136     if (apdu->which == Z_APDU_presentRequest &&
1137         m_client->m_resultSetStartPoint == 0)
1138     {
1139         Z_PresentRequest *pr = apdu->u.presentRequest;
1140         m_client->m_resultSetStartPoint = *pr->resultSetStartPoint;
1141         m_client->m_cache.copy_presentRequest(apdu->u.presentRequest);
1142     } else {
1143         m_client->m_resultSetStartPoint = 0;
1144     }
1145     if (m_client->send_to_target(apdu) < 0)
1146     {
1147         delete m_client;
1148         m_client = 0;
1149         delete this;
1150     }
1151     else
1152         m_client->m_waiting = 1;
1153 }
1154
1155 void Yaz_Proxy::connectNotify()
1156 {
1157 }
1158
1159 void Yaz_Proxy::shutdown()
1160 {
1161     // only keep if keep_alive flag is set...
1162     if (m_client && 
1163         !m_invalid_session &&
1164         m_client->m_pdu_recv < m_keepalive_limit_pdu &&
1165         m_client->m_bytes_recv+m_client->m_bytes_sent < m_keepalive_limit_bw &&
1166         m_client->m_waiting == 0)
1167     {
1168         yaz_log(LOG_LOG, "%sShutdown (client to proxy) keepalive %s",
1169                  m_session_str,
1170                  m_client->get_hostname());
1171         yaz_log(LOG_LOG, "%sbw=%d pdu=%d limit-bw=%d limit-pdu=%d",
1172                 m_session_str, m_client->m_pdu_recv,
1173                 m_client->m_bytes_sent + m_client->m_bytes_recv,
1174                 m_keepalive_limit_bw, m_keepalive_limit_pdu);
1175         assert (m_client->m_waiting != 2);
1176         // Tell client (if any) that no server connection is there..
1177         m_client->m_server = 0;
1178     }
1179     else if (m_client)
1180     {
1181         yaz_log (LOG_LOG, "%sShutdown (client to proxy) close %s",
1182                  m_session_str,
1183                  m_client->get_hostname());
1184         assert (m_client->m_waiting != 2);
1185         delete m_client;
1186     }
1187     else if (!m_parent)
1188     {
1189         yaz_log (LOG_LOG, "%sshutdown (client to proxy) bad state",
1190                  m_session_str);
1191         assert (m_parent);
1192     }
1193     else 
1194     {
1195         yaz_log (LOG_LOG, "%sShutdown (client to proxy)",
1196                  m_session_str);
1197     }
1198     if (m_parent)
1199         m_parent->pre_init();
1200     delete this;
1201 }
1202
1203 const char *Yaz_ProxyClient::get_session_str() 
1204 {
1205     if (!m_server)
1206         return "0 ";
1207     return m_server->get_session_str();
1208 }
1209
1210 void Yaz_ProxyClient::shutdown()
1211 {
1212     yaz_log (LOG_LOG, "%sShutdown (proxy to target) %s", get_session_str(),
1213              get_hostname());
1214     delete m_server;
1215     delete this;
1216 }
1217
1218 void Yaz_Proxy::failNotify()
1219 {
1220     yaz_log (LOG_LOG, "%sConnection closed by client",
1221              get_session_str());
1222     shutdown();
1223 }
1224
1225 void Yaz_ProxyClient::failNotify()
1226 {
1227     yaz_log (LOG_LOG, "%sConnection closed by target %s", 
1228              get_session_str(), get_hostname());
1229     shutdown();
1230 }
1231
1232 void Yaz_ProxyClient::connectNotify()
1233 {
1234     const char *s = get_session_str();
1235     const char *h = get_hostname();
1236     yaz_log (LOG_LOG, "%sConnection accepted by %s timeout=%d", s, h,
1237              m_target_idletime);
1238     timeout(m_target_idletime);
1239     if (!m_server)
1240         pre_init_client();
1241 }
1242
1243 IYaz_PDU_Observer *Yaz_ProxyClient::sessionNotify(IYaz_PDU_Observable
1244                                                   *the_PDU_Observable, int fd)
1245 {
1246     return new Yaz_ProxyClient(the_PDU_Observable, 0);
1247 }
1248
1249 Yaz_ProxyClient::~Yaz_ProxyClient()
1250 {
1251     if (m_prev)
1252         *m_prev = m_next;
1253     if (m_next)
1254         m_next->m_prev = m_prev;
1255     m_waiting = 2;     // for debugging purposes only.
1256     odr_destroy(m_init_odr);
1257     delete m_last_query;
1258     xfree (m_last_resultSetId);
1259     xfree (m_cookie);
1260 }
1261
1262 void Yaz_ProxyClient::pre_init_client()
1263 {
1264     Z_APDU *apdu = create_Z_PDU(Z_APDU_initRequest);
1265     Z_InitRequest *req = apdu->u.initRequest;
1266     
1267     ODR_MASK_SET(req->options, Z_Options_search);
1268     ODR_MASK_SET(req->options, Z_Options_present);
1269     ODR_MASK_SET(req->options, Z_Options_namedResultSets);
1270     ODR_MASK_SET(req->options, Z_Options_triggerResourceCtrl);
1271     ODR_MASK_SET(req->options, Z_Options_scan);
1272     ODR_MASK_SET(req->options, Z_Options_sort);
1273     ODR_MASK_SET(req->options, Z_Options_extendedServices);
1274     ODR_MASK_SET(req->options, Z_Options_delSet);
1275     
1276     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_1);
1277     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_2);
1278     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_3);
1279     
1280     if (send_to_target(apdu) < 0)
1281     {
1282         delete this;
1283     }
1284     else
1285     {
1286         m_waiting = 1;
1287         m_init_flag = 1;
1288     }
1289 }
1290
1291 void Yaz_Proxy::pre_init()
1292 {
1293     int i;
1294     const char *name = 0;
1295     const char *zurl_in_use[MAX_ZURL_PLEX];
1296     int limit_bw, limit_pdu, limit_req;
1297     int target_idletime, client_idletime;
1298     int max_clients;
1299     int keepalive_limit_bw, keepalive_limit_pdu;
1300     int pre_init;
1301
1302     Yaz_ProxyConfig *cfg = check_reconfigure();
1303
1304     zurl_in_use[0] = 0;
1305
1306     if (m_log_mask & PROXY_LOG_APDU_CLIENT)
1307         set_APDU_yazlog(1);
1308     else
1309         set_APDU_yazlog(0);
1310
1311     for (i = 0; cfg && cfg->get_target_no(i, &name, zurl_in_use,
1312                                           &limit_bw, &limit_pdu, &limit_req,
1313                                           &target_idletime, &client_idletime,
1314                                           &max_clients, 
1315                                           &keepalive_limit_bw,
1316                                           &keepalive_limit_pdu,
1317                                           &pre_init) ; i++)
1318     {
1319         if (pre_init)
1320         {
1321             int j;
1322             for (j = 0; zurl_in_use[j]; j++)
1323             {
1324                 Yaz_ProxyClient *c;
1325                 int spare = 0;
1326                 int in_use = 0;
1327                 int other = 0;
1328                 for (c = m_clientPool; c; c = c->m_next)
1329                 {
1330                     if (!strcmp(zurl_in_use[j], c->get_hostname()))
1331                     {
1332                         if (c->m_cookie == 0)
1333                         {
1334                             if (c->m_server == 0)
1335                                 spare++;
1336                             else
1337                                 in_use++;
1338                         }
1339                         else
1340                             other++;
1341                     }
1342                 }
1343                 yaz_log(LOG_LOG, "%s pre-init %s %s use=%d other=%d spare=%d preinit=%d",
1344                         m_session_str,
1345                         name, zurl_in_use[j], in_use, other, spare, pre_init);
1346                 if (spare < pre_init)
1347                 {
1348                     c = new Yaz_ProxyClient(m_PDU_Observable->clone(), this);
1349                     c->m_next = m_clientPool;
1350                     if (c->m_next)
1351                         c->m_next->m_prev = &c->m_next;
1352                     m_clientPool = c;
1353                     c->m_prev = &m_clientPool;
1354                     
1355                     if (m_log_mask & PROXY_LOG_APDU_SERVER)
1356                         c->set_APDU_yazlog(1);
1357                     else
1358                         c->set_APDU_yazlog(0);
1359
1360                     if (c->client(zurl_in_use[j]))
1361                     {
1362                         timeout(60);
1363                         delete c;
1364                         return;
1365                     }
1366                     c->timeout(30);
1367                     c->m_waiting = 1;
1368                     c->m_target_idletime = target_idletime;
1369                     c->m_seqno = m_seqno++;
1370                 }
1371             }
1372         }
1373     }
1374 }
1375
1376 void Yaz_Proxy::timeoutNotify()
1377 {
1378     if (m_parent)
1379     {
1380         if (m_bw_hold_PDU)
1381         {
1382             timeout(m_client_idletime);
1383             Z_APDU *apdu = m_bw_hold_PDU;
1384             m_bw_hold_PDU = 0;
1385             recv_Z_PDU_0(apdu);
1386         }
1387         else
1388         {
1389             yaz_log (LOG_LOG, "%sTimeout (client to proxy)", m_session_str);
1390             shutdown();
1391         }
1392     }
1393     else
1394     {
1395         timeout(600);
1396         pre_init();
1397     }
1398 }
1399
1400 void Yaz_ProxyClient::timeoutNotify()
1401 {
1402     yaz_log (LOG_LOG, "%sTimeout (proxy to target) %s", get_session_str(),
1403              get_hostname());
1404     m_waiting = 1;
1405     m_root->pre_init();
1406     shutdown();
1407 }
1408
1409 Yaz_ProxyClient::Yaz_ProxyClient(IYaz_PDU_Observable *the_PDU_Observable,
1410                                  Yaz_Proxy *parent) :
1411     Yaz_Z_Assoc (the_PDU_Observable)
1412 {
1413     m_cookie = 0;
1414     m_next = 0;
1415     m_prev = 0;
1416     m_init_flag = 0;
1417     m_last_query = 0;
1418     m_last_resultSetId = 0;
1419     m_last_resultCount = 0;
1420     m_last_ok = 0;
1421     m_sr_transform = 0;
1422     m_waiting = 0;
1423     m_init_odr = odr_createmem (ODR_DECODE);
1424     m_initResponse = 0;
1425     m_resultSetStartPoint = 0;
1426     m_bytes_sent = m_bytes_recv = 0;
1427     m_pdu_recv = 0;
1428     m_server = 0;
1429     m_seqno = 0;
1430     m_target_idletime = 600;
1431     m_root = parent;
1432 }
1433
1434 const char *Yaz_Proxy::option(const char *name, const char *value)
1435 {
1436     if (!strcmp (name, "optimize")) {
1437         if (value) {
1438             xfree (m_optimize); 
1439             m_optimize = xstrdup (value);
1440         }
1441         return m_optimize;
1442     }
1443     return 0;
1444 }
1445
1446 void Yaz_ProxyClient::recv_Z_PDU(Z_APDU *apdu, int len)
1447 {
1448     m_bytes_recv += len;
1449     m_pdu_recv++;
1450     m_waiting = 0;
1451     if (m_root->get_log_mask() & PROXY_LOG_REQ_SERVER)
1452         yaz_log (LOG_LOG, "%sReceiving %s from %s %d bytes", get_session_str(),
1453                  apdu_name(apdu), get_hostname(), len);
1454     if (apdu->which == Z_APDU_initResponse)
1455     {
1456         if (!m_server)  // if this is a pre init session , check for more
1457             m_root->pre_init();
1458         NMEM nmem = odr_extract_mem (odr_decode());
1459         odr_reset (m_init_odr);
1460         nmem_transfer (m_init_odr->mem, nmem);
1461         m_initResponse = apdu;
1462
1463         Z_InitResponse *ir = apdu->u.initResponse;
1464         char *im0 = ir->implementationName;
1465         
1466         char *im1 = (char*) 
1467             odr_malloc(m_init_odr, 20 + (im0 ? strlen(im0) : 0));
1468         *im1 = '\0';
1469         if (im0)
1470         {
1471             strcat(im1, im0);
1472             strcat(im1, " ");
1473         }
1474         strcat(im1, "(YAZ Proxy)");
1475         ir->implementationName = im1;
1476
1477         nmem_destroy (nmem);
1478     }
1479     if (apdu->which == Z_APDU_searchResponse)
1480     {
1481         Z_SearchResponse *sr = apdu->u.searchResponse;
1482         m_last_resultCount = *sr->resultCount;
1483         int status = *sr->searchStatus;
1484         if (status && (!sr->records || sr->records->which == Z_Records_DBOSD))
1485         {
1486             m_last_ok = 1;
1487             
1488             if (sr->records && sr->records->which == Z_Records_DBOSD)
1489             {
1490                 m_cache.add(odr_decode(),
1491                             sr->records->u.databaseOrSurDiagnostics, 1,
1492                             *sr->resultCount);
1493             }
1494         }
1495     }
1496     if (apdu->which == Z_APDU_presentResponse)
1497     {
1498         Z_PresentResponse *pr = apdu->u.presentResponse;
1499         if (m_sr_transform)
1500         {
1501             m_sr_transform = 0;
1502             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
1503             Z_SearchResponse *sr = new_apdu->u.searchResponse;
1504             sr->referenceId = pr->referenceId;
1505             *sr->resultCount = m_last_resultCount;
1506             sr->records = pr->records;
1507             sr->nextResultSetPosition = pr->nextResultSetPosition;
1508             sr->numberOfRecordsReturned = pr->numberOfRecordsReturned;
1509             apdu = new_apdu;
1510         }
1511         if (pr->records && 
1512             pr->records->which == Z_Records_DBOSD && m_resultSetStartPoint)
1513         {
1514             m_cache.add(odr_decode(),
1515                         pr->records->u.databaseOrSurDiagnostics,
1516                         m_resultSetStartPoint, -1);
1517             m_resultSetStartPoint = 0;
1518         }
1519     }
1520     if (m_cookie)
1521         set_otherInformationString (apdu, VAL_COOKIE, 1, m_cookie);
1522     if (m_server)
1523     {
1524         m_server->send_to_client(apdu);
1525     }
1526     if (apdu->which == Z_APDU_close)
1527     {
1528         shutdown();
1529     }
1530 }
1531
1532 void Yaz_Proxy::server(const char *addr)
1533 {
1534     Yaz_Z_Assoc::server(addr);
1535
1536     timeout(1);
1537 }
1538