* Copyright (C) 2000, Helix Code, Inc.
*/
+#include <glib.h>
#include <gnet/gnet.h>
#include "soup-context.h"
#include "soup-private.h"
+#include "soup-misc.h"
#include "soup-uri.h"
-GHashTable *servers;
+gint connection_count = 0;
+
+GHashTable *servers; /* KEY: hostname, VALUE: SoupServer */
+
+static guint most_recently_used_id = 0;
static SoupContext *
soup_context_new (SoupServer *server, SoupUri *uri)
ctx->priv = g_new0 (SoupContextPrivate, 1);
ctx->priv->server = server;
ctx->priv->keep_alive = TRUE;
- ctx->priv->chunk_size = DEFAULT_CHUNK_SIZE;
ctx->uri = uri;
- ctx->custom_headers = NULL;
return ctx;
}
if (ret) return ret;
ret = soup_context_new (serv, suri);
+ soup_context_ref (ret);
g_hash_table_insert (serv->contexts, suri->path, ret);
}
void
-soup_context_free (SoupContext *ctx)
+soup_context_ref (SoupContext *ctx)
{
+ ctx->priv->refcnt++;
+}
+
+void
+soup_context_unref (SoupContext *ctx)
+{
+ if (ctx->priv->refcnt-- == 0) {
+ SoupServer *serv = ctx->priv->server;
+
+ g_hash_table_remove (serv->contexts, ctx->uri->path);
+
+ if (g_hash_table_size (serv->contexts) == 0) {
+ GSList *conns = serv->connections;
+
+ g_hash_table_remove (servers, serv->host);
+
+ while (conns) {
+ SoupConnection *conn = conns->data;
+ gnet_tcp_socket_unref (conn->socket);
+ g_free (conn);
+ connection_count--;
+
+ conns = conns->next;
+ }
+
+ g_free (serv->host);
+ g_slist_free (serv->connections);
+ g_hash_table_destroy (serv->contexts);
+ g_free (serv);
+ }
+
+ soup_uri_free (ctx->uri);
+ g_free (ctx);
+ }
}
struct SoupContextConnectFunctor {
new_conn->in_use = TRUE;
new_conn->socket = socket;
+ connection_count++;
+
ctx->priv->server->connections =
g_slist_prepend (ctx->priv->server->connections,
new_conn);
}
}
-void
+struct SoupConnDesc {
+ SoupServer *serv;
+ SoupConnection *conn;
+};
+
+static void
+soup_prune_foreach (gchar *hostname,
+ SoupServer *serv,
+ struct SoupConnDesc *last)
+{
+ GSList *conns = serv->connections;
+
+ while (conns) {
+ SoupConnection *conn = conns->data;
+ if (!conn->in_use)
+ if (last->conn == NULL ||
+ last->conn->last_used_id > conn->last_used_id) {
+ last->conn = conn;
+ last->serv = serv;
+ }
+
+ conns = conns->next;
+ }
+}
+
+static gboolean
+soup_prune_least_used_connection (void)
+{
+ struct SoupConnDesc last;
+ last.serv = NULL;
+ last.conn = NULL;
+
+ g_hash_table_foreach (servers, (GHFunc) soup_prune_foreach, &last);
+
+ if (last.conn) {
+ last.serv->connections =
+ g_slist_remove (last.serv->connections, last.conn);
+ gnet_tcp_socket_unref (last.conn->socket);
+ g_free (last.conn);
+
+ connection_count--;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gpointer
soup_context_get_connection (SoupContext *ctx,
SoupConnectCallbackFn cb,
gpointer user_data)
{
GSList *conns;
+ if (connection_count >= soup_get_connection_limit() &&
+ !soup_prune_least_used_connection ()) {
+ //FIXME: set timeout to try pruning again
+ }
+
if (!ctx->priv->keep_alive)
- goto FORCE_NEW_CONNECTION;
+ goto NEW_CONNECTION;
conns = ctx->priv->server->connections;
if (!conn->in_use && conn->port == ctx->uri->port) {
conn->in_use = TRUE;
+
(*cb) (ctx,
SOUP_CONNECT_ERROR_NONE,
conn->socket,
user_data);
- return;
+
+ return NULL;
}
conns = conns->next;
}
- FORCE_NEW_CONNECTION:
+ NEW_CONNECTION:
{
struct SoupContextConnectFunctor *data;
data = g_new0 (struct SoupContextConnectFunctor, 1);
data->ctx = ctx;
data->cb = cb;
data->user_data = user_data;
- gnet_tcp_socket_connect_async (ctx->uri->host,
- ctx->uri->port,
- soup_context_connect_cb,
- user_data);
- return;
+ return gnet_tcp_socket_connect_async (ctx->uri->host,
+ ctx->uri->port,
+ soup_context_connect_cb,
+ user_data);
}
}
void
-soup_context_return_connection (SoupContext *ctx,
- GTcpSocket *socket)
+soup_context_release_connection (SoupContext *ctx,
+ GTcpSocket *socket)
{
SoupServer *server = ctx->priv->server;
GSList *conns = server->connections;
if (conn->socket == socket) {
if (ctx->priv->keep_alive) {
+ conn->last_used_id = ++most_recently_used_id;
conn->in_use = FALSE;
} else {
server->connections =
g_slist_remove (server->connections,
- socket);
+ conn);
gnet_tcp_socket_unref (socket);
+ g_free (conn);
connection_count--;
}
* Authors:
* Alex Graveley (alex@helixcode.com)
*
+ * soup_base64_encode() written by Joe Orton borrowed from ghttp.
+ *
* Copyright (C) 2000, Helix Code, Inc.
*/
#include <config.h>
#include <glib.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
#include <gnet/gnet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "soup-queue.h"
#include "soup-context.h"
+#include "soup-misc.h"
#include "soup-private.h"
-guint connection_count = 0;
+GSList *active_requests = NULL;
-GList *active_requests = NULL;
+static guint soup_queue_idle_tag = 0;
-static guint max_connections = 4;
+static gint
+soup_substring_index (gchar *str, gint len, gchar *substr)
+{
+ int i, sublen = strlen (substr);
+
+ for (i = 0; i < len; ++i)
+ if (str[i] == substr[0])
+ if (memcmp (&str[i], substr, sublen) == 0)
+ return i;
-static guint soup_queue_idle_tag = 0;
+ return -1;
+}
+
+static void
+soup_process_headers (SoupRequest *req, gchar *str, guint len)
+{
+ gchar *http_ver, *reason_phrase;
+ gint status_code;
+
+ sscanf (str, "%s %u %s", http_ver, &status_code, reason_phrase);
-static SoupContext *proxy_context;
+ req->response_code = status_code;
+ req->response_phrase = reason_phrase;
+
+ g_free (http_ver);
+}
static gboolean
soup_queue_read_async (GIOChannel* iochannel,
GIOCondition condition,
SoupRequest *req)
{
+ gchar read_buf[RESPONSE_BLOCK_SIZE];
guint bytes_read;
+ gint index;
GIOError error;
- if (!req->response.body) {
- req->response.body = g_malloc (RESPONSE_BLOCK_SIZE);
- req->response.length = RESPONSE_BLOCK_SIZE;
- } else if (req->priv->read_len == req->response.length) {
- req->response.length += RESPONSE_BLOCK_SIZE;
- req->response.body = g_realloc (req->response.body,
- req->response.length);
- }
-
- error = g_io_channel_read (iochannel,
- &req->response.body[req->priv->read_len],
- req->response.length - req->priv->read_len,
+ error = g_io_channel_read (iochannel,
+ read_buf,
+ sizeof (read_buf),
&bytes_read);
if (error == G_IO_ERROR_AGAIN)
return FALSE;
}
+ if (!req->priv->recv_buf)
+ req->priv->recv_buf = g_byte_array_new ();
+
if (bytes_read == 0) {
+ index = soup_substring_index (req->priv->recv_buf->data,
+ req->priv->recv_buf->len,
+ "\r\n\r\n");
+
+ req->response.length = req->priv->recv_buf->len - index + 4;
+ req->response.body =
+ g_memdup (&req->priv->recv_buf->data [index + 4],
+ req->response.length + 1);
+ req->response.body [req->response.length] = '\0';
+
req->status = SOUP_STATUS_FINISHED;
soup_request_issue_callback (req, SOUP_ERROR_NONE);
return FALSE;
}
+
+ g_byte_array_append (req->priv->recv_buf,
+ read_buf,
+ bytes_read);
req->priv->read_len += bytes_read;
+ if (!req->response_code) {
+ index = soup_substring_index (req->priv->recv_buf->data,
+ req->priv->recv_buf->len,
+ "\r\n\r\n");
+ if (index)
+ soup_process_headers (req,
+ req->priv->recv_buf->data,
+ index + 2);
+ }
+
return TRUE;
}
+const char base64_alphabet[65] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+
+static gchar *
+soup_base64_encode (gchar *text)
+{
+ char *buffer = NULL;
+ char *point = NULL;
+ int inlen = 0;
+ int outlen = 0;
+
+ /* check our args */
+ if (text == NULL)
+ return NULL;
+
+ /* Use 'buffer' to store the output. Work out how big it should be...
+ * This must be a multiple of 4 bytes */
+
+ inlen = strlen (text);
+ /* check our arg...avoid a pesky FPE */
+ if (inlen == 0) {
+ buffer = malloc (sizeof(char));
+ buffer[0] = '\0';
+ return buffer;
+ }
+
+ outlen = (inlen*4)/3;
+ if ((inlen % 3) > 0) /* got to pad */
+ outlen += 4 - (inlen % 3);
+
+ buffer = malloc (outlen + 1); /* +1 for the \0 */
+ memset (buffer, 0, outlen + 1); /* initialize to zero */
+
+ /* now do the main stage of conversion, 3 bytes at a time,
+ * leave the trailing bytes (if there are any) for later */
+
+ for (point=buffer; inlen>=3; inlen-=3, text+=3) {
+ *(point++) = base64_alphabet [*text>>2];
+ *(point++) = base64_alphabet [(*text<<4 & 0x30) |
+ *(text+1)>>4];
+ *(point++) = base64_alphabet [(*(text+1)<<2 & 0x3c) |
+ *(text+2)>>6];
+ *(point++) = base64_alphabet [*(text+2) & 0x3f];
+ }
+
+ /* Now deal with the trailing bytes */
+ if (inlen) {
+ /* We always have one trailing byte */
+ *(point++) = base64_alphabet [*text>>2];
+ *(point++) = base64_alphabet [(*text<<4 & 0x30) |
+ (inlen==2?*(text+1)>>4:0)];
+ *(point++) = (inlen == 1 ?
+ '=' :
+ base64_alphabet [*(text+1)<<2 & 0x3c]);
+ *(point++) = '=';
+ }
+
+ *point = '\0';
+
+ return buffer;
+}
+
+static void
+soup_encode_http_auth (gboolean proxy_auth, SoupUri *uri, GString *header)
+{
+ if (!uri->authmech) {
+ gchar *authpass, *encoded;
+ authpass = g_strconcat (uri->user, ":", uri->passwd);
+ encoded = soup_base64_encode (authpass);
+ g_string_sprintfa (header,
+ "%s: Basic %s\r\n",
+ proxy_auth ?
+ "Proxy-Authorization" :
+ "Authorization",
+ encoded);
+ g_free (encoded);
+ g_free (authpass);
+ }
+}
+
+#define custom_header(_name, _var) ({ \
+ gchar *val = g_hash_table_lookup (req->custom_headers, (_name)); \
+ if (val) (_var) = val; \
+})
+
+static GString *
+soup_get_request_header (SoupRequest *req)
+{
+ GString *header = g_string_new ("");
+ gchar *uri;
+ SoupContext *proxy = soup_get_proxy ();
+
+ gchar *host = req->context->uri->host,
+ *user_agent = "Soup/0.1",
+ *content_type = "text/xml",
+ *charset = "\"utf-8\"",
+ *content_length = NULL,
+ *soapaction = req->action,
+ *connection = "keep-alive",
+ *proxy_authorization = NULL,
+ *authorization = NULL;
+
+ gboolean my_content_length = FALSE;
+
+ if (req->custom_headers) {
+ custom_header ("Host", host);
+ custom_header ("User-Agent", user_agent);
+ custom_header ("Content-Type", content_type);
+ custom_header ("Charset", charset);
+ custom_header ("Content-Length", content_length);
+ custom_header ("SOAPAction", soapaction);
+ custom_header ("Proxy-Authorization", proxy_authorization);
+ custom_header ("Authorization", authorization);
+ custom_header ("Connection", connection);
+ }
+
+ if (!content_length) {
+ content_length = g_strdup_printf ("%d", req->request.length);
+ my_content_length = TRUE;
+ }
+
+ if (proxy)
+ uri = soup_uri_to_string (proxy->uri, FALSE);
+ else
+ uri = req->context->uri->path;
+
+ /* If we specify an absoluteURI in the request line, the
+ Host header MUST be ignored by the proxy. */
+
+ g_string_sprintfa (header,
+ "POST %s HTTP/1.1\r\n"
+ "Host: %s\r\n"
+ "User-Agent: %s\r\n"
+ "Content-Type: %s;\r\n"
+ "charset=%s\r\n"
+ "Content-Length: %s\r\n"
+ "SOAPAction: %s\r\n"
+ "Connection: %s\r\n",
+ uri,
+ host,
+ user_agent,
+ content_type,
+ charset,
+ content_length,
+ soapaction,
+ connection);
+
+ if (my_content_length)
+ g_free (content_length);
+
+ if (!proxy_authorization) {
+ if (proxy && proxy->uri->user)
+ soup_encode_http_auth (TRUE, proxy->uri, header);
+ } else
+ g_string_sprintfa (header,
+ "Proxy-Authorization: %s\r\n",
+ proxy_authorization);
+
+ /* FIXME: if going through a proxy, do we use the absoluteURI on
+ the request line, or encode the Authorization header into
+ the message? */
+
+ if (!authorization) {
+ if (req->context->uri->user)
+ soup_encode_http_auth (FALSE, proxy->uri, header);
+ } else
+ g_string_sprintfa (header,
+ "Authorization: %s\r\n",
+ authorization);
+
+ g_string_append (header, "\r\n");
+
+ return header;
+}
+
static gboolean
soup_queue_write_async (GIOChannel* iochannel,
GIOCondition condition,
SoupRequest *req)
{
- guint bytes_written;
+ guint head_len, body_len, total_len, total_written, bytes_written;
GIOError error;
+ gchar *write_buf;
+ guint write_len;
+
+ if (!req->priv->req_header)
+ req->priv->req_header = soup_get_request_header (req);
+
+ head_len = req->priv->req_header->len;
+ body_len = req->request.length;
+ total_len = head_len + body_len;
+ total_written = req->priv->write_len;
+
+ if (total_written < head_len) {
+ /* headers not done yet */
+ /* send rest of headers and all of body */
+ /* maybe we should just send the rest of the headers here,
+ and avoid memcpy/alloca altogether at the loss of cpu
+ cycles */
+ guint offset = head_len - total_written;
+ write_len = (offset) + body_len;
+ write_buf = alloca (write_len);
+ memcpy (write_buf,
+ &req->priv->req_header->str [offset],
+ offset);
+ memcpy (&write_buf [offset + 1],
+ req->request.body,
+ req->request.length);
+ } else if (total_written >= head_len) {
+ /* headers done, maybe some of body */
+ /* send rest of body */
+ guint offset = total_written - head_len;
+ write_buf = &req->request.body [offset];
+ write_len = body_len - offset;
+ }
error = g_io_channel_write (iochannel,
- &req->request.body[req->priv->write_len],
- req->request.length - req->priv->write_len,
+ write_buf,
+ write_len,
&bytes_written);
if (error == G_IO_ERROR_AGAIN)
return FALSE;
}
- req->priv->write_len += bytes_written;
+ total_written += bytes_written;
+ req->priv->write_len = total_written;
- if (req->priv->write_len == req->request.length) {
+ if (total_written == total_len) {
req->status = SOUP_STATUS_READING_RESPONSE;
req->priv->read_tag =
g_io_add_watch (iochannel,
- G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL,
+ G_IO_IN,
(GIOFunc) soup_queue_read_async,
req);
return FALSE;
return TRUE;
}
+
+static gboolean
+soup_queue_error_async (GIOChannel* iochannel,
+ GIOCondition condition,
+ SoupRequest *req)
+{
+ switch (condition) {
+ case G_IO_ERR:
+ case G_IO_HUP:
+ case G_IO_NVAL:
+ switch (req->status) {
+ case SOUP_STATUS_FINISHED:
+ break;
+ case SOUP_STATUS_CONNECTING:
+ soup_request_issue_callback (req,
+ SOUP_ERROR_CANT_CONNECT);
+ break;
+ default:
+ soup_request_issue_callback (req,
+ SOUP_ERROR_IO);
+ break;
+ }
+ case G_IO_IN:
+ case G_IO_OUT:
+ case G_IO_PRI:
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
static void
soup_setup_socket (GIOChannel *channel)
{
req->status = SOUP_STATUS_SENDING_REQUEST;
req->priv->socket = socket;
+ req->priv->connect_tag = NULL;
req->priv->write_tag =
g_io_add_watch (channel,
- G_IO_OUT|G_IO_ERR|G_IO_HUP|G_IO_NVAL,
+ G_IO_OUT,
(GIOFunc) soup_queue_write_async,
req);
+ req->priv->error_tag =
+ g_io_add_watch (channel,
+ G_IO_ERR|G_IO_HUP|G_IO_NVAL,
+ (GIOFunc) soup_queue_error_async,
+ req);
break;
case SOUP_CONNECT_ERROR_ADDR_RESOLVE:
case SOUP_CONNECT_ERROR_NETWORK:
soup_request_issue_callback (req, SOUP_ERROR_CANT_CONNECT);
- connection_count--;
break;
}
}
static gboolean
soup_idle_handle_new_requests (gpointer unused)
{
- GList *iter;
- gboolean work_to_do = FALSE;
-
- if (connection_count >= max_connections)
- return TRUE;
+ GSList *iter = active_requests;
- for (iter = active_requests; iter; iter = iter->next) {
+ while (iter) {
SoupRequest *req = iter->data;
-
- if (connection_count >= max_connections)
- return TRUE;
+ SoupContext *ctx, *proxy;
if (req->status != SOUP_STATUS_QUEUED)
continue;
- if (req->priv->socket) {
- GTcpSocket *sock = req->priv->socket;
- GIOChannel *channel;
- channel = gnet_tcp_socket_get_iochannel (sock);
-
- req->status = SOUP_STATUS_SENDING_REQUEST;
- req->priv->write_tag = g_io_add_watch (
- channel,
- G_IO_OUT|G_IO_ERR|G_IO_HUP|G_IO_NVAL,
- (GIOFunc) soup_queue_write_async,
- req);
- } else {
- SoupContext *ctx;
- ctx = proxy_context ? proxy_context : req->context;
- connection_count++;
-
- req->status = SOUP_STATUS_CONNECTING;
+ proxy = soup_get_proxy ();
+ ctx = proxy ? proxy : req->context;
+
+ req->status = SOUP_STATUS_CONNECTING;
+ req->priv->connect_tag =
soup_context_get_connection (ctx,
soup_queue_connect,
req);
- }
- work_to_do = TRUE;
+ iter = iter->next;
}
- if (!work_to_do) {
- soup_queue_idle_tag = 0;
- return FALSE;
- }
-
- return TRUE;
+ soup_queue_idle_tag = 0;
+ return FALSE;
}
void
soup_queue_idle_tag =
g_idle_add (soup_idle_handle_new_requests, NULL);
- if (req->response.body &&
- req->response.owner == SOUP_BUFFER_SYSTEM_OWNED) {
+ if (req->response.owner == SOUP_BUFFER_SYSTEM_OWNED) {
g_free (req->response.body);
req->response.body = NULL;
req->response.length = 0;
req->priv->user_data = user_data;
req->status = SOUP_STATUS_QUEUED;
- active_requests = g_list_append (active_requests, req);
+ soup_context_ref (req->context);
+
+ active_requests = g_slist_append (active_requests, req);
}
void
soup_queue_shutdown ()
{
- GList *iter;
+ GSList *iter;
g_source_remove (soup_queue_idle_tag);
soup_queue_idle_tag = 0;
for (iter = active_requests; iter; iter = iter->next)
soup_request_cancel (iter->data);
}
-
-void
-soup_queue_set_proxy (SoupContext *context)
-{
- if (proxy_context)
- soup_context_free (proxy_context);
-
- proxy_context = context;
-}
-
-SoupContext *
-soup_queue_get_proxy ()
-{
- return proxy_context;
-}
-