diff options
author | Xan Lopez <xan@gnome.org> | 2009-08-07 14:27:47 +0300 |
---|---|---|
committer | Xan Lopez <xan@gnome.org> | 2009-08-07 14:28:29 +0300 |
commit | f7ab5d4ac3c47dfed086ff5810d7a60957c5bc09 (patch) | |
tree | a69fb2aad2ec63c64ed6f8784ef4b8eab379a4e8 | |
parent | 8759ed475e7a8759251ccd7bb750c119ef923fe2 (diff) | |
download | libsoup-cache.tar.gz |
Initial attempt to implement cache validation.cache
-rw-r--r-- | libsoup/soup-cache.c | 121 | ||||
-rw-r--r-- | libsoup/soup-cache.h | 31 | ||||
-rw-r--r-- | libsoup/soup-session-async.c | 80 |
3 files changed, 185 insertions, 47 deletions
diff --git a/libsoup/soup-cache.c b/libsoup/soup-cache.c index c6a349cc..c1d69131 100644 --- a/libsoup/soup-cache.c +++ b/libsoup/soup-cache.c @@ -42,6 +42,7 @@ typedef struct _SoupCacheEntry gboolean writing; gboolean dirty; gboolean got_body; + gboolean being_validated; SoupMessageHeaders *headers; GOutputStream *stream; GError *error; @@ -110,14 +111,18 @@ get_cacheability (SoupCache *cache, SoupMessage *msg) switch (msg->status_code) { case SOUP_STATUS_PARTIAL_CONTENT: - case SOUP_STATUS_NOT_MODIFIED: /* We don't cache partial responses, but they only * invalidate cached full responses if the headers - * don't match. Likewise with 304 Not Modified. + * don't match. */ cacheability = SOUP_CACHE_UNCACHEABLE; break; + case SOUP_STATUS_NOT_MODIFIED: + /* A 304 response validates an existing cache entry */ + cacheability = SOUP_CACHE_VALIDATES; + break; + case SOUP_STATUS_MULTIPLE_CHOICES: case SOUP_STATUS_MOVED_PERMANENTLY: case SOUP_STATUS_GONE: @@ -186,6 +191,15 @@ copy_headers (const char *name, const char *value, SoupMessageHeaders *headers) soup_message_headers_append (headers, name, value); } +static void +update_headers (const char *name, const char *value, SoupMessageHeaders *headers) +{ + if (soup_message_headers_get (headers, name)) + soup_message_headers_replace (headers, name, value); + else + soup_message_headers_append (headers, name, value); +} + static guint soup_cache_entry_get_current_age (SoupCacheEntry *entry) { @@ -310,6 +324,7 @@ soup_cache_entry_new (SoupCache *cache, SoupMessage *msg) entry->dirty = TRUE; entry->writing = FALSE; entry->got_body = FALSE; + entry->being_validated = FALSE; entry->data = g_string_new (NULL); entry->pos = 0; entry->error = NULL; @@ -611,6 +626,24 @@ msg_got_headers_cb (SoupMessage *msg, SoupCache *cache) key = soup_message_get_cache_key (msg); soup_cache_entry_delete_by_key (cache, key); g_free (key); + } else if (cacheable & SOUP_CACHE_VALIDATES) { + char *key; + SoupCacheEntry *entry; + + key = soup_message_get_cache_key (msg); + entry = soup_cache_lookup_uri (cache, key); + g_free (key); + + g_return_if_fail (entry); + + entry->being_validated = FALSE; + + /* We update the headers of the existing cache item, + plus its age */ + soup_message_headers_foreach (msg->response_headers, + (SoupMessageHeadersForeachFunc)update_headers, + entry->headers); + soup_cache_entry_set_freshness (entry, msg); } } @@ -649,13 +682,20 @@ soup_cache_send_response (SoupCache *cache, SoupMessage *msg) SoupCacheEntry *entry; char *current_age; + g_return_if_fail (SOUP_IS_CACHE (cache)); + g_return_if_fail (SOUP_IS_MESSAGE (msg)); + key = soup_message_get_cache_key (msg); entry = soup_cache_lookup_uri (cache, key); g_return_if_fail (entry); + /* If we are told to send a response from cache any validation + in course is over by now */ + entry->being_validated = FALSE; + /* Headers */ soup_message_headers_foreach (entry->headers, - (SoupMessageHeadersForeachFunc)copy_headers, + (SoupMessageHeadersForeachFunc)update_headers, msg->response_headers); /* Add 'Age' header with the current age */ @@ -843,7 +883,7 @@ soup_cache_new (const char *cache_dir) * * Returns: whether or not the @cache has a valid response for @msg **/ -gboolean +SoupCacheResponse soup_cache_has_response (SoupCache *cache, SoupMessage *msg) { char *key; @@ -861,10 +901,10 @@ soup_cache_has_response (SoupCache *cache, SoupMessage *msg) * match */ if (!entry) - return FALSE; + return SOUP_CACHE_RESPONSE_STALE; - if (entry->dirty) - return FALSE; + if (entry->dirty || entry->being_validated) + return SOUP_CACHE_RESPONSE_STALE; /* 2. The request method associated with the stored response * allows it to be used for the presented request @@ -876,7 +916,7 @@ soup_cache_has_response (SoupCache *cache, SoupMessage *msg) * probably). */ if (msg->method != SOUP_METHOD_GET) - return FALSE; + return SOUP_CACHE_RESPONSE_STALE; /* 3. Selecting request-headers nominated by the stored * response (if any) match those presented. @@ -897,7 +937,7 @@ soup_cache_has_response (SoupCache *cache, SoupMessage *msg) if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) { g_hash_table_destroy (hash); - return FALSE; + return SOUP_CACHE_RESPONSE_STALE; } if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) { @@ -926,35 +966,35 @@ soup_cache_has_response (SoupCache *cache, SoupMessage *msg) if (max_age != -1) { guint current_age = soup_cache_entry_get_current_age (entry); - /* If we are over max-age and max-stale is not set, do - not use the value from the cache */ + /* If we are over max-age and max-stale is not + set, do not use the value from the cache + without validation */ if (max_age <= current_age && max_stale == -1) - return FALSE; + return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION; } } /* 5. The stored response is either: fresh, allowed to be * served stale or succesfully validated */ - if (entry->must_revalidate) { - return FALSE; - } + if (entry->must_revalidate) + return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION; if (!soup_cache_entry_is_fresh_enough (entry, min_fresh)) { /* Not fresh, can it be served stale? */ if (max_stale != -1) { /* G_MAXINT32 means we accept any staleness */ if (max_stale == G_MAXINT32) - return TRUE; + return SOUP_CACHE_RESPONSE_FRESH; if ((soup_cache_entry_get_current_age (entry) - entry->freshness_lifetime) <= max_stale) - return TRUE; + return SOUP_CACHE_RESPONSE_FRESH; } - return FALSE; + return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION; } - return TRUE; + return SOUP_CACHE_RESPONSE_FRESH; } /** @@ -1028,3 +1068,46 @@ soup_cache_clear (SoupCache *cache) g_hash_table_foreach (hash, (GHFunc)remove_cache_item, cache); } + +SoupMessage* +soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original) +{ + SoupMessage *msg; + SoupURI *uri; + SoupCacheEntry *entry; + char *key; + const char *value; + + g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL); + g_return_val_if_fail (SOUP_IS_MESSAGE (original), NULL); + + /* First copy the data we need from the original message */ + uri = soup_message_get_uri (original); + msg = soup_message_new_from_uri (original->method, uri); + + soup_message_headers_foreach (original->request_headers, + (SoupMessageHeadersForeachFunc)copy_headers, + msg->request_headers); + + /* Now add the validator entries in the header from the cached + data */ + key = soup_message_get_cache_key (original); + entry = soup_cache_lookup_uri (cache, key); + g_free (key); + + g_return_val_if_fail (entry, NULL); + + entry->being_validated = TRUE; + + value = soup_message_headers_get (entry->headers, "Last-Modified"); + if (value) + soup_message_headers_append (msg->request_headers, + "Last-Modified", + value); + value = soup_message_headers_get (entry->headers, "ETag"); + if (value) + soup_message_headers_append (msg->request_headers, + "ETag", + value); + return msg; +} diff --git a/libsoup/soup-cache.h b/libsoup/soup-cache.h index dc7de55a..ca1ce32e 100644 --- a/libsoup/soup-cache.h +++ b/libsoup/soup-cache.h @@ -22,9 +22,16 @@ typedef struct _SoupCachePrivate SoupCachePrivate; typedef enum { SOUP_CACHE_CACHEABLE = (1 << 0), SOUP_CACHE_UNCACHEABLE = (1 << 1), - SOUP_CACHE_INVALIDATES = (1 << 2) + SOUP_CACHE_INVALIDATES = (1 << 2), + SOUP_CACHE_VALIDATES = (1 << 3) } SoupCacheability; +typedef enum { + SOUP_CACHE_RESPONSE_FRESH, + SOUP_CACHE_RESPONSE_NEEDS_VALIDATION, + SOUP_CACHE_RESPONSE_STALE +} SoupCacheResponse; + struct _SoupCache { GObject parent_instance; @@ -43,16 +50,18 @@ typedef struct { void (*_libsoup_reserved3) (void); } SoupCacheClass; -GType soup_cache_get_type (void); -SoupCache* soup_cache_new (const char *cache_dir); -gboolean soup_cache_has_response (SoupCache *cache, - SoupMessage *msg); -void soup_cache_send_response (SoupCache *cache, - SoupMessage *msg); -SoupCacheability soup_cache_get_cacheability (SoupCache *cache, - SoupMessage *msg); -void soup_cache_flush (SoupCache *cache); -void soup_cache_clear (SoupCache *cache); +GType soup_cache_get_type (void); +SoupCache* soup_cache_new (const char *cache_dir); +SoupCacheResponse soup_cache_has_response (SoupCache *cache, + SoupMessage *msg); +void soup_cache_send_response (SoupCache *cache, + SoupMessage *msg); +SoupCacheability soup_cache_get_cacheability (SoupCache *cache, + SoupMessage *msg); +void soup_cache_flush (SoupCache *cache); +void soup_cache_clear (SoupCache *cache); +SoupMessage* soup_cache_generate_conditional_request (SoupCache *cache, + SoupMessage *original); G_END_DECLS diff --git a/libsoup/soup-session-async.c b/libsoup/soup-session-async.c index e01f8883..6d3f70ad 100644 --- a/libsoup/soup-session-async.c +++ b/libsoup/soup-session-async.c @@ -10,6 +10,7 @@ #endif #include "soup-address.h" +#include "soup-cache.h" #include "soup-session-async.h" #include "soup-session-private.h" #include "soup-address.h" @@ -291,6 +292,50 @@ got_connection (SoupConnection *conn, guint status, gpointer session) } static void +update_headers (const char *name, const char *value, SoupMessageHeaders *headers) +{ + if (soup_message_headers_get (headers, name)) + soup_message_headers_replace (headers, name, value); + else + soup_message_headers_append (headers, name, value); +} + +static void +conditional_get_ready_cb (SoupSession *session, SoupMessage *msg, SoupMessage *original) +{ + if (msg->status_code == SOUP_STATUS_NOT_MODIFIED) { + SoupCache *cache; + + cache = soup_session_get_cache (session); + soup_cache_send_response (cache, original); + } else { + SoupBuffer *buffer; + goffset offset; + + soup_message_set_status (original, msg->status_code); + soup_message_headers_foreach (msg->response_headers, + (SoupMessageHeadersForeachFunc)update_headers, + original->response_headers); + soup_message_got_headers (original); + soup_message_body_free (original->response_body); + original->response_body = soup_message_body_new (); + + offset = 0; + while (offset <= msg->response_body->length) { + buffer = soup_message_body_get_chunk (msg->response_body, offset); + if (!buffer) break; + soup_message_body_append_buffer (original->response_body, buffer); + soup_message_got_chunk (original, buffer); + offset += buffer->length; + soup_buffer_free (buffer); + } + + soup_message_got_body (original); + soup_message_finished (original); + } +} + +static void run_queue (SoupSessionAsync *sa) { SoupSession *session = SOUP_SESSION (sa); @@ -310,12 +355,6 @@ run_queue (SoupSessionAsync *sa) item = soup_message_queue_next (queue, item)) { msg = item->msg; - if (cache && soup_cache_has_response (cache, msg)) { - soup_message_set_io_status (msg, SOUP_MESSAGE_IO_STATUS_RUNNING); - soup_cache_send_response (cache, msg); - continue; - } - /* CONNECT messages are handled specially */ if (msg->method == SOUP_METHOD_CONNECT) continue; @@ -324,6 +363,24 @@ run_queue (SoupSessionAsync *sa) soup_message_io_in_progress (msg)) continue; + if (cache) { + SoupCacheResponse response; + + response = soup_cache_has_response (cache, msg); + if (response == SOUP_CACHE_RESPONSE_FRESH) { + soup_message_set_io_status (msg, SOUP_MESSAGE_IO_STATUS_RUNNING); + soup_cache_send_response (cache, msg); + continue; + } else if (response == SOUP_CACHE_RESPONSE_NEEDS_VALIDATION) { + SoupMessage *conditional_msg; + + soup_message_set_io_status (msg, SOUP_MESSAGE_IO_STATUS_RUNNING); /* ? */ + conditional_msg = soup_cache_generate_conditional_request (cache, msg); + soup_session_queue_message (session, conditional_msg, conditional_get_ready_cb, msg); + continue; + } + } + if (proxy_resolver && !item->resolved_proxy_addr) { resolve_proxy_addr (item, proxy_resolver); continue; @@ -412,21 +469,10 @@ do_idle_run_queue (SoupSession *session) } } -static gboolean -had_cache (SoupMessageQueueItem *item) -{ - SoupCache *cache; - - cache = soup_session_get_cache (item->session); - soup_cache_send_response (cache, item->session, item->msg); - return FALSE; -} - static void queue_message (SoupSession *session, SoupMessage *req, SoupSessionCallback callback, gpointer user_data) { - SoupCache *cache; SoupMessageQueueItem *item; SOUP_SESSION_CLASS (soup_session_async_parent_class)->queue_message (session, req, callback, user_data); |