2 * Copyright (c) 1998-2003, Index Data.
3 * See the file LICENSE for details.
5 * $Id: yaz-proxy.cpp,v 1.45 2003-07-25 08:57:01 adam Exp $
12 #include <yaz++/proxy.h>
14 Yaz_Proxy::Yaz_Proxy(IYaz_PDU_Observable *the_PDU_Observable) :
15 Yaz_Z_Assoc(the_PDU_Observable)
17 m_PDU_Observable = the_PDU_Observable;
24 m_proxy_authentication = 0;
28 m_optimize = xstrdup ("1");
31 Yaz_Proxy::~Yaz_Proxy()
33 xfree (m_proxyTarget);
34 xfree (m_proxy_authentication);
38 void Yaz_Proxy::set_proxy_target(const char *target)
40 xfree (m_proxyTarget);
43 m_proxyTarget = (char *) xstrdup (target);
46 void Yaz_Proxy::set_proxy_authentication (const char *auth)
48 xfree (m_proxy_authentication);
49 m_proxy_authentication = 0;
51 m_proxy_authentication = (char *) xstrdup (auth);
54 IYaz_PDU_Observer *Yaz_Proxy::sessionNotify(IYaz_PDU_Observable
55 *the_PDU_Observable, int fd)
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);
67 char *Yaz_Proxy::get_cookie(Z_OtherInformation **otherInfo)
70 Z_OtherInformationUnit *oi;
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));
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;
84 char *Yaz_Proxy::get_proxy(Z_OtherInformation **otherInfo)
87 Z_OtherInformationUnit *oi;
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;
99 Yaz_ProxyClient *Yaz_Proxy::get_client(Z_APDU *apdu)
102 Yaz_Proxy *parent = m_parent;
103 Z_OtherInformation **oi;
104 Yaz_ProxyClient *c = m_client;
106 get_otherInfoAPDU(apdu, &oi);
107 char *cookie = get_cookie(oi);
109 const char *proxy_host = get_proxy(oi);
111 set_proxy_target(proxy_host);
113 // no target specified at all?
117 if (!strcmp(m_proxyTarget, "stop"))
119 if (cookie && *cookie)
121 Yaz_ProxyClient *cc = 0;
123 for (c = parent->m_clientPool; c; c = c->m_next)
126 assert (*c->m_prev == c);
127 if (!strcmp(cookie,c->m_cookie) &&
128 !strcmp(m_proxyTarget, c->get_hostname()))
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)
142 yaz_log (LOG_LOG, "reopen target=%s", c->get_hostname());
144 c->client(m_proxyTarget);
149 c->m_last_resultCount = 0;
150 c->m_sr_transform = 0;
152 c->m_resultSetStartPoint = 0;
153 c->timeout(m_idletime);
155 c->m_seqno = parent->m_seqno;
156 if (c->m_server && c->m_server != this)
157 c->m_server->m_client = 0;
160 yaz_log (LOG_DEBUG, "get_client 1 %p %p", this, c);
166 Yaz_ProxyClient *cc = 0;
168 for (c = parent->m_clientPool; c; c = c->m_next)
171 assert (*c->m_prev == c);
172 if (c->m_server == 0 && c->m_cookie[0] == 0 &&
173 !strcmp(m_proxyTarget, c->get_hostname()))
183 yaz_log (LOG_LOG, "Reuse session %d to %d %s",
184 c->m_seqno, parent->m_seqno, c->get_hostname());
186 c->m_seqno = parent->m_seqno;
187 assert(c->m_server == 0);
196 if (apdu->which != Z_APDU_initRequest)
198 yaz_log (LOG_LOG, "no first INIT!");
201 Z_InitRequest *initRequest = apdu->u.initRequest;
203 if (!initRequest->idAuthentication)
205 if (m_proxy_authentication)
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);
218 // go through list of clients - and find the lowest/oldest one.
219 Yaz_ProxyClient *c_min = 0;
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)
226 yaz_log (LOG_LOG, " Session %-3d wait=%d %s", c->m_seqno,
227 c->m_waiting, c->get_hostname());
229 if (min_seq < 0 || c->m_seqno < min_seq)
231 min_seq = c->m_seqno;
235 if (no_of_clients >= parent->m_max_clients)
238 if (c->m_waiting || strcmp(m_proxyTarget, c->get_hostname()))
240 yaz_log (LOG_LOG, "Replace session %d",
242 if (c->m_server && c->m_server != this)
248 yaz_log (LOG_LOG, "Move session %d to %d %s",
249 c->m_seqno, parent->m_seqno, c->get_hostname());
251 strcpy (c->m_cookie, cookie);
253 c->m_cookie[0] = '\0';
254 c->m_seqno = parent->m_seqno;
255 if (c->m_server && c->m_server != this)
257 c->m_server->m_client = 0;
261 yaz_log (LOG_DEBUG, "get_client 2 %p %p", this, c);
267 yaz_log (LOG_LOG, "Making session %d %s", parent->m_seqno,
269 c = new Yaz_ProxyClient(m_PDU_Observable->clone());
270 c->m_next = parent->m_clientPool;
272 c->m_next->m_prev = &c->m_next;
273 parent->m_clientPool = c;
274 c->m_prev = &parent->m_clientPool;
277 strcpy (c->m_cookie, cookie);
279 c->m_cookie[0] = '\0';
280 yaz_log (LOG_LOG, "Connecting to %s", m_proxyTarget);
281 c->m_seqno = parent->m_seqno;
282 c->client(m_proxyTarget);
284 c->m_last_resultCount = 0;
287 c->m_sr_transform = 0;
289 c->m_resultSetStartPoint = 0;
294 yaz_log (LOG_DEBUG, "get_client 3 %p %p", this, c);
298 Z_APDU *Yaz_Proxy::result_set_optimize(Z_APDU *apdu)
300 if (*m_parent->m_optimize == '0')
301 return apdu; // don't optimize result sets..
302 if (apdu->which == Z_APDU_presentRequest)
304 Z_PresentRequest *pr = apdu->u.presentRequest;
305 Z_NamePlusRecordList *npr;
306 int toget = *pr->numberOfRecordsRequested;
307 int start = *pr->resultSetStartPoint;
309 if (m_client->m_last_resultSetId &&
310 !strcmp(m_client->m_last_resultSetId, pr->resultSetId))
312 if (m_client->m_cache.lookup (odr_encode(), &npr, start, toget,
313 pr->preferredRecordSyntax,
314 pr->recordComposition))
316 yaz_log (LOG_LOG, "Returned cache records for present request");
317 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
318 new_apdu->u.presentResponse->referenceId = pr->referenceId;
320 new_apdu->u.presentResponse->numberOfRecordsReturned
321 = odr_intdup(odr_encode(), toget);
323 new_apdu->u.presentResponse->records = (Z_Records*)
324 odr_malloc(odr_encode(), sizeof(Z_Records));
325 new_apdu->u.presentResponse->records->which = Z_Records_DBOSD;
326 new_apdu->u.presentResponse->records->u.databaseOrSurDiagnostics = npr;
327 new_apdu->u.presentResponse->nextResultSetPosition =
328 odr_intdup(odr_encode(), start+toget);
329 send_Z_PDU(new_apdu);
335 if (apdu->which != Z_APDU_searchRequest)
337 Z_SearchRequest *sr = apdu->u.searchRequest;
338 Yaz_Z_Query *this_query = new Yaz_Z_Query;
339 Yaz_Z_Databases this_databases;
341 this_databases.set(sr->num_databaseNames, (const char **)
344 this_query->set_Z_Query(sr->query);
346 if (m_client->m_last_ok && m_client->m_last_query &&
347 m_client->m_last_query->match(this_query) &&
348 !strcmp(m_client->m_last_resultSetId, sr->resultSetName) &&
349 m_client->m_last_databases.match(this_databases))
352 if (m_client->m_last_resultCount > *sr->smallSetUpperBound &&
353 m_client->m_last_resultCount < *sr->largeSetLowerBound)
355 Z_NamePlusRecordList *npr;
356 int toget = *sr->mediumSetPresentNumber;
357 Z_RecordComposition *comp = 0;
359 if (toget > m_client->m_last_resultCount)
360 toget = m_client->m_last_resultCount;
362 if (sr->mediumSetElementSetNames)
364 comp = (Z_RecordComposition *)
365 odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
366 comp->which = Z_RecordComp_simple;
367 comp->u.simple = sr->mediumSetElementSetNames;
370 if (m_client->m_cache.lookup (odr_encode(), &npr, 1, toget,
371 sr->preferredRecordSyntax, comp))
373 yaz_log (LOG_LOG, "Returned cache records for medium set");
374 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
375 new_apdu->u.searchResponse->referenceId = sr->referenceId;
376 new_apdu->u.searchResponse->resultCount =
377 &m_client->m_last_resultCount;
379 new_apdu->u.searchResponse->numberOfRecordsReturned
380 = odr_intdup(odr_encode(), toget);
382 new_apdu->u.searchResponse->presentStatus =
383 odr_intdup(odr_encode(), Z_PresentStatus_success);
384 new_apdu->u.searchResponse->records = (Z_Records*)
385 odr_malloc(odr_encode(), sizeof(Z_Records));
386 new_apdu->u.searchResponse->records->which = Z_Records_DBOSD;
387 new_apdu->u.searchResponse->records->u.databaseOrSurDiagnostics = npr;
388 new_apdu->u.searchResponse->nextResultSetPosition =
389 odr_intdup(odr_encode(), toget+1);
390 send_Z_PDU(new_apdu);
396 // send present request (medium size)
397 yaz_log (LOG_LOG, "Optimizing search for medium set");
399 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
400 Z_PresentRequest *pr = new_apdu->u.presentRequest;
401 pr->referenceId = sr->referenceId;
402 pr->resultSetId = sr->resultSetName;
403 pr->preferredRecordSyntax = sr->preferredRecordSyntax;
404 *pr->numberOfRecordsRequested = toget;
405 pr->recordComposition = comp;
406 m_client->m_sr_transform = 1;
410 else if (m_client->m_last_resultCount >= *sr->largeSetLowerBound ||
411 m_client->m_last_resultCount <= 0)
413 // large set. Return pseudo-search response immediately
414 yaz_log (LOG_LOG, "Optimizing search for large set");
415 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
416 new_apdu->u.searchResponse->referenceId = sr->referenceId;
417 new_apdu->u.searchResponse->resultCount =
418 &m_client->m_last_resultCount;
419 send_Z_PDU(new_apdu);
424 Z_NamePlusRecordList *npr;
425 int toget = m_client->m_last_resultCount;
426 Z_RecordComposition *comp = 0;
428 // send a present request (small set)
430 if (sr->smallSetElementSetNames)
432 comp = (Z_RecordComposition *)
433 odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
434 comp->which = Z_RecordComp_simple;
435 comp->u.simple = sr->smallSetElementSetNames;
438 if (m_client->m_cache.lookup (odr_encode(), &npr, 1, toget,
439 sr->preferredRecordSyntax, comp))
441 yaz_log (LOG_LOG, "Returned cache records for small set");
442 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
443 new_apdu->u.searchResponse->referenceId = sr->referenceId;
444 new_apdu->u.searchResponse->resultCount =
445 &m_client->m_last_resultCount;
447 new_apdu->u.searchResponse->numberOfRecordsReturned
448 = odr_intdup(odr_encode(), toget);
450 new_apdu->u.searchResponse->presentStatus =
451 odr_intdup(odr_encode(), Z_PresentStatus_success);
452 new_apdu->u.searchResponse->records = (Z_Records*)
453 odr_malloc(odr_encode(), sizeof(Z_Records));
454 new_apdu->u.searchResponse->records->which = Z_Records_DBOSD;
455 new_apdu->u.searchResponse->records->u.databaseOrSurDiagnostics = npr;
456 new_apdu->u.searchResponse->nextResultSetPosition =
457 odr_intdup(odr_encode(), toget+1);
458 send_Z_PDU(new_apdu);
463 yaz_log (LOG_LOG, "Optimizing search for small set");
464 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
465 Z_PresentRequest *pr = new_apdu->u.presentRequest;
466 pr->referenceId = sr->referenceId;
467 pr->resultSetId = sr->resultSetName;
468 pr->preferredRecordSyntax = sr->preferredRecordSyntax;
469 *pr->numberOfRecordsRequested = toget;
470 pr->recordComposition = comp;
471 m_client->m_sr_transform = 1;
478 delete m_client->m_last_query;
479 m_client->m_last_query = this_query;
480 m_client->m_last_ok = 0;
481 m_client->m_cache.clear();
482 m_client->m_resultSetStartPoint = 0;
484 xfree (m_client->m_last_resultSetId);
485 m_client->m_last_resultSetId = xstrdup (sr->resultSetName);
487 m_client->m_last_databases.set(sr->num_databaseNames,
488 (const char **) sr->databaseNames);
493 static const char *apdu_name(Z_APDU *apdu)
497 case Z_APDU_initRequest:
498 return "initRequest";
499 case Z_APDU_initResponse:
500 return "initResponse";
501 case Z_APDU_searchRequest:
502 return "searchRequest";
503 case Z_APDU_searchResponse:
504 return "searchResponse";
505 case Z_APDU_presentRequest:
506 return "presentRequest";
507 case Z_APDU_presentResponse:
508 return "presentResponse";
509 case Z_APDU_deleteResultSetRequest:
510 return "deleteResultSetRequest";
511 case Z_APDU_deleteResultSetResponse:
512 return "deleteResultSetResponse";
513 case Z_APDU_scanRequest:
514 return "scanRequest";
515 case Z_APDU_scanResponse:
516 return "scanResponse";
517 case Z_APDU_sortRequest:
518 return "sortRequest";
519 case Z_APDU_sortResponse:
520 return "sortResponse";
521 case Z_APDU_extendedServicesRequest:
522 return "extendedServicesRequest";
523 case Z_APDU_extendedServicesResponse:
524 return "extendedServicesResponse";
531 void Yaz_Proxy::recv_Z_PDU(Z_APDU *apdu)
533 yaz_log (LOG_LOG, "Receiving %s from client", apdu_name(apdu));
534 // Determine our client.
535 m_client = get_client(apdu);
541 m_client->m_server = this;
543 if (apdu->which == Z_APDU_initRequest)
545 if (m_client->m_init_flag)
547 Z_APDU *apdu = m_client->m_initResponse;
548 apdu->u.initResponse->otherInfo = 0;
549 if (m_client->m_cookie && *m_client->m_cookie)
550 set_otherInformationString(apdu, VAL_COOKIE, 1,
555 m_client->m_init_flag = 1;
557 apdu = result_set_optimize(apdu);
561 yaz_log (LOG_LOG, "Sending %s to %s",
562 apdu_name(apdu), m_client->get_hostname());
564 // delete other info part from PDU before sending to target
565 Z_OtherInformation **oi;
566 get_otherInfoAPDU(apdu, &oi);
570 if (apdu->which == Z_APDU_presentRequest &&
571 m_client->m_resultSetStartPoint == 0)
573 Z_PresentRequest *pr = apdu->u.presentRequest;
574 m_client->m_resultSetStartPoint = *pr->resultSetStartPoint;
575 m_client->m_cache.copy_presentRequest(apdu->u.presentRequest);
577 m_client->m_resultSetStartPoint = 0;
579 if (m_client->send_Z_PDU(apdu) < 0)
586 m_client->m_waiting = 1;
589 void Yaz_Proxy::connectNotify()
593 void Yaz_Proxy::shutdown()
595 // only keep if keep_alive flag is set...
596 if (m_keepalive && m_client && m_client->m_waiting == 0)
598 yaz_log (LOG_LOG, "shutdown (client to proxy) keepalive %s",
599 m_client->get_hostname());
600 assert (m_client->m_waiting != 2);
601 // Tell client (if any) that no server connection is there..
602 m_client->m_server = 0;
606 yaz_log (LOG_LOG, "shutdown (client to proxy) close %s",
607 m_client->get_hostname());
608 assert (m_client->m_waiting != 2);
613 yaz_log (LOG_LOG, "shutdown (client to proxy) bad state");
618 yaz_log (LOG_LOG, "shutdown (client to proxy)");
623 void Yaz_ProxyClient::shutdown()
625 yaz_log (LOG_LOG, "shutdown (proxy to server) %s", get_hostname());
630 void Yaz_Proxy::failNotify()
632 yaz_log (LOG_LOG, "Yaz_Proxy connection closed by client");
636 void Yaz_ProxyClient::failNotify()
638 yaz_log (LOG_LOG, "Yaz_ProxyClient connection closed by %s", get_hostname());
642 void Yaz_ProxyClient::connectNotify()
644 yaz_log (LOG_LOG, "Connection accepted by %s", get_hostname());
648 IYaz_PDU_Observer *Yaz_ProxyClient::sessionNotify(IYaz_PDU_Observable
649 *the_PDU_Observable, int fd)
651 return new Yaz_ProxyClient(the_PDU_Observable);
654 Yaz_ProxyClient::~Yaz_ProxyClient()
659 m_next->m_prev = m_prev;
660 m_waiting = 2; // for debugging purposes only.
661 odr_destroy(m_init_odr);
663 xfree (m_last_resultSetId);
666 void Yaz_Proxy::timeoutNotify()
668 yaz_log (LOG_LOG, "timeout (client to proxy)");
672 void Yaz_ProxyClient::timeoutNotify()
674 yaz_log (LOG_LOG, "timeout (proxy to target) %s", get_hostname());
678 Yaz_ProxyClient::Yaz_ProxyClient(IYaz_PDU_Observable *the_PDU_Observable) :
679 Yaz_Z_Assoc (the_PDU_Observable)
686 m_last_resultSetId = 0;
687 m_last_resultCount = 0;
691 m_init_odr = odr_createmem (ODR_DECODE);
693 m_resultSetStartPoint = 0;
696 const char *Yaz_Proxy::option(const char *name, const char *value)
698 if (!strcmp (name, "optimize")) {
701 m_optimize = xstrdup (value);
708 void Yaz_ProxyClient::recv_Z_PDU(Z_APDU *apdu)
711 yaz_log (LOG_LOG, "Receiving %s from %s", apdu_name(apdu),
713 if (apdu->which == Z_APDU_initResponse)
715 NMEM nmem = odr_extract_mem (odr_decode());
716 odr_reset (m_init_odr);
717 nmem_transfer (m_init_odr->mem, nmem);
718 m_initResponse = apdu;
721 if (apdu->which == Z_APDU_searchResponse)
723 Z_SearchResponse *sr = apdu->u.searchResponse;
724 m_last_resultCount = *sr->resultCount;
725 int status = *sr->searchStatus;
726 if (status && (!sr->records || sr->records->which == Z_Records_DBOSD))
730 if (sr->records && sr->records->which == Z_Records_DBOSD)
732 m_cache.add(odr_decode(),
733 sr->records->u.databaseOrSurDiagnostics, 1,
738 if (apdu->which == Z_APDU_presentResponse)
740 Z_PresentResponse *pr = apdu->u.presentResponse;
744 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
745 Z_SearchResponse *sr = new_apdu->u.searchResponse;
746 sr->referenceId = pr->referenceId;
747 *sr->resultCount = m_last_resultCount;
748 sr->records = pr->records;
749 sr->nextResultSetPosition = pr->nextResultSetPosition;
750 sr->numberOfRecordsReturned = pr->numberOfRecordsReturned;
753 if (pr->records->which == Z_Records_DBOSD && m_resultSetStartPoint)
755 m_cache.add(odr_decode(),
756 pr->records->u.databaseOrSurDiagnostics,
757 m_resultSetStartPoint, -1);
758 m_resultSetStartPoint = 0;
761 if (m_cookie && *m_cookie)
762 set_otherInformationString (apdu, VAL_COOKIE, 1, m_cookie);
765 yaz_log (LOG_LOG, "Sending %s to client", apdu_name(apdu));
766 m_server->send_Z_PDU(apdu);
768 if (apdu->which == Z_APDU_close)