Better reporting of statistics. Handling of ODR decode improved
[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.63 2003-10-20 18:31:44 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 %d %d %s",
359                          m_session_str,
360                          c->m_seqno, parent->m_seqno, c->get_hostname());
361                 
362                 c->m_seqno = parent->m_seqno;
363                 assert(c->m_server == 0);
364                 c->m_server = this;
365
366                 if (parent->m_log_mask & PROXY_LOG_APDU_SERVER)
367                     c->set_APDU_yazlog(1);
368                 else
369                     c->set_APDU_yazlog(0);
370
371                 (parent->m_seqno)++;
372                 
373                 parent->pre_init();
374                 
375                 return c;
376             }
377         }
378     }
379     if (!m_client)
380     {
381         if (apdu->which != Z_APDU_initRequest)
382         {
383             yaz_log (LOG_LOG, "no first INIT!");
384             return 0;
385         }
386         Z_InitRequest *initRequest = apdu->u.initRequest;
387
388         if (!initRequest->idAuthentication)
389         {
390             if (m_proxy_authentication)
391             {
392                 initRequest->idAuthentication =
393                     (Z_IdAuthentication *)
394                     odr_malloc (odr_encode(),
395                                 sizeof(*initRequest->idAuthentication));
396                 initRequest->idAuthentication->which =
397                     Z_IdAuthentication_open;
398                 initRequest->idAuthentication->u.open =
399                     odr_strdup (odr_encode(), m_proxy_authentication);
400             }
401         }
402
403         // go through list of clients - and find the lowest/oldest one.
404         Yaz_ProxyClient *c_min = 0;
405         int min_seq = -1;
406         int no_of_clients = 0;
407         if (parent->m_clientPool)
408             yaz_log (LOG_DEBUG, "Existing sessions");
409         for (c = parent->m_clientPool; c; c = c->m_next)
410         {
411             yaz_log (LOG_DEBUG, " Session %-3d wait=%d %s cookie=%s", c->m_seqno,
412                                c->m_waiting, c->get_hostname(),
413                                c->m_cookie ? c->m_cookie : "");
414             no_of_clients++;
415             if (min_seq < 0 || c->m_seqno < min_seq)
416             {
417                 min_seq = c->m_seqno;
418                 c_min = c;
419             }
420         }
421         if (no_of_clients >= parent->m_max_clients)
422         {
423             c = c_min;
424             if (c->m_waiting || strcmp(m_proxyTarget, c->get_hostname()))
425             {
426                 yaz_log (LOG_LOG, "%sMAXCLIENTS Destroy %d",
427                          m_session_str, c->m_seqno);
428                 if (c->m_server && c->m_server != this)
429                     delete c->m_server;
430                 c->m_server = 0;
431             }
432             else
433             {
434                 yaz_log (LOG_LOG, "%sMAXCLIENTS Reuse %d %d %s",
435                          m_session_str,
436                          c->m_seqno, parent->m_seqno, c->get_hostname());
437                 xfree (c->m_cookie);
438                 c->m_cookie = 0;
439                 if (cookie)
440                     c->m_cookie = xstrdup(cookie);
441                 c->m_seqno = parent->m_seqno;
442                 if (c->m_server && c->m_server != this)
443                 {
444                     c->m_server->m_client = 0;
445                     delete c->m_server;
446                 }
447                 (parent->m_seqno)++;
448                 c->m_target_idletime = m_target_idletime;
449                 c->timeout(m_target_idletime);
450                 
451                 if (parent->m_log_mask & PROXY_LOG_APDU_SERVER)
452                     c->set_APDU_yazlog(1);
453                 else
454                     c->set_APDU_yazlog(0);
455
456                 return c;
457             }
458         }
459         else
460         {
461             yaz_log (LOG_LOG, "%sNEW %d %s",
462                      m_session_str, parent->m_seqno, m_proxyTarget);
463             c = new Yaz_ProxyClient(m_PDU_Observable->clone(), parent);
464             c->m_next = parent->m_clientPool;
465             if (c->m_next)
466                 c->m_next->m_prev = &c->m_next;
467             parent->m_clientPool = c;
468             c->m_prev = &parent->m_clientPool;
469         }
470
471         xfree (c->m_cookie);
472         c->m_cookie = 0;
473         if (cookie)
474             c->m_cookie = xstrdup(cookie);
475
476         c->m_seqno = parent->m_seqno;
477         c->m_init_flag = 0;
478         c->m_last_resultCount = 0;
479         c->m_last_ok = 0;
480         c->m_cache.clear();
481         c->m_sr_transform = 0;
482         c->m_waiting = 0;
483         c->m_resultSetStartPoint = 0;
484         (parent->m_seqno)++;
485         if (c->client(m_proxyTarget))
486         {
487             delete c;
488             return 0;
489         }
490         c->m_target_idletime = m_target_idletime;
491         c->timeout(30);
492
493         if (parent->m_log_mask & PROXY_LOG_APDU_SERVER)
494             c->set_APDU_yazlog(1);
495         else
496             c->set_APDU_yazlog(0);
497     }
498     yaz_log (LOG_DEBUG, "get_client 3 %p %p", this, c);
499     return c;
500 }
501
502 void Yaz_Proxy::display_diagrecs(Z_DiagRec **pp, int num)
503 {
504     int i;
505     for (i = 0; i<num; i++)
506     {
507         oident *ent;
508         Z_DefaultDiagFormat *r;
509         Z_DiagRec *p = pp[i];
510         if (p->which != Z_DiagRec_defaultFormat)
511         {
512             yaz_log(LOG_LOG, "%sError no diagnostics", m_session_str);
513             return;
514         }
515         else
516             r = p->u.defaultFormat;
517         if (!(ent = oid_getentbyoid(r->diagnosticSetId)) ||
518             ent->oclass != CLASS_DIAGSET || ent->value != VAL_BIB1)
519             yaz_log(LOG_LOG, "%sError unknown diagnostic set", m_session_str);
520         switch (r->which)
521         {
522         case Z_DefaultDiagFormat_v2Addinfo:
523             yaz_log(LOG_LOG, "%sError %d %s:%s",
524                     m_session_str,
525                     *r->condition, diagbib1_str(*r->condition),
526                     r->u.v2Addinfo);
527             break;
528         case Z_DefaultDiagFormat_v3Addinfo:
529             yaz_log(LOG_LOG, "%sError %d %s:%s",
530                     m_session_str,
531                     *r->condition, diagbib1_str(*r->condition),
532                     r->u.v3Addinfo);
533             break;
534         }
535     }
536 }
537
538 void Yaz_Proxy::convert_to_marcxml(Z_NamePlusRecordList *p)
539 {
540     int i;
541
542     yaz_marc_t mt = yaz_marc_create();
543     yaz_marc_xml(mt, YAZ_MARC_MARCXML);
544     for (i = 0; i < p->num_records; i++)
545     {
546         Z_NamePlusRecord *npr = p->records[i];
547         if (npr->which == Z_NamePlusRecord_databaseRecord)
548         {
549             Z_External *r = npr->u.databaseRecord;
550             if (r->which == Z_External_octet)
551             {
552                 int rlen;
553                 char *result;
554                 if (yaz_marc_decode_buf(mt, (char*) r->u.octet_aligned->buf,
555                                         r->u.octet_aligned->len,
556                                         &result, &rlen))
557                 {
558                     yaz_iconv_t cd = yaz_iconv_open("UTF-8", "MARC-8");
559                     WRBUF wrbuf = wrbuf_alloc();
560                     
561                     char outbuf[120];
562                     size_t inbytesleft = rlen;
563                     const char *inp = result;
564                     while (cd && inbytesleft)
565                     {
566                         size_t outbytesleft = sizeof(outbuf);
567                         char *outp = outbuf;
568                         size_t r;
569                         
570                         r = yaz_iconv (cd, (char**) &inp,
571                                        &inbytesleft,
572                                        &outp, &outbytesleft);
573                         if (r == (size_t) (-1))
574                         {
575                             int e = yaz_iconv_error(cd);
576                             if (e != YAZ_ICONV_E2BIG)
577                             {
578                                 yaz_log(LOG_WARN, "conversion failure");
579                                 break;
580                             }
581                         }
582                         wrbuf_write(wrbuf, outbuf, outp - outbuf);
583                     }
584                     if (cd)
585                         yaz_iconv_close(cd);
586
587                     npr->u.databaseRecord = z_ext_record(odr_encode(),
588                                                          VAL_TEXT_XML,
589                                                          wrbuf_buf(wrbuf),
590                                                          wrbuf_len(wrbuf));
591                     wrbuf_free(wrbuf, 1);
592                 }
593             }
594         }
595     }
596     yaz_marc_destroy(mt);
597 }
598
599 int Yaz_Proxy::send_to_client(Z_APDU *apdu)
600 {
601     int len = 0;
602     if (apdu->which == Z_APDU_searchResponse)
603     {
604         Z_SearchResponse *sr = apdu->u.searchResponse;
605         Z_Records *p = sr->records;
606         if (p && p->which == Z_Records_NSD)
607         {
608             Z_DiagRec dr, *dr_p = &dr;
609             dr.which = Z_DiagRec_defaultFormat;
610             dr.u.defaultFormat = p->u.nonSurrogateDiagnostic;
611
612             display_diagrecs(&dr_p, 1);
613         }
614         else
615         {
616             if (m_marcxml_flag && p && p->which == Z_Records_DBOSD)
617                 convert_to_marcxml(p->u.databaseOrSurDiagnostics);
618             if (sr->resultCount)
619             {
620                 yaz_log(LOG_LOG, "%s%d hits", m_session_str,
621                         *sr->resultCount);
622                 if (*sr->resultCount < 0)
623                     m_invalid_session = 1;
624             }
625         }
626     }
627     else if (apdu->which == Z_APDU_presentResponse)
628     {
629         Z_PresentResponse *sr = apdu->u.presentResponse;
630         Z_Records *p = sr->records;
631         if (p && p->which == Z_Records_NSD)
632         {
633             Z_DiagRec dr, *dr_p = &dr;
634             dr.which = Z_DiagRec_defaultFormat;
635             dr.u.defaultFormat = p->u.nonSurrogateDiagnostic;
636
637             display_diagrecs(&dr_p, 1);
638         }
639         if (m_marcxml_flag && p && p->which == Z_Records_DBOSD)
640             convert_to_marcxml(p->u.databaseOrSurDiagnostics);
641     }
642     int r = send_Z_PDU(apdu, &len);
643     if (m_log_mask & PROXY_LOG_APDU_CLIENT)
644         yaz_log (LOG_DEBUG, "%sSending %s to client %d bytes", m_session_str,
645                  apdu_name(apdu), len);
646     m_bytes_sent += len;
647     m_bw_stat.add_bytes(len);
648     return r;
649 }
650
651 int Yaz_ProxyClient::send_to_target(Z_APDU *apdu)
652 {
653     int len = 0;
654     int r = send_Z_PDU(apdu, &len);
655     if (m_root->get_log_mask() & PROXY_LOG_REQ_SERVER)
656         yaz_log (LOG_LOG, "%sSending %s to %s %d bytes",
657                  get_session_str(),
658                  apdu_name(apdu), get_hostname(), len);
659     m_bytes_sent += len;
660     return r;
661 }
662
663 Z_APDU *Yaz_Proxy::result_set_optimize(Z_APDU *apdu)
664 {
665     if (apdu->which == Z_APDU_presentRequest)
666     {
667         Z_PresentRequest *pr = apdu->u.presentRequest;
668         Z_NamePlusRecordList *npr;
669         int toget = *pr->numberOfRecordsRequested;
670         int start = *pr->resultSetStartPoint;
671
672         yaz_log(LOG_LOG, "%sPresent %s %d+%d", m_session_str,
673                 pr->resultSetId, start, toget);
674
675         if (*m_parent->m_optimize == '0')
676             return apdu;
677
678         if (!m_client->m_last_resultSetId)
679         {
680             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
681             new_apdu->u.presentResponse->records =
682                 create_nonSurrogateDiagnostics(odr_encode(), 30,
683                                                pr->resultSetId);
684             send_to_client(new_apdu);
685             return 0;
686         }
687         if (!strcmp(m_client->m_last_resultSetId, pr->resultSetId))
688         {
689             if (start+toget-1 > m_client->m_last_resultCount)
690             {
691                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
692                 new_apdu->u.presentResponse->records =
693                     create_nonSurrogateDiagnostics(odr_encode(), 13, 0);
694                 send_to_client(new_apdu);
695                 return 0;
696             }
697             if (m_client->m_cache.lookup (odr_encode(), &npr, start, toget,
698                                           pr->preferredRecordSyntax,
699                                           pr->recordComposition))
700             {
701                 yaz_log (LOG_LOG, "%sReturned cached records for present request", 
702                          m_session_str);
703                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
704                 new_apdu->u.presentResponse->referenceId = pr->referenceId;
705                 
706                 new_apdu->u.presentResponse->numberOfRecordsReturned
707                     = odr_intdup(odr_encode(), toget);
708                                                                  
709                 new_apdu->u.presentResponse->records = (Z_Records*)
710                     odr_malloc(odr_encode(), sizeof(Z_Records));
711                 new_apdu->u.presentResponse->records->which = Z_Records_DBOSD;
712                 new_apdu->u.presentResponse->records->u.databaseOrSurDiagnostics = npr;
713                 new_apdu->u.presentResponse->nextResultSetPosition =
714                     odr_intdup(odr_encode(), start+toget);
715
716                 send_to_client(new_apdu);
717                 return 0;
718             }
719         }
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         if (*sr->smallSetUpperBound > 0 || *sr->mediumSetPresentNumber > 0)
1014         {
1015             int err = 0;
1016             char *addinfo = 0;
1017             Yaz_ProxyConfig *cfg = check_reconfigure();
1018
1019             if (cfg)
1020                 err = cfg->check_syntax(odr_encode(),
1021                                         m_default_target,
1022                                         sr->preferredRecordSyntax,
1023                                         &addinfo);
1024             if (err == -1)
1025             {
1026                 sr->preferredRecordSyntax =
1027                     yaz_oidval_to_z3950oid(odr_decode(), CLASS_RECSYN,
1028                                            VAL_USMARC);
1029                 m_marcxml_flag = 1;
1030             }
1031             else if (err)
1032             {
1033                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
1034                 
1035                 new_apdu->u.searchResponse->referenceId = sr->referenceId;
1036                 new_apdu->u.searchResponse->records =
1037                     create_nonSurrogateDiagnostics(odr_encode(), err, addinfo);
1038                 *new_apdu->u.searchResponse->searchStatus = 0;
1039                 
1040                 send_to_client(new_apdu);
1041                 
1042                 return 0;
1043             }
1044         }
1045     }
1046     else if (apdu->which == Z_APDU_presentRequest)
1047     {
1048         Z_PresentRequest *pr = apdu->u.presentRequest;
1049         int err = 0;
1050         char *addinfo = 0;
1051         Yaz_ProxyConfig *cfg = check_reconfigure();
1052
1053         if (cfg)
1054             err = cfg->check_syntax(odr_encode(), m_default_target,
1055                                     pr->preferredRecordSyntax,
1056                                     &addinfo);
1057         if (err == -1)
1058         {
1059             pr->preferredRecordSyntax =
1060                 yaz_oidval_to_z3950oid(odr_decode(), CLASS_RECSYN,
1061                                        VAL_USMARC);
1062             m_marcxml_flag = 1;
1063         }
1064         else if (err)
1065         {
1066             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
1067             
1068             new_apdu->u.presentResponse->referenceId = pr->referenceId;
1069             new_apdu->u.presentResponse->records =
1070                 create_nonSurrogateDiagnostics(odr_encode(), err, addinfo);
1071             *new_apdu->u.presentResponse->presentStatus =
1072                 Z_PresentStatus_failure;
1073             
1074             send_to_client(new_apdu);
1075             
1076             return 0;
1077         }
1078     }
1079     return apdu;
1080 }
1081
1082 void Yaz_Proxy::recv_Z_PDU_0(Z_APDU *apdu)
1083 {
1084     // Determine our client.
1085     m_client = get_client(apdu);
1086     if (!m_client)
1087     {
1088         delete this;
1089         return;
1090     }
1091     m_client->m_server = this;
1092
1093     if (apdu->which == Z_APDU_initRequest)
1094     {
1095         if (apdu->u.initRequest->implementationId)
1096             yaz_log(LOG_LOG, "%simplementationId: %s",
1097                     m_session_str, apdu->u.initRequest->implementationId);
1098         if (apdu->u.initRequest->implementationName)
1099             yaz_log(LOG_LOG, "%simplementationName: %s",
1100                     m_session_str, apdu->u.initRequest->implementationName);
1101         if (apdu->u.initRequest->implementationVersion)
1102             yaz_log(LOG_LOG, "%simplementationVersion: %s",
1103                     m_session_str, apdu->u.initRequest->implementationVersion);
1104         if (m_client->m_init_flag)
1105         {
1106             Z_APDU *apdu = m_client->m_initResponse;
1107             apdu->u.initResponse->otherInfo = 0;
1108             if (m_client->m_cookie && *m_client->m_cookie)
1109                 set_otherInformationString(apdu, VAL_COOKIE, 1,
1110                                            m_client->m_cookie);
1111             send_to_client(apdu);
1112             return;
1113         }
1114         m_client->m_init_flag = 1;
1115     }
1116     handle_max_record_retrieve(apdu);
1117
1118     if (apdu)
1119         apdu = handle_syntax_validation(apdu);
1120
1121     if (apdu)
1122         apdu = handle_query_validation(apdu);
1123
1124     if (apdu)
1125         apdu = result_set_optimize(apdu);
1126     if (!apdu)
1127     {
1128         m_client->timeout(m_target_idletime);  // mark it active even 
1129         // though we didn't use it
1130         return;
1131     }
1132
1133     // delete other info part from PDU before sending to target
1134     Z_OtherInformation **oi;
1135     get_otherInfoAPDU(apdu, &oi);
1136     if (oi)
1137         *oi = 0;
1138
1139     if (apdu->which == Z_APDU_presentRequest &&
1140         m_client->m_resultSetStartPoint == 0)
1141     {
1142         Z_PresentRequest *pr = apdu->u.presentRequest;
1143         m_client->m_resultSetStartPoint = *pr->resultSetStartPoint;
1144         m_client->m_cache.copy_presentRequest(apdu->u.presentRequest);
1145     } else {
1146         m_client->m_resultSetStartPoint = 0;
1147     }
1148     if (m_client->send_to_target(apdu) < 0)
1149     {
1150         delete m_client;
1151         m_client = 0;
1152         delete this;
1153     }
1154     else
1155         m_client->m_waiting = 1;
1156 }
1157
1158 void Yaz_Proxy::connectNotify()
1159 {
1160 }
1161
1162 void Yaz_Proxy::shutdown()
1163 {
1164     // only keep if keep_alive flag is set...
1165     if (m_client && 
1166         !m_invalid_session &&
1167         m_client->m_pdu_recv < m_keepalive_limit_pdu &&
1168         m_client->m_bytes_recv+m_client->m_bytes_sent < m_keepalive_limit_bw &&
1169         m_client->m_waiting == 0)
1170     {
1171         yaz_log(LOG_LOG, "%sShutdown (client to proxy) keepalive %s",
1172                  m_session_str,
1173                  m_client->get_hostname());
1174         yaz_log(LOG_LOG, "%sbw=%d pdu=%d limit-bw=%d limit-pdu=%d",
1175                 m_session_str, m_client->m_pdu_recv,
1176                 m_client->m_bytes_sent + m_client->m_bytes_recv,
1177                 m_keepalive_limit_bw, m_keepalive_limit_pdu);
1178         assert (m_client->m_waiting != 2);
1179         // Tell client (if any) that no server connection is there..
1180         m_client->m_server = 0;
1181     }
1182     else if (m_client)
1183     {
1184         yaz_log (LOG_LOG, "%sShutdown (client to proxy) close %s",
1185                  m_session_str,
1186                  m_client->get_hostname());
1187         assert (m_client->m_waiting != 2);
1188         delete m_client;
1189     }
1190     else if (!m_parent)
1191     {
1192         yaz_log (LOG_LOG, "%sshutdown (client to proxy) bad state",
1193                  m_session_str);
1194         assert (m_parent);
1195     }
1196     else 
1197     {
1198         yaz_log (LOG_LOG, "%sShutdown (client to proxy)",
1199                  m_session_str);
1200     }
1201     if (m_parent)
1202         m_parent->pre_init();
1203     delete this;
1204 }
1205
1206 const char *Yaz_ProxyClient::get_session_str() 
1207 {
1208     if (!m_server)
1209         return "0 ";
1210     return m_server->get_session_str();
1211 }
1212
1213 void Yaz_ProxyClient::shutdown()
1214 {
1215     yaz_log (LOG_LOG, "%sShutdown (proxy to target) %s", get_session_str(),
1216              get_hostname());
1217     delete m_server;
1218     delete this;
1219 }
1220
1221 void Yaz_Proxy::failNotify()
1222 {
1223     yaz_log (LOG_LOG, "%sConnection closed by client",
1224              get_session_str());
1225     shutdown();
1226 }
1227
1228 void Yaz_ProxyClient::failNotify()
1229 {
1230     yaz_log (LOG_LOG, "%sConnection closed by target %s", 
1231              get_session_str(), get_hostname());
1232     shutdown();
1233 }
1234
1235 void Yaz_ProxyClient::connectNotify()
1236 {
1237     const char *s = get_session_str();
1238     const char *h = get_hostname();
1239     yaz_log (LOG_LOG, "%sConnection accepted by %s timeout=%d", s, h,
1240              m_target_idletime);
1241     timeout(m_target_idletime);
1242     if (!m_server)
1243         pre_init_client();
1244 }
1245
1246 IYaz_PDU_Observer *Yaz_ProxyClient::sessionNotify(IYaz_PDU_Observable
1247                                                   *the_PDU_Observable, int fd)
1248 {
1249     return new Yaz_ProxyClient(the_PDU_Observable, 0);
1250 }
1251
1252 Yaz_ProxyClient::~Yaz_ProxyClient()
1253 {
1254     if (m_prev)
1255         *m_prev = m_next;
1256     if (m_next)
1257         m_next->m_prev = m_prev;
1258     m_waiting = 2;     // for debugging purposes only.
1259     odr_destroy(m_init_odr);
1260     delete m_last_query;
1261     xfree (m_last_resultSetId);
1262     xfree (m_cookie);
1263 }
1264
1265 void Yaz_ProxyClient::pre_init_client()
1266 {
1267     Z_APDU *apdu = create_Z_PDU(Z_APDU_initRequest);
1268     Z_InitRequest *req = apdu->u.initRequest;
1269     
1270     ODR_MASK_SET(req->options, Z_Options_search);
1271     ODR_MASK_SET(req->options, Z_Options_present);
1272     ODR_MASK_SET(req->options, Z_Options_namedResultSets);
1273     ODR_MASK_SET(req->options, Z_Options_triggerResourceCtrl);
1274     ODR_MASK_SET(req->options, Z_Options_scan);
1275     ODR_MASK_SET(req->options, Z_Options_sort);
1276     ODR_MASK_SET(req->options, Z_Options_extendedServices);
1277     ODR_MASK_SET(req->options, Z_Options_delSet);
1278     
1279     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_1);
1280     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_2);
1281     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_3);
1282     
1283     if (send_to_target(apdu) < 0)
1284     {
1285         delete this;
1286     }
1287     else
1288     {
1289         m_waiting = 1;
1290         m_init_flag = 1;
1291     }
1292 }
1293
1294 void Yaz_Proxy::pre_init()
1295 {
1296     int i;
1297     const char *name = 0;
1298     const char *zurl_in_use[MAX_ZURL_PLEX];
1299     int limit_bw, limit_pdu, limit_req;
1300     int target_idletime, client_idletime;
1301     int max_clients;
1302     int keepalive_limit_bw, keepalive_limit_pdu;
1303     int pre_init;
1304
1305     Yaz_ProxyConfig *cfg = check_reconfigure();
1306
1307     zurl_in_use[0] = 0;
1308
1309     if (m_log_mask & PROXY_LOG_APDU_CLIENT)
1310         set_APDU_yazlog(1);
1311     else
1312         set_APDU_yazlog(0);
1313
1314     for (i = 0; cfg && cfg->get_target_no(i, &name, zurl_in_use,
1315                                           &limit_bw, &limit_pdu, &limit_req,
1316                                           &target_idletime, &client_idletime,
1317                                           &max_clients, 
1318                                           &keepalive_limit_bw,
1319                                           &keepalive_limit_pdu,
1320                                           &pre_init) ; i++)
1321     {
1322         if (pre_init)
1323         {
1324             int j;
1325             for (j = 0; zurl_in_use[j]; j++)
1326             {
1327                 Yaz_ProxyClient *c;
1328                 int spare = 0;
1329                 int in_use = 0;
1330                 int other = 0;
1331                 for (c = m_clientPool; c; c = c->m_next)
1332                 {
1333                     if (!strcmp(zurl_in_use[j], c->get_hostname()))
1334                     {
1335                         if (c->m_cookie == 0)
1336                         {
1337                             if (c->m_server == 0)
1338                                 spare++;
1339                             else
1340                                 in_use++;
1341                         }
1342                         else
1343                             other++;
1344                     }
1345                 }
1346                 yaz_log(LOG_LOG, "%s pre-init %s %s use=%d other=%d spare=%d preinit=%d",
1347                         m_session_str,
1348                         name, zurl_in_use[j], in_use, other, spare, pre_init);
1349                 if (spare < pre_init)
1350                 {
1351                     c = new Yaz_ProxyClient(m_PDU_Observable->clone(), this);
1352                     c->m_next = m_clientPool;
1353                     if (c->m_next)
1354                         c->m_next->m_prev = &c->m_next;
1355                     m_clientPool = c;
1356                     c->m_prev = &m_clientPool;
1357                     
1358                     if (m_log_mask & PROXY_LOG_APDU_SERVER)
1359                         c->set_APDU_yazlog(1);
1360                     else
1361                         c->set_APDU_yazlog(0);
1362
1363                     if (c->client(zurl_in_use[j]))
1364                     {
1365                         timeout(60);
1366                         delete c;
1367                         return;
1368                     }
1369                     c->timeout(30);
1370                     c->m_waiting = 1;
1371                     c->m_target_idletime = target_idletime;
1372                     c->m_seqno = m_seqno++;
1373                 }
1374             }
1375         }
1376     }
1377 }
1378
1379 void Yaz_Proxy::timeoutNotify()
1380 {
1381     if (m_parent)
1382     {
1383         if (m_bw_hold_PDU)
1384         {
1385             timeout(m_client_idletime);
1386             Z_APDU *apdu = m_bw_hold_PDU;
1387             m_bw_hold_PDU = 0;
1388             recv_Z_PDU_0(apdu);
1389         }
1390         else
1391         {
1392             yaz_log (LOG_LOG, "%sTimeout (client to proxy)", m_session_str);
1393             shutdown();
1394         }
1395     }
1396     else
1397     {
1398         timeout(600);
1399         pre_init();
1400     }
1401 }
1402
1403 void Yaz_ProxyClient::timeoutNotify()
1404 {
1405     yaz_log (LOG_LOG, "%sTimeout (proxy to target) %s", get_session_str(),
1406              get_hostname());
1407     m_waiting = 1;
1408     m_root->pre_init();
1409     shutdown();
1410 }
1411
1412 Yaz_ProxyClient::Yaz_ProxyClient(IYaz_PDU_Observable *the_PDU_Observable,
1413                                  Yaz_Proxy *parent) :
1414     Yaz_Z_Assoc (the_PDU_Observable)
1415 {
1416     m_cookie = 0;
1417     m_next = 0;
1418     m_prev = 0;
1419     m_init_flag = 0;
1420     m_last_query = 0;
1421     m_last_resultSetId = 0;
1422     m_last_resultCount = 0;
1423     m_last_ok = 0;
1424     m_sr_transform = 0;
1425     m_waiting = 0;
1426     m_init_odr = odr_createmem (ODR_DECODE);
1427     m_initResponse = 0;
1428     m_resultSetStartPoint = 0;
1429     m_bytes_sent = m_bytes_recv = 0;
1430     m_pdu_recv = 0;
1431     m_server = 0;
1432     m_seqno = 0;
1433     m_target_idletime = 600;
1434     m_root = parent;
1435 }
1436
1437 const char *Yaz_Proxy::option(const char *name, const char *value)
1438 {
1439     if (!strcmp (name, "optimize")) {
1440         if (value) {
1441             xfree (m_optimize); 
1442             m_optimize = xstrdup (value);
1443         }
1444         return m_optimize;
1445     }
1446     return 0;
1447 }
1448
1449 void Yaz_ProxyClient::recv_Z_PDU(Z_APDU *apdu, int len)
1450 {
1451     m_bytes_recv += len;
1452     m_pdu_recv++;
1453     m_waiting = 0;
1454     if (m_root->get_log_mask() & PROXY_LOG_REQ_SERVER)
1455         yaz_log (LOG_LOG, "%sReceiving %s from %s %d bytes", get_session_str(),
1456                  apdu_name(apdu), get_hostname(), len);
1457     if (apdu->which == Z_APDU_initResponse)
1458     {
1459         if (!m_server)  // if this is a pre init session , check for more
1460             m_root->pre_init();
1461         NMEM nmem = odr_extract_mem (odr_decode());
1462         odr_reset (m_init_odr);
1463         nmem_transfer (m_init_odr->mem, nmem);
1464         m_initResponse = apdu;
1465
1466         Z_InitResponse *ir = apdu->u.initResponse;
1467         char *im0 = ir->implementationName;
1468         
1469         char *im1 = (char*) 
1470             odr_malloc(m_init_odr, 20 + (im0 ? strlen(im0) : 0));
1471         *im1 = '\0';
1472         if (im0)
1473         {
1474             strcat(im1, im0);
1475             strcat(im1, " ");
1476         }
1477         strcat(im1, "(YAZ Proxy)");
1478         ir->implementationName = im1;
1479
1480         nmem_destroy (nmem);
1481     }
1482     if (apdu->which == Z_APDU_searchResponse)
1483     {
1484         Z_SearchResponse *sr = apdu->u.searchResponse;
1485         m_last_resultCount = *sr->resultCount;
1486         int status = *sr->searchStatus;
1487         if (status && (!sr->records || sr->records->which == Z_Records_DBOSD))
1488         {
1489             m_last_ok = 1;
1490             
1491             if (sr->records && sr->records->which == Z_Records_DBOSD)
1492             {
1493                 m_cache.add(odr_decode(),
1494                             sr->records->u.databaseOrSurDiagnostics, 1,
1495                             *sr->resultCount);
1496             }
1497         }
1498     }
1499     if (apdu->which == Z_APDU_presentResponse)
1500     {
1501         Z_PresentResponse *pr = apdu->u.presentResponse;
1502         if (m_sr_transform)
1503         {
1504             m_sr_transform = 0;
1505             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
1506             Z_SearchResponse *sr = new_apdu->u.searchResponse;
1507             sr->referenceId = pr->referenceId;
1508             *sr->resultCount = m_last_resultCount;
1509             sr->records = pr->records;
1510             sr->nextResultSetPosition = pr->nextResultSetPosition;
1511             sr->numberOfRecordsReturned = pr->numberOfRecordsReturned;
1512             apdu = new_apdu;
1513         }
1514         if (pr->records && 
1515             pr->records->which == Z_Records_DBOSD && m_resultSetStartPoint)
1516         {
1517             m_cache.add(odr_decode(),
1518                         pr->records->u.databaseOrSurDiagnostics,
1519                         m_resultSetStartPoint, -1);
1520             m_resultSetStartPoint = 0;
1521         }
1522     }
1523     if (m_cookie)
1524         set_otherInformationString (apdu, VAL_COOKIE, 1, m_cookie);
1525     if (m_server)
1526     {
1527         m_server->send_to_client(apdu);
1528     }
1529     if (apdu->which == Z_APDU_close)
1530     {
1531         shutdown();
1532     }
1533 }
1534
1535 void Yaz_Proxy::server(const char *addr)
1536 {
1537     Yaz_Z_Assoc::server(addr);
1538
1539     timeout(1);
1540 }
1541