summaryrefslogtreecommitdiff
path: root/docs/reference/server-howto.xml
blob: 2d7971b6f71ee67c07a2cf115467a3e927857a7d (plain)
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
305
306
307
308
309
310
311
312
313
314
315
316
317
<?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&#xA0;/foo/bar/baz.html?a=1&amp;b=2&#xA0;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>.
(You must also connect to the <link
linkend="SoupMessage-finished">finished</link> 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.)
</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>
When using chunked encoding, you must also connect to the <link
linkend="SoupMessage-finished">finished</link> signal on the message,
so that you will be notified if the client disconnects between two
chunks; <type>SoupServer</type> will unref the message if that
happens, so you must stop adding new chunks to the response at that
point.
</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", &amp;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>