libsoup r1029 - in branches/libsoup-2.4: . docs/reference libsoup



Author: danw
Date: Mon Jan 14 14:10:30 2008
New Revision: 1029
URL: http://svn.gnome.org/viewvc/libsoup?rev=1029&view=rev

Log:
	* libsoup/soup-logger.c: New HTTP debug logging object. (Based on
	E2K_DEBUG and its clones.)

	* libsoup/soup-message.c (soup_message_class_init)
	(soup_message_add_header_handler)
	(soup_message_add_status_code_handler): Change things around a
	little; remove the "requeuing or cancelling the message stops
	signal emission" rule, and instead make that be a feature of just
	the header and status code handlers. (Makes the basic signal
	handlers behave more predictably.)


Added:
   branches/libsoup-2.4/libsoup/soup-logger.c
   branches/libsoup-2.4/libsoup/soup-logger.h
Modified:
   branches/libsoup-2.4/ChangeLog
   branches/libsoup-2.4/docs/reference/libsoup-docs.sgml
   branches/libsoup-2.4/docs/reference/libsoup-sections.txt
   branches/libsoup-2.4/libsoup/Makefile.am
   branches/libsoup-2.4/libsoup/soup-message.c
   branches/libsoup-2.4/libsoup/soup.h

Modified: branches/libsoup-2.4/docs/reference/libsoup-docs.sgml
==============================================================================
--- branches/libsoup-2.4/docs/reference/libsoup-docs.sgml	(original)
+++ branches/libsoup-2.4/docs/reference/libsoup-docs.sgml	Mon Jan 14 14:10:30 2008
@@ -20,6 +20,7 @@
     <xi:include href="xml/soup-auth-domain.xml"/>
     <xi:include href="xml/soup-auth-domain-basic.xml"/>
     <xi:include href="xml/soup-auth-domain-digest.xml"/>
+    <xi:include href="xml/soup-logger.xml"/>
     <xi:include href="xml/soup-message.xml"/>
     <xi:include href="xml/soup-message-headers.xml"/>
     <xi:include href="xml/soup-message-body.xml"/>

Modified: branches/libsoup-2.4/docs/reference/libsoup-sections.txt
==============================================================================
--- branches/libsoup-2.4/docs/reference/libsoup-sections.txt	(original)
+++ branches/libsoup-2.4/docs/reference/libsoup-sections.txt	Mon Jan 14 14:10:30 2008
@@ -561,3 +561,29 @@
 <SUBSECTION Private>
 soup_byte_array_get_type
 </SECTION>
+
+<SECTION>
+<FILE>soup-logger</FILE>
+<TITLE>SoupLogger</TITLE>
+SoupLogger
+SoupLoggerLogLevel
+soup_logger_new
+soup_logger_attach
+soup_logger_detach
+<SUBSECTION>
+SoupLoggerFilter
+soup_logger_set_request_filter
+soup_logger_set_response_filter
+<SUBSECTION>
+SoupLoggerPrinter
+soup_logger_set_printer
+<SUBSECTION Standard>
+SoupLoggerClass
+soup_logger_get_type
+SOUP_IS_LOGGER
+SOUP_IS_LOGGER_CLASS
+SOUP_LOGGER
+SOUP_LOGGER_CLASS
+SOUP_LOGGER_GET_CLASS
+SOUP_TYPE_LOGGER
+</SECTION>

Modified: branches/libsoup-2.4/libsoup/Makefile.am
==============================================================================
--- branches/libsoup-2.4/libsoup/Makefile.am	(original)
+++ branches/libsoup-2.4/libsoup/Makefile.am	Mon Jan 14 14:10:30 2008
@@ -55,6 +55,7 @@
 	soup-date.h		\
 	soup-form.h		\
 	soup-headers.h		\
+	soup-logger.h		\
 	soup-message.h		\
 	soup-message-body.h	\
 	soup-message-headers.h	\
@@ -115,6 +116,7 @@
 	soup-form.c			\
 	soup-gnutls.c			\
 	soup-headers.c			\
+	soup-logger.c			\
 	soup-md5-utils.h		\
 	soup-md5-utils.c		\
 	soup-message.c			\

