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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
|
<?xml version="1.0"?>
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">
<refentry id="libsoup-server-howto">
<refmeta>
<refentrytitle>Soup Server Basics</refentrytitle>
<manvolnum>3</manvolnum>
<refmiscinfo>LIBSOUP Library</refmiscinfo>
</refmeta>
<refnamediv>
<refname>Soup Server Basics</refname><refpurpose>Server-side tutorial</refpurpose>
</refnamediv>
<refsect2>
<title>Creating a SoupSession</title>
<para>
As with the client API, there is a single object that will encapsulate
most of your interactions with libsoup. In this case, <link
linkend="SoupServer"><type>SoupServer</type></link>.
</para>
<warning>
<para>
Note that <type>SoupServer</type> isn't as polished as
<type>SoupSession</type>, and thus not as stable, and the APIs
will likely change in incompatible (but not
difficult-to-port-to) ways in the future to make things nicer.
We apologize in advance for the inconvenience.
</para>
</warning>
<para>
You create the server with <link
linkend="soup-server-new"><function>soup_server_new</function></link>,
and as with the <type>SoupSession</type> constructor, you can specify
various additional options:
</para>
<variablelist>
<varlistentry>
<term><link linkend="SOUP-SERVER-PORT:CAPS"><literal>SOUP_SERVER_PORT</literal></link></term>
<listitem><para>
The TCP port to listen on. If <literal>0</literal> (or
left unspecified), some unused port will be selected for
you. (You can find out what port by calling <link
linkend="soup-server-get-port"><function>soup_server_get_port</function></link>.
</para></listitem>
</varlistentry>
<varlistentry>
<term><link linkend="SOUP-SERVER-INTERFACE:CAPS"><literal>SOUP_SERVER_INTERFACE</literal></link></term>
<listitem><para>
A <link linkend="SoupAddress"><type>SoupAddress</type></link>,
specifying the IP address of the network interface to run
the server on. If <literal>NULL</literal> (or left
unspecified), the server will listen on all interfaces.
</para></listitem>
</varlistentry>
<varlistentry>
<term><link linkend="SOUP-SERVER-SSL-CERT-FILE:CAPS"><literal>SOUP_SERVER_SSL_CERT_FILE</literal></link></term>
<listitem><para>
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.
</para></listitem>
</varlistentry>
<varlistentry>
<term><link linkend="SOUP-SERVER-SSL-KEY-FILE:CAPS"><literal>SOUP_SERVER_SSL_KEY_FILE</literal></link></term>
<listitem><para>
Points to a file containing the private key for the
<literal>SOUP_SERVER_SSL_CERT_FILE</literal>. (It may
point to the same file.)
</para></listitem>
</varlistentry>
<varlistentry>
<term><link linkend="SOUP-SERVER-ASYNC-CONTEXT:CAPS"><literal>SOUP_SERVER_ASYNC_CONTEXT</literal></link></term>
<listitem><para>
A <link linkend="GMainContext"><type>GMainContext</type></link> 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.
</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
<refsect2>
<title>Adding Handlers</title>
<para>
By default, <link linkend="SoupServer"><type>SoupServer</type></link>
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 <link
linkend="soup-server-add-handler"><function>soup_server_add_handler</function></link>
to set a callback to handle certain URI paths.
</para>
<informalexample><programlisting>
soup_server_add_handler (server, "/foo", NULL, server_callback,
unregister_callback, data);
</programlisting></informalexample>
<para>
The <literal>"/foo"</literal> 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 <literal>Request-URI</literal>, then
that handler will be called. Otherwise
<application>libsoup</application> will strip path components one by
one until it finds a matching handler. So for example, a request of
the form
"<literal>GET /foo/bar/baz.html?a=1&b=2 HTTP/1.1</literal>"
would look for handlers for "<literal>/foo/bar/baz.html</literal>",
"<literal>/foo/bar</literal>", and "<literal>/foo</literal>". If a
handler has been registered with a <literal>NULL</literal> base path,
then it is used as the default handler for any request that doesn't
match any other handler.
</para>
</refsect2>
<refsect2>
<title>Responding to Requests</title>
<para>
A handler callback looks something like this:
</para>
<informalexample><programlisting>
static void
server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data)
{
...
}
</programlisting></informalexample>
<para>
<literal>msg</literal> is the request that has been received.
<literal>data</literal> is the same data that was passed to <link
linkend="soup-server-add-handler"><function>soup_server_add_handler</function></link>.
The <link>context</link> argument contains some additional information
related to the request.
</para>
<para>
By default, <application>libsoup</application> 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 <link
linkend="soup-message-io-pause"><function>soup_message_io_pause</function></link>
on the message before returning from the callback. This will delay
sending a response until you call <link
linkend="soup-message-io-unpause"><function>soup_message_io_unpause</function></link>.
</para>
<para>
To set the response status, call <link
linkend="soup-message-set-status"><function>soup_message_set_status</function></link>
or <link
linkend="soup-message-set-status-full"><function>soup_message_set_status_full</function></link>.
If the response requires a body, the callback must call <link
linkend="soup-server-message-set-encoding"><function>soup_server_message_set_encoding</function></link>
to indicate whether it will provide the response all at once with
<literal>Content-Length</literal> encoding, or in pieces with
<literal>chunked</literal> encoding.
</para>
<refsect3>
<title>Responding with <literal>Content-Length</literal>
Encoding</title>
<para>
This is the simpler way to set a response body, if you have all of the
data available at once.
</para>
<informalexample><programlisting>
static void
server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data)
{
MyServerData *server_data = data;
SoupUri *uri = soup_message_get_uri (msg);
const char *mime_type;
GByteArray *body;
if (context->method_id != SOUP_METHOD_ID_GET) {
soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
return;
}
body = g_hash_table_lookup (server_data->bodies, uri->path);
mime_type = g_hash_table_lookup (server_data->mime_types, uri->path);
if (!body || !mime_type) {
soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
return;
}
soup_message_set_status (msg, SOUP_STATUS_OK);
soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (msg),
SOUP_TRANSFER_CONTENT_LENGTH);
soup_message_set_response (msg, mime_type, SOUP_BUFFER_USER_OWNED,
body->data, body->len);
}
</programlisting></informalexample>
</refsect3>
<refsect3>
<title>Responding with <literal>chunked</literal> Encoding</title>
<para>
If you want to supply the response body in chunks as it becomes
available, use <literal>chunked</literal> encoding instead. In this
case, call <link
linkend="soup-message-add-chunk"><function>soup_message_add_chunk</function></link> with
each chunk of the response body as it becomes available, and call
<link
linkend="soup-message-add-final-chunk"><function>soup_message_add_final_chunk</function></link>
when the response is complete. After each of these calls, you must
also call <link
linkend="soup-message-io-unpause"><function>soup_message_io_unpause</function></link> to
cause the chunk to be sent. (You do not normally need to call
<link linkend="soup-message-io-pause"><function>soup_message_io_pause</function></link>,
because I/O is automatically paused when doing a
<literal>chunked</literal> transfer if no chunks are available.)
</para>
<para>
The <emphasis role="bold"><literal>simple-proxy</literal></emphasis>
example in the <literal>tests/</literal> directory gives an example of
using <literal>chunked</literal> encoding.
</para>
</refsect3>
</refsect2>
<refsect2>
<title>Handling Authentication</title>
<para>
To have <link linkend="SoupServer"><type>SoupServer</type></link>
handle HTTP authentication for you, pass a <link
linkend="SoupAuthContext"><type>SoupAuthContext</type></link> to <link
linkend="soup-server-add-handler"><function>soup_server_add_handler</function></link>:
</para>
<informalexample><programlisting>
SoupServerAuthContext auth_ctx;
auth_ctx.types = SOUP_AUTH_TYPE_BASIC;
auth_ctx.callback = auth_callback;
auth_ctx.user_data = data;
auth_ctx.basic_info.realm = "My Realm";
soup_server_add_handler (server, "/bar", &auth_ctx, server_callback,
unregister_callback, data);
</programlisting></informalexample>
<para>
Then, every request that matches that handler will be passed to the
<literal>auth_callback</literal> first before being passed to the
<literal>server_callback</literal>:
</para>
<informalexample><programlisting>
static gboolean
auth_callback (SoupServerAuthContext *auth_ctx, SoupServerAuth *auth,
SoupMessage *msg, gpointer user_data)
{
MyServerData *server_data = user_data;
const char *username, *password;
if (!auth)
return FALSE;
username = soup_server_auth_get_user (auth);
password = g_hash_table_lookup (server_data->passwords, username);
if (!password)
return FALSE;
return soup_server_auth_check_passwd (auth, password);
}
</programlisting></informalexample>
<para>
The <literal>auth</literal> parameter indicates the authentication
information passed by the client. If no
<literal>WWW-Authenticate</literal> header was present, this will be
<literal>NULL</literal>, so we return <literal>FALSE</literal> from
the callback to indicate that the server should return a <literal>401
Unauthorized</literal> response. If it is non-<literal>NULL</literal>,
we extract the username from it, and compare it against our stored
password. Assuming it matches, we return <literal>TRUE</literal>, and
the server callback is then invoked normally.
</para>
</refsect2>
</refentry>
|