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