Added: branches/libsoup-2.4/libsoup/soup-logger.c
==============================================================================
--- (empty file)
+++ branches/libsoup-2.4/libsoup/soup-logger.c	Mon Jan 14 14:10:30 2008
@@ -0,0 +1,638 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-logger.c
+ *
+ * Copyright (C) 2001-2004 Novell, Inc.
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include "soup-logger.h"
+#include "soup-message.h"
+#include "soup-uri.h"
+
+/**
+ * SECTION:soup-logger
+ * @short_description: Debug logging support
+ *
+ * #SoupLogger watches a #SoupSession and logs the HTTP traffic that
+ * it generates, for debugging purposes. Many applications use an
+ * environment variable to determine whether or not to use
+ * #SoupLogger, and to determine the amount of debugging output.
+ *
+ * To use #SoupLogger, first create a logger with soup_logger_new(),
+ * optionally configure it with soup_logger_set_request_filter(),
+ * soup_logger_set_response_filter(), and soup_logger_set_printer(),
+ * and then attach it to a session (or multiple sessions) with
+ * soup_logger_attach().
+ *
+ * By default, the debugging output is sent to %stdout, and looks
+ * something like:
+ *
+ * <informalexample><screen>
+ * > POST /unauth HTTP/1.1
+ * > Soup-Debug-Timestamp: 1200171744
+ * > Soup-Debug: session 1 (0x612190), msg 1 (0x617000), conn 1 (0x612220)
+ * > Host: localhost
+ * > Content-Type: text/plain
+ * > Connection: close
+ * > 
+ * > This is a test.
+ *   
+ * &lt; HTTP/1.1 201 Created
+ * &lt; Soup-Debug-Timestamp: 1200171744
+ * &lt; Soup-Debug: msg 1 (0x617000)
+ * &lt; Date: Sun, 12 Jan 2008 21:02:24 GMT
+ * &lt; Content-Length: 0
+ * </screen></informalexample>
+ *
+ * The <literal>Soup-Debug-Timestamp</literal> line gives the time (as
+ * a #time_t) when the request was sent, or the response fully
+ * received.
+ *
+ * The <literal>Soup-Debug</literal> line gives further debugging
+ * information about the #SoupSession, #SoupMessage, and #SoupSocket
+ * involved; the hex numbers are the addresses of the objects in
+ * question (which may be useful if you are running in a debugger).
+ * The decimal IDs are simply counters that uniquely identify objects
+ * across the lifetime of the #SoupLogger. In particular, this can be
+ * used to identify when multiple messages are sent across the same
+ * connection.
+ **/
+
+G_DEFINE_TYPE (SoupLogger, soup_logger, G_TYPE_OBJECT)
+
+typedef struct {
+	/* We use a mutex so that if requests are being run in
+	 * multiple threads, we don't mix up the output.
+	 */
+	GMutex             *lock;
+
+	GQuark              tag;
+	GHashTable         *ids;
+
+	SoupLoggerLogLevel  level;
+	int                 max_body_size;
+
+	SoupLoggerFilter    request_filter;
+	gpointer            request_filter_data;
+	GDestroyNotify      request_filter_dnotify;
+
+	SoupLoggerFilter    response_filter;
+	gpointer            response_filter_data;
+	GDestroyNotify      response_filter_dnotify;
+
+	SoupLoggerPrinter   printer;
+	gpointer            printer_data;
+	GDestroyNotify      printer_dnotify;
+	char                direction;
+} SoupLoggerPrivate;
+#define SOUP_LOGGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_LOGGER, SoupLoggerPrivate))
+
+static void
+soup_logger_init (SoupLogger *logger)
+{
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+
+	priv->lock = g_mutex_new ();
+	priv->tag = g_quark_from_static_string (g_strdup_printf ("SoupLogger-%p", logger));
+	priv->ids = g_hash_table_new (NULL, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (object);
+
+	g_hash_table_destroy (priv->ids);
+
+	if (priv->request_filter_dnotify)
+		priv->request_filter_dnotify (priv->request_filter_data);
+	if (priv->response_filter_dnotify)
+		priv->response_filter_dnotify (priv->response_filter_data);
+	if (priv->printer_dnotify)
+		priv->printer_dnotify (priv->printer_data);
+
+	g_mutex_free (priv->lock);
+
+	G_OBJECT_CLASS (soup_logger_parent_class)->finalize (object);
+}
+
+static void
+soup_logger_class_init (SoupLoggerClass *logger_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (logger_class);
+
+	g_type_class_add_private (logger_class, sizeof (SoupLoggerPrivate));
+
+	object_class->finalize = finalize;
+}
+
+/**
+ * SoupLoggerLogLevel:
+ * @SOUP_LOGGER_LOG_NONE: No logging
+ * @SOUP_LOGGER_LOG_MINIMAL: Log the Request-Line or Status-Line and
+ * the Soup-Debug pseudo-headers
+ * @SOUP_LOGGER_LOG_HEADERS: Log the full request/response headers
+ * @SOUP_LOGGER_LOG_BODY: Log the full headers and request/response
+ * bodies.
+ *
+ * Describes the level of logging output to provide.
+ **/
+
+/**
+ * soup_logger_new:
+ * @level: the debug level
+ * @max_body_size: the maximum body size to output, or -1
+ *
+ * Creates a new #SoupLogger with the given debug level. If @level is
+ * %SOUP_LOGGER_LOG_BODY, @max_body_size gives the maximum number of
+ * bytes of the body that will be logged. (-1 means "no limit".)
+ *
+ * If you need finer control over what message parts are and aren't
+ * logged, use soup_logger_set_request_filter() and
+ * soup_logger_set_response_filter().
+ **/
+SoupLogger *
+soup_logger_new (SoupLoggerLogLevel level, int max_body_size) 
+{
+	SoupLogger *logger;
+	SoupLoggerPrivate *priv;
+
+	logger = g_object_new (SOUP_TYPE_LOGGER, NULL);
+
+	priv = SOUP_LOGGER_GET_PRIVATE (logger);
+	priv->level = level;
+	priv->max_body_size = max_body_size;
+
+	return logger;
+}
+
+/**
+ * SoupLoggerFilter:
+ * @logger: the #SoupLogger
+ * @msg: the message being logged
+ * @user_data: the data passed to soup_logger_set_request_filter()
+ * or soup_logger_set_response_filter()
+ *
+ * The prototype for a logging filter. The filter callback will be
+ * invoked for each request or response, and should analyze it and
+ * return a #SoupLoggerLogLevel value indicating how much of the
+ * message to log. Eg, it might choose between %SOUP_LOGGER_LOG_BODY
+ * and %SOUP_LOGGER_LOG_HEADERS depending on the Content-Type.
+ *
+ * Return value: a #SoupLoggerLogLevel value indicating how much of
+ * the message to log
+ **/
+
+/**
+ * soup_logger_set_request_filter:
+ * @logger: a #SoupLogger
+ * @request_filter: the callback for request debugging
+ * @filter_data: data to pass to the callback
+ * @destroy: a #GDestroyNotify to free @filter_data
+ *
+ * Sets up a filter to determine the log level for a given request.
+ * For each HTTP request @logger will invoke @request_filter to
+ * determine how much (if any) of that request to log. (If you do not
+ * set a request filter, @logger will just always log requests at the
+ * level passed to soup_logger_new().)
+ **/
+void
+soup_logger_set_request_filter (SoupLogger       *logger,
+				SoupLoggerFilter  request_filter,
+				gpointer          filter_data,
+				GDestroyNotify    destroy)
+{
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+
+	priv->request_filter         = request_filter;
+	priv->request_filter_data    = filter_data;
+	priv->request_filter_dnotify = destroy;
+}
+
+/**
+ * soup_logger_set_response_filter:
+ * @logger: a #SoupLogger
+ * @response_filter: the callback for response debugging
+ * @filter_data: data to pass to the callback
+ * @destroy: a #GDestroyNotify to free @filter_data
+ *
+ * Sets up a filter to determine the log level for a given response.
+ * For each HTTP response @logger will invoke @response_filter to
+ * determine how much (if any) of that response to log. (If you do not
+ * set a response filter, @logger will just always log responses at
+ * the level passed to soup_logger_new().)
+ **/
+void
+soup_logger_set_response_filter (SoupLogger       *logger,
+				 SoupLoggerFilter  response_filter,
+				 gpointer          filter_data,
+				 GDestroyNotify    destroy)
+{
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+
+	priv->response_filter         = response_filter;
+	priv->response_filter_data    = filter_data;
+	priv->response_filter_dnotify = destroy;
+}
+
+/**
+ * SoupLoggerPrinter:
+ * @logger: the #SoupLogger
+ * @level: the level of the information being printed.
+ * @direction: a single-character prefix to @data
+ * @data: data to print
+ * @user_data: the data passed to soup_logger_set_printer()
+ *
+ * The prototype for a custom printing callback.
+ *
+ * @level indicates what kind of information is being printed. Eg, it
+ * will be %SOUP_LOGGER_LOG_HEADERS if @data is header data.
+ *
+ * @direction is either '<', '>', or ' ', and @data is the single line
+ * to print; the printer is expected to add a terminating newline.
+ *
+ * To get the effect of the default printer, you would do:
+ *
+ * <informalexample><programlisting>
+ *	printf ("%c %s\n", direction, data);
+ * </programlisting></informalexample>
+ **/
+
+/**
+ * soup_logger_set_printer:
+ * @logger: a #SoupLogger
+ * @printer: the callback for printing logging output
+ * @printer_data: data to pass to the callback
+ * @destroy: a #GDestroyNotify to free @printer_data
+ *
+ * Sets up an alternate log printing routine, if you don't want
+ * the log to go to %stdout.
+ **/
+void
+soup_logger_set_printer (SoupLogger        *logger,
+			 SoupLoggerPrinter  printer,
+			 gpointer           printer_data,
+			 GDestroyNotify     destroy)
+{
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+
+	priv->printer         = printer;
+	priv->printer_data    = printer_data;
+	priv->printer_dnotify = destroy;
+}
+
+static guint
+soup_logger_get_id (SoupLogger *logger, gpointer object)
+{
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+
+	return GPOINTER_TO_UINT (g_object_get_qdata (object, priv->tag));
+}
+
+static guint
+soup_logger_set_id (SoupLogger *logger, gpointer object)
+{
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+	gpointer klass = G_OBJECT_GET_CLASS (object);
+	gpointer id;
+
+	id = g_hash_table_lookup (priv->ids, klass);
+	id = (char *)id + 1;
+	g_hash_table_insert (priv->ids, klass, id);
+
+	g_object_set_qdata (object, priv->tag, id);
+	return GPOINTER_TO_UINT (id);
+}
+
+static void
+soup_logger_clear_id (SoupLogger *logger, gpointer object)
+{
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+
+	g_object_set_qdata (object, priv->tag, NULL);
+}
+
+static void request_started (SoupSession *session, SoupMessage *msg,
+			     SoupSocket *socket, gpointer user_data);
+
+/**
+ * soup_logger_attach:
+ * @logger: a #SoupLogger
+ * @session: a #SoupSession
+ *
+ * Sets @logger to watch @session and print debug information for
+ * its messages.
+ *
+ * (The session will take a reference on @logger, which will be
+ * removed when you call soup_logger_detach(), or when the session is
+ * destroyed.)
+ **/
+void
+soup_logger_attach (SoupLogger  *logger,
+		    SoupSession *session)
+{
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+
+	if (!soup_logger_get_id (logger, session))
+		soup_logger_set_id (logger, session);
+	g_signal_connect (session, "request_started",
+			  G_CALLBACK (request_started), logger);
+
+	g_object_set_qdata_full (G_OBJECT (session), priv->tag,
+				 g_object_ref (logger),
+				 g_object_unref);
+}
+
+/**
+ * soup_logger_detach:
+ * @logger: a #SoupLogger
+ * @session: a #SoupSession
+ *
+ * Stops @logger from watching @session.
+ **/
+void
+soup_logger_detach (SoupLogger  *logger,
+		    SoupSession *session)
+{
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+
+	g_signal_handlers_disconnect_by_func (session, request_started, logger);
+
+	g_object_set_qdata (G_OBJECT (session), priv->tag, NULL);
+}
+
+static void
+soup_logger_print (SoupLogger *logger, SoupLoggerLogLevel level,
+		   const char *format, ...)
+{
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+	va_list args;
+	char *data, *line, *end;
+
+	va_start (args, format);
+	data = g_strdup_vprintf (format, args);
+	va_end (args);
+
+	if (level == SOUP_LOGGER_LOG_BODY && priv->max_body_size > 0) {
+		if (strlen (data) > priv->max_body_size + 6)
+			strcpy (data + priv->max_body_size, "\n[...]");
+	}
+
+	line = data;
+	do {
+		end = strchr (line, '\n');
+		if (end)
+			*end = '\0';
+		if (priv->printer) {
+			priv->printer (logger, level, priv->direction,
+				       line, priv->printer_data);
+		} else
+			printf ("%c %s\n", priv->direction, line);
+
+		line = end + 1;
+	} while (end && *line);
+
+	g_free (data);
+}
+
+static void
+print_header (const char *name, const char *value, gpointer logger)
+{
+	if (!g_ascii_strcasecmp (name, "Authorization") &&
+	    !g_ascii_strncasecmp (value, "Basic ", 6)) {
+		char *decoded, *p;
+		gsize len;
+
+		decoded = (char *)g_base64_decode (value + 6, &len);
+		if (!decoded)
+			decoded = g_strdup (value);
+		p = strchr (decoded, ':');
+		if (p) {
+			while (++p < decoded + len)
+				*p = '*';
+		}
+		soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS,
+				   "%s: Basic [%.*s]", name, len, decoded);
+		g_free (decoded);
+	} else {
+		soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS,
+				   "%s: %s", name, value);
+	}
+}
+
+static void
+print_request (SoupLogger *logger, SoupMessage *msg,
+	       SoupSession *session, SoupSocket *socket,
+	       gboolean restarted)
+{
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+	SoupLoggerLogLevel log_level;
+	SoupURI *uri;
+
+	if (priv->request_filter) {
+		log_level = priv->request_filter (logger, msg,
+						  priv->request_filter_data);
+	} else
+		log_level = priv->level;
+
+	if (log_level == SOUP_LOGGER_LOG_NONE)
+		return;
+
+	priv->direction = '>';
+
+	uri = soup_message_get_uri (msg);
+	if (msg->method == SOUP_METHOD_CONNECT) {
+		soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL,
+				   "CONNECT %s:%u HTTP/1.%d",
+				   uri->host, uri->port,
+				   soup_message_get_http_version (msg));
+	} else {
+		soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL,
+				   "%s %s%s%s HTTP/1.%d",
+				   msg->method, uri->path,
+				   uri->query ? "?" : "",
+				   uri->query ? uri->query : "",
+				   soup_message_get_http_version (msg));
+	}
+
+	soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL,
+			   "Soup-Debug-Timestamp: %lu",
+			   (unsigned long)time (0));
+	soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL,
+			   "Soup-Debug: session %u (%p), msg %u (%p), conn %u (%p)%s",
+			   soup_logger_get_id (logger, session), session,
+			   soup_logger_get_id (logger, msg), msg,
+			   soup_logger_get_id (logger, socket), socket,
+			   restarted ? ", restarted" : "");
+
+	if (log_level == SOUP_LOGGER_LOG_MINIMAL)
+		return;
+
+	print_header ("Host", uri->host, logger);
+	soup_message_headers_foreach (msg->request_headers,
+				      print_header, logger);
+	if (log_level == SOUP_LOGGER_LOG_HEADERS)
+		return;
+
+	if (msg->request_body->length) {
+		SoupBuffer *request;
+
+		request = soup_message_body_flatten (msg->request_body);
+		soup_buffer_free (request);
+
+		if (soup_message_headers_get_expectations (msg->request_headers) != SOUP_EXPECTATION_CONTINUE) {
+			soup_logger_print (logger, SOUP_LOGGER_LOG_BODY,
+					   "\n%s", msg->request_body->data);
+		}
+	}
+}
+
+static void
+print_response (SoupLogger *logger, SoupMessage *msg)
+{
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+	SoupLoggerLogLevel log_level;
+
+	if (priv->response_filter) {
+		log_level = priv->response_filter (logger, msg,
+						   priv->response_filter_data);
+	} else
+		log_level = priv->level;
+
+	if (log_level == SOUP_LOGGER_LOG_NONE)
+		return;
+
+	priv->direction = '<';
+
+	soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL,
+			   "HTTP/1.%d %u %s\n",
+			   soup_message_get_http_version (msg),
+			   msg->status_code, msg->reason_phrase);
+
+	soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL,
+			   "Soup-Debug-Timestamp: %lu",
+			   (unsigned long)time (0));
+	soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL,
+			   "Soup-Debug: msg %u (%p)",
+			   soup_logger_get_id (logger, msg), msg);
+
+	if (log_level == SOUP_LOGGER_LOG_MINIMAL)
+		return;
+
+	soup_message_headers_foreach (msg->response_headers,
+				     print_header, logger);
+	if (log_level == SOUP_LOGGER_LOG_HEADERS)
+		return;
+
+	if (msg->response_body->length) {
+		soup_logger_print (logger, SOUP_LOGGER_LOG_BODY,
+				   "\n%s", msg->response_body->data);
+	}
+}
+
+static void
+got_informational (SoupMessage *msg, gpointer user_data)
+{
+	SoupLogger *logger = user_data;
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+
+	g_mutex_lock (priv->lock);
+
+	print_response (logger, msg);
+	priv->direction = ' ';
+	soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, "");
+
+	if (msg->status_code == SOUP_STATUS_CONTINUE && msg->request_body->data) {
+		SoupLoggerLogLevel log_level;
+
+		priv->direction = '>';
+		soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL,
+				   "[Now sending request body...]");
+
+		if (priv->request_filter) {
+			log_level = priv->request_filter (logger, msg,
+							  priv->request_filter_data);
+		} else
+			log_level = priv->level;
+
+		if (log_level == SOUP_LOGGER_LOG_BODY) {
+			soup_logger_print (logger, SOUP_LOGGER_LOG_BODY,
+					   "%s", msg->request_body->data);
+		}
+
+		priv->direction = ' ';
+		soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, "");
+	}
+
+	g_mutex_unlock (priv->lock);
+}
+
+static void
+got_body (SoupMessage *msg, gpointer user_data)
+{
+	SoupLogger *logger = user_data;
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+
+	g_mutex_lock (priv->lock);
+
+	print_response (logger, msg);
+	priv->direction = ' ';
+	soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, "");
+
+	g_mutex_unlock (priv->lock);
+}
+
+static void
+finished_handler (SoupMessage *msg, gpointer user_data)
+{
+	SoupLogger *logger = user_data;
+
+	g_signal_handlers_disconnect_by_func (msg, got_informational, logger);
+	g_signal_handlers_disconnect_by_func (msg, got_body, logger);
+	g_signal_handlers_disconnect_by_func (msg, finished_handler, logger);
+
+	soup_logger_clear_id (logger, msg);
+}
+
+static void
+request_started (SoupSession *session, SoupMessage *msg,
+		 SoupSocket *socket, gpointer user_data)
+{
+	SoupLogger *logger = user_data;
+	SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
+	gboolean restarted;
+	guint msg_id;
+
+	msg_id = soup_logger_get_id (logger, msg);
+	if (msg_id)
+		restarted = TRUE;
+	else {
+		msg_id = soup_logger_set_id (logger, msg);
+		restarted = FALSE;
+
+		g_signal_connect (msg, "got-informational",
+				  G_CALLBACK (got_informational),
+				  logger);
+		g_signal_connect (msg, "got-body",
+				  G_CALLBACK (got_body),
+				  logger);
+		g_signal_connect (msg, "finished",
+				  G_CALLBACK (finished_handler),
+				  logger);
+	}
+
+	if (!soup_logger_get_id (logger, socket))
+		soup_logger_set_id (logger, socket);
+
+	print_request (logger, msg, session, socket, restarted);
+	priv->direction = ' ';
+	soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, "");
+}

