libsoup Server Basicslibsoup Server Basics — Server-side tutorial |
Creating a SoupServer
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
a few additional options:
A GTlsCertificate (containing a private key) that will be used when handling HTTPS requests on the server. |
|
Set this to |
|
Allows you to set a Server header string that will be sent on all responses. |
|
Allow you to tell the server to recognize additional URI
schemes as aliases for " |
Adding Listening Sockets
To tell the server where to listen, call soup_server_listen
(to listen on a specific GSocketAddress), soup_server_listen_all
(to listen on a given port on all network interfaces), or soup_server_listen_local
(to listen to a given port on the loopback interface only). You can
call any of these functions multiple times, to set up multiple
listening sockets.
To set up an HTTPS server, you must first either set the SOUP_SERVER_TLS_CERTIFICATE
property, or else call soup_server_set_ssl_cert_file
.
After that you can pass the SOUP_SERVER_LISTEN_HTTPS
option to soup_server_listen
,
etc.
By default, servers listen for both IPv4 and IPv6 connections; if
you don't want this, use the SOUP_SERVER_LISTEN_IPV4_ONLY
or SOUP_SERVER_LISTEN_IPV6_ONLY
options.
The server runs asynchronously, in the thread-default GMainContext of the thread in which the "listen" calls were made.
The Old SoupServer Listening API
soup_server_listen
,
etc, are available only in libsoup 2.46 and
later. In earlier versions, there was a simpler API, in which a server
could only listen on a single port, determined at construct time
either by passing the SOUP_SERVER_INTERFACE
property (to specify a SoupAddress to listen on),
or the SOUP_SERVER_PORT
property (to specify a port to listen on, on all interfaces). The SOUP_SERVER_SSL_CERT_FILE
and SOUP_SERVER_SSL_KEY_FILE
properties could be used to create an HTTP server.
When using this API, if SoupServer is unable to bind
the listening socket, or unable to read the provided certificate or
key files, then it will return NULL
from its
constructor (with no further indication of what exactly went wrong).
Additionally, when using this API, it is necessary to call soup_server_run
or soup_server_run_async
to start the server after creating it.
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.
1 2 |
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.php?a=1&b=2 HTTP/1.1
"
would look for handlers for "/foo/bar/baz.php
",
"/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:
1 2 3 4 5 6 7 8 9 10 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
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 examples/
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
:
1 2 3 4 5 6 7 8 9 10 11 |
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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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
PUT
requests, but not GET
requests), use a SoupAuthDomainFilter.