1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-message.c: Asyncronous Callback-based SOAP Request Queue.
6 * Alex Graveley (alex@helixcode.com)
8 * Copyright (C) 2000, Helix Code, Inc.
11 #include "soup-message.h"
12 #include "soup-context.h"
13 #include "soup-private.h"
17 * @context: a %SoupContext for the destination endpoint.
18 * @action: a string which will be used as the SOAPAction header for the created
21 * Creates a new empty %SoupMessage, which will connect to the URL represented
22 * by @context. The new message has a status of @SOUP_STATUS_IDLE.
24 * Return value: the new %SoupMessage.
27 soup_message_new (SoupContext *context, SoupAction action)
30 ret = g_new0 (SoupMessage, 1);
31 ret->priv = g_new0 (SoupMessagePrivate, 1);
32 ret->status = SOUP_STATUS_IDLE;
33 ret->action = g_strdup (action);
34 ret->context = context;
35 ret->method = SOUP_METHOD_POST;
37 soup_context_ref (context);
43 * soup_message_new_full:
44 * @context: a %SoupContext for the destination endpoint.
45 * @action: a string which will be used as the SOAPAction header for the created
47 * @req_owner: the %SoupOwnership of the passed data buffer.
48 * @req_body: a data buffer containing the body of the message request.
49 * @req_length: the byte length of @req_body.
51 * Creates a new %SoupMessage, which will connect to the URL represented by
52 * @context. The new message has a status of @SOUP_STATUS_IDLE. The request data
53 * buffer will be filled from @req_owner, @req_body, and @req_length
56 * Return value: the new %SoupMessage.
59 soup_message_new_full (SoupContext *context,
61 SoupOwnership req_owner,
65 SoupMessage *ret = soup_message_new (context, action);
67 ret->request.owner = req_owner;
68 ret->request.body = req_body;
69 ret->request.length = req_length;
74 #define source_remove(_src) \
75 ({ if ((_src)) { g_source_remove ((_src)); (_src) = 0; }})
78 * soup_message_cleanup:
79 * @req: a %SoupMessage.
80 * @action: a string which will be used as the SOAPAction header for the created
83 * Frees any temporary resources created in the processing of @req. Request and
84 * response data buffers are left intact.
87 soup_message_cleanup (SoupMessage *req)
89 g_return_if_fail (req != NULL);
91 source_remove (req->priv->read_tag);
92 source_remove (req->priv->write_tag);
93 source_remove (req->priv->error_tag);
94 source_remove (req->priv->timeout_tag);
96 if (req->priv->connect_tag) {
97 soup_context_cancel_connect (req->priv->connect_tag);
98 req->priv->connect_tag = NULL;
100 if (req->priv->conn) {
101 soup_connection_release (req->priv->conn);
102 req->priv->conn = NULL;
104 if (req->priv->recv_buf) {
105 g_byte_array_free (req->priv->recv_buf, TRUE);
106 req->priv->recv_buf = NULL;
109 req->priv->write_len = 0;
110 req->priv->header_len = 0;
111 req->priv->content_length = 0;
112 req->priv->is_chunked = FALSE;
114 soup_active_requests = g_slist_remove (soup_active_requests, req);
118 soup_message_remove_header (gchar *name, gchar *value, gpointer unused)
126 * @req: a %SoupMessage to destroy.
128 * Destroys the %SoupMessage pointed to by @req. Request and response headers
129 * are freed. Request and response data buffers are also freed if their
130 * ownership is %SOUP_BUFFER_SYSTEM_OWNED. The message's destination context
131 * will be de-referenced.
134 soup_message_free (SoupMessage *req)
136 g_return_if_fail (req != NULL);
138 soup_message_cleanup (req);
140 soup_context_unref (req->context);
142 if (req->request.owner == SOUP_BUFFER_SYSTEM_OWNED)
143 g_free (req->request.body);
144 if (req->response.owner == SOUP_BUFFER_SYSTEM_OWNED)
145 g_free (req->response.body);
147 if (req->priv->req_header)
148 g_string_free (req->priv->req_header, TRUE);
150 if (req->request_headers) {
151 g_hash_table_foreach (req->request_headers,
152 (GHFunc) soup_message_remove_header,
154 g_hash_table_destroy (req->request_headers);
157 if (req->response_headers) {
158 g_hash_table_foreach (req->response_headers,
159 (GHFunc) soup_message_remove_header,
161 g_hash_table_destroy (req->response_headers);
164 g_slist_foreach (req->priv->content_handlers, (GFunc) g_free, NULL);
165 g_slist_free (req->priv->content_handlers);
168 g_free (req->action);
173 * soup_message_issue_callback:
174 * @req: a %SoupMessage currently being processed.
175 * @error: a %SoupErrorCode to be passed to %req's completion callback.
177 * Finalizes the message request, by first freeing any temporary resources, then
178 * issuing the callback function pointer passed in %soup_message_new or
179 * %soup_message_new_full. If, after returning from the callback, the message
180 * has not been requeued, @msg is destroyed using %soup_message_free.
183 soup_message_issue_callback (SoupMessage *req, SoupErrorCode error)
185 g_return_if_fail (req != NULL);
187 /* make sure we don't have some icky recursion if the callback
188 runs the main loop, and the connection has some data or error
189 which causes the callback to be run again */
190 soup_message_cleanup (req);
192 req->priv->errorcode = error;
194 if (req->priv->callback) {
195 (*req->priv->callback) (req, error, req->priv->user_data);
197 /* Free it only if callback exist, its probably a sync call */
198 if (req->status != SOUP_STATUS_QUEUED)
199 soup_message_free (req);
204 * soup_message_cancel:
205 * @req: a %SoupMessage currently being processed.
207 * Cancel a running message, and issue completion callback with a
208 * %SoupTransferStatus of %SOUP_ERROR_CANCELLED. If not requeued by the
209 * completion callback, the @msg will be destroyed.
212 soup_message_cancel (SoupMessage *req)
214 soup_message_issue_callback (req, SOUP_ERROR_CANCELLED);
218 soup_message_set_header (GHashTable **hash,
222 gpointer old_name, old_value;
225 *hash = g_hash_table_new (soup_str_case_hash,
226 soup_str_case_equal);
227 else if (g_hash_table_lookup_extended (*hash,
231 g_hash_table_remove (*hash, name);
236 g_hash_table_insert (*hash, g_strdup (name), g_strdup (value));
240 * soup_message_set_request_header:
241 * @req: a %SoupMessage.
242 * @name: header name.
243 * @value: header value.
245 * Adds a new transport header to be sent on an outgoing request.
248 soup_message_set_request_header (SoupMessage *req,
252 g_return_if_fail (req != NULL);
253 g_return_if_fail (name != NULL || name [0] != '\0');
255 if (req->priv->req_header) {
256 g_string_free (req->priv->req_header, TRUE);
257 req->priv->req_header = NULL;
260 soup_message_set_header (&req->request_headers, name, value);
264 * soup_message_get_request_header:
265 * @req: a %SoupMessage.
266 * @name: header name.
268 * Lookup the transport request header with a key equal to @name.
270 * Return value: the header's value or NULL if not found.
273 soup_message_get_request_header (SoupMessage *req,
276 g_return_val_if_fail (req != NULL, NULL);
277 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
279 return req->request_headers ?
280 g_hash_table_lookup (req->request_headers, name) : NULL;
284 * soup_message_set_response_header:
285 * @req: a %SoupMessage.
286 * @name: header name.
287 * @value: header value.
289 * Adds a new transport header to be sent on an outgoing response.
292 soup_message_set_response_header (SoupMessage *req,
296 g_return_if_fail (req != NULL);
297 g_return_if_fail (name != NULL || name [0] != '\0');
299 soup_message_set_header (&req->response_headers, name, value);
303 * soup_message_get_response_header:
304 * @req: a %SoupMessage.
305 * @name: header name.
307 * Lookup the transport response header with a key equal to @name.
309 * Return value: the header's value or NULL if not found.
312 soup_message_get_response_header (SoupMessage *req,
315 g_return_val_if_fail (req != NULL, NULL);
316 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
318 return req->response_headers ?
319 g_hash_table_lookup (req->response_headers, name) : NULL;
324 * @msg: a %SoupMessage.
326 * Syncronously send @msg. This call will not return until the transfer is
327 * finished successfully or there is an unrecoverable error.
329 * @msg is not free'd upon return.
331 * Return value: the %SoupErrorCode of the error encountered while sending, or
335 soup_message_send (SoupMessage *msg)
337 soup_message_queue (msg, NULL, NULL);
340 g_main_iteration (TRUE);
341 if (msg->status == SOUP_STATUS_FINISHED ||
342 msg->priv->errorcode != SOUP_ERROR_NONE)
343 return msg->priv->errorcode;
346 return SOUP_ERROR_NONE;
350 soup_message_set_method (SoupMessage *msg, const gchar *method)
352 g_return_if_fail (msg != NULL);
353 g_return_if_fail (method != NULL);
355 msg->method = method;
359 soup_message_get_method (SoupMessage *msg)
361 g_return_val_if_fail (msg != NULL, NULL);
367 RESPONSE_HEADER_HANDLER,
368 RESPONSE_CODE_HANDLER,
369 RESPONSE_BODY_HANDLER
373 SoupHandlerType type;
374 SoupHandlerFn handler_cb;
377 SoupHandlerKind kind;
383 soup_message_add_handler (SoupMessage *msg,
384 SoupHandlerType type,
385 SoupHandlerFn handler_cb,
387 SoupHandlerKind kind,
391 SoupHandlerData *data;
393 data = g_new0 (SoupHandlerData, 1);
395 data->handler_cb = handler_cb;
396 data->user_data = user_data;
398 data->header = header;
401 msg->priv->content_handlers =
402 g_slist_append (msg->priv->content_handlers, data);
406 soup_message_add_header_handler (SoupMessage *msg,
408 SoupHandlerType type,
409 SoupHandlerFn handler_cb,
412 g_return_if_fail (msg != NULL);
413 g_return_if_fail (header != NULL);
414 g_return_if_fail (handler_cb != NULL);
416 soup_message_add_handler (msg,
420 RESPONSE_HEADER_HANDLER,
426 soup_message_add_response_code_handler (SoupMessage *msg,
428 SoupHandlerType type,
429 SoupHandlerFn handler_cb,
432 g_return_if_fail (msg != NULL);
433 g_return_if_fail (code != 0);
434 g_return_if_fail (handler_cb != NULL);
436 soup_message_add_handler (msg,
440 RESPONSE_CODE_HANDLER,
446 soup_message_add_body_handler (SoupMessage *msg,
447 SoupHandlerType type,
448 SoupHandlerFn handler_cb,
451 g_return_if_fail (msg != NULL);
452 g_return_if_fail (handler_cb != NULL);
454 soup_message_add_handler (msg,
458 RESPONSE_BODY_HANDLER,
464 soup_message_run_handlers (SoupMessage *msg, SoupHandlerType invoke_type)
467 SoupErrorCode retval = SOUP_ERROR_NONE;
469 g_return_val_if_fail (msg != NULL, retval);
471 for (list = msg->priv->content_handlers; list; list = list->next) {
472 SoupHandlerData *data = list->data;
474 if (data->type != invoke_type) continue;
476 switch (data->kind) {
477 case RESPONSE_HEADER_HANDLER:
478 if (!soup_message_get_response_header (msg,
482 case RESPONSE_CODE_HANDLER:
483 if (msg->response_code != data->code) continue;
487 retval = (*data->handler_cb) (msg, data->user_data);
489 if (retval != SOUP_ERROR_NONE) break;
490 if (msg->status == SOUP_STATUS_QUEUED) break;
497 soup_message_redirect (SoupMessage *msg, gpointer user_data)
499 const gchar *new_url;
501 switch (msg->response_code) {
502 case 300: /* Multiple Choices */
503 case 301: /* Moved Permanently */
504 case 302: /* Moved Temporarily */
505 case 303: /* See Other */
506 case 305: /* Use Proxy */
509 return SOUP_ERROR_NONE;
512 if (!(msg->priv->flags & SOUP_MESSAGE_FOLLOW_REDIRECT))
513 return SOUP_ERROR_NONE;
515 new_url = soup_message_get_response_header (msg, "Location");
517 soup_context_unref (msg->context);
518 msg->context = soup_context_get (new_url);
520 if (!msg->context) return SOUP_ERROR_MALFORMED_HEADER;
522 soup_message_queue (msg,
524 msg->priv->user_data);
527 return SOUP_ERROR_NONE;
531 soup_message_set_flags (SoupMessage *msg, guint flags)
533 g_return_if_fail (msg != NULL);
535 if ((flags & SOUP_MESSAGE_FOLLOW_REDIRECT) &&
536 !(msg->priv->msg_flags & SOUP_MESSAGE_FOLLOW_REDIRECT))
537 soup_message_add_header_handler (msg,
539 SOUP_HANDLER_PRE_BODY,
540 soup_message_redirect,
543 msg->priv->msg_flags = flags;
547 soup_message_get_flags (SoupMessage *msg)
549 return msg->priv->msg_flags;