Added: branches/libsoup-2.4/libsoup/soup-logger.h
==============================================================================
--- (empty file)
+++ branches/libsoup-2.4/libsoup/soup-logger.h	Mon Jan 14 14:10:30 2008
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifndef SOUP_LOGGER_H
+#define SOUP_LOGGER_H 1
+
+#include <libsoup/soup-types.h>
+
+#define SOUP_TYPE_LOGGER            (soup_logger_get_type ())
+#define SOUP_LOGGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_LOGGER, SoupLogger))
+#define SOUP_LOGGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_LOGGER, SoupLoggerClass))
+#define SOUP_IS_LOGGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_LOGGER))
+#define SOUP_IS_LOGGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_LOGGER))
+#define SOUP_LOGGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_LOGGER, SoupLoggerClass))
+
+typedef struct SoupLogger      SoupLogger;
+typedef struct SoupLoggerClass SoupLoggerClass;
+
+typedef enum {
+	SOUP_LOGGER_LOG_NONE,
+	SOUP_LOGGER_LOG_MINIMAL,
+	SOUP_LOGGER_LOG_HEADERS,
+	SOUP_LOGGER_LOG_BODY
+} SoupLoggerLogLevel;
+
+typedef SoupLoggerLogLevel (*SoupLoggerFilter)  (SoupLogger         *logger,
+						 SoupMessage        *msg,
+						 gpointer            user_data);
+
+typedef void               (*SoupLoggerPrinter) (SoupLogger         *logger,
+						 SoupLoggerLogLevel  level,
+						 char                direction,
+						 const char         *data,
+						 gpointer            user_data);
+
+struct SoupLogger {
+	GObject parent;
+
+};
+
+struct SoupLoggerClass {
+	GObjectClass parent_class;
+
+};
+
+GType       soup_logger_get_type    (void);
+
+SoupLogger *soup_logger_new         (SoupLoggerLogLevel  log_level,
+				     int                 max_body_size);
+void        soup_logger_attach      (SoupLogger         *logger,
+				     SoupSession        *session);
+void        soup_logger_detach      (SoupLogger         *logger,
+				     SoupSession        *session);
+
+void        soup_logger_set_request_filter  (SoupLogger        *logger,
+					     SoupLoggerFilter   request_filter,
+					     gpointer           filter_data,
+					     GDestroyNotify     destroy);
+void        soup_logger_set_response_filter (SoupLogger        *logger,
+					     SoupLoggerFilter   response_filter,
+					     gpointer           filter_data,
+					     GDestroyNotify     destroy);
+
+void        soup_logger_set_printer         (SoupLogger        *logger,
+					     SoupLoggerPrinter  printer,
+					     gpointer           printer_data,
+					     GDestroyNotify     destroy);
+
+#endif /* SOUP_LOGGER_H */

