Annotation of libwww/Library/src/HTCache.c, revision 2.25
2.1 frystyk 1: /* HTCache.c
2: ** CACHE WRITER
3: **
4: ** (c) COPYRIGHT MIT 1995.
5: ** Please first read the full copyright statement in the file COPYRIGH.
2.25 ! frystyk 6: ** @(#) $Id: HTCache.c,v 2.24 1996/09/09 20:53:31 frystyk Exp $
2.1 frystyk 7: **
8: ** This modules manages the cache
9: **
10: ** History:
11: ** HFN: spawned from HTFwrite
12: ** HWL: converted the caching scheme to be hierachical by taking
13: ** AL code from Deamon
14: **
15: */
16:
17: /* Library include files */
2.15 frystyk 18: #include "sysdep.h"
2.19 frystyk 19: #include "WWWUtil.h"
20: #include "WWWCore.h"
2.22 frystyk 21: #include "WWWTrans.h"
22: #include "WWWApp.h"
23: #include "HTNetMan.h"
2.1 frystyk 24: #include "HTCache.h" /* Implemented here */
25:
2.7 frystyk 26: /* This is the default cache directory: */
2.22 frystyk 27: #define HT_CACHE_ROOT "/tmp/w3c-lib"
28: #define HT_CACHE_INDEX ".index"
29: #define HT_CACHE_META ".meta"
30:
31: #define HASH_SIZE 67
32: #define DUMP_FREQUENCY 10 /* Dump index after 10 loads */
33:
34: #define MEGA 0x100000L
35: #define CACHE_SIZE (20*MEGA) /* Default cache size is 20M */
2.23 frystyk 36: #define MIN_CACHE_SIZE (5*MEGA) /* Min cache size */
2.22 frystyk 37: #define SIZE_BUFFER (1*MEGA) /* Buffer for metainfo and directories */
38:
39: /* Final states have negative value */
40: typedef enum _CacheState {
41: CL_ERROR = -3,
42: CL_NO_DATA = -2,
43: CL_GOT_DATA = -1,
44: CL_BEGIN = 0,
45: CL_NEED_INDEX,
46: CL_NEED_HEAD,
47: CL_NEED_BODY,
48: CL_NEED_OPEN_FILE,
49: CL_NEED_CONTENT
50: } CacheState;
51:
52: /* This is the context structure for the this module */
53: typedef struct _cache_info {
54: CacheState state; /* Current state of the connection */
55: char * local; /* Local representation of file name */
56: BOOL meta; /* Are we reading metainformation? */
57: struct stat stat_info; /* Contains actual file chosen */
58: } cache_info;
59:
60: struct _HTCache {
2.24 frystyk 61: /* Location */
2.22 frystyk 62: int hash;
63: char * url;
64: char * cachename;
2.24 frystyk 65:
66: /* Cache validators */
67: char * etag;
68: time_t date;
69: time_t expires;
70:
71: /* GC parameters */
2.22 frystyk 72: long size;
73: int hits;
74: time_t freshness_lifetime;
75: time_t response_time;
76: time_t corrected_initial_age;
77: BOOL must_revalidate;
78: HTRequest * lock;
79: };
2.1 frystyk 80:
81: struct _HTStream {
2.15 frystyk 82: const HTStreamClass * isa;
2.1 frystyk 83: FILE * fp;
2.7 frystyk 84: HTCache * cache;
2.1 frystyk 85: HTRequest * request;
2.22 frystyk 86: HTChunk * buffer; /* For index reading */
87: HTEOLState EOLstate;
88: };
89:
90: struct _HTInputStream {
91: const HTInputStreamClass * isa;
2.1 frystyk 92: };
93:
2.22 frystyk 94: /* Cache parameters */
95: PRIVATE BOOL HTCacheEnable = NO; /* Disabled by default */
2.1 frystyk 96: PRIVATE char * HTCacheRoot = NULL; /* Destination for cache */
2.22 frystyk 97: PRIVATE HTExpiresMode HTExpMode = HT_EXPIRES_IGNORE;
98: PRIVATE HTDisconnectedMode DisconnectedMode = HT_DISCONNECT_NONE;
2.1 frystyk 99:
2.22 frystyk 100: /* List of cache entries */
101: PRIVATE HTList ** CacheTable = NULL;
102:
103: /* Cache size variables */
104: PRIVATE long HTCacheSize = CACHE_SIZE;
105: PRIVATE long HTTotalSize = 0L;
106:
107: PRIVATE int new_entries = 0; /* Number of new entries */
2.2 frystyk 108:
2.1 frystyk 109: /* ------------------------------------------------------------------------- */
2.22 frystyk 110: /* CACHE GARBAGE COLLECTOR */
2.1 frystyk 111: /* ------------------------------------------------------------------------- */
112:
2.22 frystyk 113: PRIVATE BOOL HTCacheGarbage (void)
2.1 frystyk 114: {
2.22 frystyk 115: long old_size = HTTotalSize;
116: if (CACHE_TRACE) HTTrace("Cache....... Garbage collecting\n");
117: if (CacheTable) {
118: time_t cur_time = time(NULL);
119: HTList * cur;
120: int cnt;
2.23 frystyk 121: int hits;
2.22 frystyk 122:
123: /*
124: ** Tell the use that we're gc'ing.
125: */
2.1 frystyk 126: {
2.22 frystyk 127: HTAlertCallback * cbf = HTAlert_find(HT_PROG_GC);
128: if (cbf) (*cbf)(NULL, HT_PROG_GC, HT_MSG_NULL,NULL, NULL, NULL);
129: }
130:
131: /*
132: ** Walk through and delete all the expired entries. If this is not
133: ** sufficient then take the fresh ones which have the lowest cache
134: ** hit count. This algorithm could be made a lot fancier by including
135: ** the size and also the pain it took to get the document in the first
136: ** case. It could also include max_stale.
137: */
2.23 frystyk 138: if (CACHE_TRACE) HTTrace("Cache....... Collecting Stale entries\n");
2.22 frystyk 139: for (cnt=0; cnt<HASH_SIZE; cnt++) {
140: if ((cur = CacheTable[cnt])) {
141: HTCache * pres;
142: HTList * old_cur = cur;
143: while ((pres = (HTCache *) HTList_nextObject(cur)) != NULL) {
144: time_t resident_time = cur_time - pres->response_time;
145: time_t current_age = pres->corrected_initial_age +
146: resident_time;
147: if (pres->freshness_lifetime < current_age) {
148: HTCache_remove(pres);
149: cur = old_cur;
150: } else {
151: old_cur = cur;
152: }
2.1 frystyk 153: }
154: }
155: }
2.23 frystyk 156:
157: /*
158: ** We must at least free the min buffer size so that we don't
159: ** dead lock ourselves. We start from the bottom up by taking
160: ** all the documents with 0 hits, 1 hits, 2 hits, etc.
161: */
162: hits = 0;
163: while (1) {
164: BOOL removed = NO;
165: if (CACHE_TRACE)
166: HTTrace("Cache....... Collecting entries with %d hits\n",hits);
167: if (HTTotalSize + SIZE_BUFFER > HTCacheSize) {
168: for (cnt=0; cnt<HASH_SIZE; cnt++) {
169: if ((cur = CacheTable[cnt])) {
170: HTCache * pres;
171: HTList * old_cur = cur;
172: while ((pres = (HTCache *) HTList_nextObject(cur))) {
173: if (pres->hits <= hits) {
174: HTCache_remove(pres);
175: cur = old_cur;
176: removed = YES;
177: } else {
178: old_cur = cur;
179: }
180: }
181: }
182: }
183: } else
184: break;
185: if (!removed) break;
186: hits++;
187: }
2.22 frystyk 188: if (CACHE_TRACE)
189: HTTrace("Cache....... Size reduced from %ld to %ld\n",
190: old_size, HTTotalSize);
2.23 frystyk 191: /*
192: ** Dump the new content to the index file
193: */
194: HTCacheIndex_write(HTCacheRoot);
195: new_entries = 0;
2.22 frystyk 196: return YES;
2.1 frystyk 197: }
2.22 frystyk 198: return NO;
2.1 frystyk 199: }
200:
2.22 frystyk 201: /* ------------------------------------------------------------------------- */
202: /* CACHE INDEX */
203: /* ------------------------------------------------------------------------- */
2.1 frystyk 204:
2.22 frystyk 205: PRIVATE char * cache_index_name (const char * cache_root)
2.1 frystyk 206: {
2.22 frystyk 207: if (cache_root) {
208: char * location = NULL;
209: if ((location = (char *)
210: HT_MALLOC(strlen(cache_root) + strlen(HT_CACHE_INDEX) + 1)) == NULL)
211: HT_OUTOFMEM("cache_index_name");
212: strcpy(location, cache_root);
213: strcat(location, HT_CACHE_INDEX);
214: return location;
2.1 frystyk 215: }
2.22 frystyk 216: return NULL;
2.1 frystyk 217: }
218:
219: /*
2.22 frystyk 220: ** Remove the cache index file
2.1 frystyk 221: */
2.22 frystyk 222: PUBLIC BOOL HTCacheIndex_delete (const char * cache_root)
2.1 frystyk 223: {
2.22 frystyk 224: if (cache_root) {
225: char * index = cache_index_name(cache_root);
226: REMOVE(index);
227: HT_FREE(index);
2.1 frystyk 228: return YES;
2.22 frystyk 229: }
230: return NO;
231: }
2.1 frystyk 232:
2.22 frystyk 233: /*
234: ** Walk through the list of cached objects and save them to disk.
235: ** We override any existing version but that is normally OK as we have
236: ** already read its contents.
237: */
238: PUBLIC BOOL HTCacheIndex_write (const char * cache_root)
239: {
240: if (cache_root && CacheTable) {
241: char * index = cache_index_name(cache_root);
242: FILE * fp = NULL;
243: if (CACHE_TRACE) HTTrace("Cache Index. Writing index `%s\'\n", index);
244:
245: /*
246: ** Open the file for writing. Note - we don't take a backup!
247: ** This should probably be fixed!
248: */
249: if (!index) return NO;
250: if ((fp = fopen(index, "wb")) == NULL) {
251: if (CACHE_TRACE)
252: HTTrace("Cache Index. Can't open `%s\' for writing\n", index);
253: HT_FREE(index);
254: return NO;
255: }
2.1 frystyk 256:
2.22 frystyk 257: /*
258: ** Walk through the list and write it out. The format is really
2.25 ! frystyk 259: ** simple as we keep it all in ASCII.
2.22 frystyk 260: */
261: {
262: HTList * cur;
263: int cnt;
264: for (cnt=0; cnt<HASH_SIZE; cnt++) {
265: if ((cur = CacheTable[cnt])) {
266: HTCache * pres;
2.24 frystyk 267: while ((pres = (HTCache *) HTList_nextObject(cur))) {
268: if (fprintf(fp, "%s %s %s %ld %ld %ld %d %d %ld %ld %ld %c\r\n",
269: pres->url,
270: pres->cachename,
271: pres->etag ? pres->etag : "@",
272: pres->date,
273: pres->expires,
274: pres->size,
275: pres->hash,
276: pres->hits,
277: pres->freshness_lifetime,
278: pres->response_time,
2.22 frystyk 279: pres->corrected_initial_age,
280: pres->must_revalidate+0x30) < 0) {
281: if (CACHE_TRACE)
282: HTTrace("Cache Index. Error writing cache index\n");
283: return NO;
284: }
285: }
286: }
287: }
288: }
2.1 frystyk 289:
2.22 frystyk 290: /* Done writing */
291: fclose(fp);
292: HT_FREE(index);
293: }
2.1 frystyk 294: return NO;
295: }
296:
297: /*
2.22 frystyk 298: ** Load one line of index file
299: ** Returns YES if line OK, else NO
2.2 frystyk 300: */
2.22 frystyk 301: PRIVATE BOOL HTCacheIndex_parseLine (char * line)
2.2 frystyk 302: {
2.22 frystyk 303: HTCache * cache = NULL;
304: if (line) {
305: char validate;
306: if ((cache = (HTCache *) HT_CALLOC(1, sizeof(HTCache))) == NULL)
307: HT_OUTOFMEM("HTCacheIndex_parseLine");
308:
309: /*
310: ** Read the line and create the cache object
311: */
312: {
313: char * url = HTNextField(&line);
314: char * cachename = HTNextField(&line);
2.24 frystyk 315: char * etag = HTNextField(&line);
2.22 frystyk 316: StrAllocCopy(cache->url, url);
317: StrAllocCopy(cache->cachename, cachename);
2.24 frystyk 318: if (strcmp(etag, "@")) StrAllocCopy(cache->etag, etag);
2.22 frystyk 319: }
2.24 frystyk 320: if (sscanf(line, "%ld %ld %ld %d %d %ld %ld %ld %c",
321: &cache->date,
322: &cache->expires,
323: &cache->size,
324: &cache->hash,
325: &cache->hits,
2.22 frystyk 326: &cache->freshness_lifetime,
327: &cache->response_time,
328: &cache->corrected_initial_age,
329: &validate) < 0) {
330: if (CACHE_TRACE) HTTrace("Cache Index. Error reading cache index\n");
331: return NO;
332: }
333: cache->must_revalidate = validate-0x30;
334:
335: /*
2.25 ! frystyk 336: ** Create the new anchor and fill in the information that we have read
! 337: ** in the index. This is for example the etag and date
! 338: */
! 339: if (cache) {
! 340: HTAnchor * anchor = HTAnchor_findAddress(cache->url);
! 341: HTParentAnchor * parent = HTAnchor_parent(anchor);
! 342: HTAnchor_setDate(parent, cache->date);
! 343: HTAnchor_setExpires(parent, cache->expires);
! 344: if (cache->etag) HTAnchor_setEtag(parent, cache->etag);
! 345: }
! 346:
! 347: /*
2.22 frystyk 348: ** Create the cache table if not already existent and add the new
349: ** entry. Also check that the hash is still within bounds
350: */
351: if (!CacheTable) {
352: if ((CacheTable = (HTList **) HT_CALLOC(HASH_SIZE,
353: sizeof(HTList *))) == NULL)
354: HT_OUTOFMEM("HTCache_parseLine");
355: }
356: if (cache->hash >= 0 && cache->hash < HASH_SIZE) {
357: int hash = cache->hash;
358: if (!CacheTable[hash]) CacheTable[hash] = HTList_new();
359: HTList_addObject(CacheTable[hash], (void *) cache);
2.2 frystyk 360: }
2.22 frystyk 361:
362: /* Update the total cache size */
363: HTTotalSize += cache->size;
364:
365: return YES;
2.2 frystyk 366: }
2.22 frystyk 367: return NO;
2.2 frystyk 368: }
369:
370: /*
2.22 frystyk 371: ** Folding is either of CF LWS, LF LWS, CRLF LWS
2.2 frystyk 372: */
2.22 frystyk 373: PRIVATE int HTCacheIndex_put_block (HTStream * me, const char * b, int l)
2.2 frystyk 374: {
2.22 frystyk 375: while (l > 0) {
376: if (me->EOLstate == EOL_FCR) {
377: if (*b == LF) /* CRLF */
378: me->EOLstate = EOL_FLF;
379: else if (WHITE(*b)) /* Folding: CR SP */
380: me->EOLstate = EOL_DOT;
381: else { /* New line */
382: HTCacheIndex_parseLine(HTChunk_data(me->buffer));
383: me->EOLstate = EOL_BEGIN;
384: HTChunk_clear(me->buffer);
385: continue;
386: }
387: } else if (me->EOLstate == EOL_FLF) {
388: if (WHITE(*b)) /* Folding: LF SP or CR LF SP */
389: me->EOLstate = EOL_DOT;
390: else { /* New line */
391: HTCacheIndex_parseLine(HTChunk_data(me->buffer));
392: me->EOLstate = EOL_BEGIN;
393: HTChunk_clear(me->buffer);
394: continue;
395: }
396: } else if (me->EOLstate == EOL_DOT) {
397: if (WHITE(*b)) {
398: me->EOLstate = EOL_BEGIN;
399: HTChunk_putc(me->buffer, ' ');
400: } else {
401: HTCacheIndex_parseLine(HTChunk_data(me->buffer));
402: me->EOLstate = EOL_BEGIN;
403: HTChunk_clear(me->buffer);
404: continue;
405: }
406: } else if (*b == CR) {
407: me->EOLstate = EOL_FCR;
408: } else if (*b == LF) {
409: me->EOLstate = EOL_FLF; /* Line found */
410: } else
411: HTChunk_putc(me->buffer, *b);
412: l--; b++;
2.2 frystyk 413: }
2.22 frystyk 414: return HT_OK;
2.2 frystyk 415: }
416:
2.22 frystyk 417: PRIVATE int HTCacheIndex_put_character (HTStream * me, char c)
418: {
419: return HTCacheIndex_put_block(me, &c, 1);
420: }
2.2 frystyk 421:
2.22 frystyk 422: PRIVATE int HTCacheIndex_put_string (HTStream * me, const char * s)
2.1 frystyk 423: {
2.22 frystyk 424: return HTCacheIndex_put_block(me, s, (int) strlen(s));
425: }
2.1 frystyk 426:
2.22 frystyk 427: PRIVATE int HTCacheIndex_flush (HTStream * me)
428: {
429: if (me) {
430: char * flush = HTChunk_data(me->buffer);
431: if (flush) HTCacheIndex_parseLine(flush);
432: HTChunk_clear(me->buffer);
433: }
434: return HT_OK;
435: }
2.1 frystyk 436:
2.22 frystyk 437: PRIVATE int HTCacheIndex_free (HTStream * me)
438: {
439: if (me) {
440: int status = HTCacheIndex_flush(me);
441: if (APP_TRACE) HTTrace("Cache Index. FREEING....\n");
442: HTChunk_delete(me->buffer);
443: HT_FREE(me);
444: return status;
445: }
446: return HT_ERROR;
447: }
2.1 frystyk 448:
2.22 frystyk 449: PRIVATE int HTCacheIndex_abort (HTStream * me, HTList * e)
450: {
451: if (me) {
452: int status = HT_ERROR;
453: if (APP_TRACE) HTTrace("Cache Index. ABORTING...\n");
454: HTChunk_delete(me->buffer);
455: HT_FREE(me);
456: return status;
2.1 frystyk 457: }
2.22 frystyk 458: return HT_ERROR;
459: }
2.1 frystyk 460:
2.22 frystyk 461: /* Structured Object Class
462: ** -----------------------
463: */
464: PRIVATE const HTStreamClass HTCacheIndexClass =
465: {
466: "CacheIndexParser",
467: HTCacheIndex_flush,
468: HTCacheIndex_free,
469: HTCacheIndex_abort,
470: HTCacheIndex_put_character,
471: HTCacheIndex_put_string,
472: HTCacheIndex_put_block
473: };
2.1 frystyk 474:
2.22 frystyk 475: PRIVATE HTStream * HTCacheIndexReader (HTRequest * request)
476: {
477: HTStream * me;
478: if ((me = (HTStream *) HT_CALLOC(1, sizeof(HTStream))) == NULL)
479: HT_OUTOFMEM("HTCacheIndexs");
480: me->isa = &HTCacheIndexClass;
481: me->request = request;
482: me->buffer = HTChunk_new(512);
483: me->EOLstate = EOL_BEGIN;
484: return me;
485: }
486:
487: /*
488: ** Read the saved set of cached entries from disk. we only allow the index
489: ** ro be read when there is no entries in memory. That way we can ensure
490: ** consistancy.
491: */
492: PUBLIC BOOL HTCacheIndex_read (const char * cache_root)
493: {
494: BOOL status = NO;
495: if (cache_root && CacheTable == NULL) {
496: char * file = cache_index_name(cache_root);
497: char * index = HTParse(file, "cache:", PARSE_ALL);
2.23 frystyk 498: HTAnchor * anchor = HTAnchor_findAddress(index);
2.22 frystyk 499: HTRequest * request = HTRequest_new();
500: HTRequest_setPreemptive(request, YES);
501: HTRequest_setOutputFormat(request, WWW_SOURCE);
502: HTRequest_setOutputStream(request, HTCacheIndexReader(request));
2.23 frystyk 503: HTRequest_setAnchor(request, anchor);
504: HTAlert_setInteractive(NO);
505: status = HTLoad(request, NO);
2.22 frystyk 506: HTRequest_delete(request);
507: HT_FREE(file);
508: HT_FREE(index);
2.1 frystyk 509: }
2.22 frystyk 510: return status;
2.1 frystyk 511: }
512:
2.22 frystyk 513: /* ------------------------------------------------------------------------- */
514: /* CACHE PARAMETERS */
515: /* ------------------------------------------------------------------------- */
2.1 frystyk 516:
2.22 frystyk 517: PRIVATE BOOL create_cache_root (const char * cache_root)
2.1 frystyk 518: {
519: struct stat stat_info;
2.22 frystyk 520: char * loc = NULL;
2.1 frystyk 521: char * cur = NULL;
522: BOOL create = NO;
2.22 frystyk 523: if (!cache_root) return NO;
524: StrAllocCopy(loc, cache_root); /* Get our own copy */
525: cur = loc+1;
2.1 frystyk 526: while ((cur = strchr(cur, '/'))) {
2.22 frystyk 527: *cur = '\0';
528: if (create || HT_STAT(loc, &stat_info) == -1) {
529: create = YES; /* To avoid doing stat()s in vain */
530: if (CACHE_TRACE) HTTrace("Cache....... Creating dir `%s\'\n", loc);
531: if (MKDIR(loc, 0777) < 0) {
532: if (CACHE_TRACE) HTTrace("Cache....... can't create\n");
533: HT_FREE(loc);
2.1 frystyk 534: return NO;
535: }
536: } else {
2.22 frystyk 537: if (CACHE_TRACE)
538: HTTrace("Cache....... dir `%s\' already exists\n", loc);
2.1 frystyk 539: }
2.22 frystyk 540: *cur++ = '/';
2.1 frystyk 541: }
2.22 frystyk 542: HT_FREE(loc);
2.1 frystyk 543: return YES;
544: }
545:
2.22 frystyk 546: /*
547: ** If `cache_root' is NULL then the current value (might be a define)
548: ** Should we check if the cache_root is actually OK? I think not!
2.1 frystyk 549: */
2.22 frystyk 550: PRIVATE BOOL HTCacheMode_setRoot (const char * cache_root)
2.1 frystyk 551: {
2.22 frystyk 552: StrAllocCopy(HTCacheRoot, cache_root ? cache_root : HT_CACHE_ROOT);
553: if (*(HTCacheRoot+strlen(HTCacheRoot)-1) != '/')
554: StrAllocCat(HTCacheRoot, "/");
555: if (create_cache_root(HTCacheRoot) == NO) return NO;
556: if (CACHE_TRACE) HTTrace("Cache Root.. Root set to `%s\'\n", HTCacheRoot);
557: return YES;
2.1 frystyk 558: }
559:
560: /*
2.22 frystyk 561: ** Return the value of the cache root. The cache root can only be
562: ** set through the HTCacheInit() function
563: */
564: PUBLIC const char * HTCacheMode_getRoot (void)
565: {
566: return HTCacheRoot;
2.1 frystyk 567: }
568:
2.22 frystyk 569: /*
2.1 frystyk 570: ** If `cache_root' is NULL then reuse old value or use HT_CACHE_ROOT.
571: ** An empty string will make '/' as cache root
2.12 frystyk 572: ** We can only enable the cache if the HTSecure flag is not set. This
573: ** is for example the case if using an application as a telnet shell.
2.1 frystyk 574: */
2.22 frystyk 575: PUBLIC BOOL HTCacheInit (const char * cache_root, int size)
2.1 frystyk 576: {
2.22 frystyk 577: if (!HTLib_secure() && !HTCacheRoot) {
578:
579: /*
580: ** Find an appropriate root for the cache
581: */
582: if (HTCacheMode_setRoot(cache_root) != YES) return NO;
583:
584: /*
585: ** Set the max size of the cache
586: */
587: HTCacheMode_setMaxSize(size);
588:
589: /*
590: ** Look for the cache index and read the contents
591: */
592: HTCacheIndex_read(HTCacheRoot);
593:
594: /*
595: ** Do caching from now on
596: */
2.12 frystyk 597: HTCacheEnable = YES;
598: return YES;
599: }
600: return NO;
2.1 frystyk 601: }
602:
2.22 frystyk 603: /*
604: ** Turns off the cache and updates entries on disk.
2.1 frystyk 605: */
2.22 frystyk 606: PUBLIC BOOL HTCacheTerminate (void)
2.1 frystyk 607: {
2.22 frystyk 608: /*
609: ** Write the index to file
610: */
611: HTCacheIndex_write(HTCacheRoot);
612:
613: /*
614: ** Cleanup memory by deleting all HTCache objects
615: */
616: HTCache_deleteAll();
617:
618: /*
619: ** Don't do anymore caching from now on
620: */
621: HT_FREE(HTCacheRoot);
2.1 frystyk 622: HTCacheEnable = NO;
623: return YES;
624: }
625:
2.22 frystyk 626: /*
627: ** The cache can be temporarily suspended by using the enable/disable
628: ** flag. This does not prevent the cache from being enabled/disable at
629: ** a later point in time.
2.1 frystyk 630: */
2.22 frystyk 631: PUBLIC void HTCacheMode_setEnabled (BOOL mode)
632: {
633: HTCacheEnable = mode;
634: }
635:
636: PUBLIC BOOL HTCacheMode_enabled (void)
2.1 frystyk 637: {
2.12 frystyk 638: return HTCacheEnable;
2.1 frystyk 639: }
640:
2.22 frystyk 641: /*
642: ** We can set the cache to operate in disconnected mode in which we only
643: ** return (valid) responses from the cache. Disconnected mode does not
644: ** automatically deliver stale documents as this must be declared
645: ** explicitly.
2.1 frystyk 646: */
2.22 frystyk 647: PUBLIC void HTCacheMode_setDisconnected (HTDisconnectedMode mode)
2.1 frystyk 648: {
2.22 frystyk 649: DisconnectedMode = mode;
2.1 frystyk 650: }
651:
2.22 frystyk 652: PUBLIC HTDisconnectedMode HTCacheMode_disconnected (void)
2.1 frystyk 653: {
2.22 frystyk 654: return DisconnectedMode;
2.1 frystyk 655: }
656:
2.22 frystyk 657: PUBLIC BOOL HTCacheMode_isDisconnected (HTReload mode)
2.1 frystyk 658: {
2.22 frystyk 659: return (DisconnectedMode != HT_DISCONNECT_NONE);
2.1 frystyk 660: }
661:
2.7 frystyk 662: /*
663: ** Set the mode for how we handle Expires header from the local history
664: ** list. The following modes are available:
665: **
666: ** HT_EXPIRES_IGNORE : No update in the history list
667: ** HT_EXPIRES_NOTIFY : The user is notified but no reload
668: ** HT_EXPIRES_AUTO : Automatic reload
669: */
2.22 frystyk 670: PUBLIC void HTCacheMode_setExpires (HTExpiresMode mode)
2.7 frystyk 671: {
672: HTExpMode = mode;
673: }
674:
2.22 frystyk 675: PUBLIC HTExpiresMode HTCacheMode_expires (void)
2.7 frystyk 676: {
677: return HTExpMode;
678: }
679:
2.22 frystyk 680: /*
681: ** Cache size management. We set the default cache size to 20M.
682: ** We set the minimum size to 5M in order not to get into weird
683: ** problems while writing the cache. The size is indicated in Mega
684: ** bytes
685: */
686: PUBLIC BOOL HTCacheMode_setMaxSize (int size)
687: {
688: long new_size = size < 5 ? MIN_CACHE_SIZE : size * MEGA;
689: if (new_size < HTTotalSize) HTCacheGarbage();
690: HTCacheSize = new_size - SIZE_BUFFER;
691: if (CACHE_TRACE) HTTrace("Cache...... Total cache size: %ld\n", new_size);
692: return YES;
693: }
694:
695: PUBLIC int HTCacheMode_maxSize (void)
696: {
697: return HTCacheSize / MEGA;
698: }
699:
2.7 frystyk 700: /* ------------------------------------------------------------------------- */
2.22 frystyk 701: /* CACHE OBJECT */
2.2 frystyk 702: /* ------------------------------------------------------------------------- */
703:
2.22 frystyk 704: PRIVATE BOOL free_object (HTCache * me)
705: {
706: HT_FREE(me->url);
707: HT_FREE(me->cachename);
708: HT_FREE(me);
709: return YES;
710: }
711:
712: PRIVATE BOOL delete_object (HTList * list, HTCache * me)
713: {
714: if (CACHE_TRACE) HTTrace("Cache....... delete %p from list %p\n",me, list);
715: HTList_removeObject(list, (void *) me);
716: HTTotalSize -= me->size;
717: free_object(me);
718: return YES;
719: }
720:
721: /*
722: ** Create directory path for cache file
723: **
724: ** On exit:
725: ** return YES
726: ** if directories created -- after that caller
727: ** can rely on fopen(cfn,"w") succeeding.
728: **
729: */
730: PRIVATE BOOL HTCache_createLocation (HTCache * me)
731: {
732: if (me && HTCacheRoot) {
733: BOOL status = YES;
734: char * path = NULL;
735: struct stat stat_info;
736: if ((path = (char *) HT_MALLOC(strlen(HTCacheRoot) + 10)) == NULL)
737: HT_OUTOFMEM("HTCache_createLocation");
738: sprintf(path, "%s%d", HTCacheRoot, me->hash);
739: if (HT_STAT(path, &stat_info) == -1) {
740: if (CACHE_TRACE) HTTrace("Cache....... Create dir `%s\'\n", path);
741: if (MKDIR(path, 0777) < 0) {
742: if (CACHE_TRACE) HTTrace("Cache....... Can't create...\n");
743: status = NO;
744: }
745: } else {
746: if (CACHE_TRACE)
747: HTTrace("Cache....... Directory `%s\' already exists\n", path);
748: }
749: HT_FREE(path);
750: return status;
751: }
752: return NO;
753: }
754:
755: /*
756: ** Find a cache filename for this cache object.
757: */
758: PRIVATE BOOL HTCache_findName (HTCache * me)
759: {
760: if (me) {
761: /*
762: ** Create path for this cache entry. We base the cache location on the
763: ** hash calculated as a function of the URL. That way, we ensure a
764: ** resonably uniform distribution.
765: */
766: me->cachename = HTGetTmpFileName(NULL);
767: return HTCache_createLocation(me);
768: }
769: return NO;
770: }
771:
772: /*
2.24 frystyk 773: ** Calculate the corrected_initial_age of the object. We use the time
774: ** when this function is called as the response_time as this is when
775: ** we have received the complete response. This may cause a delay if
776: ** the reponse header is very big but should not cause any non-correct
777: ** behavior.
778: */
779: PRIVATE BOOL calculate_time (HTCache * me, HTRequest * request)
780: {
781: if (me && request) {
782: HTParentAnchor * anchor = HTRequest_anchor(request);
783: me->response_time = time(NULL);
784: me->date = HTAnchor_date(anchor);
785: me->expires = HTAnchor_expires(anchor);
786: {
787: time_t apparent_age = HTMAX(0, me->response_time - me->date);
788: time_t corrected_received_age = HTMAX(apparent_age, HTAnchor_age(anchor));
789: time_t response_delay = me->response_time - HTRequest_date(request);
790: me->corrected_initial_age = corrected_received_age + response_delay;
791: }
792:
793: /*
794: ** Estimate an expires time using the max-age and expires time. If we
795: ** don't have an explicit expires time then set it to 10% of the LM
796: ** date. If no LM date is available then use 24 hours.
797: */
798: {
799: time_t freshness_lifetime = HTAnchor_maxAge(anchor);
800: if (freshness_lifetime < 0) {
801: if (me->expires < 0) {
802: time_t lm = HTAnchor_lastModified(anchor);
803: if (lm < 0)
804: freshness_lifetime = 24*3600; /* 24 hours */
805: else
806: freshness_lifetime = (me->date - lm) / 10;
807: } else
808: freshness_lifetime = me->expires - me->date;
809: }
810: me->freshness_lifetime = HTMAX(0, freshness_lifetime);
811: }
812: if (CACHE_TRACE) {
813: HTTrace("Cache....... Received Age %d, corrected %d, freshness lifetime %d\n",
814: HTAnchor_age(anchor),
815: me->corrected_initial_age,
816: me->freshness_lifetime);
817: }
818: return YES;
819: }
820: return NO;
821: }
822:
823: /*
2.22 frystyk 824: ** Create a new cache entry and add it to the list
825: */
826: PRIVATE HTCache * HTCache_new (HTRequest * request, HTParentAnchor * anchor)
827: {
828: HTList * list = NULL; /* Current list in cache */
829: HTCache * pres = NULL;
830: int hash = 0;
831: char * url = NULL;
832: if (!request || !anchor) {
833: if (CORE_TRACE) HTTrace("Cache....... Bad argument\n");
834: return NULL;
835: }
836:
837: /* Find a hash for this anchor */
838: if ((url = HTAnchor_address((HTAnchor *) anchor))) {
839: char * ptr;
840: for (ptr=url; *ptr; ptr++)
841: hash = (int) ((hash * 3 + (*(unsigned char *) ptr)) % HASH_SIZE);
842: if (!CacheTable) {
843: if ((CacheTable = (HTList **) HT_CALLOC(HASH_SIZE,
844: sizeof(HTList *))) == NULL)
845: HT_OUTOFMEM("HTCache_new");
846: }
847: if (!CacheTable[hash]) CacheTable[hash] = HTList_new();
848: list = CacheTable[hash];
849: } else
850: return NULL;
851:
852: /* Search the cache */
853: {
854: HTList * cur = list;
855: while ((pres = (HTCache *) HTList_nextObject(cur))) {
856: if (!strcmp(pres->url, url)) break;
857: }
858: }
859:
860: /* If not found then create new cache object, else use existing one */
861: if (!pres) {
862: if ((pres = (HTCache *) HT_CALLOC(1, sizeof(HTCache))) == NULL)
863: HT_OUTOFMEM("HTCache_new");
864: pres->hash = hash;
865: pres->url = url;
866: HTCache_findName(pres);
867: HTList_addObject(list, (void *) pres);
868: new_entries++;
2.24 frystyk 869: } else
870: HT_FREE(url);
2.22 frystyk 871:
872: if (HTCache_hasLock(pres)) {
873: if (CACHE_TRACE) HTTrace("Cache....... Entry %p locked\n");
874: return pres;
875: }
876:
2.24 frystyk 877: /* Calculate the various times */
878: calculate_time(pres, request);
879:
880: /* Update the etag */
2.22 frystyk 881: {
2.24 frystyk 882: char * etag = HTAnchor_etag(anchor);
883: if (etag) StrAllocCopy(pres->etag, etag);
2.22 frystyk 884: }
885:
2.24 frystyk 886: /* Must we revalidate this every time */
2.22 frystyk 887: pres->must_revalidate = HTAnchor_mustRevalidate(anchor);
888: return pres;
889: }
890:
891: /*
892: ** Set the size of a cached object. We don't consider the metainformation as
893: ** part of the size which is the the reason for why the min cache size should
894: ** not be less than 5M. When we set the cache size we also check whether we
895: ** should run the gc or not.
896: */
897: PUBLIC BOOL HTCache_setSize (HTCache * cache, long size)
898: {
899: if (cache) {
900: if (cache->size > 0) HTTotalSize -= cache->size;
901: cache->size = size;
902: HTTotalSize += size;
903: if (CACHE_TRACE) HTTrace("Cache....... Total size %ld\n", HTTotalSize);
904: if (HTTotalSize > HTCacheSize) HTCacheGarbage();
905: return YES;
906: }
907: return NO;
908: }
909:
910: PUBLIC long HTCache_size (HTCache * cache)
911: {
912: return cache ? cache->size : -1;
913: }
914:
2.2 frystyk 915: /*
916: ** Verifies if a cache object exists for this URL and if so returns a URL
917: ** for the cached object. It does not verify whether the object is valid or
918: ** not, for example it might have expired.
919: **
2.17 frystyk 920: ** Returns: file name If OK (must be freed by caller)
2.2 frystyk 921: ** NULL If no cache object found
922: */
2.22 frystyk 923: PUBLIC HTCache * HTCache_find (HTParentAnchor * anchor)
924: {
925: HTList * list = NULL;
926: HTCache * pres = NULL;
927:
928: /* Find a hash entry for this URL */
929: if (HTCacheMode_enabled() && anchor && CacheTable) {
930: char * url = HTAnchor_address((HTAnchor *) anchor);
931: int hash = 0;
932: char * ptr = url;
933: for (; *ptr; ptr++)
934: hash = (int) ((hash * 3 + (*(unsigned char *) ptr)) % HASH_SIZE);
935: if (!CacheTable[hash]) {
936: HT_FREE(url);
937: return NULL;
938: }
939: list = CacheTable[hash];
940:
941: /* Search the cache */
942: {
943: HTList * cur = list;
944: while ((pres = (HTCache *) HTList_nextObject(cur))) {
945: if (!strcmp(pres->url, url)) {
946: if (CACHE_TRACE) HTTrace("Cache....... Found %p hits %d\n",
947: pres, pres->hits);
948: break;
949: }
950: }
951: }
952: HT_FREE(url);
953: }
954: return pres;
955: }
956:
957: /* HTCache_delete
958: ** --------------
959: ** Deletes a cache entry
960: */
961: PRIVATE BOOL HTCache_delete (HTCache * cache)
2.2 frystyk 962: {
2.22 frystyk 963: if (cache && CacheTable) {
964: HTList * cur = CacheTable[cache->hash];
965: return cur && delete_object(cur, cache);
966: }
967: return NO;
968: }
969:
970: /* HTCache_deleteAll
971: ** -----------------
972: ** Destroys all cache entried in memory but does not write anything to
973: ** disk
974: */
975: PUBLIC BOOL HTCache_deleteAll (void)
976: {
977: if (CacheTable) {
978: HTList * cur;
979: int cnt;
980:
981: /* Delete the rest */
982: for (cnt=0; cnt<HASH_SIZE; cnt++) {
983: if ((cur = CacheTable[cnt])) {
984: HTCache * pres;
985: while ((pres = (HTCache *) HTList_nextObject(cur)) != NULL)
986: free_object(pres);
987: }
988: HTList_delete(CacheTable[cnt]);
2.2 frystyk 989: }
2.22 frystyk 990: HT_FREE(CacheTable);
991: HTTotalSize = 0L;
992: return YES;
2.2 frystyk 993: }
2.22 frystyk 994: return NO;
2.2 frystyk 995: }
996:
997: /*
2.25 ! frystyk 998: ** Is we have a valid entry in the cache then we also need a location
! 999: ** where we can get it. Hopefully, we may be able to access it
! 1000: ** thourgh one of our protocol modules, for example the local file
! 1001: ** module. The name returned is in URL syntax and must be freed by
! 1002: ** the caller
! 1003: */
! 1004: PRIVATE char * HTCache_location (HTCache * cache, BOOL meta)
! 1005: {
! 1006: char * local = NULL;
! 1007: if (cache && HTCacheRoot) {
! 1008: if (meta) {
! 1009: if ((local = (char *) HT_MALLOC(strlen(HTCacheRoot) + 10 +
! 1010: strlen(HT_CACHE_META) +
! 1011: strlen(cache->cachename))) == NULL)
! 1012: HT_OUTOFMEM("HTCache_location");
! 1013: sprintf(local, "%s%d/%s%s", HTCacheRoot, cache->hash, cache->cachename,
! 1014: HT_CACHE_META);
! 1015: } else {
! 1016: if ((local = (char *) HT_MALLOC(strlen(HTCacheRoot) + 10 +
! 1017: strlen(cache->cachename))) == NULL)
! 1018: HT_OUTOFMEM("HTCache_location");
! 1019: sprintf(local, "%s%d/%s", HTCacheRoot, cache->hash, cache->cachename);
! 1020: }
! 1021: }
! 1022: return local;
! 1023: }
! 1024:
! 1025: /*
! 1026: ** Remove from disk. You must explicitly remove a lock
! 1027: ** before this operation can succeed
! 1028: */
! 1029: PRIVATE BOOL flush_object (HTCache * cache)
! 1030: {
! 1031: if (cache && !HTCache_hasLock(cache)) {
! 1032: char * head = HTCache_location(cache, YES);
! 1033: char * body = HTCache_location(cache, NO);
! 1034: REMOVE(head);
! 1035: REMOVE(body);
! 1036: HT_FREE(head);
! 1037: HT_FREE(body);
! 1038: return YES;
! 1039: }
! 1040: return NO;
! 1041: }
! 1042:
! 1043: /* HTCache_flushAll
! 1044: ** ----------------
! 1045: ** Destroys all cache entried in memory and on disk. Resets the cache
! 1046: ** to empty but the cache does not have to be reinitialized before we
! 1047: ** can use it again.
! 1048: */
! 1049: PUBLIC BOOL HTCache_flushAll (void)
! 1050: {
! 1051: if (CacheTable) {
! 1052: HTList * cur;
! 1053: int cnt;
! 1054:
! 1055: /* Delete the rest */
! 1056: for (cnt=0; cnt<HASH_SIZE; cnt++) {
! 1057: if ((cur = CacheTable[cnt])) {
! 1058: HTCache * pres;
! 1059: while ((pres = (HTCache *) HTList_nextObject(cur)) != NULL) {
! 1060: flush_object(pres);
! 1061: free_object(pres);
! 1062: }
! 1063: }
! 1064: HTList_delete(CacheTable[cnt]);
! 1065: CacheTable[cnt] = NULL;
! 1066: }
! 1067:
! 1068: /* Write the new empty index to disk */
! 1069: HTCacheIndex_write(HTCacheRoot);
! 1070: HTTotalSize = 0L;
! 1071: return YES;
! 1072: }
! 1073: return NO;
! 1074: }
! 1075:
! 1076: /*
2.2 frystyk 1077: ** This function checks whether a document has expired or not.
1078: ** The check is based on the metainformation passed in the anchor object
2.22 frystyk 1079: ** The function returns the level of validation needed for getting a fresh
1080: ** version. We also check the cache control directives in the request to
1081: ** see if they change the freshness discission.
1082: */
1083: PUBLIC HTReload HTCache_isFresh (HTCache * cache, HTRequest * request)
1084: {
1085: HTAssocList * cc = HTRequest_cacheControl(request);
1086: if (cache) {
1087: time_t max_age = -1;
1088: time_t max_stale = -1;
1089: time_t min_fresh = -1;
1090:
1091: /*
1092: ** In case this entry is of type "must-revalidate" then we just
1093: ** go ahead and validate it.
1094: */
1095: if (cache->must_revalidate)
1096: return HT_CACHE_VALIDATE;
1097: /*
1098: ** Check whether we have any special constraints like min-fresh in
1099: ** the cache control
1100: */
1101: if (cc) {
1102: char * token = NULL;
1103: if ((token = HTAssocList_findObject(cc, "max-age")))
1104: max_age = atol(token);
1105: if ((token = HTAssocList_findObject(cc, "max-stale")))
1106: max_stale = atol(token);
1107: if ((token = HTAssocList_findObject(cc, "min-fresh")))
1108: min_fresh = atol(token);
1109: }
1110:
1111: /*
1112: ** Now do the checking against the age constraints that we've got
1113: */
1114: {
1115: time_t resident_time = time(NULL) - cache->response_time;
1116: time_t current_age = cache->corrected_initial_age + resident_time;
1117:
1118: /*
1119: ** Check that the max-age, max-stale, and min-fresh directives
1120: ** given in the request cache control header is followed.
1121: */
1122: if (max_age >= 0 && current_age > max_age) {
1123: if (CACHE_TRACE) HTTrace("Cache....... Max-age validation\n");
1124: return HT_CACHE_VALIDATE;
1125: }
1126: if (min_fresh >= 0 &&
1127: cache->freshness_lifetime < current_age + min_fresh) {
1128: if (CACHE_TRACE) HTTrace("Cache....... Min-fresh validation\n");
1129: return HT_CACHE_VALIDATE;
1130: }
1131:
1132: return (cache->freshness_lifetime +
1133: (max_stale >= 0 ? max_stale : 0) > current_age) ?
1134: HT_CACHE_OK : HT_CACHE_VALIDATE;
1135: }
1136: }
1137: return HT_CACHE_FLUSH;
1138: }
1139:
1140: /*
1141: ** While we are creating a new cache object or while we are validating an
1142: ** existing one, we must have a lock on the entry so that not other
1143: ** requests can get to it in the mean while.
1144: */
1145: PUBLIC BOOL HTCache_getLock (HTCache * cache, HTRequest * request)
1146: {
1147: if (cache && request) {
1148: if (CACHE_TRACE) HTTrace("Cache....... Locking cache entry %p\n", cache);
1149: cache->lock = request;
1150: return YES;
1151: }
1152: return NO;
1153: }
1154:
1155: PUBLIC BOOL HTCache_releaseLock (HTCache * cache)
1156: {
1157: if (cache) {
1158: if (CACHE_TRACE) HTTrace("Cache....... Unlocking cache entry %p\n", cache);
1159: cache->lock = NULL;
1160: return YES;
1161: }
1162: return NO;
1163: }
1164:
1165: PUBLIC BOOL HTCache_hasLock (HTCache * cache)
1166: {
1167: return cache && cache->lock;
1168: }
1169:
1170: PUBLIC BOOL HTCache_breakLock (HTCache * cache, HTRequest * request)
1171: {
1172: if (cache && cache->lock) {
1173: if (cache->lock == request) {
1174: if (CACHE_TRACE)
1175: HTTrace("Cache....... Breaking lock on entry %p\n", cache);
1176: cache->lock = NULL;
1177: return YES;
1178: }
1179: }
1180: return NO;
1181: }
1182:
1183: /*
1184: ** Is we have a valid entry in the cache then we also need a location
1185: ** where we can get it. Hopefully, we may be able to access it
1186: ** thourgh one of our protocol modules, for example the local file
1187: ** module. The name returned is in URL syntax and must be freed by
1188: ** the caller
1189: */
1190: PUBLIC char * HTCache_name (HTCache * cache)
1191: {
1192: if (cache && HTCacheRoot) {
1193: char * local = HTCache_location(cache, NO);
1194: char * url = HTParse(local, "cache:", PARSE_ALL);
1195: HT_FREE(local);
1196: return url;
1197: }
1198: return NULL;
1199: }
1200:
1201: /*
1202: ** Remove from memory AND from disk. You must explicitly remove a lock
1203: ** before this operation can succeed
2.2 frystyk 1204: */
2.22 frystyk 1205: PUBLIC BOOL HTCache_remove (HTCache * cache)
2.2 frystyk 1206: {
2.25 ! frystyk 1207: return flush_object(cache) && HTCache_delete(cache);
2.22 frystyk 1208: }
1209:
1210: /*
1211: ** Walk through the set of headers and write those out that we are allowed
1212: ** to store in the cache. We look into the connection header to see what the
1213: ** hop-by-hop headers are and also into the cache-control directive to see what
1214: ** headers should not be cached.
1215: */
1216: PRIVATE BOOL meta_write (FILE * fp, HTRequest * request, HTAssocList * headers)
1217: {
1218: if (fp && request && headers) {
1219: HTParentAnchor * anchor = HTRequest_anchor(request);
1220: HTAssocList * connection = HTRequest_serverConnection(request);
1221: char * nocache = HTAnchor_noCache(anchor);
1222:
1223: /*
2.24 frystyk 1224: ** Check whether either the connection header or the cache control
1225: ** header includes header names that we should not cache
2.22 frystyk 1226: */
1227: if (connection || nocache) {
1228:
2.24 frystyk 1229: /*
1230: ** Walk though the cache control no-cache directive
1231: */
1232: if (nocache) {
1233: char * line = NULL;
1234: char * ptr = NULL;
1235: char * field = NULL;
1236: StrAllocCopy(line, nocache); /* Get our own copy */
1237: ptr = line;
1238: while ((field = HTNextField(&ptr)) != NULL)
1239: HTAssocList_removeObject(headers, field);
1240: HT_FREE(line);
1241: }
1242:
1243: /*
1244: ** Walk though the connection header
1245: */
1246: if (connection) {
1247: HTAssoc * pres;
1248: while ((pres=(HTAssoc *) HTAssocList_nextObject(connection))) {
1249: char * field = HTAssoc_name(pres);
1250: HTAssocList_removeObject(headers, field);
1251: }
1252: }
2.22 frystyk 1253: }
2.24 frystyk 1254:
2.22 frystyk 1255: /*
1256: ** Write out the remaining list of headerss
1257: */
1258: {
1259: HTAssocList * cur = headers;
1260: HTAssoc * pres;
1261: while ((pres = (HTAssoc *) HTAssocList_nextObject(cur))) {
1262: if (fprintf(fp, "%s: %s\n", HTAssoc_name(pres), HTAssoc_value(pres))<0) {
1263: if (CACHE_TRACE) HTTrace("Cache....... Error writing metainfo\n");
1264: return NO;
1265: }
1266: }
1267: }
1268: return YES;
1269: }
1270: return NO;
2.2 frystyk 1271: }
1272:
1273: /* ------------------------------------------------------------------------- */
2.22 frystyk 1274: /* CACHE WRITER */
2.1 frystyk 1275: /* ------------------------------------------------------------------------- */
1276:
2.22 frystyk 1277: /*
1278: ** Save the metainformation along with the data object. If no headers
1279: ** are available then the meta file is empty
1280: */
2.23 frystyk 1281: PUBLIC BOOL HTCache_writeMeta (HTCache * cache, HTRequest * request)
2.22 frystyk 1282: {
1283: if (cache && request) {
1284: BOOL status;
1285: FILE * fp;
1286: HTAssocList * headers = HTRequest_headers(request);
1287: char * name = HTCache_location(cache, YES);
1288: if ((fp = fopen(name, "wb")) == NULL) {
1289: if (CACHE_TRACE)
1290: HTTrace("Cache....... Can't open `%s\' for writing\n", name);
1291: HTCache_remove(cache);
1292: HT_FREE(name);
1293: return NO;
1294: }
1295: status = meta_write(fp, request, headers);
1296: fclose(fp);
1297: HT_FREE(name);
1298: return status;
1299: }
1300: return NO;
1301: }
1302:
1303: /*
1304: ** Merge metainformation with existing version. This means that we have had a
2.24 frystyk 1305: ** successful validation and hence a true cache hit. We only regard the
1306: ** following headers: Date, etag, content-location, expires, cache-control,
1307: ** and vary.
2.22 frystyk 1308: */
2.23 frystyk 1309: PUBLIC BOOL HTCache_updateMeta (HTCache * cache, HTRequest * request)
2.22 frystyk 1310: {
2.24 frystyk 1311: if (cache && request) {
1312: HTParentAnchor * anchor = HTRequest_anchor(request);
1313: char * etag = HTAnchor_etag(anchor);
2.22 frystyk 1314: cache->hits++;
2.24 frystyk 1315: if (etag) StrAllocCopy(cache->etag, etag);
1316: return calculate_time(cache, request);
2.22 frystyk 1317: }
1318: return NO;
1319: }
1320:
1321: PUBLIC BOOL HTCache_addHit (HTCache * cache)
1322: {
1323: if (cache) {
1324: cache->hits++;
1325: if (CACHE_TRACE) HTTrace("Cache....... Hits for %p is %d\n",
1326: cache, cache->hits);
1327: return YES;
1328: }
1329: return NO;
1330: }
1331:
2.12 frystyk 1332: PRIVATE int HTCache_flush (HTStream * me)
2.1 frystyk 1333: {
1334: return (fflush(me->fp) == EOF) ? HT_ERROR : HT_OK;
1335: }
1336:
2.15 frystyk 1337: PRIVATE int HTCache_putBlock (HTStream * me, const char * s, int l)
2.1 frystyk 1338: {
1339: int status = (fwrite(s, 1, l, me->fp) != l) ? HT_ERROR : HT_OK;
1340: if (l > 1 && status == HT_OK)
1341: (void) HTCache_flush(me);
1342: return status;
1343: }
1344:
2.12 frystyk 1345: PRIVATE int HTCache_putChar (HTStream * me, char c)
2.1 frystyk 1346: {
1347: return HTCache_putBlock(me, &c, 1);
1348: }
1349:
2.15 frystyk 1350: PRIVATE int HTCache_putString (HTStream * me, const char * s)
2.1 frystyk 1351: {
1352: return HTCache_putBlock(me, s, (int) strlen(s));
1353: }
1354:
2.17 frystyk 1355: PRIVATE int HTCache_free (HTStream * me)
2.1 frystyk 1356: {
2.22 frystyk 1357: if (me) {
1358: if (me->fp) fclose(me->fp);
1359: /*
1360: ** We are done storing the object body and can update the cache entry.
1361: ** Also update the meta information entry on disk as well. When we
1362: ** are done we don't need the lock anymore.
1363: */
1364: if (me->cache) {
1365: HTParentAnchor * anchor = HTRequest_anchor(me->request);
2.23 frystyk 1366: HTCache_writeMeta(me->cache, me->request);
2.22 frystyk 1367: HTCache_releaseLock(me->cache);
1368:
1369: /*
1370: ** Set the size and maybe do gc
1371: */
1372: HTCache_setSize(me->cache, HTAnchor_length(anchor));
1373: }
1374:
1375: /*
1376: ** In order not to loose information, we dump the current cache index
1377: ** every time we have created DUMP_FREQUENCY new entries
1378: */
1379: if (new_entries > DUMP_FREQUENCY) {
1380: HTCacheIndex_write(HTCacheRoot);
1381: new_entries = 0;
1382: }
1383: HT_FREE(me);
1384: }
2.1 frystyk 1385: return HT_OK;
1386: }
1387:
2.12 frystyk 1388: PRIVATE int HTCache_abort (HTStream * me, HTList * e)
2.1 frystyk 1389: {
2.22 frystyk 1390: if (CACHE_TRACE) HTTrace("Cache....... ABORTING\n");
1391: if (me->fp) fclose(me->fp);
2.23 frystyk 1392: if (me->cache) {
1393: HTCache_breakLock(me->cache, me->request);
1394: HTCache_remove(me->cache);
1395: }
2.13 frystyk 1396: HT_FREE(me);
2.1 frystyk 1397: return HT_ERROR;
1398: }
1399:
2.15 frystyk 1400: PRIVATE const HTStreamClass HTCacheClass =
2.1 frystyk 1401: {
1402: "Cache",
1403: HTCache_flush,
2.17 frystyk 1404: HTCache_free,
2.1 frystyk 1405: HTCache_abort,
1406: HTCache_putChar,
1407: HTCache_putString,
1408: HTCache_putBlock
1409: };
1410:
2.22 frystyk 1411: PUBLIC HTStream * HTCacheWriter (HTRequest * request,
1412: void * param,
1413: HTFormat input_format,
1414: HTFormat output_format,
1415: HTStream * output_stream)
2.1 frystyk 1416: {
2.22 frystyk 1417: HTCache * cache = NULL;
1418: FILE * fp = NULL;
2.19 frystyk 1419: HTParentAnchor * anchor = HTRequest_anchor(request);
2.12 frystyk 1420: if (!HTCacheEnable) {
2.14 eric 1421: if (CACHE_TRACE) HTTrace("Cache....... Not enabled\n");
2.22 frystyk 1422: return NULL;
2.1 frystyk 1423: }
1424:
2.22 frystyk 1425: /* Get a new cache entry */
1426: if ((cache = HTCache_new(request, anchor)) == NULL) {
1427: if (CACHE_TRACE) HTTrace("Cache....... Can't get a cache object\n");
1428: return NULL;
1429: }
1430:
1431: /* Test that the cached object is not locked */
1432: if (HTCache_hasLock(cache)) {
1433: if (HTCache_breakLock(cache, request) == NO) {
1434: if (CACHE_TRACE) HTTrace("Cache....... Entry already in use\n");
1435: return NULL;
1436: }
1437: }
1438: HTCache_getLock(cache, request);
1439:
1440: /*
1441: ** Test that we can actually write to the cache file. If the entry already
1442: ** existed then it will be overridden with the new data.
1443: */
1444: {
1445: char * name = HTCache_location(cache, NO);
1446: if ((fp = fopen(name, "wb")) == NULL) {
1447: if (CACHE_TRACE)
1448: HTTrace("Cache....... Can't open `%s\' for writing\n", name);
1449: HTCache_delete(cache);
1450: HT_FREE(name);
1451: return NULL;
1452: } else
1453: if (CACHE_TRACE) HTTrace("Cache....... Creating file %s\n", name);
1454: HT_FREE(name);
1455: }
2.1 frystyk 1456:
1457: /* Set up the stream */
2.22 frystyk 1458: {
1459: HTStream * me = NULL;
1460: if ((me = (HTStream *) HT_CALLOC(1, sizeof(HTStream))) == NULL)
1461: HT_OUTOFMEM("Cache");
1462: me->isa = &HTCacheClass;
1463: me->request = request;
1464: me->cache = cache;
1465: me->fp = fp;
1466: return me;
1467: }
1468: return NULL;
1469: }
1470:
1471: /* ------------------------------------------------------------------------- */
1472: /* CACHE READER */
1473: /* ------------------------------------------------------------------------- */
1474:
1475: /*
1476: ** This function closes the connection and frees memory.
1477: ** Returns YES on OK, else NO
1478: */
1479: PRIVATE int CacheCleanup (HTRequest * request, int status)
1480: {
1481: HTNet * net = HTRequest_net(request);
1482: cache_info * cache = (cache_info *) HTNet_context(net);
1483: HTStream * input = HTRequest_inputStream(request);
1484:
1485: /* Free stream with data TO local cache system */
2.24 frystyk 1486: #if 0
2.22 frystyk 1487: if (input) {
1488: if (status == HT_INTERRUPTED)
1489: (*input->isa->abort)(input, NULL);
1490: else
1491: (*input->isa->_free)(input);
1492: HTRequest_setInputStream(request, NULL);
1493: }
2.24 frystyk 1494: #endif
2.22 frystyk 1495: if (status != HT_IGNORE) {
1496: if (cache) {
1497: HT_FREE(cache->local);
1498: HT_FREE(cache);
1499: }
1500: HTNet_delete(net, status);
1501: } else if (cache) {
2.24 frystyk 1502: HTChannel * channel = HTNet_channel(net);
1503: HTChannel_delete(channel, HT_OK);
2.22 frystyk 1504: HT_FREE(cache->local);
1505: }
1506: return YES;
1507: }
1508:
1509: /*
1510: ** This load function loads an object from the cache and puts it to the
1511: ** output defined by the request object. For the moment, this load function
1512: ** handles the persistent cache as if it was on local file but in fact
1513: ** it could be anywhere.
1514: **
1515: ** Returns HT_ERROR Error has occured in call back
1516: ** HT_OK Call back was OK
1517: */
1518: PUBLIC int HTLoadCache (SOCKET soc, HTRequest * request, SockOps ops)
1519: {
1520: int status = HT_ERROR;
1521: HTNet * net = HTRequest_net(request);
1522: HTParentAnchor * anchor = HTRequest_anchor(request);
1523: cache_info * cache; /* Specific access information */
1524:
1525: /*
1526: ** Initiate a new cache structure and bind to request structure
1527: ** This is actually state CACHE_BEGIN, but it can't be in the state
1528: ** machine as we need the structure first.
1529: */
1530: if (ops == FD_NONE) {
1531: if (PROT_TRACE) HTTrace("Load Cache.. Looking for `%s\'\n",
1532: HTAnchor_physical(anchor));
1533: if ((cache = (cache_info *) HT_CALLOC(1, sizeof(cache_info))) == NULL)
1534: HT_OUTOFMEM("HTLoadCACHE");
1535: cache->state = CL_BEGIN;
1536: HTNet_setContext(net, cache);
1537: } if (ops == FD_CLOSE) { /* Interrupted */
1538: HTRequest_addError(request, ERR_FATAL, NO, HTERR_INTERRUPTED,
1539: NULL, 0, "HTLoadCache");
1540: CacheCleanup(request, HT_INTERRUPTED);
1541: return HT_OK;
2.1 frystyk 1542: } else
2.22 frystyk 1543: cache = (cache_info *) HTNet_context(net); /* Get existing copy */
1544:
1545: /* Now jump into the machine. We know the state from the previous run */
1546: while (1) {
1547: switch (cache->state) {
1548: case CL_BEGIN:
1549: if (HTLib_secure()) {
1550: if (PROT_TRACE)
1551: HTTrace("Load Cache.. No access to local file system\n");
1552: cache->state = CL_ERROR;
1553: break;
1554: }
1555: cache->state = CacheTable ?
1556: (HTAnchor_headerParsed(anchor) ? CL_NEED_BODY : CL_NEED_HEAD) :
1557: CL_NEED_INDEX;
1558: break;
1559:
1560: case CL_NEED_HEAD:
1561: {
1562: char * meta = NULL;
1563: StrAllocCopy(meta, HTAnchor_physical(anchor));
1564: StrAllocCat(meta, HT_CACHE_META);
1565: cache->meta = YES;
1566: cache->local = HTWWWToLocal(meta, "",
1567: HTRequest_userProfile(request));
2.24 frystyk 1568: HT_FREE(meta);
2.22 frystyk 1569: if (cache->local) {
1570: if (HT_STAT(cache->local, &cache->stat_info) == -1) {
1571: if (PROT_TRACE)
1572: HTTrace("Load Cache.. Not found `%s\'\n",cache->local);
1573: HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_FOUND,
1574: NULL, 0, "HTLoadCache");
1575: cache->state = CL_ERROR;
1576: break;
1577: }
1578:
1579: /*
1580: ** The cache entry may be empty in which case we just return
1581: */
1582: if (!cache->stat_info.st_size) {
1583: HTRequest_addError(request, ERR_FATAL, NO,HTERR_NO_CONTENT,
1584: NULL, 0, "HTLoadCache");
1585: cache->state = CL_GOT_DATA;
1586: } else
1587: cache->state = CL_NEED_OPEN_FILE;
1588: } else
1589: cache->state = CL_ERROR;
1590: break;
1591: }
1592:
1593: case CL_NEED_INDEX:
1594: case CL_NEED_BODY:
1595: cache->local = HTWWWToLocal(HTAnchor_physical(anchor), "",
1596: HTRequest_userProfile(request));
1597: if (cache->local) {
1598: if (HT_STAT(cache->local, &cache->stat_info) == -1) {
1599: if (PROT_TRACE)
1600: HTTrace("Load Cache.. Not found `%s\'\n", cache->local);
1601: HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_FOUND,
1602: NULL, 0, "HTLoadCache");
1603: cache->state = CL_ERROR;
1604: break;
1605: }
1606:
1607: /*
1608: ** The cache entry may be empty in which case we just return
1609: */
1610: if (!cache->stat_info.st_size) {
1611: HTRequest_addError(request, ERR_FATAL, NO,HTERR_NO_CONTENT,
1612: NULL, 0, "HTLoadCache");
1613: cache->state = CL_NO_DATA;
1614: } else
1615: cache->state = CL_NEED_OPEN_FILE;
1616: } else
1617: cache->state = CL_ERROR;
1618: break;
1619:
1620: case CL_NEED_OPEN_FILE:
1621: status = HTFileOpen(net, cache->local, HT_FT_RDONLY);
1622: if (status == HT_OK) {
1623: /*
1624: ** Create the stream pipe FROM the channel to the application.
1625: ** The target for the input stream pipe is set up using the
1626: ** stream stack.
1627: */
1628: if (cache->meta) {
1629: HTNet_getInput(net,
1630: HTStreamStack(WWW_MIME_HEAD,
1631: HTRequest_debugFormat(request),
1632: HTRequest_debugStream(request),
1633: request, NO), NULL, 0);
1634: cache->state = CL_NEED_CONTENT;
1635: } else {
1636: HTNet_getInput(net,
1637: HTStreamStack(HTAnchor_format(anchor),
1638: HTRequest_outputFormat(request),
1639: HTRequest_outputStream(request),
1640: request, YES), NULL, 0);
1641: HTRequest_setOutputConnected(request, YES);
1642: HTRequest_addError(request, ERR_INFO, NO, HTERR_OK,
1643: NULL, 0, "HTLoadCache");
1644: cache->state = CL_NEED_CONTENT;
1645:
1646: #ifndef NO_UNIX_IO
1647: /* If we are _not_ using preemptive mode and we are Unix fd's
1648: ** then return here to get the same effect as when we are
1649: ** connecting to a socket. That way, HTCache acts just like any
1650: ** other protocol module even though we are in fact doing
1651: ** blocking connect
1652: */
1653: if (!net->preemptive) {
1654: if (PROT_TRACE) HTTrace("Load Cache.. returning\n");
1655: HTEvent_register(net->sockfd, request, (SockOps) FD_READ,
1656: net->cbf, net->priority);
1657: return HT_OK;
1658: }
1659: #endif
1660: }
1661: } else if (status == HT_WOULD_BLOCK || status == HT_PENDING)
1662: return HT_OK;
1663: else {
1664: HTRequest_addError(request, ERR_INFO, NO, HTERR_INTERNAL,
1665: NULL, 0, "HTLoadCache");
1666: cache->state = CL_ERROR; /* Error or interrupt */
1667: }
1668: break;
1669:
1670: case CL_NEED_CONTENT:
1671: status = (*net->input->isa->read)(net->input);
1672: if (status == HT_WOULD_BLOCK)
1673: return HT_OK;
1674: else if (status == HT_LOADED || status == HT_CLOSED) {
1675: cache->state = CL_GOT_DATA;
1676: } else {
1677: HTRequest_addError(request, ERR_INFO, NO, HTERR_FORBIDDEN,
1678: NULL, 0, "HTLoadCache");
1679: cache->state = CL_ERROR;
1680: }
1681: break;
1682:
1683: case CL_GOT_DATA:
1684: if (cache->meta) {
1685: CacheCleanup(request, HT_IGNORE);
1686: cache->state = CL_NEED_BODY;
1687: cache->meta = NO;
1688: } else {
1689: CacheCleanup(request, HT_LOADED);
1690: return HT_OK;
1691: }
1692: break;
2.1 frystyk 1693:
2.22 frystyk 1694: case CL_NO_DATA:
1695: CacheCleanup(request, HT_NO_DATA);
1696: return HT_OK;
1697: break;
1698:
1699: case CL_ERROR:
1700: CacheCleanup(request, HT_ERROR);
1701: return HT_OK;
1702: break;
1703: }
1704: } /* End of while(1) */
2.1 frystyk 1705: }
Webmaster