As stated in my previous design report, the cache manager uses a simple hash table and does table lookups to check the cache.
Using this framework and taking advantage of the plethora of utility functions included with it, implementing the caching mechanism of choice is not particularly hard.
The cache implementation can be roughly divided into two parts, cache reading and cache writing:
When the client object enters the 'Cache open' state, the proxy knows that the remote host does not have to be accessed, the cache entry can be used instead. It locates the correct cache entry and moves to the 'Send cached data' state. There it creates a netpipe between the cache and the remote client, and moves the data between the two ends of that netpipe. The state updating function will, as before, make sure that the client object does not change states until the data has been sent to the remote client. When the data has been sent, the client object enters the 'Client cleanup' state where the memory allocated for the client is released and the client object is deleted.
When the client object enter the 'Processing cache' state, it starts by checking if the response should be in cache and if the response is valid (200). If so, it creates the necessary cache file descriptors and hash keys, and moves to the 'Write to cache' state. There it creates the netpipe object which will handle the data transfer between the buffer and the cache file. After the data has been written to the cache, the client object goes to the 'Finish cache write' state, where the expire time is set and finally the hash table updated.
The expire date of each cache entry is set to a constant time period, defined as a global constant by the user. Expired cache entries are checked every time the cache is accessed, and deleted if expired. The cache is also purged of expired entries when the cache mapping is periodically written to disk.
Writing the cache mapping periodically to disk was nicely implemented in Jeremy's and Seth's framework. This allows a graceful reboot after a system crash, since most of the already written cache can be re-used. I decided to incorporte that implementation into my version, even though my original design listed it as a possible future project, since the code was already at hand.