1 // -*- mode: c++; c-basic-offset:4 -*-
2 
3 // This file is part of libdap, A C++ implementation of the OPeNDAP Data
4 // Access Protocol.
5 
6 // Copyright (c) 2008 OPeNDAP, Inc.
7 // Author: James Gallagher <jgallagher@opendap.org>
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 //
23 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
24 
25 #ifndef _http_cache_table_h
26 #define _http_cache_table_h
27 
28 //#define DODS_DEBUG
29 
30 #include <pthread.h>
31 
32 #ifdef WIN32
33 #include <io.h>   // stat for win32? 09/05/02 jhrg
34 #endif
35 
36 #include <cstring>
37 
38 #include <string>
39 #include <vector>
40 #include <map>
41 
42 #ifndef _http_cache_h
43 #include "HTTPCache.h"
44 #endif
45 
46 #ifndef _error_h
47 #include "Error.h"
48 #endif
49 
50 #ifndef _internalerr_h
51 #include "InternalErr.h"
52 #endif
53 
54 #ifndef _util_h
55 #include "util.h"
56 #endif
57 
58 #ifndef _debug_h
59 #include "debug.h"
60 #endif
61 
62  //long_to_string(code));
63 #define LOCK(m) do { \
64 	int code = pthread_mutex_lock((m)); \
65 	if (code != 0) \
66 		throw InternalErr(__FILE__, __LINE__, string("Mutex lock: ") + strerror(code)); \
67     } while(0);
68 
69 //+ long_to_string(code));
70 #define UNLOCK(m) do { \
71 	int code = pthread_mutex_unlock((m)); \
72 	if (code != 0) \
73 		throw InternalErr(__FILE__, __LINE__, string("Mutex unlock: ") + strerror(code)); \
74     } while(0);
75 
76 #define TRYLOCK(m) pthread_mutex_trylock((m))
77 #define INIT(m) pthread_mutex_init((m), 0)
78 #define DESTROY(m) pthread_mutex_destroy((m))
79 
80 //using namespace std;
81 
82 namespace libdap {
83 
84 int get_hash(const string &url);
85 
86 /** The table of entries in the client-side cache. This class maintains a table
87  of CacheEntries, where one instance of CacheEntry is made for
88  each item in the cache. When an item is accessed it is either
89  locked for reading or writing. When locked for reading the entry is
90  recorded on a list of read-locked entries. The caller must explicitly
91  free the entry for it to be removed from this list (which is the only
92  way it can be opened for writing). An entry can be accessed by multiple
93  readers but only one writer.
94 
95  @note The CacheEntry class used to contain a lock that was used to ensure
96  that the entry was locked during any changes to any of its fields. That
97  has been removed - its now the responsibility of the caller. This change
98  was made because it's likely the caller will need to lock all of the methods
99  that operate on a CacheEntry anyway, so the CacheEntry-specific lock was
100  redundant. */
101 class HTTPCacheTable {
102 public:
103     /** A struct used to store information about responses in the
104      cache's volatile memory.
105 
106      About entry locking: An entry is locked using both a mutex and a
107      counter. The counter keeps track of how many clients are accessing a
108      given entry while the mutex provides a guarantee that updates to the
109      counter are MT-safe. In addition, the HTTPCacheTable object maintains a
110      map which binds the FILE* returned to a client with a given entry.
111      This way the client can tell the HTTPCacheTable object that it is done
112      with <code>FILE *response</code> and the class can arrange to update
113      the lock counter and mutex. */
114     struct CacheEntry {
115     private:
116         string url; // Location
117         int hash;
118         int hits; // Hit counts
119         string cachename;
120 
121         string etag;
122         time_t lm; // Last modified
123         time_t expires;
124         time_t date; // From the response header.
125         time_t age;
126         time_t max_age; // From Cache-Control
127 
128         unsigned long size; // Size of cached entity body
129         bool range; // Range is not currently supported. 10/02/02 jhrg
130 
131         time_t freshness_lifetime;
132         time_t response_time;
133         time_t corrected_initial_age;
134 
135         bool must_revalidate;
136         bool no_cache; // This field is not saved in the index.
137 
138         int readers;
139         pthread_mutex_t d_response_lock; // set if being read
140         pthread_mutex_t d_response_write_lock; // set if being written
141 
142         // Allow HTTPCacheTable methods access and the test class, too
143         friend class HTTPCacheTable;
144         friend class HTTPCacheTest;
145 
146         // Allow access by the functors used in HTTPCacheTable
147         friend class DeleteCacheEntry;
148         friend class WriteOneCacheEntry;
149         friend class DeleteExpired;
150         friend class DeleteByHits;
151         friend class DeleteBySize;
152 
153     public:
get_cachenameCacheEntry154         string get_cachename()
155         {
156             return cachename;
157         }
get_etagCacheEntry158         string get_etag()
159         {
160             return etag;
161         }
get_lmCacheEntry162         time_t get_lm()
163         {
164             return lm;
165         }
get_expiresCacheEntry166         time_t get_expires()
167         {
168             return expires;
169         }
get_max_ageCacheEntry170         time_t get_max_age()
171         {
172             return max_age;
173         }
set_sizeCacheEntry174         void set_size(unsigned long sz)
175         {
176             size = sz;
177         }
get_freshness_lifetimeCacheEntry178         time_t get_freshness_lifetime()
179         {
180             return freshness_lifetime;
181         }
get_response_timeCacheEntry182         time_t get_response_time()
183         {
184             return response_time;
185         }
get_corrected_initial_ageCacheEntry186         time_t get_corrected_initial_age()
187         {
188             return corrected_initial_age;
189         }
get_must_revalidateCacheEntry190         bool get_must_revalidate()
191         {
192             return must_revalidate;
193         }
set_no_cacheCacheEntry194         void set_no_cache(bool state)
195         {
196             no_cache = state;
197         }
is_no_cacheCacheEntry198         bool is_no_cache()
199         {
200             return no_cache;
201         }
202 
lock_read_responseCacheEntry203         void lock_read_response()
204         {
205             DBG(cerr << "Try locking read response... (" << hex << &d_response_lock << dec << ") ");
206             int status = TRYLOCK(&d_response_lock);
207             if (status != 0 /*&& status == EBUSY*/) {
208                 // If locked, wait for any writers
209                 LOCK(&d_response_write_lock);
210                 UNLOCK(&d_response_write_lock);
211             }
212 
213             readers++; // Record number of readers
214 
215             DBGN(cerr << "Done" << endl);
216 
217         }
218 
unlock_read_responseCacheEntry219         void unlock_read_response()
220         {
221             readers--;
222             if (readers == 0) {
223                 DBG(cerr << "Unlocking read response... (" << hex << &d_response_lock << dec << ") ");
224                 UNLOCK(&d_response_lock); DBGN(cerr << "Done" << endl);
225             }
226         }
227 
lock_write_responseCacheEntry228         void lock_write_response()
229         {
230             DBG(cerr << "locking write response... (" << hex << &d_response_lock << dec << ") ");
231             LOCK(&d_response_lock);
232             LOCK(&d_response_write_lock); DBGN(cerr << "Done" << endl);
233         }
234 
unlock_write_responseCacheEntry235         void unlock_write_response()
236         {
237             DBG(cerr << "Unlocking write response... (" << hex << &d_response_lock << dec << ") ");
238             UNLOCK(&d_response_write_lock);
239             UNLOCK(&d_response_lock); DBGN(cerr << "Done" << endl);
240         }
241 
CacheEntryCacheEntry242         CacheEntry() :
243             url(""), hash(-1), hits(0), cachename(""), etag(""), lm(-1), expires(-1), date(-1), age(-1), max_age(-1), size(
244                 0), range(false), freshness_lifetime(0), response_time(0), corrected_initial_age(0), must_revalidate(
245                 false), no_cache(false), readers(0)
246         {
247             INIT(&d_response_lock);
248             INIT(&d_response_write_lock);
249         }
CacheEntryCacheEntry250         CacheEntry(const string &u) :
251             url(u), hash(-1), hits(0), cachename(""), etag(""), lm(-1), expires(-1), date(-1), age(-1), max_age(-1), size(
252                 0), range(false), freshness_lifetime(0), response_time(0), corrected_initial_age(0), must_revalidate(
253                 false), no_cache(false), readers(0)
254         {
255             INIT(&d_response_lock);
256             INIT(&d_response_write_lock);
257             hash = get_hash(url);
258         }
259     };
260 
261     // Typedefs for CacheTable. A CacheTable is a vector of vectors of
262     // CacheEntries. The outer vector is accessed using the hash value.
263     // Entries with matching hashes occupy successive positions in the inner
264     // vector (that's how hash collisions are resolved). Search the inner
265     // vector for a specific match.
266     typedef vector<CacheEntry *> CacheEntries;
267     typedef CacheEntries::iterator CacheEntriesIter;
268 
269     typedef CacheEntries **CacheTable;    // Array of pointers to CacheEntries
270 
271     friend class HTTPCacheTest;
272 
273 private:
274     CacheTable d_cache_table;
275 
276     string d_cache_root;
277     unsigned int d_block_size; // File block size.
278     unsigned long d_current_size;
279 
280     string d_cache_index;
281     int d_new_entries;
282 
283     map<FILE *, HTTPCacheTable::CacheEntry *> d_locked_entries;
284 
285     // Make these private to prevent use
286     HTTPCacheTable(const HTTPCacheTable &);
287     HTTPCacheTable &operator=(const HTTPCacheTable &);
288     HTTPCacheTable();
289 
get_cache_table()290     CacheTable &get_cache_table()
291     {
292         return d_cache_table;
293     }
294 
295     CacheEntry *get_locked_entry_from_cache_table(int hash, const string &url); /*const*/
296 
297 public:
298     HTTPCacheTable(const string &cache_root, int block_size);
299     ~HTTPCacheTable();
300 
301     //@{ @name Accessors/Mutators
get_current_size()302     unsigned long get_current_size() const
303     {
304         return d_current_size;
305     }
set_current_size(unsigned long sz)306     void set_current_size(unsigned long sz)
307     {
308         d_current_size = sz;
309     }
310 
get_block_size()311     unsigned int get_block_size() const
312     {
313         return d_block_size;
314     }
set_block_size(unsigned int sz)315     void set_block_size(unsigned int sz)
316     {
317         d_block_size = sz;
318     }
319 
get_new_entries()320     int get_new_entries() const
321     {
322         return d_new_entries;
323     }
increment_new_entries()324     void increment_new_entries()
325     {
326         ++d_new_entries;
327     }
328 
get_cache_root()329     string get_cache_root()
330     {
331         return d_cache_root;
332     }
set_cache_root(const string & cr)333     void set_cache_root(const string &cr)
334     {
335         d_cache_root = cr;
336     }
337     //@}
338 
339     void delete_expired_entries(time_t time = 0);
340     void delete_by_hits(int hits);
341     void delete_by_size(unsigned int size);
342     void delete_all_entries();
343 
344     bool cache_index_delete();
345     bool cache_index_read();
346     CacheEntry *cache_index_parse_line(const char *line);
347     void cache_index_write();
348 
349     string create_hash_directory(int hash);
350     void create_location(CacheEntry *entry);
351 
352     void add_entry_to_cache_table(CacheEntry *entry);
353     void remove_cache_entry(HTTPCacheTable::CacheEntry *entry);
354 
355     void remove_entry_from_cache_table(const string &url);
356     CacheEntry *get_locked_entry_from_cache_table(const string &url);
357     CacheEntry *get_write_locked_entry_from_cache_table(const string &url);
358 
359     void calculate_time(HTTPCacheTable::CacheEntry *entry, int default_expiration, time_t request_time);
360     void parse_headers(HTTPCacheTable::CacheEntry *entry, unsigned long max_entry_size, const vector<string> &headers);
361 
362     // These should move back to HTTPCache
363     void bind_entry_to_data(CacheEntry *entry, FILE *body);
364     void uncouple_entry_from_data(FILE *body);
365     bool is_locked_read_responses();
366 };
367 
368 } // namespace libdap
369 #endif
370