Modified: branches/libsoup-2.4/libsoup/soup-message.c
==============================================================================
--- branches/libsoup-2.4/libsoup/soup-message.c	(original)
+++ branches/libsoup-2.4/libsoup/soup-message.c	Mon Jan 14 14:10:30 2008
@@ -87,12 +87,6 @@
 	LAST_PROP
 };
 
-static void got_foo_signal_wrapper (GClosure *closure, GValue *return_value,
-				    guint n_param_values,
-				    const GValue *param_values,
-				    gpointer invocation_hint,
-				    gpointer marshal_data);
-
 static void got_body (SoupMessage *req);
 static void restarted (SoupMessage *req);
 static void finished (SoupMessage *req);
@@ -241,8 +235,8 @@
 	 * be erased after this signal is done.
 	 *
 	 * If you cancel or requeue @msg while processing this signal,
-	 * the signal emission will be stopped, and @msg's connection
-	 * will be closed.
+	 * then the current HTTP I/O will be stopped after this signal
+	 * emission finished, and @msg's connection will be closed.
 	 **/
 	signals[GOT_INFORMATIONAL] =
 		g_signal_new ("got_informational",
@@ -250,7 +244,7 @@
 			      G_SIGNAL_RUN_FIRST,
 			      G_STRUCT_OFFSET (SoupMessageClass, got_informational),
 			      NULL, NULL,
-			      got_foo_signal_wrapper,
+			      soup_marshal_NONE__NONE,
 			      G_TYPE_NONE, 0);
 
 	/**
@@ -268,11 +262,12 @@
 	 * to connect to a subset of emissions of this signal.
 	 *
 	 * If you cancel or requeue @msg while processing this signal,
-	 * the signal emission will be stopped, and @msg's connection
-	 * will be closed. (If you need to requeue a message--eg,
-	 * after handling authentication or redirection--it is usually
-	 * better to requeue it from a #SoupMessage::got_body handler
-	 * rather than a #SoupMessage::got_header handler, so that the
+	 * then the current HTTP I/O will be stopped after this signal
+	 * emission finished, and @msg's connection will be closed.
+	 * (If you need to requeue a message--eg, after handling
+	 * authentication or redirection--it is usually better to
+	 * requeue it from a #SoupMessage::got_body handler rather
+	 * than a #SoupMessage::got_header handler, so that the
 	 * existing HTTP connection can be reused.)
 	 **/
 	signals[GOT_HEADERS] =
