Implemented upper-limit on proxy-to-target sessions.
[yazpp-moved-to-github.git] / src / yaz-proxy.cpp
1 /*
2  * Copyright (c) 1998-1999, Index Data.
3  * See the file LICENSE for details.
4  * Sebastian Hammer, Adam Dickmeiss
5  * 
6  * $Log: yaz-proxy.cpp,v $
7  * Revision 1.12  2000-07-04 13:48:49  adam
8  * Implemented upper-limit on proxy-to-target sessions.
9  *
10  * Revision 1.11  1999/12/06 13:52:45  adam
11  * Modified for new location of YAZ header files. Experimental threaded
12  * operation.
13  *
14  * Revision 1.10  1999/11/10 10:02:34  adam
15  * Work on proxy.
16  *
17  * Revision 1.9  1999/09/13 12:53:44  adam
18  * Proxy removes OtherInfo Proxy Address and Session ID. Other
19  * Otherinfo remains untouched.
20  *
21  * Revision 1.8  1999/05/04 10:53:00  adam
22  * Changed the way the PROXY behaves when lost cookie is received.
23  *
24  * Revision 1.7  1999/04/28 13:31:17  adam
25  * Better result set optimisation for proxy.
26  *
27  * Revision 1.6  1999/04/27 07:52:13  adam
28  * Improved proxy; added query match for result set re-use.
29  *
30  * Revision 1.5  1999/04/21 12:09:01  adam
31  * Many improvements. Modified to proxy server to work with "sessions"
32  * based on cookies.
33  *
34  * Revision 1.4  1999/04/20 10:30:05  adam
35  * Implemented various stuff for client and proxy. Updated calls
36  * to ODR to reflect new name parameter.
37  *
38  * Revision 1.3  1999/04/09 11:46:57  adam
39  * Added object Yaz_Z_Assoc. Much more functional client.
40  *
41  * Revision 1.2  1999/01/28 13:08:46  adam
42  * Yaz_PDU_Assoc better encapsulated. Memory leak fix in
43  * yaz-socket-manager.cc.
44  *
45  * Revision 1.1.1.1  1999/01/28 09:41:07  adam
46  * First implementation of YAZ++.
47  *
48  */
49
50 #include <assert.h>
51
52 #include <yaz/log.h>
53 #include <yaz-proxy.h>
54
55 Yaz_Proxy::Yaz_Proxy(IYaz_PDU_Observable *the_PDU_Observable) :
56     Yaz_Z_Assoc(the_PDU_Observable)
57 {
58     m_PDU_Observable = the_PDU_Observable;
59     m_client = 0;
60     m_parent = 0;
61     m_clientPool = 0;
62     m_seqno = 1;
63     m_keepalive = 1;
64     m_proxyTarget = 0;
65     m_max_clients = 20;
66 }
67
68 Yaz_Proxy::~Yaz_Proxy()
69 {
70     xfree (m_proxyTarget);
71 }
72
73
74 void Yaz_Proxy::set_proxyTarget(const char *target)
75 {
76     xfree (m_proxyTarget);
77     m_proxyTarget = 0;
78     if (target)
79         m_proxyTarget = (char *) xstrdup (target);
80 }
81
82 IYaz_PDU_Observer *Yaz_Proxy::clone(IYaz_PDU_Observable
83                                     *the_PDU_Observable)
84 {
85     Yaz_Proxy *new_proxy = new Yaz_Proxy(the_PDU_Observable);
86     new_proxy->m_parent = this;
87     new_proxy->timeout(120);
88     new_proxy->set_proxyTarget(m_proxyTarget);
89     return new_proxy;
90 }
91
92 char *Yaz_Proxy::get_cookie(Z_OtherInformation **otherInfo)
93 {
94     int oid[OID_SIZE];
95     Z_OtherInformationUnit *oi;
96     struct oident ent;
97     ent.proto = PROTO_Z3950;
98     ent.oclass = CLASS_USERINFO;
99     ent.value = (oid_value) VAL_COOKIE;
100     assert (oid_ent_to_oid (&ent, oid));
101
102     if (oid_ent_to_oid (&ent, oid) && 
103         (oi = update_otherInformation(otherInfo, 0, oid, 1, 1)) &&
104         oi->which == Z_OtherInfo_characterInfo)
105         return oi->information.characterInfo;
106     return 0;
107 }
108
109 char *Yaz_Proxy::get_proxy(Z_OtherInformation **otherInfo)
110 {
111     int oid[OID_SIZE];
112     Z_OtherInformationUnit *oi;
113     struct oident ent;
114     ent.proto = PROTO_Z3950;
115     ent.oclass = CLASS_USERINFO;
116     ent.value = (oid_value) VAL_PROXY;
117     if (oid_ent_to_oid (&ent, oid) &&
118         (oi = update_otherInformation(otherInfo, 0, oid, 1, 1)) &&
119         oi->which == Z_OtherInfo_characterInfo)
120         return oi->information.characterInfo;
121     return 0;
122 }
123
124 Yaz_ProxyClient *Yaz_Proxy::get_client(Z_APDU *apdu)
125 {
126     assert (m_parent);
127     Yaz_Proxy *parent = m_parent;
128     Z_OtherInformation **oi;
129     Yaz_ProxyClient *c = m_client;
130     
131     get_otherInfoAPDU(apdu, &oi);
132     char *cookie = get_cookie(oi);
133     logf (LOG_LOG, "Yaz_Proxy::get_client cookie=%s", cookie ? cookie :
134           "null");
135     if (cookie)
136     {
137         for (c = parent->m_clientPool; c; c = c->m_next)
138         {
139             assert (c->m_prev);
140             assert (*c->m_prev == c);
141             if (!strcmp(cookie,c->m_cookie))
142             {
143                 logf (LOG_LOG, "Yaz_Proxy::get_client found cached target");
144                 c->m_seqno = parent->m_seqno;
145                 return c;
146             }
147         }       
148     }
149     if (!m_client)
150     {
151         if (apdu->which == Z_APDU_initRequest)
152         {
153             logf (LOG_LOG, "got InitRequest");
154             
155             const char *proxy_host =
156                 get_proxy(&apdu->u.initRequest->otherInfo);
157             if (proxy_host)
158                 set_proxyTarget(proxy_host);
159             logf (LOG_LOG, "proxy_host = %s", m_proxyTarget ?
160                   m_proxyTarget:"none");
161         }
162         else
163         {
164             logf (LOG_LOG, "no first INIT!");
165             return 0;
166         }
167         if (!m_proxyTarget)
168             return 0;
169
170         Yaz_ProxyClient *c_min = 0;
171         int min_seq = -1;
172         int no_of_clients = 0;
173         for (c = parent->m_clientPool; c; c = c->m_next)
174         {
175             no_of_clients++;
176             logf (LOG_LOG, "found seqno = %d", c->m_seqno);
177             if (min_seq < 0 || c->m_seqno < min_seq)
178             {
179                 min_seq = c->m_seqno;
180                 c_min = c;
181             }
182         }
183         if (no_of_clients >= parent->m_max_clients)
184         {
185             c = c_min;
186             logf (LOG_LOG, "Yaz_Proxy::get_client re-using session %d",
187                   c->m_seqno);
188             if (c->m_server)
189                 delete c->m_server;
190             c->m_server = 0;
191         }
192         else
193         {
194             logf (LOG_LOG, "Yaz_Proxy::get_client making new session");
195             c = new Yaz_ProxyClient(m_PDU_Observable->clone());
196             c->m_next = parent->m_clientPool;
197             if (c->m_next)
198                 c->m_next->m_prev = &c->m_next;
199             parent->m_clientPool = c;
200             c->m_prev = &parent->m_clientPool;
201         }
202         sprintf (c->m_cookie, "%d", parent->m_seqno);
203         logf (LOG_LOG, "Yaz_Proxy::get_client new session %s", c->m_cookie);
204         c->m_seqno = parent->m_seqno;
205         c->client(m_proxyTarget);
206         c->m_init_flag = 0;
207
208         delete c->m_last_query;
209         c->m_last_query = 0;
210         c->m_last_resultCount = 0;
211         c->m_sr_transform = 0;
212
213         c->timeout(600);
214
215         (parent->m_seqno)++;
216     }
217     return c;
218 }
219
220 Z_APDU *Yaz_Proxy::result_set_optimize(Z_APDU *apdu)
221 {
222     if (apdu->which != Z_APDU_searchRequest)
223         return apdu;
224     Z_SearchRequest *sr = apdu->u.searchRequest;
225     Yaz_Z_Query *this_query = new Yaz_Z_Query;
226     
227     this_query->set_Z_Query(sr->query);
228     
229     if (m_client->m_last_query &&
230         m_client->m_last_query->match(this_query))
231     {
232         delete this_query;
233         if (m_client->m_last_resultCount > *sr->smallSetUpperBound &&
234             m_client->m_last_resultCount < *sr->largeSetLowerBound)
235         {
236             // medium Set
237             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
238             Z_PresentRequest *pr = new_apdu->u.presentRequest;
239             pr->referenceId = sr->referenceId;
240             pr->resultSetId = sr->resultSetName;
241             pr->preferredRecordSyntax = sr->preferredRecordSyntax;
242             *pr->numberOfRecordsRequested = *sr->mediumSetPresentNumber;
243             if (sr->mediumSetElementSetNames)
244             {
245                 pr->recordComposition = (Z_RecordComposition *)
246                     odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
247                 pr->recordComposition->which = Z_RecordComp_simple;
248                 pr->recordComposition->u.simple = sr->mediumSetElementSetNames;
249             }
250             m_client->m_sr_transform = 1;
251             return new_apdu;
252         }
253         else if (m_client->m_last_resultCount >= *sr->largeSetLowerBound ||
254             m_client->m_last_resultCount == 0)
255         {
256             // large set
257             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
258             new_apdu->u.searchResponse->referenceId = sr->referenceId;
259             new_apdu->u.searchResponse->resultCount =
260                 &m_client->m_last_resultCount;
261             send_Z_PDU(new_apdu);
262             return 0;
263         }
264         else
265         {
266             // small set
267             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
268             Z_PresentRequest *pr = new_apdu->u.presentRequest;
269             pr->referenceId = sr->referenceId;
270             pr->resultSetId = sr->resultSetName;
271             pr->preferredRecordSyntax = sr->preferredRecordSyntax;
272             *pr->numberOfRecordsRequested = m_client->m_last_resultCount;
273             if (sr->smallSetElementSetNames)
274             {
275                 pr->recordComposition = (Z_RecordComposition *)
276                     odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
277                 pr->recordComposition->which = Z_RecordComp_simple;
278                 pr->recordComposition->u.simple = sr->smallSetElementSetNames;
279             }
280             m_client->m_sr_transform = 1;
281             return new_apdu;
282         }
283     }
284     else
285     {
286         logf (LOG_LOG, "query doesn't match");
287         delete m_client->m_last_query;
288         m_client->m_last_query = this_query;
289     }
290     return apdu;
291 }
292
293 void Yaz_Proxy::recv_Z_PDU(Z_APDU *apdu)
294 {
295     logf (LOG_LOG, "Yaz_Proxy::recv_Z_PDU");
296     // Determine our client.
297     m_client = get_client(apdu);
298     if (!m_client)
299     {
300         delete this;
301         return;
302     }
303     m_client->m_server = this;
304
305 #if 0
306     Z_OtherInformation **oi;
307     get_otherInfoAPDU(apdu, &oi);
308     *oi = 0;
309 #endif
310
311     if (apdu->which == Z_APDU_initRequest)
312     {
313         if (m_client->m_init_flag)
314         {
315             Z_APDU *apdu = create_Z_PDU(Z_APDU_initResponse);
316             if (m_client->m_cookie)
317                 set_otherInformationString(apdu, VAL_COOKIE, 1,
318                                            m_client->m_cookie);
319             send_Z_PDU(apdu);
320             return;
321         }
322         m_client->m_init_flag = 1;
323     }
324     apdu = result_set_optimize(apdu);
325     if (!apdu)
326         return;
327
328     logf (LOG_LOG, "Yaz_ProxyClient::send_Z_PDU");
329     if (m_client->send_Z_PDU(apdu) < 0)
330     {
331         delete m_client;
332         delete this;
333     }
334 }
335
336 void Yaz_Proxy::connectNotify()
337 {
338 }
339
340 void Yaz_Proxy::shutdown()
341 {
342     logf (LOG_LOG, "shutdown (client to proxy)");
343     if (m_keepalive)
344     {
345         // Tell client (if any) that no server connection is there..
346         if (m_client)
347             m_client->m_server = 0;
348     }
349     else
350     {
351         delete m_client;
352     }
353     delete this;
354 }
355
356 void Yaz_ProxyClient::shutdown()
357 {
358     logf (LOG_LOG, "shutdown (proxy to server)");
359     delete m_server;
360     delete this;
361 }
362
363 void Yaz_Proxy::failNotify()
364 {
365     logf (LOG_LOG, "connection closed by client");
366     shutdown();
367 }
368
369 void Yaz_ProxyClient::failNotify()
370 {
371     logf (LOG_LOG, "connection closed by target");
372     shutdown();
373 }
374
375 void Yaz_ProxyClient::connectNotify()
376 {
377     logf (LOG_LOG, "connection accepted by target");
378 }
379
380 IYaz_PDU_Observer *Yaz_ProxyClient::clone(IYaz_PDU_Observable
381                                           *the_PDU_Observable)
382 {
383     return new Yaz_ProxyClient(the_PDU_Observable);
384 }
385
386 Yaz_ProxyClient::~Yaz_ProxyClient()
387 {
388     if (m_prev)
389     {
390         *m_prev = m_next;
391         if (m_next)
392             m_next->m_prev = m_prev;
393     }
394     delete m_last_query;
395 }
396
397 void Yaz_Proxy::timeoutNotify()
398 {
399     logf (LOG_LOG, "timeout (client to proxy)");
400     shutdown();
401 }
402
403 void Yaz_ProxyClient::timeoutNotify()
404 {
405     logf (LOG_LOG, "timeout (proxy to target)");
406     shutdown();
407 }
408
409 Yaz_ProxyClient::Yaz_ProxyClient(IYaz_PDU_Observable *the_PDU_Observable) :
410     Yaz_Z_Assoc (the_PDU_Observable)
411 {
412     m_cookie[0] = 0;
413     m_next = 0;
414     m_prev = 0;
415     m_init_flag = 0;
416     m_last_query = 0;
417     m_last_resultCount = 0;
418     m_sr_transform = 0;
419 }
420
421 void Yaz_ProxyClient::recv_Z_PDU(Z_APDU *apdu)
422 {
423     logf (LOG_LOG, "Yaz_ProxyClient::recv_Z_PDU");
424     if (apdu->which == Z_APDU_searchResponse)
425         m_last_resultCount = *apdu->u.searchResponse->resultCount;
426     if (apdu->which == Z_APDU_presentResponse && m_sr_transform)
427     {
428         m_sr_transform = 0;
429         Z_PresentResponse *pr = apdu->u.presentResponse;
430         Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
431         Z_SearchResponse *sr = new_apdu->u.searchResponse;
432         sr->referenceId = pr->referenceId;
433         *sr->resultCount = m_last_resultCount;
434         sr->records = pr->records;
435         sr->nextResultSetPosition = pr->nextResultSetPosition;
436         sr->numberOfRecordsReturned = pr->numberOfRecordsReturned;
437         apdu = new_apdu;
438     }
439     if (m_cookie)
440         set_otherInformationString (apdu, VAL_COOKIE, 1, m_cookie);
441     if (m_server)
442     {
443         logf (LOG_LOG, "Yaz_Proxy::send_Z_PDU");
444         m_server->send_Z_PDU(apdu);
445     }
446 }