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