@@ -281,7 +276,7 @@
 			      G_SIGNAL_RUN_FIRST,
 			      G_STRUCT_OFFSET (SoupMessageClass, got_headers),
 			      NULL, NULL,
-			      got_foo_signal_wrapper,
+			      soup_marshal_NONE__NONE,
 			      G_TYPE_NONE, 0);
 
 	/**
@@ -295,8 +290,8 @@
 	 * the other side.
 	 *
 	 * If you cancel or requeue @msg while processing this signal,
-	 * the signal emission will be stopped, and @msg's connection
-	 * will be closed.
+	 * then the current HTTP I/O will be stopped after this signal
+	 * emission finished, and @msg's connection will be closed.
 	 **/
 	signals[GOT_CHUNK] =
 		g_signal_new ("got_chunk",
@@ -304,7 +299,7 @@
 			      G_SIGNAL_RUN_FIRST,
 			      G_STRUCT_OFFSET (SoupMessageClass, got_chunk),
 			      NULL, NULL,
-			      got_foo_signal_wrapper,
+			      soup_marshal_NONE__BOXED,
 			      G_TYPE_NONE, 1,
 			      SOUP_TYPE_BUFFER);
 
@@ -320,13 +315,6 @@
 	 * See also soup_message_add_header_handler() and
 	 * soup_message_add_status_code_handler(), which can be used
 	 * to connect to a subset of emissions of this signal.
