summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXan Lopez <xan@gnome.org>2009-08-07 14:27:47 +0300
committerXan Lopez <xan@gnome.org>2009-08-07 14:28:29 +0300
commitf7ab5d4ac3c47dfed086ff5810d7a60957c5bc09 (patch)
treea69fb2aad2ec63c64ed6f8784ef4b8eab379a4e8
parent8759ed475e7a8759251ccd7bb750c119ef923fe2 (diff)
downloadlibsoup-cache.tar.gz
Initial attempt to implement cache validation.cache
-rw-r--r--libsoup/soup-cache.c121
-rw-r--r--libsoup/soup-cache.h31
-rw-r--r--libsoup/soup-session-async.c80
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);