libdap Updated for version 3.21.0
libdap4 is an implementation of OPeNDAP's DAP protocol.
HTTPCacheTable.cc
1
2// -*- mode: c++; c-basic-offset:4 -*-
3
4// This file is part of libdap, A C++ implementation of the OPeNDAP Data
5// Access Protocol.
6
7// Copyright (c) 2002,2003 OPeNDAP, Inc.
8// Author: James Gallagher <jgallagher@opendap.org>
9//
10// This library is free software; you can redistribute it and/or
11// modify it under the terms of the GNU Lesser General Public
12// License as published by the Free Software Foundation; either
13// version 2.1 of the License, or (at your option) any later version.
14//
15// This library is distributed in the hope that it will be useful,
16// but WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18// Lesser General Public License for more details.
19//
20// You should have received a copy of the GNU Lesser General Public
21// License along with this library; if not, write to the Free Software
22// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23//
24// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
25
26#include "config.h"
27
28#include <limits.h>
29#include <unistd.h> // for stat
30#include <sys/types.h> // for stat and mkdir
31#include <sys/stat.h>
32
33#include <cstring>
34#include <cerrno>
35
36#include <iostream>
37#include <sstream>
38#include <algorithm>
39#include <iterator>
40#include <set>
41
42#include "Error.h"
43#include "InternalErr.h"
44#include "ResponseTooBigErr.h"
45#ifndef WIN32
46#include "SignalHandler.h"
47#endif
48#include "HTTPCacheInterruptHandler.h"
49#include "HTTPCacheTable.h"
50#include "HTTPCacheMacros.h"
51
52#include "util_mit.h"
53#include "debug.h"
54
55#ifdef WIN32
56#include <direct.h>
57#include <time.h>
58#include <fcntl.h>
59#define MKDIR(a,b) _mkdir((a))
60#define REMOVE(a) do { \
61 int s = remove((a)); \
62 if (s != 0) \
63 throw InternalErr(__FILE__, __LINE__, "Cache error; could not remove file: " + long_to_string(s)); \
64 } while(0);
65#define MKSTEMP(a) _open(_mktemp((a)),_O_CREAT,_S_IREAD|_S_IWRITE)
66#define DIR_SEPARATOR_CHAR '\\'
67#define DIR_SEPARATOR_STR "\\"
68#else
69#define MKDIR(a,b) mkdir((a), (b))
70#define MKSTEMP(a) mkstemp((a))
71#define DIR_SEPARATOR_CHAR '/'
72#define DIR_SEPARATOR_STR "/"
73#endif
74
75#define CACHE_META ".meta"
76#define CACHE_INDEX ".index"
77#define CACHE_EMPTY_ETAG "@cache@"
78
79#define NO_LM_EXPIRATION 24*3600 // 24 hours
80#define MAX_LM_EXPIRATION 48*3600 // Max expiration from LM
81
82// If using LM to find the expiration then take 10% and no more than
83// MAX_LM_EXPIRATION.
84#ifndef LM_EXPIRATION
85#define LM_EXPIRATION(t) (min((MAX_LM_EXPIRATION), static_cast<int>((t) / 10)))
86#endif
87
88const int CACHE_TABLE_SIZE = 1499;
89
90using namespace std;
91
92namespace libdap {
93
97int
98get_hash(const string &url)
99{
100 int hash = 0;
101
102 for (const char *ptr = url.c_str(); *ptr; ptr++)
103 hash = (int)((hash * 3 + (*(unsigned char *)ptr)) % CACHE_TABLE_SIZE);
104
105 return hash;
106}
107
108HTTPCacheTable::HTTPCacheTable(const string &cache_root, int block_size) :
109 d_cache_root(cache_root), d_block_size(block_size), d_current_size(0), d_new_entries(0)
110{
111 d_cache_index = cache_root + CACHE_INDEX;
112
113 d_cache_table = new CacheEntries*[CACHE_TABLE_SIZE];
114
115 // Initialize the cache table.
116 for (int i = 0; i < CACHE_TABLE_SIZE; ++i)
117 d_cache_table[i] = 0;
118
119 cache_index_read();
120}
121
125static inline void
126delete_cache_entry(HTTPCacheTable::CacheEntry *e)
127{
128 DBG2(cerr << "Deleting CacheEntry: " << e << endl);
129 delete e;
130}
131
132HTTPCacheTable::~HTTPCacheTable()
133{
134 for (int i = 0; i < CACHE_TABLE_SIZE; ++i) {
135 HTTPCacheTable::CacheEntries *cp = get_cache_table()[i];
136 if (cp) {
137 // delete each entry
138 for_each(cp->begin(), cp->end(), delete_cache_entry);
139
140 // now delete the vector that held the entries
141 delete get_cache_table()[i];
142 get_cache_table()[i] = 0;
143 }
144 }
145
146 delete[] d_cache_table;
147}
148
156class DeleteExpired : public unary_function<HTTPCacheTable::CacheEntry *&, void> {
157 time_t d_time;
158 HTTPCacheTable &d_table;
159
160public:
161 DeleteExpired(HTTPCacheTable &table, time_t t) :
162 d_time(t), d_table(table) {
163 if (!t)
164 d_time = time(0); // 0 == now
165 }
166
167 void operator()(HTTPCacheTable::CacheEntry *&e) {
168 if (e && !e->readers && (e->freshness_lifetime
169 < (e->corrected_initial_age + (d_time - e->response_time)))) {
170 DBG(cerr << "Deleting expired cache entry: " << e->url << endl);
171 d_table.remove_cache_entry(e);
172 delete e; e = 0;
173 }
174 }
175};
176
177// @param time base deletes againt this time, defaults to 0 (now)
178void HTTPCacheTable::delete_expired_entries(time_t time) {
179 // Walk through and delete all the expired entries.
180 for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
181 HTTPCacheTable::CacheEntries *slot = get_cache_table()[cnt];
182 if (slot) {
183 for_each(slot->begin(), slot->end(), DeleteExpired(*this, time));
184 slot->erase(remove(slot->begin(), slot->end(),
185 static_cast<HTTPCacheTable::CacheEntry *>(0)), slot->end());
186 }
187 }
188}
189
196class DeleteByHits : public unary_function<HTTPCacheTable::CacheEntry *&, void> {
197 HTTPCacheTable &d_table;
198 int d_hits;
199
200public:
201 DeleteByHits(HTTPCacheTable &table, int hits) :
202 d_table(table), d_hits(hits) {
203 }
204
205 void operator()(HTTPCacheTable::CacheEntry *&e) {
206 if (e && !e->readers && e->hits <= d_hits) {
207 DBG(cerr << "Deleting cache entry: " << e->url << endl);
208 d_table.remove_cache_entry(e);
209 delete e; e = 0;
210 }
211 }
212};
213
214void
215HTTPCacheTable::delete_by_hits(int hits) {
216 for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
217 if (get_cache_table()[cnt]) {
218 HTTPCacheTable::CacheEntries *slot = get_cache_table()[cnt];
219 for_each(slot->begin(), slot->end(), DeleteByHits(*this, hits));
220 slot->erase(remove(slot->begin(), slot->end(),
221 static_cast<HTTPCacheTable::CacheEntry*>(0)),
222 slot->end());
223
224 }
225 }
226}
227
232class DeleteBySize : public unary_function<HTTPCacheTable::CacheEntry *&, void> {
233 HTTPCacheTable &d_table;
234 unsigned int d_size;
235
236public:
237 DeleteBySize(HTTPCacheTable &table, unsigned int size) :
238 d_table(table), d_size(size) {
239 }
240
241 void operator()(HTTPCacheTable::CacheEntry *&e) {
242 if (e && !e->readers && e->size > d_size) {
243 DBG(cerr << "Deleting cache entry: " << e->url << endl);
244 d_table.remove_cache_entry(e);
245 delete e; e = 0;
246 }
247 }
248};
249
250void HTTPCacheTable::delete_by_size(unsigned int size) {
251 for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
252 if (get_cache_table()[cnt]) {
253 HTTPCacheTable::CacheEntries *slot = get_cache_table()[cnt];
254 for_each(slot->begin(), slot->end(), DeleteBySize(*this, size));
255 slot->erase(remove(slot->begin(), slot->end(),
256 static_cast<HTTPCacheTable::CacheEntry*>(0)),
257 slot->end());
258
259 }
260 }
261}
262
269
276bool
277HTTPCacheTable::cache_index_delete()
278{
279 d_new_entries = 0;
280
281 return (REMOVE_BOOL(d_cache_index.c_str()) == 0);
282}
283
292bool
293HTTPCacheTable::cache_index_read()
294{
295 FILE *fp = fopen(d_cache_index.c_str(), "r");
296 // If the cache index can't be opened that's OK; start with an empty
297 // cache. 09/05/02 jhrg
298 if (!fp) {
299 return false;
300 }
301
302 char line[1024];
303 while (!feof(fp) && fgets(line, 1024, fp)) {
304 add_entry_to_cache_table(cache_index_parse_line(line));
305 DBG2(cerr << line << endl);
306 }
307
308 int res = fclose(fp) ;
309 if (res) {
310 DBG(cerr << "HTTPCache::cache_index_read - Failed to close " << (void *)fp << endl);
311 }
312
313 d_new_entries = 0;
314
315 return true;
316}
317
326HTTPCacheTable::cache_index_parse_line(const char *line)
327{
328 // Read the line and create the cache object
330 istringstream iss(line);
331 iss >> entry->url;
332 iss >> entry->cachename;
333
334 iss >> entry->etag;
335 if (entry->etag == CACHE_EMPTY_ETAG)
336 entry->etag = "";
337
338 iss >> entry->lm;
339 iss >> entry->expires;
340 iss >> entry->size;
341 iss >> entry->range; // range is not used. 10/02/02 jhrg
342
343 iss >> entry->hash;
344 iss >> entry->hits;
345 iss >> entry->freshness_lifetime;
346 iss >> entry->response_time;
347 iss >> entry->corrected_initial_age;
348
349 iss >> entry->must_revalidate;
350
351 return entry;
352}
353
356class WriteOneCacheEntry :
357 public unary_function<HTTPCacheTable::CacheEntry *, void>
358{
359
360 FILE *d_fp;
361
362public:
363 WriteOneCacheEntry(FILE *fp) : d_fp(fp)
364 {}
365
366 void operator()(HTTPCacheTable::CacheEntry *e)
367 {
368 if (e && fprintf(d_fp,
369 "%s %s %s %ld %ld %ld %c %d %d %ld %ld %ld %c\r\n",
370 e->url.c_str(),
371 e->cachename.c_str(),
372 e->etag == "" ? CACHE_EMPTY_ETAG : e->etag.c_str(),
373 (long)(e->lm),
374 (long)(e->expires),
375 e->size,
376 e->range ? '1' : '0', // not used. 10/02/02 jhrg
377 e->hash,
378 e->hits,
379 (long)(e->freshness_lifetime),
380 (long)(e->response_time),
381 (long)(e->corrected_initial_age),
382 e->must_revalidate ? '1' : '0') < 0)
383 throw Error(internal_error, "Cache Index. Error writing cache index\n");
384 }
385};
386
396void
397HTTPCacheTable::cache_index_write()
398{
399 DBG(cerr << "Cache Index. Writing index " << d_cache_index << endl);
400
401 // Open the file for writing.
402 FILE * fp = NULL;
403 if ((fp = fopen(d_cache_index.c_str(), "wb")) == NULL) {
404 throw Error(string("Cache Index. Can't open `") + d_cache_index
405 + string("' for writing"));
406 }
407
408 // Walk through the list and write it out. The format is really
409 // simple as we keep it all in ASCII.
410
411 for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
412 HTTPCacheTable::CacheEntries *cp = get_cache_table()[cnt];
413 if (cp)
414 for_each(cp->begin(), cp->end(), WriteOneCacheEntry(fp));
415 }
416
417 /* Done writing */
418 int res = fclose(fp);
419 if (res) {
420 DBG(cerr << "HTTPCache::cache_index_write - Failed to close "
421 << (void *)fp << endl);
422 }
423
424 d_new_entries = 0;
425}
426
428
441string
442HTTPCacheTable::create_hash_directory(int hash)
443{
444 ostringstream path;
445 path << d_cache_root << hash;
446
447 // Save the mask
448 mode_t mask = umask(0);
449
450 // Ignore the error if the directory exists
451 errno = 0;
452 if (mkdir(path.str().c_str(), 0777) < 0 && errno != EEXIST) {
453 umask(mask);
454 throw Error(internal_error, "Could not create the directory for the cache at '" + path.str() + "' (" + strerror(errno) + ").");
455 }
456
457 // Restore themask
458 umask(mask);
459
460 return path.str();
461}
462
477void
478HTTPCacheTable::create_location(HTTPCacheTable::CacheEntry *entry)
479{
480 string hash_dir = create_hash_directory(entry->hash);
481#ifdef WIN32
482 hash_dir += "\\dodsXXXXXX";
483#else
484 hash_dir += "/dodsXXXXXX"; // mkstemp uses six characters.
485#endif
486
487 // mkstemp uses the storage passed to it; must be writable and local.
488 // char *templat = new char[hash_dir.size() + 1];
489 vector<char> templat(hash_dir.size() + 1);
490 strncpy(templat.data(), hash_dir.c_str(), hash_dir.size() + 1);
491
492 // Open truncated for update. NB: mkstemp() returns a file descriptor.
493 // man mkstemp says "... The file is opened with the O_EXCL flag,
494 // guaranteeing that when mkstemp returns successfully we are the only
495 // user." 09/19/02 jhrg
496#ifndef WIN32
497 // Make sure that temp files are accessible only by the owner.
498 umask(077);
499#endif
500 int fd = MKSTEMP(templat.data()); // fd mode is 666 or 600 (Unix)
501 if (fd < 0) {
502 // delete[] templat; templat = 0;
503 // close(fd); Calling close() when fd is < 0 is a bad idea! jhrg 7/2/15
504 throw Error(internal_error, "The HTTP Cache could not create a file to hold the response; it will not be cached.");
505 }
506
507 entry->cachename = templat.data();
508 // delete[] templat; templat = 0;
509 close(fd);
510}
511
512
514static inline int
515entry_disk_space(int size, unsigned int block_size)
516{
517 unsigned int num_of_blocks = (size + block_size) / block_size;
518
519 DBG(cerr << "size: " << size << ", block_size: " << block_size
520 << ", num_of_blocks: " << num_of_blocks << endl);
521
522 return num_of_blocks * block_size;
523}
524
528
534void
535HTTPCacheTable::add_entry_to_cache_table(CacheEntry *entry)
536{
537 int hash = entry->hash;
538 if (hash > CACHE_TABLE_SIZE-1 || hash < 0)
539 throw InternalErr(__FILE__, __LINE__, "Hash value too large!");
540
541 if (!d_cache_table[hash])
542 d_cache_table[hash] = new CacheEntries;
543
544 d_cache_table[hash]->push_back(entry);
545
546 DBG(cerr << "add_entry_to_cache_table, current_size: " << d_current_size
547 << ", entry->size: " << entry->size << ", block size: " << d_block_size
548 << endl);
549
550 d_current_size += entry_disk_space(entry->size, d_block_size);
551
552 DBG(cerr << "add_entry_to_cache_table, current_size: " << d_current_size << endl);
553
554 increment_new_entries();
555}
556
561HTTPCacheTable::get_locked_entry_from_cache_table(const string &url) /*const*/
562{
563 return get_locked_entry_from_cache_table(get_hash(url), url);
564}
565
574HTTPCacheTable::get_locked_entry_from_cache_table(int hash, const string &url) /*const*/
575{
576 DBG(cerr << "url: " << url << "; hash: " << hash << endl);
577 DBG(cerr << "d_cache_table: " << hex << d_cache_table << dec << endl);
578 if (d_cache_table[hash]) {
579 CacheEntries *cp = d_cache_table[hash];
580 for (CacheEntriesIter i = cp->begin(); i != cp->end(); ++i) {
581 // Must test *i because perform_garbage_collection may have
582 // removed this entry; the CacheEntry will then be null.
583 if ((*i) && (*i)->url == url) {
584 (*i)->lock_read_response(); // Lock the response
585 return *i;
586 }
587 }
588 }
589
590 return 0;
591}
592
599HTTPCacheTable::CacheEntry *
600HTTPCacheTable::get_write_locked_entry_from_cache_table(const string &url)
601{
602 int hash = get_hash(url);
603 if (d_cache_table[hash]) {
604 CacheEntries *cp = d_cache_table[hash];
605 for (CacheEntriesIter i = cp->begin(); i != cp->end(); ++i) {
606 // Must test *i because perform_garbage_collection may have
607 // removed this entry; the CacheEntry will then be null.
608 if ((*i) && (*i)->url == url) {
609 (*i)->lock_write_response(); // Lock the response
610 return *i;
611 }
612 }
613 }
614
615 return 0;
616}
617
625void
626HTTPCacheTable::remove_cache_entry(HTTPCacheTable::CacheEntry *entry)
627{
628 // This should never happen; all calls to this method are protected by
629 // the caller, hence the InternalErr.
630 if (entry->readers)
631 throw InternalErr(__FILE__, __LINE__, "Tried to delete a cache entry that is in use.");
632
633 REMOVE(entry->cachename.c_str());
634 REMOVE(string(entry->cachename + CACHE_META).c_str());
635
636 DBG(cerr << "remove_cache_entry, current_size: " << get_current_size() << endl);
637
638 unsigned int eds = entry_disk_space(entry->size, get_block_size());
639 set_current_size((eds > get_current_size()) ? 0 : get_current_size() - eds);
640
641 DBG(cerr << "remove_cache_entry, current_size: " << get_current_size() << endl);
642}
643
646class DeleteCacheEntry: public unary_function<HTTPCacheTable::CacheEntry *&, void>
647{
648 string d_url;
649 HTTPCacheTable *d_cache_table;
650
651public:
652 DeleteCacheEntry(HTTPCacheTable *c, const string &url)
653 : d_url(url), d_cache_table(c)
654 {}
655
656 void operator()(HTTPCacheTable::CacheEntry *&e)
657 {
658 if (e && e->url == d_url) {
659 e->lock_write_response();
660 d_cache_table->remove_cache_entry(e);
661 e->unlock_write_response();
662 delete e; e = 0;
663 }
664 }
665};
666
673void
674HTTPCacheTable::remove_entry_from_cache_table(const string &url)
675{
676 int hash = get_hash(url);
677 if (d_cache_table[hash]) {
678 CacheEntries *cp = d_cache_table[hash];
679 for_each(cp->begin(), cp->end(), DeleteCacheEntry(this, url));
680 cp->erase(remove(cp->begin(), cp->end(), static_cast<HTTPCacheTable::CacheEntry*>(0)),
681 cp->end());
682 }
683}
684
687class DeleteUnlockedCacheEntry: public unary_function<HTTPCacheTable::CacheEntry *&, void> {
688 HTTPCacheTable &d_table;
689
690public:
691 DeleteUnlockedCacheEntry(HTTPCacheTable &t) :
692 d_table(t)
693 {
694 }
695 void operator()(HTTPCacheTable::CacheEntry *&e)
696 {
697 if (e) {
698 d_table.remove_cache_entry(e);
699 delete e;
700 e = 0;
701 }
702 }
703};
704
705void HTTPCacheTable::delete_all_entries()
706{
707 // Walk through the cache table and, for every entry in the cache, delete
708 // it on disk and in the cache table.
709 for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
710 HTTPCacheTable::CacheEntries *slot = get_cache_table()[cnt];
711 if (slot) {
712 for_each(slot->begin(), slot->end(), DeleteUnlockedCacheEntry(*this));
713 slot->erase(remove(slot->begin(), slot->end(), static_cast<HTTPCacheTable::CacheEntry *> (0)), slot->end());
714 }
715 }
716
717 cache_index_delete();
718}
719
733void
734HTTPCacheTable::calculate_time(HTTPCacheTable::CacheEntry *entry, int default_expiration, time_t request_time)
735{
736 entry->response_time = time(NULL);
737 time_t apparent_age = max(0, static_cast<int>(entry->response_time - entry->date));
738 time_t corrected_received_age = max(apparent_age, entry->age);
739 time_t response_delay = entry->response_time - request_time;
740 entry->corrected_initial_age = corrected_received_age + response_delay;
741
742 // Estimate an expires time using the max-age and expires time. If we
743 // don't have an explicit expires time then set it to 10% of the LM date
744 // (although max 24 h). If no LM date is available then use 24 hours.
745 time_t freshness_lifetime = entry->max_age;
746 if (freshness_lifetime < 0) {
747 if (entry->expires < 0) {
748 if (entry->lm < 0) {
749 freshness_lifetime = default_expiration;
750 }
751 else {
752 freshness_lifetime = LM_EXPIRATION(entry->date - entry->lm);
753 }
754 }
755 else
756 freshness_lifetime = entry->expires - entry->date;
757 }
758
759 entry->freshness_lifetime = max(0, static_cast<int>(freshness_lifetime));
760
761 DBG2(cerr << "Cache....... Received Age " << entry->age
762 << ", corrected " << entry->corrected_initial_age
763 << ", freshness lifetime " << entry->freshness_lifetime << endl);
764}
765
777void HTTPCacheTable::parse_headers(HTTPCacheTable::CacheEntry *entry, unsigned long max_entry_size,
778 const vector<string> &headers)
779{
780 vector<string>::const_iterator i;
781 for (i = headers.begin(); i != headers.end(); ++i) {
782 // skip a blank header.
783 if ((*i).empty())
784 continue;
785
786 string::size_type colon = (*i).find(':');
787
788 // skip a header with no colon in it.
789 if (colon == string::npos)
790 continue;
791
792 string header = (*i).substr(0, (*i).find(':'));
793 string value = (*i).substr((*i).find(": ") + 2);
794 DBG2(cerr << "Header: " << header << endl);DBG2(cerr << "Value: " << value << endl);
795
796 if (header == "ETag") {
797 entry->etag = value;
798 }
799 else if (header == "Last-Modified") {
800 entry->lm = parse_time(value.c_str());
801 }
802 else if (header == "Expires") {
803 entry->expires = parse_time(value.c_str());
804 }
805 else if (header == "Date") {
806 entry->date = parse_time(value.c_str());
807 }
808 else if (header == "Age") {
809 entry->age = parse_time(value.c_str());
810 }
811 else if (header == "Content-Length") {
812 unsigned long clength = strtoul(value.c_str(), 0, 0);
813 if (clength > max_entry_size)
814 entry->set_no_cache(true);
815 }
816 else if (header == "Cache-Control") {
817 // Ignored Cache-Control values: public, private, no-transform,
818 // proxy-revalidate, s-max-age. These are used by shared caches.
819 // See section 14.9 of RFC 2612. 10/02/02 jhrg
820 if (value == "no-cache" || value == "no-store")
821 // Note that we *can* store a 'no-store' response in volatile
822 // memory according to RFC 2616 (section 14.9.2) but those
823 // will be rare coming from DAP servers. 10/02/02 jhrg
824 entry->set_no_cache(true);
825 else if (value == "must-revalidate")
826 entry->must_revalidate = true;
827 else if (value.find("max-age") != string::npos) {
828 string max_age = value.substr(value.find("=") + 1);
829 entry->max_age = parse_time(max_age.c_str());
830 }
831 }
832 }
833}
834
836
837// @TODO Change name to record locked response
838void HTTPCacheTable::bind_entry_to_data(HTTPCacheTable::CacheEntry *entry, FILE *body) {
839 entry->hits++; // Mark hit
840 d_locked_entries[body] = entry; // record lock, see release_cached_r...
841}
842
843void HTTPCacheTable::uncouple_entry_from_data(FILE *body) {
844
845 HTTPCacheTable::CacheEntry *entry = d_locked_entries[body];
846 if (!entry)
847 throw InternalErr("There is no cache entry for the response given.");
848
849 d_locked_entries.erase(body);
850 entry->unlock_read_response();
851
852 if (entry->readers < 0)
853 throw InternalErr("An unlocked entry was released");
854}
855
856bool HTTPCacheTable::is_locked_read_responses() {
857 return !d_locked_entries.empty();
858}
859
860} // namespace libdap
A class for error processing.
Definition Error.h:94
void remove_cache_entry(HTTPCacheTable::CacheEntry *entry)
A class for software fault reporting.
Definition InternalErr.h:65
top level DAP object to house generic methods
Definition AISConnect.cc:30
int get_hash(const string &url)
time_t parse_time(const char *str, bool expand)
Definition util_mit.cc:153