-	 *
-	 * If you cancel or requeue @msg while processing this signal,
-	 * the signal emission will be stopped. However, unlike with
-	 * the other "got" signals, this will not necessarily result
-	 * in @msg's connection being closed, since the server is
-	 * already done with the message at this point and so the
-	 * connection is already ready for a new request.
 	 **/
 	signals[GOT_BODY] =
 		g_signal_new ("got_body",
@@ -334,7 +322,7 @@
 			      G_SIGNAL_RUN_FIRST,
 			      G_STRUCT_OFFSET (SoupMessageClass, got_body),
 			      NULL, NULL,
-			      got_foo_signal_wrapper,
+			      soup_marshal_NONE__NONE,
 			      G_TYPE_NONE, 0);
 
 	/**
@@ -650,33 +638,6 @@
 	g_signal_emit (msg, signals[WROTE_BODY], 0);
 }
 
-/* Wraps the marshaller for got_informational, got_headers, got_chunk,
- * and got_body, and stops the emission afterward if the message has
- * been requeued. We can't use a GSignalAccumulator, because that
- * doesn't have access to the object it's accumulating for. Hrmph.
- */
-static void
-got_foo_signal_wrapper (GClosure *closure, GValue *return_value,
-			guint n_param_values, const GValue *param_values,
-			gpointer invocation_hint, gpointer marshal_data)
-{
-	SoupMessage *msg = g_value_get_object (&param_values[0]);
-	GSignalInvocationHint *hint = invocation_hint;
-
-	if (hint->signal_id == signals[GOT_CHUNK]) {
-		soup_marshal_NONE__BOXED (closure, return_value,
-					    n_param_values, param_values,
-					    invocation_hint, marshal_data);
-	} else {
-		soup_marshal_NONE__NONE (closure, return_value,
-					 n_param_values, param_values,
-					 invocation_hint, marshal_data);
-	}
-
-	if (SOUP_MESSAGE_IS_STARTING (msg))
-		g_signal_stop_emission (msg, hint->signal_id, hint->detail);
-}
-
 /**
  * soup_message_got_informational:
  * @msg: a #SoupMessage
@@ -788,18 +749,10 @@
 	g_signal_emit (msg, signals[FINISHED], 0);
 }
 
-typedef struct {
-	SoupMessage *msg;
-	char *header_name;
-} SoupMessageHeaderHandlerData;
-
 static void
-header_handler_free (gpointer data, GClosure *closure)
+header_handler_free (gpointer header_name, GClosure *closure)
 {
-	SoupMessageHeaderHandlerData *hhd = data;
-
-	g_free (hhd->header_name);
-	g_slice_free (SoupMessageHeaderHandlerData, hhd);
+	g_free (header_name);
 }
 
 static void
@@ -807,16 +760,22 @@
 			    guint n_param_values, const GValue *param_values,
 			    gpointer invocation_hint, gpointer marshal_data)
 {
-	SoupMessageHeaderHandlerData *hhd = marshal_data;
+	SoupMessage *msg = g_value_get_object (&param_values[0]);
+	SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+	const char *header_name = marshal_data;
 	SoupMessageHeaders *hdrs;
 
-	/* If status_code is 0, we're still processing the request
-	 * side; if it's not, we're processing the response side.
+	if (priv->io_status != SOUP_MESSAGE_IO_STATUS_RUNNING)
+		return;
+
+	/* If status_code is SOUP_STATUS_NONE, we're still processing
+	 * the request side; if it's not, we're processing the
+	 * response side.
 	 */
