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