Fixed problem processEvent which in rare cases return 0.
[yazpp-moved-to-github.git] / src / yaz-proxy.cpp
1 /*
2  * Copyright (c) 1998-2001, Index Data.
3  * See the file LICENSE for details.
4  * 
5  * $Id: yaz-proxy.cpp,v 1.28 2001-11-04 22:36:21 adam Exp $
6  */
7
8 #include <assert.h>
9 #include <time.h>
10
11 #include <yaz/log.h>
12 #include <yaz++/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_max_clients = 50;
25     m_seed = time(0);
26 }
27
28 Yaz_Proxy::~Yaz_Proxy()
29 {
30     xfree (m_proxyTarget);
31 }
32
33 void Yaz_Proxy::set_proxyTarget(const char *target)
34 {
35     xfree (m_proxyTarget);
36     m_proxyTarget = 0;
37     if (target)
38         m_proxyTarget = (char *) xstrdup (target);
39 }
40
41 IYaz_PDU_Observer *Yaz_Proxy::sessionNotify(IYaz_PDU_Observable
42                                             *the_PDU_Observable, int fd)
43 {
44     Yaz_Proxy *new_proxy = new Yaz_Proxy(the_PDU_Observable);
45     new_proxy->m_parent = this;
46     new_proxy->timeout(500);
47     new_proxy->set_proxyTarget(m_proxyTarget);
48     new_proxy->set_APDU_log(get_APDU_log());
49     return new_proxy;
50 }
51
52 char *Yaz_Proxy::get_cookie(Z_OtherInformation **otherInfo)
53 {
54     int oid[OID_SIZE];
55     Z_OtherInformationUnit *oi;
56     struct oident ent;
57     ent.proto = PROTO_Z3950;
58     ent.oclass = CLASS_USERINFO;
59     ent.value = (oid_value) VAL_COOKIE;
60     assert (oid_ent_to_oid (&ent, oid));
61
62     if (oid_ent_to_oid (&ent, oid) && 
63         (oi = update_otherInformation(otherInfo, 0, oid, 1, 1)) &&
64         oi->which == Z_OtherInfo_characterInfo)
65         return oi->information.characterInfo;
66     return 0;
67 }
68
69 char *Yaz_Proxy::get_proxy(Z_OtherInformation **otherInfo)
70 {
71     int oid[OID_SIZE];
72     Z_OtherInformationUnit *oi;
73     struct oident ent;
74     ent.proto = PROTO_Z3950;
75     ent.oclass = CLASS_USERINFO;
76     ent.value = (oid_value) VAL_PROXY;
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 Yaz_ProxyClient *Yaz_Proxy::get_client(Z_APDU *apdu)
85 {
86     assert (m_parent);
87     Yaz_Proxy *parent = m_parent;
88     Z_OtherInformation **oi;
89     Yaz_ProxyClient *c = m_client;
90     
91     get_otherInfoAPDU(apdu, &oi);
92     char *cookie = get_cookie(oi);
93     yaz_log (LOG_LOG, "Yaz_Proxy::get_client cookie=%s", cookie ? cookie :
94           "null");
95
96     const char *proxy_host = get_proxy(oi);
97     if (proxy_host)
98         set_proxyTarget(proxy_host);
99     yaz_log (LOG_LOG, "proxy_host = %s", m_proxyTarget ? m_proxyTarget:"none");
100     
101     // no target specified at all?
102     if (!m_proxyTarget)
103         return 0;
104
105     if (cookie && *cookie)
106     {
107         yaz_log (LOG_LOG, "lookup of clients cookie=%s target=%s",
108               cookie, m_proxyTarget);
109         Yaz_ProxyClient *cc = 0;
110         
111         for (c = parent->m_clientPool; c; c = c->m_next)
112         {
113             yaz_log (LOG_LOG, " found client cookie = %s target=%s seqno=%d",
114                   c->m_cookie, c->get_hostname(), c->m_seqno);
115             assert (c->m_prev);
116             assert (*c->m_prev == c);
117             if (!strcmp(cookie,c->m_cookie) &&
118                 !strcmp(m_proxyTarget, c->get_hostname()))
119             {
120                 yaz_log (LOG_LOG, "found!");
121                 cc = c;
122             }
123         }
124         if (cc)
125         {
126             // found it in cache
127             c = cc;
128             // The following handles "cancel"
129             // If connection is busy (waiting for PDU) and
130             // we have an initRequest we can safely do re-open
131             if (c->m_waiting && apdu->which == Z_APDU_initRequest)
132             {
133                 yaz_log (LOG_LOG, "reopen target=%s", c->get_hostname());
134                 c->close();
135                 c->client(m_proxyTarget);
136                 c->m_init_flag = 0;
137                 
138                 delete c->m_last_query;
139                 c->m_last_query = 0;
140                 c->m_last_resultCount = 0;
141                 c->m_sr_transform = 0;
142                 c->m_waiting = 0;
143                 c->timeout(600); 
144             }
145             c->m_seqno = parent->m_seqno;
146             if (c->m_server && c->m_server != this)
147                 c->m_server->m_client = 0;
148             c->m_server = this;
149             c->m_seqno = parent->m_seqno;
150             (parent->m_seqno)++;
151             yaz_log (LOG_LOG, "get_client 1 %p %p", this, c);
152             return c;
153         }
154     }
155     if (!m_client)
156     {
157         if (apdu->which != Z_APDU_initRequest)
158         {
159             yaz_log (LOG_LOG, "no first INIT!");
160             return 0;
161         }
162         yaz_log (LOG_LOG, "got InitRequest");
163             
164         // go through list of clients - and find the lowest/oldest one.
165         Yaz_ProxyClient *c_min = 0;
166         int min_seq = -1;
167         int no_of_clients = 0;
168         for (c = parent->m_clientPool; c; c = c->m_next)
169         {
170             no_of_clients++;
171             if (min_seq < 0 || c->m_seqno < min_seq)
172             {
173                 min_seq = c->m_seqno;
174                 c_min = c;
175             }
176         }
177         if (no_of_clients >= parent->m_max_clients)
178         {
179             c = c_min;
180             if (c->m_waiting || strcmp(m_proxyTarget, c->get_hostname()))
181             {
182                 yaz_log (LOG_LOG, "Yaz_Proxy::get_client re-init session %d",
183                       c->m_seqno);
184                 if (c->m_server && c->m_server != this)
185                     delete c->m_server;
186                 c->m_server = 0;
187             }
188             else
189             {
190                 yaz_log (LOG_LOG,
191                          "Yaz_Proxy::get_client re-use session %d to %d",
192                       c->m_seqno, parent->m_seqno);
193                 if (cookie)
194                     strcpy (c->m_cookie, cookie);
195                 else
196                     c->m_cookie[0] = '\0';
197                 c->m_seqno = parent->m_seqno;
198                 if (c->m_server && c->m_server != this)
199                 {
200                     c->m_server->m_client = 0;
201                     delete c->m_server;
202                 }
203                 (parent->m_seqno)++;
204                 yaz_log (LOG_LOG, "get_client 2 %p %p", this, c);
205                 return c;
206             }
207         }
208         else
209         {
210             yaz_log (LOG_LOG, "Yaz_Proxy::get_client making session %d",
211                   parent->m_seqno);
212             c = new Yaz_ProxyClient(m_PDU_Observable->clone());
213             c->m_next = parent->m_clientPool;
214             if (c->m_next)
215                 c->m_next->m_prev = &c->m_next;
216             parent->m_clientPool = c;
217             c->m_prev = &parent->m_clientPool;
218         }
219         if (cookie)
220             strcpy (c->m_cookie, cookie);
221         else
222             c->m_cookie[0] = '\0';
223         yaz_log (LOG_LOG, "Yaz_Proxy::get_client connect to %s",
224                  m_proxyTarget);
225         c->m_seqno = parent->m_seqno;
226         c->client(m_proxyTarget);
227         c->m_init_flag = 0;
228
229         delete c->m_last_query;
230         c->m_last_query = 0;
231         c->m_last_resultCount = 0;
232         c->m_sr_transform = 0;
233         c->m_waiting = 0;
234         c->timeout(10);
235
236         (parent->m_seqno)++;
237     }
238     yaz_log (LOG_LOG, "get_client 3 %p %p", this, c);
239     return c;
240 }
241
242 Z_APDU *Yaz_Proxy::result_set_optimize(Z_APDU *apdu)
243 {
244     if (apdu->which != Z_APDU_searchRequest)
245         return apdu;
246     Z_SearchRequest *sr = apdu->u.searchRequest;
247     Yaz_Z_Query *this_query = new Yaz_Z_Query;
248     
249     this_query->set_Z_Query(sr->query);
250     
251     if (m_client->m_last_query &&
252         m_client->m_last_query->match(this_query))
253     {
254         delete this_query;
255         if (m_client->m_last_resultCount > *sr->smallSetUpperBound &&
256             m_client->m_last_resultCount < *sr->largeSetLowerBound)
257         {
258             // medium Set
259             yaz_log (LOG_LOG, "Yaz_Proxy::result_set_optimize medium set");
260             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
261             Z_PresentRequest *pr = new_apdu->u.presentRequest;
262             pr->referenceId = sr->referenceId;
263             pr->resultSetId = sr->resultSetName;
264             pr->preferredRecordSyntax = sr->preferredRecordSyntax;
265             *pr->numberOfRecordsRequested = *sr->mediumSetPresentNumber;
266             if (sr->mediumSetElementSetNames)
267             {
268                 pr->recordComposition = (Z_RecordComposition *)
269                     odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
270                 pr->recordComposition->which = Z_RecordComp_simple;
271                 pr->recordComposition->u.simple = sr->mediumSetElementSetNames;
272             }
273             m_client->m_sr_transform = 1;
274             return new_apdu;
275         }
276         else if (m_client->m_last_resultCount >= *sr->largeSetLowerBound ||
277             m_client->m_last_resultCount == 0)
278         {
279             // large set
280             yaz_log (LOG_LOG, "Yaz_Proxy::result_set_optimize large set");
281             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
282             new_apdu->u.searchResponse->referenceId = sr->referenceId;
283             new_apdu->u.searchResponse->resultCount =
284                 &m_client->m_last_resultCount;
285             send_Z_PDU(new_apdu);
286             return 0;
287         }
288         else
289         {
290             // small set
291             yaz_log (LOG_LOG, "Yaz_Proxy::result_set_optimize small set");
292             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
293             Z_PresentRequest *pr = new_apdu->u.presentRequest;
294             pr->referenceId = sr->referenceId;
295             pr->resultSetId = sr->resultSetName;
296             pr->preferredRecordSyntax = sr->preferredRecordSyntax;
297             *pr->numberOfRecordsRequested = m_client->m_last_resultCount;
298             if (sr->smallSetElementSetNames)
299             {
300                 pr->recordComposition = (Z_RecordComposition *)
301                     odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
302                 pr->recordComposition->which = Z_RecordComp_simple;
303                 pr->recordComposition->u.simple = sr->smallSetElementSetNames;
304             }
305             m_client->m_sr_transform = 1;
306             return new_apdu;
307         }
308     }
309     else
310     {
311         yaz_log (LOG_LOG, "Yaz_Proxy::result_set_optimize new set");
312         delete m_client->m_last_query;
313         m_client->m_last_query = this_query;
314     }
315     return apdu;
316 }
317
318 void Yaz_Proxy::recv_Z_PDU(Z_APDU *apdu)
319 {
320     yaz_log (LOG_LOG, "Yaz_Proxy::recv_Z_PDU");
321     // Determine our client.
322     m_client = get_client(apdu);
323     if (!m_client)
324     {
325         delete this;
326         return;
327     }
328     m_client->m_server = this;
329
330     if (apdu->which == Z_APDU_initRequest)
331     {
332         if (m_client->m_init_flag)
333         {
334             Z_APDU *apdu = create_Z_PDU(Z_APDU_initResponse);
335             if (m_client->m_cookie)
336                 set_otherInformationString(apdu, VAL_COOKIE, 1,
337                                            m_client->m_cookie);
338             send_Z_PDU(apdu);
339             return;
340         }
341         m_client->m_init_flag = 1;
342     }
343     apdu = result_set_optimize(apdu);
344     if (!apdu)
345         return;
346
347     yaz_log (LOG_LOG, "Yaz_ProxyClient::send_Z_PDU %s",
348              m_client->get_hostname());
349
350     // delete other info part from PDU before sending to target
351     Z_OtherInformation **oi;
352     get_otherInfoAPDU(apdu, &oi);
353     if (oi)
354         *oi = 0;
355
356     if (m_client->send_Z_PDU(apdu) < 0)
357     {
358         delete m_client;
359         m_client = 0;
360         delete this;
361     }
362     else
363         m_client->m_waiting = 1;
364 }
365
366 void Yaz_Proxy::connectNotify()
367 {
368 }
369
370 void Yaz_Proxy::shutdown()
371 {
372     yaz_log (LOG_LOG, "shutdown (client to proxy)");
373     // only keep if keep_alive flag and cookie is set...
374     if (m_keepalive && m_client && m_client->m_cookie[0])
375     {
376         yaz_log (LOG_LOG, "shutdown - keepalive this=%p, m_server=%p",
377             this, m_client->m_server);
378         if (m_client->m_waiting == 2)
379             abort();
380         // Tell client (if any) that no server connection is there..
381         m_client->m_server = 0;
382     }
383     else if (m_client)
384     {
385         yaz_log (LOG_LOG, "deleting %p %p", this, m_client);
386         if (m_client->m_waiting == 2)
387             abort();
388         delete m_client;
389     }
390     else if (!m_parent)
391     {
392         yaz_log (LOG_LOG, "abort %p", this);
393         abort();
394     }
395     delete this;
396 }
397
398 void Yaz_ProxyClient::shutdown()
399 {
400     yaz_log (LOG_LOG, "shutdown (proxy to server) %s", get_hostname());
401     delete m_server;
402     delete this;
403 }
404
405 void Yaz_Proxy::failNotify()
406 {
407     yaz_log (LOG_LOG, "connection closed by client");
408     shutdown();
409 }
410
411 void Yaz_ProxyClient::failNotify()
412 {
413     yaz_log (LOG_LOG, "Yaz_ProxyClient connection closed by %s", get_hostname());
414     shutdown();
415 }
416
417 void Yaz_ProxyClient::connectNotify()
418 {
419     yaz_log (LOG_LOG, "Yaz_ProxyClient connection accepted by %s",
420           get_hostname());
421     timeout(600);
422 }
423
424 IYaz_PDU_Observer *Yaz_ProxyClient::sessionNotify(IYaz_PDU_Observable
425                                                   *the_PDU_Observable, int fd)
426 {
427     return new Yaz_ProxyClient(the_PDU_Observable);
428 }
429
430 Yaz_ProxyClient::~Yaz_ProxyClient()
431 {
432     if (m_prev)
433         *m_prev = m_next;
434     if (m_next)
435         m_next->m_prev = m_prev;
436     m_waiting = 2;     // for debugging purposes only.
437     delete m_last_query;
438 }
439
440 void Yaz_Proxy::timeoutNotify()
441 {
442     yaz_log (LOG_LOG, "timeout (client to proxy)");
443     shutdown();
444 }
445
446 void Yaz_ProxyClient::timeoutNotify()
447 {
448     yaz_log (LOG_LOG, "timeout (proxy to target) %s", get_hostname());
449     shutdown();
450 }
451
452 Yaz_ProxyClient::Yaz_ProxyClient(IYaz_PDU_Observable *the_PDU_Observable) :
453     Yaz_Z_Assoc (the_PDU_Observable)
454 {
455     m_cookie[0] = 0;
456     m_next = 0;
457     m_prev = 0;
458     m_init_flag = 0;
459     m_last_query = 0;
460     m_last_resultCount = 0;
461     m_sr_transform = 0;
462     m_waiting = 0;
463 }
464
465 void Yaz_ProxyClient::recv_Z_PDU(Z_APDU *apdu)
466 {
467     m_waiting = 0;
468     yaz_log (LOG_LOG, "Yaz_ProxyClient::recv_Z_PDU %s", get_hostname());
469     if (apdu->which == Z_APDU_searchResponse)
470     {
471         m_last_resultCount = *apdu->u.searchResponse->resultCount;
472         int status = *apdu->u.searchResponse->searchStatus;
473         if (! status || (
474                 apdu->u.searchResponse->records &&
475                 apdu->u.searchResponse->records->which != Z_Records_DBOSD))
476         {
477             delete m_last_query;
478             m_last_query = 0;
479         }
480     }
481     if (apdu->which == Z_APDU_presentResponse && m_sr_transform)
482     {
483         m_sr_transform = 0;
484         Z_PresentResponse *pr = apdu->u.presentResponse;
485         Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
486         Z_SearchResponse *sr = new_apdu->u.searchResponse;
487         sr->referenceId = pr->referenceId;
488         *sr->resultCount = m_last_resultCount;
489         sr->records = pr->records;
490         sr->nextResultSetPosition = pr->nextResultSetPosition;
491         sr->numberOfRecordsReturned = pr->numberOfRecordsReturned;
492         apdu = new_apdu;
493     }
494     if (m_cookie)
495         set_otherInformationString (apdu, VAL_COOKIE, 1, m_cookie);
496     if (m_server)
497     {
498         yaz_log (LOG_LOG, "Yaz_Proxy::send_Z_PDU");
499         m_server->send_Z_PDU(apdu);
500     }
501 }