Soup Server Basics

Soup Server Basics — Server-side tutorial

Creating a SoupSession

As with the client API, there is a single object that will encapsulate most of your interactions with libsoup. In this case, SoupServer.

You create the server with soup_server_new, and as with the SoupSession constructor, you can specify various additional options:

SOUP_SERVER_PORT

The TCP port to listen on. If 0 (or left unspecified), some unused port will be selected for you. (You can find out what port by calling soup_server_get_port.

SOUP_SERVER_INTERFACE

A SoupAddress, specifying the IP address of the network interface to run the server on. If NULL (or left unspecified), the server will listen on all interfaces.

SOUP_SERVER_SSL_CERT_FILE

Points to a file containing an SSL certificate to use. If this is set, then the server will speak HTTPS; otherwise it will speak HTTP.

SOUP_SERVER_SSL_KEY_FILE

Points to a file containing the private key for the SOUP_SERVER_SSL_CERT_FILE. (It may point to the same file.)

SOUP_SERVER_ASYNC_CONTEXT

A GMainContext which the server will use for asynchronous operations. This can be set if you want to use a SoupServer in a thread other than the main thread.

SOUP_SERVER_RAW_PATHS

Set this to TRUE if you don't want libsoup to decode %-encoding in the Request-URI. (Eg, because you need to treat "/foo/bar" and "/foo%2Fbar" as different paths.


Adding Handlers

By default, SoupServer returns "404 Not Found" in response to all requests (except ones that it can't parse, which get "400 Bad Request"). To override this behavior, call soup_server_add_handler to set a callback to handle certain URI paths.

	soup_server_add_handler (server, "/foo", server_callback,
				 data, destroy_notify);

The "/foo" indicates the base path for this handler. When a request comes in, if there is a handler registered for exactly the path in the request's Request-URI, then that handler will be called. Otherwise libsoup will strip path components one by one until it finds a matching handler. So for example, a request of the form "GET /foo/bar/baz.html?a=1&b=2 HTTP/1.1" would look for handlers for "/foo/bar/baz.html", "/foo/bar", and "/foo". If a handler has been registered with a NULL base path, then it is used as the default handler for any request that doesn't match any other handler.


Responding to Requests

A handler callback looks something like this:

static void
server_callback (SoupServer        *server,
		 SoupMessage       *msg, 
		 const char        *path,
		 GHashTable        *query,
		 SoupClientContext *client,
		 gpointer           user_data)
{
	...
}

msg is the request that has been received and user_data is the data that was passed to soup_server_add_handler. path is the path (from msg's URI), and query contains the result of parsing the URI query field. (It is NULL if there was no query.) client is a SoupClientContext, which contains additional information about the client (including its IP address, and whether or not it used HTTP authentication).

By default, libsoup assumes that you have completely finished processing the message when you return from the callback, and that it can therefore begin sending the response. If you are not ready to send a response immediately (eg, you have to contact another server, or wait for data from a database), you must call soup_server_pause_message on the message before returning from the callback. This will delay sending a response until you call soup_server_unpause_message. (You must also connect to the finished signal on the message in this case, so that you can break off processing if the client unexpectedly disconnects before you start sending the data.)

To set the response status, call soup_message_set_status or soup_message_set_status_full. If the response requires a body, you must decide whether to use Content-Length encoding (the default), or chunked encoding.

Responding with Content-Length Encoding

This is the simpler way to set a response body, if you have all of the data available at once.

static void
server_callback (SoupServer        *server,
		 SoupMessage       *msg, 
		 const char        *path,
		 GHashTable        *query,
		 SoupClientContext *client,
		 gpointer           user_data)
{
	MyServerData *server_data = user_data;
	const char *mime_type;
	GByteArray *body;

	if (msg->method != SOUP_METHOD_GET) {
		soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
		return;
	}

	/* This is somewhat silly. Presumably your server will do
	 * something more interesting.
	 */
	body = g_hash_table_lookup (server_data->bodies, path);
	mime_type = g_hash_table_lookup (server_data->mime_types, path);
	if (!body || !mime_type) {
		soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
		return;
	}

	soup_message_set_status (msg, SOUP_STATUS_OK);
	soup_message_set_response (msg, mime_type, SOUP_MEMORY_COPY,
				   body->data, body->len);
}

Responding with chunked Encoding

If you want to supply the response body in chunks as it becomes available, use chunked encoding instead. In this case, first call soup_message_headers_set_encoding (msg->response_headers, SOUP_ENCODING_CHUNKED) to tell libsoup that you'll be using chunked encoding. Then call soup_message_body_append (or soup_message_body_append_buffer) on msg->response_body with each chunk of the response body as it becomes available, and call soup_message_body_complete when the response is complete. After each of these calls, you must also call soup_server_unpause_message to cause the chunk to be sent. (You do not normally need to call soup_server_pause_message, because I/O is automatically paused when doing a chunked transfer if no chunks are available.)

When using chunked encoding, you must also connect to the finished signal on the message, so that you will be notified if the client disconnects between two chunks; SoupServer will unref the message if that happens, so you must stop adding new chunks to the response at that point. (An alternate possibility is to write each new chunk only when the wrote_chunk signal is emitted indicating that the previous one was written successfully.)

The simple-proxy example in the tests/ directory gives an example of using chunked encoding.


Handling Authentication

To have SoupServer handle HTTP authentication for you, create a SoupAuthDomainBasic or SoupAuthDomainDigest, and pass it to soup_server_add_auth_domain:

	SoupAuthDomain *domain;

	domain = soup_auth_domain_basic_new (
		SOUP_AUTH_DOMAIN_REALM, "My Realm",
		SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, auth_callback,
		SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA, auth_data,
		SOUP_AUTH_DOMAIN_ADD_PATH, "/foo",
		SOUP_AUTH_DOMAIN_ADD_PATH, "/bar/private",
		NULL);
	soup_server_add_auth_domain (server, domain);
	g_object_unref (domain);

Then, every request under one of the auth domain's paths will be passed to the auth_callback first before being passed to the server_callback:

static gboolean
auth_callback (SoupAuthDomain *domain, SoupMessage *msg,
	       const char *username, const char *password,
	       gpointer user_data)
{
	MyServerData *server_data = user_data;
	MyUserData *user;

	user = my_server_data_lookup_user (server_data, username);
	if (!user)
		return FALSE;

	/* FIXME: Don't do this. Keeping a cleartext password database
	 * is bad.
	 */
	return strcmp (password, user->password) == 0;
}

The SoupAuthDomainBasicAuthCallback is given the username and password from the Authorization header and must determine, in some server-specific manner, whether or not to accept them. (In this example we compare the password against a cleartext password database, but it would be better to store the password somehow encoded, as in the UNIX password database. Alternatively, you may need to delegate the password check to PAM or some other service.)

If you are using Digest authentication, note that SoupAuthDomainDigestAuthCallback works completely differently (since the server doesn't receive the cleartext password from the client in that case, so there's no way to compare it directly). See the documentation for SoupAuthDomainDigest for more details.

You can have multiple SoupAuthDomains attached to a SoupServer, either in separate parts of the path hierarchy, or overlapping. (Eg, you might want to accept either Basic or Digest authentication for a given path.) When more than one auth domain covers a given path, the request will be accepted if the user authenticates successfully against any of the domains.

If you want to require authentication for some requests under a certain path, but not all of them (eg, you want to authenticate PUTs, but not GETs), use a SoupAuthDomainFilter.