-	hdrs = (hhd->msg->status_code == 0) ?
-		hhd->msg->request_headers : hhd->msg->response_headers;
+	hdrs = (msg->status_code == SOUP_STATUS_NONE) ?
+		msg->request_headers : msg->response_headers;
 
-	if (soup_message_headers_get (hdrs, hhd->header_name)) {
+	if (soup_message_headers_get (hdrs, header_name)) {
 		closure->marshal (closure, return_value, n_param_values,
 				  param_values, invocation_hint,
 				  ((GCClosure *)closure)->callback);
@@ -832,8 +791,9 @@
  * @user_data: data to pass to @handler_cb
  *
  * Adds a signal handler to @msg for @signal, as with
- * g_signal_connect(). However, @callback will only be run if @msg has
- * a header named @header.
+ * g_signal_connect(), but with two differences: the @callback will
+ * only be run if @msg has a header named @header, and it will only be
+ * run if no earlier handler cancelled or requeued the message.
  *
  * If @signal is one of the "got" signals (eg, "got_headers"), or
  * "finished" or "restarted", then @header is matched against the
@@ -853,7 +813,7 @@
 {
 	SoupMessagePrivate *priv;
 	GClosure *closure;
-	SoupMessageHeaderHandlerData *hhd;
+	char *header_name;
 
 	g_return_val_if_fail (SOUP_IS_MESSAGE (msg), 0);
 	g_return_val_if_fail (signal != NULL, 0);
@@ -864,11 +824,11 @@
 
 	closure = g_cclosure_new (callback, user_data, NULL);
 
-	hhd = g_slice_new (SoupMessageHeaderHandlerData);
-	hhd->msg = msg;
-	hhd->header_name = g_strdup (header);
-	g_closure_set_meta_marshal (closure, hhd, header_handler_metamarshal);
-	g_closure_add_finalize_notifier (closure, hhd, header_handler_free);
+	header_name = g_strdup (header);
+	g_closure_set_meta_marshal (closure, header_name,
+				    header_handler_metamarshal);
+	g_closure_add_finalize_notifier (closure, header_name,
+					 header_handler_free);
 
 	return g_signal_connect_closure (msg, signal, closure, FALSE);
 }
@@ -879,8 +839,12 @@
 			    gpointer invocation_hint, gpointer marshal_data)
 {
 	SoupMessage *msg = g_value_get_object (&param_values[0]);
+	SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
 	guint status = GPOINTER_TO_UINT (marshal_data);
 
+	if (priv->io_status != SOUP_MESSAGE_IO_STATUS_RUNNING)
+		return;
+
 	if (msg->status_code == status) {
 		closure->marshal (closure, return_value, n_param_values,
 				  param_values, invocation_hint,
@@ -897,8 +861,9 @@
  * @user_data: data to pass to @handler_cb
  *
  * Adds a signal handler to @msg for @signal, as with
- * g_signal_connect(). However, @callback will only be run if @msg has
- * the status @status_code.
+ * g_signal_connect() but with two differences: the @callback will
+ * only be run if @msg has the status @status_code, and it will only
+ * be run if no earlier handler cancelled or requeued the message.
  *
  * @signal must be a signal that will be emitted after @msg's status
  * is set. For a client #SoupMessage, this means it can't be a "wrote"

Modified: branches/libsoup-2.4/libsoup/soup.h
==============================================================================
--- branches/libsoup-2.4/libsoup/soup.h	(original)
+++ branches/libsoup-2.4/libsoup/soup.h	Mon Jan 14 14:10:30 2008
@@ -19,6 +19,7 @@
 #include <libsoup/soup-enum-types.h>
 #include <libsoup/soup-form.h>
 #include <libsoup/soup-headers.h>
+#include <libsoup/soup-logger.h>
 #include <libsoup/soup-message.h>
 #include <libsoup/soup-method.h>
 #include <libsoup/soup-misc.h>



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