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;
113 req->priv->cur_chunk_len = 0;
114 req->priv->cur_chunk_idx = 0;
116 soup_active_requests = g_slist_remove (soup_active_requests, req);
120 soup_message_remove_header (gchar *name, gchar *value, gpointer unused)
128 * @req: a %SoupMessage to destroy.
130 * Destroys the %SoupMessage pointed to by @req. Request and response headers
131 * are freed. Request and response data buffers are also freed if their
132 * ownership is %SOUP_BUFFER_SYSTEM_OWNED. The message's destination context
133 * will be de-referenced.
136 soup_message_free (SoupMessage *req)
138 g_return_if_fail (req != NULL);
140 soup_message_cleanup (req);
142 soup_context_unref (req->context);
144 if (req->request.owner == SOUP_BUFFER_SYSTEM_OWNED)
145 g_free (req->request.body);
146 if (req->response.owner == SOUP_BUFFER_SYSTEM_OWNED)
147 g_free (req->response.body);
149 if (req->priv->req_header)
150 g_string_free (req->priv->req_header, TRUE);
152 if (req->request_headers) {
153 g_hash_table_foreach (req->request_headers,
154 (GHFunc) soup_message_remove_header,
156 g_hash_table_destroy (req->request_headers);
159 if (req->response_headers) {
160 g_hash_table_foreach (req->response_headers,
161 (GHFunc) soup_message_remove_header,
163 g_hash_table_destroy (req->response_headers);
166 g_slist_foreach (req->priv->content_handlers, (GFunc) g_free, NULL);
167 g_slist_free (req->priv->content_handlers);
170 g_free (req->action);
175 * soup_message_issue_callback:
176 * @req: a %SoupMessage currently being processed.
177 * @error: a %SoupErrorCode to be passed to %req's completion callback.
179 * Finalizes the message request, by first freeing any temporary resources, then
180 * issuing the callback function pointer passed in %soup_message_new or
181 * %soup_message_new_full. If, after returning from the callback, the message
182 * has not been requeued, @msg is destroyed using %soup_message_free.
185 soup_message_issue_callback (SoupMessage *req, SoupErrorCode error)
187 g_return_if_fail (req != NULL);
189 /* make sure we don't have some icky recursion if the callback
190 runs the main loop, and the connection has some data or error
191 which causes the callback to be run again */
192 soup_message_cleanup (req);
194 req->priv->errorcode = error;
196 if (req->priv->callback) {
197 (*req->priv->callback) (req, error, req->priv->user_data);
199 /* Free it only if callback exist, its probably a sync call */
200 if (req->status != SOUP_STATUS_QUEUED)
201 soup_message_free (req);
206 * soup_message_cancel:
207 * @req: a %SoupMessage currently being processed.
209 * Cancel a running message, and issue completion callback with a
210 * %SoupTransferStatus of %SOUP_ERROR_CANCELLED. If not requeued by the
211 * completion callback, the @msg will be destroyed.
214 soup_message_cancel (SoupMessage *req)
216 soup_message_issue_callback (req, SOUP_ERROR_CANCELLED);
220 soup_message_set_header (GHashTable **hash,
224 gpointer old_name, old_value;
227 *hash = g_hash_table_new (soup_str_case_hash,
228 soup_str_case_equal);
229 else if (g_hash_table_lookup_extended (*hash,
233 g_hash_table_remove (*hash, name);
238 g_hash_table_insert (*hash, g_strdup (name), g_strdup (value));
242 * soup_message_set_request_header:
243 * @req: a %SoupMessage.
244 * @name: header name.
245 * @value: header value.
247 * Adds a new transport header to be sent on an outgoing request.
250 soup_message_set_request_header (SoupMessage *req,
254 g_return_if_fail (req != NULL);
255 g_return_if_fail (name != NULL || name [0] != '\0');
257 if (req->priv->req_header) {
258 g_string_free (req->priv->req_header, TRUE);
259 req->priv->req_header = NULL;
262 soup_message_set_header (&req->request_headers, name, value);
266 * soup_message_get_request_header:
267 * @req: a %SoupMessage.
268 * @name: header name.
270 * Lookup the transport request header with a key equal to @name.
272 * Return value: the header's value or NULL if not found.
275 soup_message_get_request_header (SoupMessage *req,
278 g_return_val_if_fail (req != NULL, NULL);
279 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
281 return req->request_headers ?
282 g_hash_table_lookup (req->request_headers, name) : NULL;
286 * soup_message_set_response_header:
287 * @req: a %SoupMessage.
288 * @name: header name.
289 * @value: header value.
291 * Adds a new transport header to be sent on an outgoing response.
294 soup_message_set_response_header (SoupMessage *req,
298 g_return_if_fail (req != NULL);
299 g_return_if_fail (name != NULL || name [0] != '\0');
301 soup_message_set_header (&req->response_headers, name, value);
305 * soup_message_get_response_header:
306 * @req: a %SoupMessage.
307 * @name: header name.
309 * Lookup the transport response header with a key equal to @name.
311 * Return value: the header's value or NULL if not found.
314 soup_message_get_response_header (SoupMessage *req,
317 g_return_val_if_fail (req != NULL, NULL);
318 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
320 return req->response_headers ?
321 g_hash_table_lookup (req->response_headers, name) : NULL;
326 * @msg: a %SoupMessage.
328 * Syncronously send @msg. This call will not return until the transfer is
329 * finished successfully or there is an unrecoverable error.
331 * @msg is not free'd upon return.
333 * Return value: the %SoupErrorCode of the error encountered while sending, or
337 soup_message_send (SoupMessage *msg)
339 soup_message_queue (msg, NULL, NULL);
342 g_main_iteration (TRUE);
343 if (msg->status == SOUP_STATUS_FINISHED ||
344 msg->priv->errorcode != SOUP_ERROR_NONE)
345 return msg->priv->errorcode;
348 return SOUP_ERROR_NONE;
352 soup_message_set_method (SoupMessage *msg, const gchar *method)
354 g_return_if_fail (msg != NULL);
355 g_return_if_fail (method != NULL);
357 msg->method = method;
361 soup_message_get_method (SoupMessage *msg)
363 g_return_val_if_fail (msg != NULL, NULL);
369 RESPONSE_HEADER_HANDLER,
370 RESPONSE_CODE_HANDLER,
371 RESPONSE_BODY_HANDLER
375 SoupHandlerType type;
376 SoupHandlerFn handler_cb;
379 SoupHandlerKind kind;
385 soup_message_add_handler (SoupMessage *msg,
386 SoupHandlerType type,
387 SoupHandlerFn handler_cb,
389 SoupHandlerKind kind,
393 SoupHandlerData *data;
395 data = g_new0 (SoupHandlerData, 1);
397 data->handler_cb = handler_cb;
398 data->user_data = user_data;
400 data->header = header;
403 msg->priv->content_handlers =
404 g_slist_append (msg->priv->content_handlers, data);
408 soup_message_add_header_handler (SoupMessage *msg,
410 SoupHandlerType type,
411 SoupHandlerFn handler_cb,
414 g_return_if_fail (msg != NULL);
415 g_return_if_fail (header != NULL);
416 g_return_if_fail (handler_cb != NULL);
418 soup_message_add_handler (msg,
422 RESPONSE_HEADER_HANDLER,
428 soup_message_add_response_code_handler (SoupMessage *msg,
430 SoupHandlerType type,
431 SoupHandlerFn handler_cb,
434 g_return_if_fail (msg != NULL);
435 g_return_if_fail (code != 0);
436 g_return_if_fail (handler_cb != NULL);
438 soup_message_add_handler (msg,
442 RESPONSE_CODE_HANDLER,
448 soup_message_add_body_handler (SoupMessage *msg,
449 SoupHandlerType type,
450 SoupHandlerFn handler_cb,
453 g_return_if_fail (msg != NULL);
454 g_return_if_fail (handler_cb != NULL);
456 soup_message_add_handler (msg,
460 RESPONSE_BODY_HANDLER,
466 soup_message_run_handlers (SoupMessage *msg, SoupHandlerType invoke_type)
469 SoupErrorCode retval = SOUP_ERROR_NONE;
471 g_return_val_if_fail (msg != NULL, retval);
473 for (list = msg->priv->content_handlers; list; list = list->next) {
474 SoupHandlerData *data = list->data;
476 if (data->type != invoke_type) continue;
478 switch (data->kind) {
479 case RESPONSE_HEADER_HANDLER:
480 if (!soup_message_get_response_header (msg,
484 case RESPONSE_CODE_HANDLER:
485 if (msg->response_code != data->code) continue;
487 case RESPONSE_BODY_HANDLER:
491 retval = (*data->handler_cb) (msg, data->user_data);
493 if (retval != SOUP_ERROR_NONE) break;
494 if (msg->status == SOUP_STATUS_QUEUED) break;
501 soup_message_remove_handler (SoupMessage *msg,
502 SoupHandlerFn handler_cb,
505 GSList *iter = msg->priv->content_handlers;
508 SoupHandlerData *data = iter->data;
510 if (data->handler_cb == handler_cb &&
511 data->user_data == user_data) {
512 msg->priv->content_handlers =
513 g_slist_remove_link (
514 msg->priv->content_handlers,
525 soup_message_redirect (SoupMessage *msg, gpointer user_data)
527 const gchar *new_url;
529 switch (msg->response_code) {
530 case 300: /* Multiple Choices */
531 case 301: /* Moved Permanently */
532 case 302: /* Moved Temporarily */
533 case 303: /* See Other */
534 case 305: /* Use Proxy */
537 return SOUP_ERROR_NONE;
540 if (!(msg->priv->msg_flags & SOUP_MESSAGE_FOLLOW_REDIRECT))
541 return SOUP_ERROR_NONE;
543 new_url = soup_message_get_response_header (msg, "Location");
545 soup_context_unref (msg->context);
546 msg->context = soup_context_get (new_url);
548 if (!msg->context) return SOUP_ERROR_MALFORMED_HEADER;
550 soup_message_queue (msg,
552 msg->priv->user_data);
555 return SOUP_ERROR_NONE;
558 static inline gboolean
559 ADDED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
561 return ((newflags & find) && !(msg->priv->msg_flags & find));
564 static inline gboolean
565 REMOVED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
567 return (!(newflags & find) && (msg->priv->msg_flags & find));
571 soup_message_set_flags (SoupMessage *msg, guint flags)
573 g_return_if_fail (msg != NULL);
575 if (ADDED_FLAG (msg, flags, SOUP_MESSAGE_FOLLOW_REDIRECT))
576 soup_message_add_header_handler (msg,
578 SOUP_HANDLER_PRE_BODY,
579 soup_message_redirect,
581 else if (REMOVED_FLAG (msg, flags, SOUP_MESSAGE_FOLLOW_REDIRECT))
582 soup_message_remove_handler (msg,
583 soup_message_redirect,
586 msg->priv->msg_flags = flags;
590 soup_message_get_flags (SoupMessage *msg)
592 return msg->priv->msg_flags;