Output connections set to 150
[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.47 2003-09-03 11:30:26 adam Exp $
6  */
7
8 #include <assert.h>
9 #include <time.h>
10
11 #include <yaz/log.h>
12 #include <yaz++/proxy.h>
13
14 Yaz_Proxy::Yaz_Proxy(IYaz_PDU_Observable *the_PDU_Observable) :
15     Yaz_Z_Assoc(the_PDU_Observable)
16 {
17     m_PDU_Observable = the_PDU_Observable;
18     m_client = 0;
19     m_parent = 0;
20     m_clientPool = 0;
21     m_seqno = 1;
22     m_keepalive = 1;
23     m_proxyTarget = 0;
24     m_proxy_authentication = 0;
25     m_max_clients = 150;
26     m_seed = time(0);
27     m_idletime = 600;
28     m_optimize = xstrdup ("1");
29 }
30
31 Yaz_Proxy::~Yaz_Proxy()
32 {
33     xfree (m_proxyTarget);
34     xfree (m_proxy_authentication);
35     xfree (m_optimize);
36 }
37
38 void Yaz_Proxy::set_proxy_target(const char *target)
39 {
40     xfree (m_proxyTarget);
41     m_proxyTarget = 0;
42     if (target)
43         m_proxyTarget = (char *) xstrdup (target);
44 }
45
46 void Yaz_Proxy::set_proxy_authentication (const char *auth)
47 {
48     xfree (m_proxy_authentication);
49     m_proxy_authentication = 0;
50     if (auth)
51         m_proxy_authentication = (char *) xstrdup (auth);
52 }
53
54 IYaz_PDU_Observer *Yaz_Proxy::sessionNotify(IYaz_PDU_Observable
55                                             *the_PDU_Observable, int fd)
56 {
57     Yaz_Proxy *new_proxy = new Yaz_Proxy(the_PDU_Observable);
58     new_proxy->m_parent = this;
59     new_proxy->timeout(m_idletime);
60     new_proxy->set_proxy_target(m_proxyTarget);
61     new_proxy->set_APDU_log(get_APDU_log());
62     new_proxy->set_proxy_authentication(m_proxy_authentication);
63     yaz_log (LOG_LOG, "New session p=%p", new_proxy);
64     return new_proxy;
65 }
66
67 char *Yaz_Proxy::get_cookie(Z_OtherInformation **otherInfo)
68 {
69     int oid[OID_SIZE];
70     Z_OtherInformationUnit *oi;
71     struct oident ent;
72     ent.proto = PROTO_Z3950;
73     ent.oclass = CLASS_USERINFO;
74     ent.value = (oid_value) VAL_COOKIE;
75     assert (oid_ent_to_oid (&ent, oid));
76
77     if (oid_ent_to_oid (&ent, oid) && 
78         (oi = update_otherInformation(otherInfo, 0, oid, 1, 1)) &&
79         oi->which == Z_OtherInfo_characterInfo)
80         return oi->information.characterInfo;
81     return 0;
82 }
83
84 char *Yaz_Proxy::get_proxy(Z_OtherInformation **otherInfo)
85 {
86     int oid[OID_SIZE];
87     Z_OtherInformationUnit *oi;
88     struct oident ent;
89     ent.proto = PROTO_Z3950;
90     ent.oclass = CLASS_USERINFO;
91     ent.value = (oid_value) VAL_PROXY;
92     if (oid_ent_to_oid (&ent, oid) &&
93         (oi = update_otherInformation(otherInfo, 0, oid, 1, 1)) &&
94         oi->which == Z_OtherInfo_characterInfo)
95         return oi->information.characterInfo;
96     return 0;
97 }
98
99 Yaz_ProxyClient *Yaz_Proxy::get_client(Z_APDU *apdu)
100 {
101     assert (m_parent);
102     Yaz_Proxy *parent = m_parent;
103     Z_OtherInformation **oi;
104     Yaz_ProxyClient *c = m_client;
105     
106     get_otherInfoAPDU(apdu, &oi);
107     char *cookie = get_cookie(oi);
108
109     const char *proxy_host = get_proxy(oi);
110     if (proxy_host)
111         set_proxy_target(proxy_host);
112     
113     // no target specified at all?
114     if (!m_proxyTarget)
115         return 0;
116
117     if (!strcmp(m_proxyTarget, "stop"))
118         exit (0);
119     if (cookie && *cookie)
120     {
121         Yaz_ProxyClient *cc = 0;
122         
123         for (c = parent->m_clientPool; c; c = c->m_next)
124         {
125             assert (c->m_prev);
126             assert (*c->m_prev == c);
127             if (c->m_cookie && !strcmp(cookie,c->m_cookie) &&
128                 !strcmp(m_proxyTarget, c->get_hostname()))
129             {
130                 cc = c;
131             }
132         }
133         if (cc)
134         {
135             // found it in cache
136             c = cc;
137             // The following handles "cancel"
138             // If connection is busy (waiting for PDU) and
139             // we have an initRequest we can safely do re-open
140             if (c->m_waiting && apdu->which == Z_APDU_initRequest)
141             {
142                 yaz_log (LOG_LOG, "reopen target=%s", c->get_hostname());
143                 c->close();
144                 c->client(m_proxyTarget);
145                 c->m_init_flag = 0;
146
147                 c->m_last_ok = 0;
148                 c->m_cache.clear();
149                 c->m_last_resultCount = 0;
150                 c->m_sr_transform = 0;
151                 c->m_waiting = 0;
152                 c->m_resultSetStartPoint = 0;
153                 c->timeout(m_idletime); 
154             }
155             c->m_seqno = parent->m_seqno;
156             if (c->m_server && c->m_server != this)
157                 c->m_server->m_client = 0;
158             c->m_server = this;
159             (parent->m_seqno)++;
160             yaz_log (LOG_DEBUG, "get_client 1 %p %p", this, c);
161             return c;
162         }
163     }
164     else if (!c)
165     {
166         Yaz_ProxyClient *cc = 0;
167         
168         for (c = parent->m_clientPool; c; c = c->m_next)
169         {
170             assert (c->m_prev);
171             assert (*c->m_prev == c);
172             if (c->m_server == 0 && c->m_cookie == 0 && 
173                 !strcmp(m_proxyTarget, c->get_hostname()))
174             {
175                 cc = c;
176             }
177         }
178         if (cc)
179         {
180             // found it in cache
181             c = cc;
182
183             yaz_log (LOG_LOG, "Reuse session %d to %d %s",
184                      c->m_seqno, parent->m_seqno, c->get_hostname());
185
186             c->m_seqno = parent->m_seqno;
187             assert(c->m_server == 0);
188             c->m_server = this;
189             
190             (parent->m_seqno)++;
191             return c;
192         }
193     }
194     if (!m_client)
195     {
196         if (apdu->which != Z_APDU_initRequest)
197         {
198             yaz_log (LOG_LOG, "no first INIT!");
199             return 0;
200         }
201         Z_InitRequest *initRequest = apdu->u.initRequest;
202
203         if (!initRequest->idAuthentication)
204         {
205             if (m_proxy_authentication)
206             {
207                 initRequest->idAuthentication =
208                     (Z_IdAuthentication *)
209                     odr_malloc (odr_encode(),
210                                 sizeof(*initRequest->idAuthentication));
211                 initRequest->idAuthentication->which =
212                     Z_IdAuthentication_open;
213                 initRequest->idAuthentication->u.open =
214                     odr_strdup (odr_encode(), m_proxy_authentication);
215             }
216         }
217
218         // go through list of clients - and find the lowest/oldest one.
219         Yaz_ProxyClient *c_min = 0;
220         int min_seq = -1;
221         int no_of_clients = 0;
222         if (parent->m_clientPool)
223             yaz_log (LOG_LOG, "Existing sessions");
224         for (c = parent->m_clientPool; c; c = c->m_next)
225         {
226             yaz_log (LOG_LOG, " Session %-3d wait=%d %s cookie=%s", c->m_seqno,
227                                c->m_waiting, c->get_hostname(),
228                                c->m_cookie ? c->m_cookie : "");
229             no_of_clients++;
230             if (min_seq < 0 || c->m_seqno < min_seq)
231             {
232                 min_seq = c->m_seqno;
233                 c_min = c;
234             }
235         }
236         if (no_of_clients >= parent->m_max_clients)
237         {
238             c = c_min;
239             if (c->m_waiting || strcmp(m_proxyTarget, c->get_hostname()))
240             {
241                 yaz_log (LOG_LOG, "Replace session %d",
242                       c->m_seqno);
243                 if (c->m_server && c->m_server != this)
244                     delete c->m_server;
245                 c->m_server = 0;
246             }
247             else
248             {
249                 yaz_log (LOG_LOG, "Move session %d to %d %s",
250                       c->m_seqno, parent->m_seqno, c->get_hostname());
251                 xfree (c->m_cookie);
252                 c->m_cookie = 0;
253                 if (cookie)
254                     c->m_cookie = xstrdup(cookie);
255                 c->m_seqno = parent->m_seqno;
256                 if (c->m_server && c->m_server != this)
257                 {
258                     c->m_server->m_client = 0;
259                     delete c->m_server;
260                 }
261                 (parent->m_seqno)++;
262                 yaz_log (LOG_DEBUG, "get_client 2 %p %p", this, c);
263                 return c;
264             }
265         }
266         else
267         {
268             yaz_log (LOG_LOG, "Making session %d %s", parent->m_seqno,
269                             m_proxyTarget);
270             c = new Yaz_ProxyClient(m_PDU_Observable->clone());
271             c->m_next = parent->m_clientPool;
272             if (c->m_next)
273                 c->m_next->m_prev = &c->m_next;
274             parent->m_clientPool = c;
275             c->m_prev = &parent->m_clientPool;
276         }
277
278         xfree (c->m_cookie);
279         c->m_cookie = 0;
280         if (cookie)
281             c->m_cookie = xstrdup(cookie);
282
283         yaz_log (LOG_LOG, "Connecting to %s", m_proxyTarget);
284         c->m_seqno = parent->m_seqno;
285         c->client(m_proxyTarget);
286         c->m_init_flag = 0;
287         c->m_last_resultCount = 0;
288         c->m_last_ok = 0;
289         c->m_cache.clear();
290         c->m_sr_transform = 0;
291         c->m_waiting = 0;
292         c->m_resultSetStartPoint = 0;
293         c->timeout(20);
294
295         (parent->m_seqno)++;
296     }
297     yaz_log (LOG_DEBUG, "get_client 3 %p %p", this, c);
298     return c;
299 }
300
301 Z_APDU *Yaz_Proxy::result_set_optimize(Z_APDU *apdu)
302 {
303     if (*m_parent->m_optimize == '0')
304         return apdu;      // don't optimize result sets..
305     if (apdu->which == Z_APDU_presentRequest)
306     {
307         Z_PresentRequest *pr = apdu->u.presentRequest;
308         Z_NamePlusRecordList *npr;
309         int toget = *pr->numberOfRecordsRequested;
310         int start = *pr->resultSetStartPoint;
311
312         if (m_client->m_last_resultSetId &&
313             !strcmp(m_client->m_last_resultSetId, pr->resultSetId))
314         {
315             if (m_client->m_cache.lookup (odr_encode(), &npr, start, toget,
316                                           pr->preferredRecordSyntax,
317                                           pr->recordComposition))
318             {
319                 yaz_log (LOG_LOG, "Returned cache records for present request");
320                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
321                 new_apdu->u.presentResponse->referenceId = pr->referenceId;
322                 
323                 new_apdu->u.presentResponse->numberOfRecordsReturned
324                     = odr_intdup(odr_encode(), toget);
325                                                                  
326                 new_apdu->u.presentResponse->records = (Z_Records*)
327                     odr_malloc(odr_encode(), sizeof(Z_Records));
328                 new_apdu->u.presentResponse->records->which = Z_Records_DBOSD;
329                 new_apdu->u.presentResponse->records->u.databaseOrSurDiagnostics = npr;
330                 new_apdu->u.presentResponse->nextResultSetPosition =
331                     odr_intdup(odr_encode(), start+toget);
332                 send_Z_PDU(new_apdu);
333                 return 0;
334             }
335         }
336     }
337
338     if (apdu->which != Z_APDU_searchRequest)
339         return apdu;
340     Z_SearchRequest *sr = apdu->u.searchRequest;
341     Yaz_Z_Query *this_query = new Yaz_Z_Query;
342     Yaz_Z_Databases this_databases;
343
344     this_databases.set(sr->num_databaseNames, (const char **)
345                        sr->databaseNames);
346     
347     this_query->set_Z_Query(sr->query);
348     
349     if (m_client->m_last_ok && m_client->m_last_query &&
350         m_client->m_last_query->match(this_query) &&
351         !strcmp(m_client->m_last_resultSetId, sr->resultSetName) &&
352         m_client->m_last_databases.match(this_databases))
353     {
354         delete this_query;
355         if (m_client->m_last_resultCount > *sr->smallSetUpperBound &&
356             m_client->m_last_resultCount < *sr->largeSetLowerBound)
357         {
358             Z_NamePlusRecordList *npr;
359             int toget = *sr->mediumSetPresentNumber;
360             Z_RecordComposition *comp = 0;
361
362             if (toget > m_client->m_last_resultCount)
363                 toget = m_client->m_last_resultCount;
364             
365             if (sr->mediumSetElementSetNames)
366             {
367                 comp = (Z_RecordComposition *)
368                     odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
369                 comp->which = Z_RecordComp_simple;
370                 comp->u.simple = sr->mediumSetElementSetNames;
371             }
372  
373             if (m_client->m_cache.lookup (odr_encode(), &npr, 1, toget,
374                                           sr->preferredRecordSyntax, comp))
375             {
376                 yaz_log (LOG_LOG, "Returned cache records for medium set");
377                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
378                 new_apdu->u.searchResponse->referenceId = sr->referenceId;
379                 new_apdu->u.searchResponse->resultCount =
380                     &m_client->m_last_resultCount;
381                 
382                 new_apdu->u.searchResponse->numberOfRecordsReturned
383                     = odr_intdup(odr_encode(), toget);
384                                                         
385                 new_apdu->u.searchResponse->presentStatus =
386                     odr_intdup(odr_encode(), Z_PresentStatus_success);
387                 new_apdu->u.searchResponse->records = (Z_Records*)
388                     odr_malloc(odr_encode(), sizeof(Z_Records));
389                 new_apdu->u.searchResponse->records->which = Z_Records_DBOSD;
390                 new_apdu->u.searchResponse->records->u.databaseOrSurDiagnostics = npr;
391                 new_apdu->u.searchResponse->nextResultSetPosition =
392                     odr_intdup(odr_encode(), toget+1);
393                 send_Z_PDU(new_apdu);
394                 return 0;
395             }
396             else
397             {
398                 // medium Set
399                 // send present request (medium size)
400                 yaz_log (LOG_LOG, "Optimizing search for medium set");
401
402                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
403                 Z_PresentRequest *pr = new_apdu->u.presentRequest;
404                 pr->referenceId = sr->referenceId;
405                 pr->resultSetId = sr->resultSetName;
406                 pr->preferredRecordSyntax = sr->preferredRecordSyntax;
407                 *pr->numberOfRecordsRequested = toget;
408                 pr->recordComposition = comp;
409                 m_client->m_sr_transform = 1;
410                 return new_apdu;
411             }
412         }
413         else if (m_client->m_last_resultCount >= *sr->largeSetLowerBound ||
414             m_client->m_last_resultCount <= 0)
415         {
416             // large set. Return pseudo-search response immediately
417             yaz_log (LOG_LOG, "Optimizing search for large set");
418             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
419             new_apdu->u.searchResponse->referenceId = sr->referenceId;
420             new_apdu->u.searchResponse->resultCount =
421                 &m_client->m_last_resultCount;
422             send_Z_PDU(new_apdu);
423             return 0;
424         }
425         else
426         {
427             Z_NamePlusRecordList *npr;
428             int toget = m_client->m_last_resultCount;
429             Z_RecordComposition *comp = 0;
430             // small set
431             // send a present request (small set)
432             
433             if (sr->smallSetElementSetNames)
434             {
435                 comp = (Z_RecordComposition *)
436                     odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
437                 comp->which = Z_RecordComp_simple;
438                 comp->u.simple = sr->smallSetElementSetNames;
439             }
440
441             if (m_client->m_cache.lookup (odr_encode(), &npr, 1, toget,
442                                           sr->preferredRecordSyntax, comp))
443             {
444                 yaz_log (LOG_LOG, "Returned cache records for small set");
445                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
446                 new_apdu->u.searchResponse->referenceId = sr->referenceId;
447                 new_apdu->u.searchResponse->resultCount =
448                     &m_client->m_last_resultCount;
449                 
450                 new_apdu->u.searchResponse->numberOfRecordsReturned
451                     = odr_intdup(odr_encode(), toget);
452                                                                  
453                 new_apdu->u.searchResponse->presentStatus =
454                     odr_intdup(odr_encode(), Z_PresentStatus_success);
455                 new_apdu->u.searchResponse->records = (Z_Records*)
456                     odr_malloc(odr_encode(), sizeof(Z_Records));
457                 new_apdu->u.searchResponse->records->which = Z_Records_DBOSD;
458                 new_apdu->u.searchResponse->records->u.databaseOrSurDiagnostics = npr;
459                 new_apdu->u.searchResponse->nextResultSetPosition =
460                     odr_intdup(odr_encode(), toget+1);
461                 send_Z_PDU(new_apdu);
462                 return 0;
463             }
464             else
465             {
466                 yaz_log (LOG_LOG, "Optimizing search for small set");
467                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
468                 Z_PresentRequest *pr = new_apdu->u.presentRequest;
469                 pr->referenceId = sr->referenceId;
470                 pr->resultSetId = sr->resultSetName;
471                 pr->preferredRecordSyntax = sr->preferredRecordSyntax;
472                 *pr->numberOfRecordsRequested = toget;
473                 pr->recordComposition = comp;
474                 m_client->m_sr_transform = 1;
475                 return new_apdu;
476             }
477         }
478     }
479     else
480     {
481         delete m_client->m_last_query;
482         m_client->m_last_query = this_query;
483         m_client->m_last_ok = 0;
484         m_client->m_cache.clear();
485         m_client->m_resultSetStartPoint = 0;
486
487         xfree (m_client->m_last_resultSetId);
488         m_client->m_last_resultSetId = xstrdup (sr->resultSetName);
489
490         m_client->m_last_databases.set(sr->num_databaseNames,
491                                        (const char **) sr->databaseNames);
492     }
493     return apdu;
494 }
495
496 static const char *apdu_name(Z_APDU *apdu)
497 {
498     switch (apdu->which)
499     {
500     case Z_APDU_initRequest:
501         return "initRequest";
502     case Z_APDU_initResponse:
503         return "initResponse";
504     case Z_APDU_searchRequest:
505         return "searchRequest";
506     case Z_APDU_searchResponse:
507         return "searchResponse";
508     case Z_APDU_presentRequest:
509         return "presentRequest";
510     case Z_APDU_presentResponse:
511         return "presentResponse";
512     case Z_APDU_deleteResultSetRequest:
513         return "deleteResultSetRequest";
514     case Z_APDU_deleteResultSetResponse:
515         return "deleteResultSetResponse";
516     case Z_APDU_scanRequest:
517         return "scanRequest";
518     case Z_APDU_scanResponse:
519         return "scanResponse";
520     case Z_APDU_sortRequest:
521         return "sortRequest";
522     case Z_APDU_sortResponse:
523         return "sortResponse";
524     case Z_APDU_extendedServicesRequest:
525         return "extendedServicesRequest";
526     case Z_APDU_extendedServicesResponse:
527         return "extendedServicesResponse";
528     case Z_APDU_close:
529         return "close";
530     }
531     return "other";
532 }
533
534 void Yaz_Proxy::recv_Z_PDU(Z_APDU *apdu)
535 {
536     yaz_log (LOG_LOG, "Receiving %s from client", apdu_name(apdu));
537     // Determine our client.
538     m_client = get_client(apdu);
539     if (!m_client)
540     {
541         delete this;
542         return;
543     }
544     m_client->m_server = this;
545
546     if (apdu->which == Z_APDU_initRequest)
547     {
548         if (m_client->m_init_flag)
549         {
550             Z_APDU *apdu = m_client->m_initResponse;
551             apdu->u.initResponse->otherInfo = 0;
552             if (m_client->m_cookie && *m_client->m_cookie)
553                 set_otherInformationString(apdu, VAL_COOKIE, 1,
554                                            m_client->m_cookie);
555             send_Z_PDU(apdu);
556             return;
557         }
558         m_client->m_init_flag = 1;
559     }
560     apdu = result_set_optimize(apdu);
561     if (!apdu)
562         return;
563
564     yaz_log (LOG_LOG, "Sending %s to %s",
565                     apdu_name(apdu), m_client->get_hostname());
566
567     // delete other info part from PDU before sending to target
568     Z_OtherInformation **oi;
569     get_otherInfoAPDU(apdu, &oi);
570     if (oi)
571         *oi = 0;
572
573     if (apdu->which == Z_APDU_presentRequest &&
574         m_client->m_resultSetStartPoint == 0)
575     {
576         Z_PresentRequest *pr = apdu->u.presentRequest;
577         m_client->m_resultSetStartPoint = *pr->resultSetStartPoint;
578         m_client->m_cache.copy_presentRequest(apdu->u.presentRequest);
579     } else {
580         m_client->m_resultSetStartPoint = 0;
581     }
582     if (m_client->send_Z_PDU(apdu) < 0)
583     {
584         delete m_client;
585         m_client = 0;
586         delete this;
587     }
588     else
589         m_client->m_waiting = 1;
590 }
591
592 void Yaz_Proxy::connectNotify()
593 {
594 }
595
596 void Yaz_Proxy::shutdown()
597 {
598     // only keep if keep_alive flag is set...
599     if (m_keepalive && m_client && m_client->m_waiting == 0)
600     {
601         yaz_log (LOG_LOG, "shutdown (client to proxy) keepalive %s",
602                  m_client->get_hostname());
603         assert (m_client->m_waiting != 2);
604         // Tell client (if any) that no server connection is there..
605         m_client->m_server = 0;
606     }
607     else if (m_client)
608     {
609         yaz_log (LOG_LOG, "shutdown (client to proxy) close %s",
610                  m_client->get_hostname());
611         assert (m_client->m_waiting != 2);
612         delete m_client;
613     }
614     else if (!m_parent)
615     {
616         yaz_log (LOG_LOG, "shutdown (client to proxy) bad state");
617         assert (m_parent);
618     }
619     else 
620     {
621         yaz_log (LOG_LOG, "shutdown (client to proxy)");
622     }
623     delete this;
624 }
625
626 void Yaz_ProxyClient::shutdown()
627 {
628     yaz_log (LOG_LOG, "shutdown (proxy to server) %s", get_hostname());
629     delete m_server;
630     delete this;
631 }
632
633 void Yaz_Proxy::failNotify()
634 {
635     yaz_log (LOG_LOG, "Yaz_Proxy connection closed by client");
636     shutdown();
637 }
638
639 void Yaz_ProxyClient::failNotify()
640 {
641     yaz_log (LOG_LOG, "Yaz_ProxyClient connection closed by %s", get_hostname());
642     shutdown();
643 }
644
645 void Yaz_ProxyClient::connectNotify()
646 {
647     yaz_log (LOG_LOG, "Connection accepted by %s", get_hostname());
648     timeout(600);
649 }
650
651 IYaz_PDU_Observer *Yaz_ProxyClient::sessionNotify(IYaz_PDU_Observable
652                                                   *the_PDU_Observable, int fd)
653 {
654     return new Yaz_ProxyClient(the_PDU_Observable);
655 }
656
657 Yaz_ProxyClient::~Yaz_ProxyClient()
658 {
659     if (m_prev)
660         *m_prev = m_next;
661     if (m_next)
662         m_next->m_prev = m_prev;
663     m_waiting = 2;     // for debugging purposes only.
664     odr_destroy(m_init_odr);
665     delete m_last_query;
666     xfree (m_last_resultSetId);
667     xfree (m_cookie);
668 }
669
670 void Yaz_Proxy::timeoutNotify()
671 {
672     yaz_log (LOG_LOG, "timeout (client to proxy)");
673     shutdown();
674 }
675
676 void Yaz_ProxyClient::timeoutNotify()
677 {
678     yaz_log (LOG_LOG, "timeout (proxy to target) %s", get_hostname());
679     shutdown();
680 }
681
682 Yaz_ProxyClient::Yaz_ProxyClient(IYaz_PDU_Observable *the_PDU_Observable) :
683     Yaz_Z_Assoc (the_PDU_Observable)
684 {
685     m_cookie = 0;
686     m_next = 0;
687     m_prev = 0;
688     m_init_flag = 0;
689     m_last_query = 0;
690     m_last_resultSetId = 0;
691     m_last_resultCount = 0;
692     m_last_ok = 0;
693     m_sr_transform = 0;
694     m_waiting = 0;
695     m_init_odr = odr_createmem (ODR_DECODE);
696     m_initResponse = 0;
697     m_resultSetStartPoint = 0;
698 }
699
700 const char *Yaz_Proxy::option(const char *name, const char *value)
701 {
702     if (!strcmp (name, "optimize")) {
703         if (value) {
704             xfree (m_optimize); 
705             m_optimize = xstrdup (value);
706         }
707         return m_optimize;
708     }
709     return 0;
710 }
711
712 void Yaz_ProxyClient::recv_Z_PDU(Z_APDU *apdu)
713 {
714     m_waiting = 0;
715     yaz_log (LOG_LOG, "Receiving %s from %s", apdu_name(apdu),
716                      get_hostname());
717     if (apdu->which == Z_APDU_initResponse)
718     {
719         NMEM nmem = odr_extract_mem (odr_decode());
720         odr_reset (m_init_odr);
721         nmem_transfer (m_init_odr->mem, nmem);
722         m_initResponse = apdu;
723         nmem_destroy (nmem);
724     }
725     if (apdu->which == Z_APDU_searchResponse)
726     {
727         Z_SearchResponse *sr = apdu->u.searchResponse;
728         m_last_resultCount = *sr->resultCount;
729         int status = *sr->searchStatus;
730         if (status && (!sr->records || sr->records->which == Z_Records_DBOSD))
731         {
732             m_last_ok = 1;
733             
734             if (sr->records && sr->records->which == Z_Records_DBOSD)
735             {
736                 m_cache.add(odr_decode(),
737                             sr->records->u.databaseOrSurDiagnostics, 1,
738                             *sr->resultCount);
739             }
740         }
741     }
742     if (apdu->which == Z_APDU_presentResponse)
743     {
744         Z_PresentResponse *pr = apdu->u.presentResponse;
745         if (m_sr_transform)
746         {
747             m_sr_transform = 0;
748             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
749             Z_SearchResponse *sr = new_apdu->u.searchResponse;
750             sr->referenceId = pr->referenceId;
751             *sr->resultCount = m_last_resultCount;
752             sr->records = pr->records;
753             sr->nextResultSetPosition = pr->nextResultSetPosition;
754             sr->numberOfRecordsReturned = pr->numberOfRecordsReturned;
755             apdu = new_apdu;
756         }
757         if (pr->records->which == Z_Records_DBOSD && m_resultSetStartPoint)
758         {
759             m_cache.add(odr_decode(),
760                         pr->records->u.databaseOrSurDiagnostics,
761                         m_resultSetStartPoint, -1);
762             m_resultSetStartPoint = 0;
763         }
764     }
765     if (m_cookie)
766         set_otherInformationString (apdu, VAL_COOKIE, 1, m_cookie);
767     if (m_server)
768     {
769         yaz_log (LOG_LOG, "Sending %s to client", apdu_name(apdu));
770         m_server->send_Z_PDU(apdu);
771     }
772     if (apdu->which == Z_APDU_close)
773     {
774         shutdown();
775     }
776 }