libdap  Updated for version 3.17.0
HTTPCacheTable.cc
00001 
00002 // -*- mode: c++; c-basic-offset:4 -*-
00003 
00004 // This file is part of libdap, A C++ implementation of the OPeNDAP Data
00005 // Access Protocol.
00006 
00007 // Copyright (c) 2002,2003 OPeNDAP, Inc.
00008 // Author: James Gallagher <jgallagher@opendap.org>
00009 //
00010 // This library is free software; you can redistribute it and/or
00011 // modify it under the terms of the GNU Lesser General Public
00012 // License as published by the Free Software Foundation; either
00013 // version 2.1 of the License, or (at your option) any later version.
00014 //
00015 // This library is distributed in the hope that it will be useful,
00016 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00017 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00018 // Lesser General Public License for more details.
00019 //
00020 // You should have received a copy of the GNU Lesser General Public
00021 // License along with this library; if not, write to the Free Software
00022 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00023 //
00024 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
00025 
00026 #include "config.h"
00027 
00028 // #define DODS_DEBUG
00029 
00030 // TODO: Remove unneeded includes.
00031 
00032 #include <pthread.h>
00033 #include <limits.h>
00034 #include <unistd.h>   // for stat
00035 #include <sys/types.h>  // for stat and mkdir
00036 #include <sys/stat.h>
00037 
00038 #include <cstring>
00039 #include <cerrno>
00040 
00041 #include <iostream>
00042 #include <sstream>
00043 #include <algorithm>
00044 #include <iterator>
00045 #include <set>
00046 
00047 #include "Error.h"
00048 #include "InternalErr.h"
00049 #include "ResponseTooBigErr.h"
00050 #ifndef WIN32
00051 #include "SignalHandler.h"
00052 #endif
00053 #include "HTTPCacheInterruptHandler.h"
00054 #include "HTTPCacheTable.h"
00055 #include "HTTPCacheMacros.h"
00056 
00057 #include "util_mit.h"
00058 #include "debug.h"
00059 
00060 #ifdef WIN32
00061 #include <direct.h>
00062 #include <time.h>
00063 #include <fcntl.h>
00064 #define MKDIR(a,b) _mkdir((a))
00065 #define REMOVE(a) do { \
00066                 int s = remove((a)); \
00067                 if (s != 0) \
00068                         throw InternalErr(__FILE__, __LINE__, "Cache error; could not remove file: " + long_to_string(s)); \
00069         } while(0);
00070 #define MKSTEMP(a) _open(_mktemp((a)),_O_CREAT,_S_IREAD|_S_IWRITE)
00071 #define DIR_SEPARATOR_CHAR '\\'
00072 #define DIR_SEPARATOR_STR "\\"
00073 #else
00074 #define MKDIR(a,b) mkdir((a), (b))
00075 #define MKSTEMP(a) mkstemp((a))
00076 #define DIR_SEPARATOR_CHAR '/'
00077 #define DIR_SEPARATOR_STR "/"
00078 #endif
00079 
00080 #define CACHE_META ".meta"
00081 #define CACHE_INDEX ".index"
00082 #define CACHE_EMPTY_ETAG "@cache@"
00083 
00084 #define NO_LM_EXPIRATION 24*3600 // 24 hours
00085 #define MAX_LM_EXPIRATION 48*3600 // Max expiration from LM
00086 
00087 // If using LM to find the expiration then take 10% and no more than
00088 // MAX_LM_EXPIRATION.
00089 #ifndef LM_EXPIRATION
00090 #define LM_EXPIRATION(t) (min((MAX_LM_EXPIRATION), static_cast<int>((t) / 10)))
00091 #endif
00092 
00093 const int CACHE_TABLE_SIZE = 1499;
00094 
00095 using namespace std;
00096 
00097 namespace libdap {
00098 
00102 int
00103 get_hash(const string &url)
00104 {
00105     int hash = 0;
00106 
00107     for (const char *ptr = url.c_str(); *ptr; ptr++)
00108         hash = (int)((hash * 3 + (*(unsigned char *)ptr)) % CACHE_TABLE_SIZE);
00109 
00110     return hash;
00111 }
00112 
00113 HTTPCacheTable::HTTPCacheTable(const string &cache_root, int block_size) :
00114     d_cache_root(cache_root), d_block_size(block_size), d_current_size(0), d_new_entries(0)
00115 {
00116     d_cache_index = cache_root + CACHE_INDEX;
00117 
00118     d_cache_table = new CacheEntries*[CACHE_TABLE_SIZE];
00119 
00120     // Initialize the cache table.
00121     for (int i = 0; i < CACHE_TABLE_SIZE; ++i)
00122         d_cache_table[i] = 0;
00123 
00124     cache_index_read();
00125 }
00126 
00130 static inline void
00131 delete_cache_entry(HTTPCacheTable::CacheEntry *e)
00132 {
00133     DBG2(cerr << "Deleting CacheEntry: " << e << endl);
00134     delete e;
00135 }
00136 
00137 HTTPCacheTable::~HTTPCacheTable()
00138 {
00139     for (int i = 0; i < CACHE_TABLE_SIZE; ++i) {
00140         HTTPCacheTable::CacheEntries *cp = get_cache_table()[i];
00141         if (cp) {
00142             // delete each entry
00143             for_each(cp->begin(), cp->end(), delete_cache_entry);
00144 
00145             // now delete the vector that held the entries
00146             delete get_cache_table()[i];
00147             get_cache_table()[i] = 0;
00148         }
00149     }
00150 
00151     delete[] d_cache_table;
00152 }
00153 
00161 class DeleteExpired : public unary_function<HTTPCacheTable::CacheEntry *&, void> {
00162         time_t d_time;
00163         HTTPCacheTable &d_table;
00164 
00165 public:
00166         DeleteExpired(HTTPCacheTable &table, time_t t) :
00167                 d_time(t), d_table(table) {
00168                 if (!t)
00169                         d_time = time(0); // 0 == now
00170         } 
00171 
00172         void operator()(HTTPCacheTable::CacheEntry *&e) {
00173                 if (e && !e->readers && (e->freshness_lifetime
00174                                 < (e->corrected_initial_age + (d_time - e->response_time)))) {
00175                         DBG(cerr << "Deleting expired cache entry: " << e->url << endl);
00176                         d_table.remove_cache_entry(e);
00177                         delete e; e = 0;
00178                 }
00179         }
00180 };
00181 
00182 // @param time base deletes againt this time, defaults to 0 (now)
00183 void HTTPCacheTable::delete_expired_entries(time_t time) {
00184         // Walk through and delete all the expired entries.
00185         for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
00186                 HTTPCacheTable::CacheEntries *slot = get_cache_table()[cnt];
00187                 if (slot) {
00188                         for_each(slot->begin(), slot->end(), DeleteExpired(*this, time));
00189                         slot->erase(remove(slot->begin(), slot->end(),
00190                                         static_cast<HTTPCacheTable::CacheEntry *>(0)), slot->end());
00191                 }
00192         }
00193 }
00194 
00201 class DeleteByHits : public unary_function<HTTPCacheTable::CacheEntry *&, void> {
00202         HTTPCacheTable &d_table;
00203         int d_hits;
00204 
00205 public:
00206         DeleteByHits(HTTPCacheTable &table, int hits) :
00207                 d_table(table), d_hits(hits) {
00208         }
00209 
00210         void operator()(HTTPCacheTable::CacheEntry *&e) {
00211                 if (e && !e->readers && e->hits <= d_hits) {
00212                         DBG(cerr << "Deleting cache entry: " << e->url << endl);
00213                         d_table.remove_cache_entry(e);
00214                         delete e; e = 0;
00215                 }
00216         }
00217 };
00218 
00219 void 
00220 HTTPCacheTable::delete_by_hits(int hits) {
00221     for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
00222         if (get_cache_table()[cnt]) {
00223             HTTPCacheTable::CacheEntries *slot = get_cache_table()[cnt];
00224             for_each(slot->begin(), slot->end(), DeleteByHits(*this, hits));
00225             slot->erase(remove(slot->begin(), slot->end(),
00226                                static_cast<HTTPCacheTable::CacheEntry*>(0)),
00227                         slot->end());
00228 
00229         }
00230     }
00231 }
00232 
00237 class DeleteBySize : public unary_function<HTTPCacheTable::CacheEntry *&, void> {
00238         HTTPCacheTable &d_table;
00239         unsigned int d_size;
00240 
00241 public:
00242         DeleteBySize(HTTPCacheTable &table, unsigned int size) :
00243                 d_table(table), d_size(size) {
00244         }
00245 
00246         void operator()(HTTPCacheTable::CacheEntry *&e) {
00247                 if (e && !e->readers && e->size > d_size) {
00248                         DBG(cerr << "Deleting cache entry: " << e->url << endl);
00249                         d_table.remove_cache_entry(e);
00250                         delete e; e = 0;
00251                 }
00252         }
00253 };
00254 
00255 void HTTPCacheTable::delete_by_size(unsigned int size) {
00256     for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
00257         if (get_cache_table()[cnt]) {
00258             HTTPCacheTable::CacheEntries *slot = get_cache_table()[cnt];
00259             for_each(slot->begin(), slot->end(), DeleteBySize(*this, size));
00260             slot->erase(remove(slot->begin(), slot->end(),
00261                                static_cast<HTTPCacheTable::CacheEntry*>(0)),
00262                         slot->end());
00263 
00264         }
00265     }
00266 }
00267 
00274 
00281 bool
00282 HTTPCacheTable::cache_index_delete()
00283 {
00284         d_new_entries = 0;
00285         
00286     return (REMOVE_BOOL(d_cache_index.c_str()) == 0);
00287 }
00288 
00297 bool
00298 HTTPCacheTable::cache_index_read()
00299 {
00300     FILE *fp = fopen(d_cache_index.c_str(), "r");
00301     // If the cache index can't be opened that's OK; start with an empty
00302     // cache. 09/05/02 jhrg
00303     if (!fp) {
00304         return false;
00305     }
00306 
00307     char line[1024];
00308     while (!feof(fp) && fgets(line, 1024, fp)) {
00309         add_entry_to_cache_table(cache_index_parse_line(line));
00310         DBG2(cerr << line << endl);
00311     }
00312 
00313     int res = fclose(fp) ;
00314     if (res) {
00315         DBG(cerr << "HTTPCache::cache_index_read - Failed to close " << (void *)fp << endl);
00316     }
00317 
00318     d_new_entries = 0;
00319     
00320     return true;
00321 }
00322 
00330 HTTPCacheTable::CacheEntry *
00331 HTTPCacheTable::cache_index_parse_line(const char *line)
00332 {
00333     // Read the line and create the cache object
00334         HTTPCacheTable::CacheEntry *entry = new HTTPCacheTable::CacheEntry;
00335     istringstream iss(line);
00336     iss >> entry->url;
00337     iss >> entry->cachename;
00338 
00339     iss >> entry->etag;
00340     if (entry->etag == CACHE_EMPTY_ETAG)
00341         entry->etag = "";
00342 
00343     iss >> entry->lm;
00344     iss >> entry->expires;
00345     iss >> entry->size;
00346     iss >> entry->range; // range is not used. 10/02/02 jhrg
00347 
00348     iss >> entry->hash;
00349     iss >> entry->hits;
00350     iss >> entry->freshness_lifetime;
00351     iss >> entry->response_time;
00352     iss >> entry->corrected_initial_age;
00353 
00354     iss >> entry->must_revalidate;
00355 
00356     return entry;
00357 }
00358 
00361 class WriteOneCacheEntry :
00362         public unary_function<HTTPCacheTable::CacheEntry *, void>
00363 {
00364 
00365     FILE *d_fp;
00366 
00367 public:
00368     WriteOneCacheEntry(FILE *fp) : d_fp(fp)
00369     {}
00370 
00371     void operator()(HTTPCacheTable::CacheEntry *e)
00372     {
00373         if (e && fprintf(d_fp,
00374                          "%s %s %s %ld %ld %ld %c %d %d %ld %ld %ld %c\r\n",
00375                          e->url.c_str(),
00376                          e->cachename.c_str(),
00377                          e->etag == "" ? CACHE_EMPTY_ETAG : e->etag.c_str(),
00378                          (long)(e->lm),
00379                          (long)(e->expires),
00380                          e->size,
00381                          e->range ? '1' : '0', // not used. 10/02/02 jhrg
00382                          e->hash,
00383                          e->hits,
00384                          (long)(e->freshness_lifetime),
00385                          (long)(e->response_time),
00386                          (long)(e->corrected_initial_age),
00387                          e->must_revalidate ? '1' : '0') < 0)
00388             throw Error(internal_error, "Cache Index. Error writing cache index\n");
00389     }
00390 };
00391 
00401 void
00402 HTTPCacheTable::cache_index_write()
00403 {
00404     DBG(cerr << "Cache Index. Writing index " << d_cache_index << endl);
00405 
00406     // Open the file for writing.
00407     FILE * fp = NULL;
00408     if ((fp = fopen(d_cache_index.c_str(), "wb")) == NULL) {
00409         throw Error(string("Cache Index. Can't open `") + d_cache_index
00410                     + string("' for writing"));
00411     }
00412 
00413     // Walk through the list and write it out. The format is really
00414     // simple as we keep it all in ASCII.
00415 
00416     for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
00417         HTTPCacheTable::CacheEntries *cp = get_cache_table()[cnt];
00418         if (cp)
00419             for_each(cp->begin(), cp->end(), WriteOneCacheEntry(fp));
00420     }
00421 
00422     /* Done writing */
00423     int res = fclose(fp);
00424     if (res) {
00425         DBG(cerr << "HTTPCache::cache_index_write - Failed to close "
00426             << (void *)fp << endl);
00427     }
00428 
00429     d_new_entries = 0;
00430 }
00431 
00433 
00446 string
00447 HTTPCacheTable::create_hash_directory(int hash)
00448 {
00449 #if 0
00450     struct stat stat_info;
00451     ostringstream path;
00452 
00453     path << d_cache_root << hash;
00454     string p = path.str();
00455 
00456     if (stat(p.c_str(), &stat_info) == -1) {
00457         DBG2(cerr << "Cache....... Create dir " << p << endl);
00458         if (MKDIR(p.c_str(), 0777) < 0) {
00459             DBG2(cerr << "Cache....... Can't create..." << endl);
00460             throw Error("Could not create cache slot to hold response! Check the write permissions on your disk cache directory. Cache root: " + d_cache_root + ".");
00461         }
00462     }
00463     else {
00464         DBG2(cerr << "Cache....... Directory " << p << " already exists"
00465              << endl);
00466     }
00467 
00468     return p;
00469 #endif
00470 
00471     ostringstream path;
00472     path << d_cache_root << hash;
00473 
00474     // Save the mask
00475     mode_t mask = umask(0);
00476 
00477     // Ignore the error if the directory exists
00478     errno = 0;
00479     if (mkdir(path.str().c_str(), 0777) < 0 && errno != EEXIST) {
00480         umask(mask);
00481         throw Error(internal_error, "Could not create the directory for the cache at '" + path.str() + "' (" + strerror(errno) + ").");
00482     }
00483 
00484     // Restore themask
00485     umask(mask);
00486 
00487     return path.str();
00488 }
00489 
00504 void
00505 HTTPCacheTable::create_location(HTTPCacheTable::CacheEntry *entry)
00506 {
00507     string hash_dir = create_hash_directory(entry->hash);
00508 #ifdef WIN32
00509     hash_dir += "\\dodsXXXXXX";
00510 #else
00511     hash_dir += "/dodsXXXXXX"; // mkstemp uses six characters.
00512 #endif
00513 
00514     // mkstemp uses the storage passed to it; must be writable and local.
00515     // char *templat = new char[hash_dir.size() + 1];
00516     vector<char> templat(hash_dir.size() + 1);
00517     strncpy(&templat[0], hash_dir.c_str(), hash_dir.size() + 1);
00518 
00519     // Open truncated for update. NB: mkstemp() returns a file descriptor.
00520     // man mkstemp says "... The file is opened with the O_EXCL flag,
00521     // guaranteeing that when mkstemp returns successfully we are the only
00522     // user." 09/19/02 jhrg
00523 #ifndef WIN32
00524     // Make sure that temp files are accessible only by the owner.
00525     umask(077);
00526 #endif
00527     int fd = MKSTEMP(&templat[0]); // fd mode is 666 or 600 (Unix)
00528     if (fd < 0) {
00529         // delete[] templat; templat = 0;
00530         // close(fd); Calling close() when fd is < 0 is a bad idea! jhrg 7/2/15
00531         throw Error(internal_error, "The HTTP Cache could not create a file to hold the response; it will not be cached.");
00532     }
00533 
00534     entry->cachename = &templat[0];
00535     // delete[] templat; templat = 0;
00536     close(fd);
00537 }
00538 
00539 
00541 static inline int
00542 entry_disk_space(int size, unsigned int block_size)
00543 {
00544     unsigned int num_of_blocks = (size + block_size) / block_size;
00545     
00546     DBG(cerr << "size: " << size << ", block_size: " << block_size
00547         << ", num_of_blocks: " << num_of_blocks << endl);
00548 
00549     return num_of_blocks * block_size;
00550 }
00551 
00555 
00561 void
00562 HTTPCacheTable::add_entry_to_cache_table(CacheEntry *entry)
00563 {
00564     int hash = entry->hash;
00565     if (hash > CACHE_TABLE_SIZE-1 || hash < 0)
00566         throw InternalErr(__FILE__, __LINE__, "Hash value too large!");
00567 
00568     if (!d_cache_table[hash])
00569         d_cache_table[hash] = new CacheEntries;
00570 
00571     d_cache_table[hash]->push_back(entry);
00572     
00573     DBG(cerr << "add_entry_to_cache_table, current_size: " << d_current_size
00574         << ", entry->size: " << entry->size << ", block size: " << d_block_size 
00575         << endl);
00576     
00577     d_current_size += entry_disk_space(entry->size, d_block_size);
00578 
00579     DBG(cerr << "add_entry_to_cache_table, current_size: " << d_current_size << endl);
00580     
00581     increment_new_entries();
00582 }
00583 
00587 HTTPCacheTable::CacheEntry *
00588 HTTPCacheTable::get_locked_entry_from_cache_table(const string &url) /*const*/
00589 {
00590     return get_locked_entry_from_cache_table(get_hash(url), url);
00591 }
00592 
00600 HTTPCacheTable::CacheEntry *
00601 HTTPCacheTable::get_locked_entry_from_cache_table(int hash, const string &url) /*const*/
00602 {
00603     DBG(cerr << "url: " << url << "; hash: " << hash << endl);
00604     DBG(cerr << "d_cache_table: " << hex << d_cache_table << dec << endl);
00605     if (d_cache_table[hash]) {
00606         CacheEntries *cp = d_cache_table[hash];
00607         for (CacheEntriesIter i = cp->begin(); i != cp->end(); ++i) {
00608             // Must test *i because perform_garbage_collection may have
00609             // removed this entry; the CacheEntry will then be null.
00610             if ((*i) && (*i)->url == url) {
00611                 (*i)->lock_read_response(); // Lock the response
00612                 return *i;
00613             }
00614         }
00615     }
00616 
00617     return 0;
00618 }
00619 
00626 HTTPCacheTable::CacheEntry *
00627 HTTPCacheTable::get_write_locked_entry_from_cache_table(const string &url)
00628 {
00629         int hash = get_hash(url);
00630     if (d_cache_table[hash]) {
00631         CacheEntries *cp = d_cache_table[hash];
00632         for (CacheEntriesIter i = cp->begin(); i != cp->end(); ++i) {
00633             // Must test *i because perform_garbage_collection may have
00634             // removed this entry; the CacheEntry will then be null.
00635             if ((*i) && (*i)->url == url) {
00636                 (*i)->lock_write_response();    // Lock the response
00637                 return *i;
00638             }
00639         }
00640     }
00641 
00642     return 0;
00643 }
00644 
00652 void
00653 HTTPCacheTable::remove_cache_entry(HTTPCacheTable::CacheEntry *entry)
00654 {
00655     // This should never happen; all calls to this method are protected by
00656     // the caller, hence the InternalErr.
00657     if (entry->readers)
00658         throw InternalErr(__FILE__, __LINE__, "Tried to delete a cache entry that is in use.");
00659 
00660     REMOVE(entry->cachename.c_str());
00661     REMOVE(string(entry->cachename + CACHE_META).c_str());
00662 
00663     DBG(cerr << "remove_cache_entry, current_size: " << get_current_size() << endl);
00664 
00665     unsigned int eds = entry_disk_space(entry->size, get_block_size());
00666     set_current_size((eds > get_current_size()) ? 0 : get_current_size() - eds);
00667     
00668     DBG(cerr << "remove_cache_entry, current_size: " << get_current_size() << endl);
00669 }
00670 
00673 class DeleteCacheEntry: public unary_function<HTTPCacheTable::CacheEntry *&, void>
00674 {
00675     string d_url;
00676     HTTPCacheTable *d_cache_table;
00677 
00678 public:
00679     DeleteCacheEntry(HTTPCacheTable *c, const string &url)
00680             : d_url(url), d_cache_table(c)
00681     {}
00682 
00683     void operator()(HTTPCacheTable::CacheEntry *&e)
00684     {
00685         if (e && e->url == d_url) {
00686                 e->lock_write_response();
00687             d_cache_table->remove_cache_entry(e);
00688                 e->unlock_write_response();
00689             delete e; e = 0;
00690         }
00691     }
00692 };
00693 
00700 void
00701 HTTPCacheTable::remove_entry_from_cache_table(const string &url)
00702 {
00703     int hash = get_hash(url);
00704     if (d_cache_table[hash]) {
00705         CacheEntries *cp = d_cache_table[hash];
00706         for_each(cp->begin(), cp->end(), DeleteCacheEntry(this, url));
00707         cp->erase(remove(cp->begin(), cp->end(), static_cast<HTTPCacheTable::CacheEntry*>(0)),
00708                   cp->end());
00709     }
00710 }
00711 
00714 class DeleteUnlockedCacheEntry: public unary_function<HTTPCacheTable::CacheEntry *&, void> {
00715     HTTPCacheTable &d_table;
00716 
00717 public:
00718     DeleteUnlockedCacheEntry(HTTPCacheTable &t) :
00719         d_table(t)
00720     {
00721     }
00722     void operator()(HTTPCacheTable::CacheEntry *&e)
00723     {
00724         if (e) {
00725             d_table.remove_cache_entry(e);
00726             delete e;
00727             e = 0;
00728         }
00729     }
00730 };
00731 
00732 void HTTPCacheTable::delete_all_entries()
00733 {
00734     // Walk through the cache table and, for every entry in the cache, delete
00735     // it on disk and in the cache table.
00736     for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
00737         HTTPCacheTable::CacheEntries *slot = get_cache_table()[cnt];
00738         if (slot) {
00739             for_each(slot->begin(), slot->end(), DeleteUnlockedCacheEntry(*this));
00740             slot->erase(remove(slot->begin(), slot->end(), static_cast<HTTPCacheTable::CacheEntry *> (0)), slot->end());
00741         }
00742     }
00743 
00744     cache_index_delete();
00745 }
00746 
00760 void
00761 HTTPCacheTable::calculate_time(HTTPCacheTable::CacheEntry *entry, int default_expiration, time_t request_time)
00762 {
00763     entry->response_time = time(NULL);
00764     time_t apparent_age = max(0, static_cast<int>(entry->response_time - entry->date));
00765     time_t corrected_received_age = max(apparent_age, entry->age);
00766     time_t response_delay = entry->response_time - request_time;
00767     entry->corrected_initial_age = corrected_received_age + response_delay;
00768 
00769     // Estimate an expires time using the max-age and expires time. If we
00770     // don't have an explicit expires time then set it to 10% of the LM date
00771     // (although max 24 h). If no LM date is available then use 24 hours.
00772     time_t freshness_lifetime = entry->max_age;
00773     if (freshness_lifetime < 0) {
00774         if (entry->expires < 0) {
00775             if (entry->lm < 0) {
00776                 freshness_lifetime = default_expiration;
00777             }
00778             else {
00779                 freshness_lifetime = LM_EXPIRATION(entry->date - entry->lm);
00780             }
00781         }
00782         else
00783             freshness_lifetime = entry->expires - entry->date;
00784     }
00785 
00786     entry->freshness_lifetime = max(0, static_cast<int>(freshness_lifetime));
00787 
00788     DBG2(cerr << "Cache....... Received Age " << entry->age
00789          << ", corrected " << entry->corrected_initial_age
00790          << ", freshness lifetime " << entry->freshness_lifetime << endl);
00791 }
00792 
00804 void HTTPCacheTable::parse_headers(HTTPCacheTable::CacheEntry *entry, unsigned long max_entry_size,
00805         const vector<string> &headers)
00806 {
00807     vector<string>::const_iterator i;
00808     for (i = headers.begin(); i != headers.end(); ++i) {
00809         // skip a blank header.
00810         if ((*i).empty())
00811             continue;
00812 
00813         string::size_type colon = (*i).find(':');
00814 
00815         // skip a header with no colon in it.
00816         if (colon == string::npos)
00817             continue;
00818 
00819         string header = (*i).substr(0, (*i).find(':'));
00820         string value = (*i).substr((*i).find(": ") + 2);
00821         DBG2(cerr << "Header: " << header << endl);DBG2(cerr << "Value: " << value << endl);
00822 
00823         if (header == "ETag") {
00824             entry->etag = value;
00825         }
00826         else if (header == "Last-Modified") {
00827             entry->lm = parse_time(value.c_str());
00828         }
00829         else if (header == "Expires") {
00830             entry->expires = parse_time(value.c_str());
00831         }
00832         else if (header == "Date") {
00833             entry->date = parse_time(value.c_str());
00834         }
00835         else if (header == "Age") {
00836             entry->age = parse_time(value.c_str());
00837         }
00838         else if (header == "Content-Length") {
00839             unsigned long clength = strtoul(value.c_str(), 0, 0);
00840             if (clength > max_entry_size)
00841                 entry->set_no_cache(true);
00842         }
00843         else if (header == "Cache-Control") {
00844             // Ignored Cache-Control values: public, private, no-transform,
00845             // proxy-revalidate, s-max-age. These are used by shared caches.
00846             // See section 14.9 of RFC 2612. 10/02/02 jhrg
00847             if (value == "no-cache" || value == "no-store")
00848                 // Note that we *can* store a 'no-store' response in volatile
00849                 // memory according to RFC 2616 (section 14.9.2) but those
00850                 // will be rare coming from DAP servers. 10/02/02 jhrg
00851                 entry->set_no_cache(true);
00852             else if (value == "must-revalidate")
00853                 entry->must_revalidate = true;
00854             else if (value.find("max-age") != string::npos) {
00855                 string max_age = value.substr(value.find("=" + 1));
00856                 entry->max_age = parse_time(max_age.c_str());
00857             }
00858         }
00859     }
00860 }
00861 
00863 
00864 // @TODO Change name to record locked response
00865 void HTTPCacheTable::bind_entry_to_data(HTTPCacheTable::CacheEntry *entry, FILE *body) {
00866         entry->hits++;  // Mark hit
00867     d_locked_entries[body] = entry; // record lock, see release_cached_r...
00868 }
00869 
00870 void HTTPCacheTable::uncouple_entry_from_data(FILE *body) {
00871 
00872     HTTPCacheTable::CacheEntry *entry = d_locked_entries[body];
00873     if (!entry)
00874         throw InternalErr("There is no cache entry for the response given.");
00875 
00876     d_locked_entries.erase(body);
00877     entry->unlock_read_response();
00878 
00879     if (entry->readers < 0)
00880         throw InternalErr("An unlocked entry was released");
00881 }
00882 
00883 bool HTTPCacheTable::is_locked_read_responses() {
00884         return !d_locked_entries.empty();
00885 }
00886 
00887 } // namespace libdap