[libsoup/cache] Initial attempt to implement cache validation.



commit f7ab5d4ac3c47dfed086ff5810d7a60957c5bc09
Author: Xan Lopez <xan gnome org>
Date:   Fri Aug 7 14:27:47 2009 +0300

    Initial attempt to implement cache validation.

 libsoup/soup-cache.c         |  121 +++++++++++++++++++++++++++++++++++-------
 libsoup/soup-cache.h         |   31 +++++++----
 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 c6a349c..c1d6913 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 dc7de55..ca1ce32 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 e01f888..6d3f70a 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);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]