2003-08-19 Dan Winship <danw@ximian.com>
+ * libsoup/soup-socket.c (soup_socket_read,
+ soup_socket_read_until, soup_socket_write): New API for doing
+ socket IO. Works both synchronously and asynchronously, and
+ buffers data to prevent the "100 Continue" problem.
+ (soup_socket_set_flag): Replaces formerly-private
+ soup_set_sockopts. (primarily to let the caller turn off
+ SOUP_SOCKET_FLAG_NONBLOCKING).
+
+ * libsoup/soup-transfer.c (soup_transfer_read,
+ soup_transfer_write, soup_transfer_write_simple): Take a
+ SoupSocket instead of a GIOChannel. Use the new socket IO api.
+ Changed the prototypes of some of the callbacks to be less
+ hackish.
+
+ * libsoup/soup-connection.c (soup_connection_get_socket): Replaces
+ soup_connection_get_iochannel.
+
+ * libsoup/soup-message.c: Fix up for soup-transfer changes
+
+ * libsoup/soup-queue.c: Likewise
+
+ * libsoup/soup-server.c: Likewise
+
+ * tests/revserver.c: A slightly more complicated replacement for
+ timeserver. (Does both reads and writes)
+
+2003-08-19 Dan Winship <danw@ximian.com>
+
* libsoup/soup-socks.[ch]: Remove this. RC doesn't let you
- configure it anyway, and no one has complained, and it looks like
- the SOCKS5 auth code doesn't actually work anyway...
+ configure it, and no one has complained, and it looks like the
+ SOCKS5 auth code doesn't actually work anyway...
* libsoup/soup-queue.c (proxy_connect): Remove SOCKS code.
}
/**
- * soup_connection_get_iochannel:
+ * soup_connection_get_socket:
* @conn: a #SoupConnection.
*
- * Returns a #GIOChannel used for IO operations on the network
- * connection represented by @conn.
- *
- * Return value: a pointer to the #GIOChannel used for IO on @conn
+ * Return value: @conn's socket
*/
-GIOChannel *
-soup_connection_get_iochannel (SoupConnection *conn)
+SoupSocket *
+soup_connection_get_socket (SoupConnection *conn)
{
g_return_val_if_fail (SOUP_IS_CONNECTION (conn), NULL);
- if (!conn->priv->socket)
- return NULL;
-
- return soup_socket_get_iochannel (conn->priv->socket);
+ return conn->priv->socket;
}
void soup_connection_disconnect (SoupConnection *conn);
gboolean soup_connection_is_connected (SoupConnection *conn);
-GIOChannel *soup_connection_get_iochannel (SoupConnection *conn);
+SoupSocket *soup_connection_get_socket (SoupConnection *conn);
void soup_connection_set_in_use (SoupConnection *conn,
gboolean in_use);
SOUP_ERROR_CANCELLED = 1,
SOUP_ERROR_CANT_RESOLVE = 2,
SOUP_ERROR_CANT_RESOLVE_PROXY = 3,
- SOUP_ERROR_CANT_CONNECT = 2,
- SOUP_ERROR_CANT_CONNECT_PROXY = 3,
- SOUP_ERROR_IO = 4,
- SOUP_ERROR_MALFORMED = 5,
- SOUP_ERROR_CANT_AUTHENTICATE = 6,
- SOUP_ERROR_CANT_AUTHENTICATE_PROXY = 7,
- SOUP_ERROR_SSL_FAILED = 8,
+ SOUP_ERROR_CANT_CONNECT = 4,
+ SOUP_ERROR_CANT_CONNECT_PROXY = 5,
+ SOUP_ERROR_IO = 6,
+ SOUP_ERROR_MALFORMED = 7,
+ SOUP_ERROR_CANT_AUTHENTICATE = 8,
+ SOUP_ERROR_CANT_AUTHENTICATE_PROXY = 9,
+ SOUP_ERROR_SSL_FAILED = 10,
/*
* HTTP Response Codes
}
static void
-release_connection (const SoupDataBuffer *data,
- gpointer user_data)
+release_connection (char *body,
+ guint len,
+ gpointer user_data)
{
SoupConnection *conn = user_data;
soup_connection_set_in_use (conn, FALSE);
g_object_unref (conn);
-
- if (data->owner == SOUP_BUFFER_SYSTEM_OWNED)
- g_free (data->body);
+ g_free (body);
}
static void
}
static void
-requeue_read_finished (const SoupDataBuffer *buf,
- gpointer user_data)
+requeue_read_finished (char *body,
+ guint len,
+ gpointer user_data)
{
SoupMessage *msg = user_data;
SoupConnection *conn = msg->connection;
- if (buf->owner == SOUP_BUFFER_SYSTEM_OWNED)
- g_free (buf->body);
+ g_free (body);
if (soup_connection_is_connected (conn)) {
soup_connection_mark_old (conn);
}
static void
-soup_queue_read_headers_cb (const GString *headers,
+soup_queue_read_headers_cb (char *headers,
+ guint headers_len,
SoupTransferEncoding *encoding,
- gint *content_len,
+ int *content_len,
gpointer user_data)
{
SoupMessage *req = user_data;
GHashTable *resp_hdrs;
SoupMethodId meth_id;
- if (!soup_headers_parse_response (headers->str,
- headers->len,
+ if (!soup_headers_parse_response (headers, headers_len,
req->response_headers,
&version,
&req->errorcode,
- (gchar **) &req->errorphrase)) {
+ (char **) &req->errorphrase)) {
soup_message_set_error_full (req,
SOUP_ERROR_MALFORMED,
"Unable to parse response "
}
static void
-soup_queue_read_chunk_cb (const SoupDataBuffer *data,
- gpointer user_data)
+soup_queue_read_chunk_cb (const char *chunk,
+ guint len,
+ gpointer user_data)
{
SoupMessage *req = user_data;
- req->response.owner = data->owner;
- req->response.length = data->length;
- req->response.body = data->body;
+ req->response.owner = SOUP_BUFFER_STATIC;
+ req->response.length = len;
+ req->response.body = (char *)chunk;
soup_message_run_handlers (req, SOUP_HANDLER_BODY_CHUNK);
}
static void
-soup_queue_read_done_cb (const SoupDataBuffer *data,
- gpointer user_data)
+soup_queue_read_done_cb (char *body,
+ guint len,
+ gpointer user_data)
{
SoupMessage *req = user_data;
const char *connection;
else
soup_connection_mark_old (req->connection);
- req->response.owner = data->owner;
- req->response.length = data->length;
- req->response.body = data->body;
+ req->response.owner = SOUP_BUFFER_SYSTEM_OWNED;
+ req->response.length = len;
+ req->response.body = body;
soup_transfer_read_unref (req->priv->read_tag);
if (req->errorclass == SOUP_ERROR_CLASS_INFORMATIONAL) {
- GIOChannel *channel;
+ SoupSocket *sock;
gboolean overwrt;
- channel = soup_connection_get_iochannel (req->connection);
+ sock = soup_connection_get_socket (req->connection);
overwrt = req->priv->msg_flags & SOUP_MESSAGE_OVERWRITE_CHUNKS;
req->priv->read_tag =
- soup_transfer_read (channel,
+ soup_transfer_read (sock,
overwrt,
soup_queue_read_headers_cb,
soup_queue_read_chunk_cb,
static void
start_request (SoupContext *ctx, SoupMessage *req)
{
- GIOChannel *channel;
+ SoupSocket *sock;
gboolean overwrt;
- channel = soup_connection_get_iochannel (req->connection);
- if (!channel) {
+ sock = soup_connection_get_socket (req->connection);
+ if (!socket) { /* FIXME */
SoupProtocol proto;
gchar *phrase;
}
req->priv->write_tag =
- soup_transfer_write_simple (channel,
+ soup_transfer_write_simple (sock,
soup_get_request_header (req),
&req->request,
soup_queue_write_done_cb,
overwrt = req->priv->msg_flags & SOUP_MESSAGE_OVERWRITE_CHUNKS;
req->priv->read_tag =
- soup_transfer_read (channel,
+ soup_transfer_read (sock,
overwrt,
soup_queue_read_headers_cb,
soup_queue_read_chunk_cb,
SoupMessage *req = user_data;
req->priv->connect_tag = NULL;
- g_object_ref (conn);
+ if (conn)
+ g_object_ref (conn);
if (req->connection)
g_object_unref (req->connection);
req->connection = conn;
#include <config.h>
#endif
-#ifdef HAVE_SYS_FILIO_H
-#include <sys/filio.h>
-#endif
-
-#ifdef HAVE_SYS_IOCTL_H
-#include <sys/ioctl.h>
-#endif
-
-#include <string.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
#include "soup-server.h"
GMainLoop *loop;
SoupSocket *listen_sock;
+ GSList *client_socks;
GHashTable *handlers; /* KEY: path, VALUE: SoupServerHandler */
SoupServerHandler *default_handler;
gboolean finished;
};
-
static void
init (GObject *object)
{
if (server->priv->listen_sock)
g_object_unref (server->priv->listen_sock);
+ while (server->priv->client_socks) {
+ SoupSocket *sock = server->priv->client_socks->data;
+
+ soup_socket_disconnect (sock);
+ server->priv->client_socks =
+ g_slist_remove (server->priv->client_socks, sock);
+ }
+
if (server->priv->default_handler)
free_handler (server, server->priv->default_handler);
g_free (buf);
}
-typedef struct {
- SoupServer *server;
- SoupSocket *server_sock;
-} ServerConnectData;
-
-static gboolean start_another_request (GIOChannel *server_chan,
- GIOCondition condition,
- gpointer user_data);
+static void start_request (SoupServer *, SoupSocket *);
static gboolean
check_close_connection (SoupMessage *msg)
}
return close_connection;
-} /* check_close_connection */
+}
static void
destroy_message (SoupMessage *msg)
SoupServerMessage *server_msg = msg->priv->server_msg;
if (server_sock) {
- GIOChannel *chan;
-
- chan = soup_socket_get_iochannel (server_sock);
-
/*
* Close the socket if we're using HTTP/1.0 and
* "Connection: keep-alive" isn't specified, or if we're
* using HTTP/1.1 and "Connection: close" was specified.
*/
- if (check_close_connection (msg)) {
- g_io_channel_close (chan);
- g_object_unref (server_sock);
- }
- else {
- /*
- * Listen for another request on this connection
- */
- ServerConnectData *data;
-
- data = g_new0 (ServerConnectData, 1);
- data->server = msg->priv->server;
- data->server_sock = server_sock;
-
- g_io_add_watch (chan,
- G_IO_IN|G_IO_PRI|
- G_IO_ERR|G_IO_HUP|G_IO_NVAL,
- start_another_request,
- data);
- }
+ if (check_close_connection (msg))
+ soup_socket_disconnect (server_sock);
+ else
+ start_request (server, server_sock);
}
if (server_msg) {
g_slist_free (server_msg->chunks);
g_free (server_msg);
}
-
- g_object_unref (server);
-
g_free ((char *) msg->method);
soup_message_free (msg);
+
+ g_object_unref (server);
}
static void
issue_bad_request (SoupMessage *msg)
{
GString *header;
- GIOChannel *channel;
set_response_error (msg, SOUP_ERROR_BAD_REQUEST, NULL, NULL);
FALSE,
SOUP_TRANSFER_CONTENT_LENGTH);
- channel = soup_socket_get_iochannel (msg->priv->server_sock);
msg->priv->write_tag =
- soup_transfer_write_simple (channel,
+ soup_transfer_write_simple (msg->priv->server_sock,
header,
&msg->response,
write_done_cb,
}
static void
-read_headers_cb (const GString *headers,
+read_headers_cb (char *headers,
+ guint headers_len,
SoupTransferEncoding *encoding,
- gint *content_len,
+ int *content_len,
gpointer user_data)
{
SoupMessage *msg = user_data;
SoupContext *ctx;
char *req_path = NULL;
- if (!soup_headers_parse_request (headers->str,
- headers->len,
+ if (!soup_headers_parse_request (headers, headers_len,
msg->request_headers,
(char **) &msg->method,
&req_path,
static void
call_handler (SoupMessage *req,
- const SoupDataBuffer *req_data,
+ char *body,
+ guint len,
const char *handler_path)
{
SoupServer *server = req->priv->server;
g_return_if_fail (req != NULL);
- req->request.owner = req_data->owner;
- req->request.length = req_data->length;
- req->request.body = req_data->body;
+ req->request.owner = SOUP_BUFFER_SYSTEM_OWNED;
+ req->request.length = len;
+ req->request.body = body;
req->status = SOUP_STATUS_FINISHED;
}
static void
-read_done_cb (const SoupDataBuffer *data,
- gpointer user_data)
+read_done_cb (char *body,
+ guint len,
+ gpointer user_data)
{
SoupMessage *req = user_data;
SoupSocket *server_sock = req->priv->server_sock;
- GIOChannel *channel;
soup_transfer_read_unref (req->priv->read_tag);
req->priv->read_tag = 0;
- call_handler (req, data, soup_context_get_uri (req->context)->path);
-
- channel = soup_socket_get_iochannel (server_sock);
+ call_handler (req, body, len,
+ soup_context_get_uri (req->context)->path);
if (req->priv->server_msg) {
SoupTransferEncoding encoding;
encoding = SOUP_TRANSFER_CHUNKED;
req->priv->write_tag =
- soup_transfer_write (channel,
+ soup_transfer_write (server_sock,
encoding,
get_header_cb,
get_chunk_cb,
TRUE,
SOUP_TRANSFER_CONTENT_LENGTH);
req->priv->write_tag =
- soup_transfer_write_simple (channel,
+ soup_transfer_write_simple (server_sock,
header,
&req->response,
write_done_cb,
return;
}
-static SoupMessage *
-message_new (SoupServer *server)
+static void
+start_request (SoupServer *server, SoupSocket *server_sock)
{
SoupMessage *msg;
- /*
- * Create an empty message to hold request state.
- */
+ /* Listen for another request on this connection */
msg = soup_message_new (NULL, NULL);
- if (msg) {
- msg->method = NULL;
- msg->priv->server = server;
- g_object_ref (server);
- }
-
- return msg;
+ msg->method = NULL;
+ msg->priv->server = g_object_ref (server);
+ msg->priv->server_sock = server_sock;
+ msg->priv->read_tag =
+ soup_transfer_read (server_sock,
+ FALSE,
+ read_headers_cb,
+ NULL,
+ read_done_cb,
+ error_cb,
+ msg);
}
-static gboolean
-start_another_request (GIOChannel *server_chan,
- GIOCondition condition,
- gpointer user_data)
+static void
+socket_disconnected (SoupSocket *sock, SoupServer *server)
{
- ServerConnectData *data = user_data;
- SoupMessage *msg;
- int fd, cnt;
-
- fd = g_io_channel_unix_get_fd (server_chan);
-
- if (!(condition & G_IO_IN) ||
- ioctl (fd, FIONREAD, &cnt) < 0 ||
- cnt <= 0)
- g_object_unref (data->server_sock);
- else {
- msg = message_new (data->server);
- if (!msg) {
- g_warning ("Unable to create new incoming message\n");
- g_object_unref (data->server_sock);
- } else {
- msg->priv->server_sock = data->server_sock;
- msg->priv->read_tag =
- soup_transfer_read (server_chan,
- FALSE,
- read_headers_cb,
- NULL,
- read_done_cb,
- error_cb,
- msg);
- }
- }
-
- g_free (data);
- return FALSE;
+ server->priv->client_socks =
+ g_slist_remove (server->priv->client_socks, sock);
+ g_signal_handlers_disconnect_by_func (sock, socket_disconnected, server);
}
static void
new_connection (SoupSocket *listner, SoupSocket *sock, gpointer user_data)
{
SoupServer *server = user_data;
- SoupMessage *msg;
-
- msg = message_new (server);
- if (!msg) {
- g_warning ("Unable to create new incoming message\n");
- return;
- }
- msg->priv->server_sock = g_object_ref (sock);
- msg->priv->read_tag =
- soup_transfer_read (soup_socket_get_iochannel (sock),
- FALSE,
- read_headers_cb,
- NULL,
- read_done_cb,
- error_cb,
- msg);
+ server->priv->client_socks =
+ g_slist_prepend (server->priv->client_socks, sock);
+ g_signal_connect (sock, "disconnected",
+ G_CALLBACK (socket_disconnected), server);
+ start_request (server, sock);
}
void
/*
* soup-socket.c: Socket networking code.
*
- * Based on code in David Helder's GNET Networking Library,
- * Copyright (C) 2000 David Helder & Andrew Lanoix.
- *
- * All else Copyright (C) 2000-2003, Ximian, Inc.
+ * Copyright (C) 2000-2003, Ximian, Inc.
*/
#ifdef HAVE_CONFIG_H
#include <errno.h>
#include <fcntl.h>
+#include <signal.h>
#include <string.h>
#include <unistd.h>
enum {
CONNECT_RESULT,
+ READABLE,
+ WRITABLE,
+ DISCONNECTED,
NEW_CONNECTION,
LAST_SIGNAL
};
guint watch;
gboolean server, ssl;
+
+ guint read_tag, write_tag, error_tag;
+ GByteArray *read_buf;
};
static void
soup_marshal_NONE__INT,
G_TYPE_NONE, 1,
G_TYPE_INT);
+ signals[READABLE] =
+ g_signal_new ("readable",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (SoupSocketClass, readable),
+ NULL, NULL,
+ soup_marshal_NONE__NONE,
+ G_TYPE_NONE, 0);
+ signals[WRITABLE] =
+ g_signal_new ("writable",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (SoupSocketClass, writable),
+ NULL, NULL,
+ soup_marshal_NONE__NONE,
+ G_TYPE_NONE, 0);
+ signals[DISCONNECTED] =
+ g_signal_new ("disconnected",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (SoupSocketClass, disconnected),
+ NULL, NULL,
+ soup_marshal_NONE__NONE,
+ G_TYPE_NONE, 0);
signals[NEW_CONNECTION] =
g_signal_new ("new_connection",
G_OBJECT_CLASS_TYPE (object_class),
return g_object_new (SOUP_TYPE_SOCKET, NULL);
}
-#define SOUP_SOCKET_NONBLOCKING (1<<0)
-#define SOUP_SOCKET_NONBUFFERED (1<<1)
-#define SOUP_SOCKET_REUSEADDR (1<<2)
-
-static void
-soup_set_sockopts (int sockfd, int opts)
+void
+soup_socket_set_flag (SoupSocket *sock, SoupSocketFlag flag, gboolean value)
{
- int flags;
-
- if (opts & SOUP_SOCKET_NONBLOCKING) {
- flags = fcntl (sockfd, F_GETFL, 0);
- if (flags != -1)
- fcntl (sockfd, F_SETFL, flags | O_NONBLOCK);
- }
+ int fdflags, opt;
- if (opts & SOUP_SOCKET_NONBUFFERED) {
- flags = 1;
- setsockopt (sockfd, IPPROTO_TCP, TCP_NODELAY,
- &flags, sizeof (flags));
- }
-
- if (opts & SOUP_SOCKET_REUSEADDR) {
- flags = 1;
- setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR,
- &flags, sizeof (flags));
+ g_return_if_fail (SOUP_IS_SOCKET (sock));
+ g_return_if_fail (sock->priv->sockfd != -1);
+
+ switch (flag) {
+ case SOUP_SOCKET_FLAG_NONBLOCKING:
+ fdflags = fcntl (sock->priv->sockfd, F_GETFL, 0);
+ g_return_if_fail (fdflags != -1);
+
+ if (value)
+ fdflags |= O_NONBLOCK;
+ else
+ fdflags &= ~O_NONBLOCK;
+
+ fcntl (sock->priv->sockfd, F_SETFL, fdflags);
+ break;
+
+ case SOUP_SOCKET_FLAG_NODELAY:
+ opt = (value != FALSE);
+ setsockopt (sock->priv->sockfd, IPPROTO_TCP, TCP_NODELAY,
+ &opt, sizeof (opt));
+ break;
+
+ case SOUP_SOCKET_FLAG_REUSEADDR:
+ opt = (value != FALSE);
+ setsockopt (sock->priv->sockfd, SOL_SOCKET, SO_REUSEADDR,
+ &opt, sizeof (opt));
+ break;
}
}
sock->priv->sockfd = socket (sa->sa_family, SOCK_STREAM, 0);
if (sock->priv->sockfd < 0)
goto cant_connect;
- soup_set_sockopts (sock->priv->sockfd,
- SOUP_SOCKET_NONBLOCKING | SOUP_SOCKET_NONBUFFERED);
+ soup_socket_set_flag (sock, SOUP_SOCKET_FLAG_NONBLOCKING, TRUE);
+ soup_socket_set_flag (sock, SOUP_SOCKET_FLAG_NODELAY, TRUE);
/* Connect (non-blocking) */
status = connect (sock->priv->sockfd, sa, len);
if (sockfd == -1)
return TRUE;
- soup_set_sockopts (sockfd,
- SOUP_SOCKET_NONBLOCKING | SOUP_SOCKET_NONBUFFERED);
-
new = soup_socket_new ();
new->priv->sockfd = sockfd;
+ soup_socket_set_flag (new, SOUP_SOCKET_FLAG_NONBLOCKING, TRUE);
+ soup_socket_set_flag (new, SOUP_SOCKET_FLAG_NODELAY, TRUE);
+
new->priv->remote_addr = soup_address_new_from_sockaddr ((struct sockaddr *)&sa, sa_len);
new->priv->server = TRUE;
if (sock->priv->ssl) {
new->priv->ssl = TRUE;
soup_socket_start_ssl (new);
- }
+ } else
+ soup_socket_get_iochannel (new);
g_signal_emit (sock, signals[NEW_CONNECTION], 0, new);
g_object_unref (new);
sock->priv->sockfd = socket (sa->sa_family, SOCK_STREAM, 0);
if (sock->priv->sockfd < 0)
goto cant_listen;
- soup_set_sockopts (sock->priv->sockfd,
- SOUP_SOCKET_NONBLOCKING | SOUP_SOCKET_REUSEADDR);
+ soup_socket_set_flag (sock, SOUP_SOCKET_FLAG_NONBLOCKING, TRUE);
+ soup_socket_set_flag (sock, SOUP_SOCKET_FLAG_REUSEADDR, TRUE);
/* Bind */
if (bind (sock->priv->sockfd, sa, sa_len) != 0)
sock->priv->iochannel =
g_io_channel_unix_new (sock->priv->sockfd);
g_io_channel_set_close_on_unref (sock->priv->iochannel, TRUE);
+ g_io_channel_set_encoding (sock->priv->iochannel, NULL, NULL);
+ g_io_channel_set_buffered (sock->priv->iochannel, FALSE);
}
return sock->priv->iochannel;
}
+void
+soup_socket_disconnect (SoupSocket *sock)
+{
+ g_return_if_fail (SOUP_IS_SOCKET (sock));
+
+ if (!sock->priv->iochannel)
+ return;
+
+ g_io_channel_unref (sock->priv->iochannel);
+ sock->priv->iochannel = NULL;
+
+ if (sock->priv->read_tag) {
+ g_source_remove (sock->priv->read_tag);
+ sock->priv->read_tag = 0;
+ }
+ if (sock->priv->write_tag) {
+ g_source_remove (sock->priv->write_tag);
+ sock->priv->write_tag = 0;
+ }
+ if (sock->priv->error_tag) {
+ g_source_remove (sock->priv->error_tag);
+ sock->priv->error_tag = 0;
+ }
+
+ /* Give all readers a chance to notice the connection close */
+ g_signal_emit (sock, signals[READABLE], 0);
+
+ /* FIXME: can't disconnect until all data is read */
+
+ /* Then let everyone know we're disconnected */
+ g_signal_emit (sock, signals[DISCONNECTED], 0);
+}
+
+gboolean
+soup_socket_is_connected (SoupSocket *sock)
+{
+ g_return_val_if_fail (SOUP_IS_SOCKET (sock), FALSE);
+
+ return sock->priv->iochannel != NULL;
+}
+
SoupAddress *
soup_socket_get_local_address (SoupSocket *sock)
return sock->priv->remote_addr;
}
+
+
+
+
+static gboolean
+socket_read_watch (GIOChannel *chan, GIOCondition cond, gpointer user_data)
+{
+ SoupSocket *sock = user_data;
+
+ sock->priv->read_tag = 0;
+ g_signal_emit (sock, signals[READABLE], 0);
+
+ return FALSE;
+}
+
+static SoupSocketIOStatus
+read_from_network (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread)
+{
+ GIOStatus status;
+
+ if (!sock->priv->iochannel)
+ return SOUP_SOCKET_EOF;
+
+ status = g_io_channel_read_chars (sock->priv->iochannel,
+ buffer, len, nread, NULL);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ case G_IO_STATUS_AGAIN:
+ if (*nread > 0)
+ return SOUP_SOCKET_OK;
+
+ if (!sock->priv->read_tag) {
+ sock->priv->read_tag =
+ g_io_add_watch (sock->priv->iochannel, G_IO_IN,
+ socket_read_watch, sock);
+ }
+ return SOUP_SOCKET_WOULD_BLOCK;
+
+ case G_IO_STATUS_EOF:
+ return SOUP_SOCKET_EOF;
+
+ default:
+ soup_socket_disconnect (sock);
+ return SOUP_SOCKET_ERROR;
+ }
+}
+
+static SoupSocketIOStatus
+read_from_buf (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread)
+{
+ GByteArray *read_buf = sock->priv->read_buf;
+
+ *nread = MIN (read_buf->len, len);
+ memcpy (buffer, read_buf->data, *nread);
+
+ if (*nread == read_buf->len) {
+ g_byte_array_free (read_buf, TRUE);
+ sock->priv->read_buf = NULL;
+ } else {
+ memcpy (read_buf->data, read_buf->data + *nread,
+ read_buf->len - *nread);
+ g_byte_array_set_size (read_buf, read_buf->len - *nread);
+ }
+
+ return SOUP_SOCKET_OK;
+}
+
+/**
+ * soup_socket_read:
+ * @sock: the socket
+ * @buffer: buffer to read into
+ * @len: size of @buffer in bytes
+ * @nread: on return, the number of bytes read into @buffer
+ *
+ * Attempts to read up to @len bytes from @sock into @buffer. If some
+ * data is successfully read, soup_socket_read() will return
+ * %SOUP_SOCKET_OK, and *@nread will contain the number of bytes
+ * actually read.
+ *
+ * If @sock is non-blocking, and no data is available, the return
+ * value will be %SOUP_SOCKET_WOULD_BLOCK. In this case, the caller
+ * can connect to the %readable signal to know when there is more data
+ * to read. (NB: You MUST read all available data off the socket
+ * first. The %readable signal will only be emitted after
+ * soup_socket_read() has returned %SOUP_SOCKET_WOULD_BLOCK.)
+ *
+ * Return value: a #SoupSocketIOStatus, as described above (or
+ * %SOUP_SOCKET_EOF if the socket is no longer connected, or
+ * %SOUP_SOCKET_ERROR on any other error).
+ **/
+SoupSocketIOStatus
+soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread)
+{
+ g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_SOCKET_ERROR);
+
+ if (sock->priv->read_buf)
+ return read_from_buf (sock, buffer, len, nread);
+ else
+ return read_from_network (sock, buffer, len, nread);
+}
+
+/**
+ * soup_socket_read_until:
+ * @sock: the socket
+ * @buffer: buffer to read into
+ * @len: size of @buffer in bytes
+ * @boundary: boundary to read until
+ * @boundary_len: length of @boundary in bytes
+ * @nread: on return, the number of bytes read into @buffer
+ * @got_boundary: on return, whether or not the data in @buffer
+ * ends with the boundary string
+ *
+ * Like soup_socket_read(), but reads no further than the first
+ * occurrence of @boundary. (If the boundary is found, it will be
+ * included in the returned data, and *@got_boundary will be set to
+ * %TRUE.) Any data after the boundary will returned in future reads.
+ *
+ * Return value: as for soup_socket_read()
+ **/
+SoupSocketIOStatus
+soup_socket_read_until (SoupSocket *sock, gpointer buffer, gsize len,
+ gconstpointer boundary, gsize boundary_len,
+ gsize *nread, gboolean *got_boundary)
+{
+ SoupSocketIOStatus status;
+ GByteArray *read_buf;
+ guint match_len, prev_len;
+ guint8 *p, *end;
+
+ g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_SOCKET_ERROR);
+ g_return_val_if_fail (len >= boundary_len, SOUP_SOCKET_ERROR);
+
+ *got_boundary = FALSE;
+
+ if (!sock->priv->read_buf)
+ sock->priv->read_buf = g_byte_array_new ();
+ read_buf = sock->priv->read_buf;
+
+ if (read_buf->len < boundary_len) {
+ prev_len = read_buf->len;
+ g_byte_array_set_size (read_buf, len);
+ status = read_from_network (sock,
+ read_buf->data + prev_len,
+ len - prev_len, nread);
+ read_buf->len = prev_len + *nread;
+
+ if (status != SOUP_SOCKET_OK)
+ return status;
+ }
+
+ /* Scan for the boundary */
+ end = read_buf->data + read_buf->len;
+ for (p = read_buf->data; p <= end - boundary_len; p++) {
+ if (!memcmp (p, boundary, boundary_len)) {
+ p += boundary_len;
+ *got_boundary = TRUE;
+ break;
+ }
+ }
+
+ /* Return everything up to 'p' (which is either just after the
+ * boundary, or @boundary_len - 1 bytes before the end of the
+ * buffer).
+ */
+ match_len = p - read_buf->data;
+ return read_from_buf (sock, buffer, MIN (len, match_len), nread);
+}
+
+static gboolean
+socket_write_watch (GIOChannel *chan, GIOCondition condition, gpointer user_data)
+{
+ SoupSocket *sock = user_data;
+
+ sock->priv->write_tag = 0;
+ g_signal_emit (sock, signals[WRITABLE], 0);
+
+ return FALSE;
+}
+
+/**
+ * soup_socket_write:
+ * @sock: the socket
+ * @buffer: data to write
+ * @len: size of @buffer, in bytes
+ * @nwrite: on return, number of bytes written
+ *
+ * Attempts to write @len bytes from @buffer to @sock. If some data is
+ * successfully written, the resturn status will be
+ * %SOUP_SOCKET_SUCCESS, and *@nwrote will contain the number of bytes
+ * actually written.
+ *
+ * If @sock is non-blocking, and no data could be written right away,
+ * the return value will be %SOUP_SOCKET_WOULD_BLOCK. In this case,
+ * the caller can connect to the %writable signal to know when more
+ * data can be written. (NB: %writable is only emitted after a
+ * %SOUP_SOCKET_WOULD_BLOCK.)
+ *
+ * Return value: a #SoupSocketIOStatus, as described above (or
+ * %SOUP_SOCKET_EOF or %SOUP_SOCKET_ERROR).
+ **/
+SoupSocketIOStatus
+soup_socket_write (SoupSocket *sock, gconstpointer buffer,
+ gsize len, gsize *nwrote)
+{
+ GIOStatus status;
+ gpointer pipe_handler;
+
+ g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_SOCKET_ERROR);
+
+ if (!sock->priv->iochannel)
+ return SOUP_SOCKET_EOF;
+ if (sock->priv->write_tag)
+ return SOUP_SOCKET_WOULD_BLOCK;
+
+ pipe_handler = signal (SIGPIPE, SIG_IGN);
+ status = g_io_channel_write_chars (sock->priv->iochannel,
+ buffer, len, nwrote, NULL);
+ signal (SIGPIPE, pipe_handler);
+ if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_AGAIN) {
+ soup_socket_disconnect (sock);
+ return SOUP_SOCKET_ERROR;
+ }
+
+ if (*nwrote)
+ return SOUP_SOCKET_OK;
+
+ sock->priv->write_tag =
+ g_io_add_watch (sock->priv->iochannel, G_IO_OUT,
+ socket_write_watch, sock);
+ return SOUP_SOCKET_WOULD_BLOCK;
+}
/* signals */
void (*connect_result) (SoupSocket *, SoupKnownErrorCode);
+ void (*readable) (SoupSocket *);
+ void (*writable) (SoupSocket *);
+ void (*disconnected) (SoupSocket *);
void (*new_connection) (SoupSocket *, SoupSocket *);
} SoupSocketClass;
+typedef enum {
+ SOUP_SOCKET_FLAG_NONBLOCKING,
+ SOUP_SOCKET_FLAG_NODELAY,
+ SOUP_SOCKET_FLAG_REUSEADDR,
+} SoupSocketFlag;
+
GType soup_socket_get_type (void);
SoupSocket *soup_socket_new (void);
+void soup_socket_set_flag (SoupSocket *sock,
+ SoupSocketFlag flag,
+ gboolean value);
void soup_socket_connect (SoupSocket *sock,
SoupAddress *rem_addr);
SoupAddress *local_addr);
void soup_socket_start_ssl (SoupSocket *sock);
+void soup_socket_disconnect (SoupSocket *sock);
+gboolean soup_socket_is_connected (SoupSocket *sock);
typedef void (*SoupSocketCallback) (SoupSocket *sock,
SoupKnownErrorCode status,
SoupAddress *soup_socket_get_local_address (SoupSocket *sock);
SoupAddress *soup_socket_get_remote_address (SoupSocket *sock);
+
+typedef enum {
+ SOUP_SOCKET_OK,
+ SOUP_SOCKET_WOULD_BLOCK,
+ SOUP_SOCKET_EOF,
+ SOUP_SOCKET_ERROR
+} SoupSocketIOStatus;
+
+SoupSocketIOStatus soup_socket_read (SoupSocket *sock,
+ gpointer buffer,
+ guint len,
+ guint *nread);
+SoupSocketIOStatus soup_socket_read_until (SoupSocket *sock,
+ gpointer buffer,
+ guint len,
+ gconstpointer boundary,
+ guint boundary_len,
+ guint *nread,
+ gboolean *got_boundary);
+
+SoupSocketIOStatus soup_socket_write (SoupSocket *sock,
+ gconstpointer buffer,
+ guint len,
+ guint *nwrote);
+
+
#endif /* SOUP_SOCKET_H */
#include "soup-transfer.h"
#include "soup-private.h"
-#undef DUMP
-
-#ifdef DUMP
-static void
-DUMP_READ (guchar *data, gint bytes_read)
-{
- gchar *buf = alloca (bytes_read + 1);
- memcpy (buf, data, bytes_read);
- buf[bytes_read] = '\0';
-
- g_warning ("READ %d\n----------\n%s\n----------\n", bytes_read, buf);
-}
-static void
-DUMP_WRITE (guchar *data, gint bytes_written)
-{
- gchar *buf = alloca (bytes_written + 1);
- memcpy (buf, data, bytes_written);
- buf[bytes_written] = '\0';
-
- g_warning ("WRITE %d\n----------\n%s\n----------\n", bytes_written,buf);
-}
-#else
-# define DUMP_READ(x,y)
-# define DUMP_WRITE(x,y)
-#endif
-
-typedef struct {
- /*
- * Length of the current chunk data.
- */
- guint len;
-
- /*
- * Index into the recv buffer where this chunk's data begins.
- * 0 if overwrite chunks is active.
- */
- guint idx;
-} SoupTransferChunkState;
+typedef enum {
+ SOUP_READER_STATE_HEADERS,
+ SOUP_READER_STATE_READ_TO_EOF,
+ SOUP_READER_STATE_CONTENT_LENGTH,
+ SOUP_READER_STATE_CHUNK_SIZE,
+ SOUP_READER_STATE_CHUNK,
+ SOUP_READER_STATE_BETWEEN_CHUNKS,
+ SOUP_READER_STATE_TRAILERS
+} SoupReaderState;
struct _SoupReader {
int ref_count;
- GIOChannel *channel;
+ SoupSocket *sock;
+ guint idle_tag;
guint read_tag;
guint err_tag;
- /*
- * If TRUE, a callback has been issued which references recv_buf.
- * If the transfer is cancelled before a reference exists, the contents
- * of recv_buf are free'd.
- */
- gboolean callback_issued;
-
- GByteArray *recv_buf;
- guint header_len;
-
- gboolean overwrite_chunks;
+ SoupReaderState state;
+ GByteArray *body_buf;
+ GByteArray *meta_buf;
SoupTransferEncoding encoding;
- guint content_length;
- SoupTransferChunkState chunk_state;
+ guint read_length;
SoupReadHeadersDoneFn headers_done_cb;
SoupReadChunkFn read_chunk_cb;
gpointer user_data;
};
-struct _SoupWriter {
- int ref_count;
-
- GIOChannel *channel;
- guint write_tag;
- guint err_tag;
-
- SoupTransferEncoding encoding;
- GByteArray *write_buf;
-
- gboolean headers_done;
- gint chunk_cnt;
-
- SoupWriteGetHeaderFn get_header_cb;
- SoupWriteGetChunkFn get_chunk_cb;
- SoupWriteDoneFn write_done_cb;
- SoupWriteErrorFn error_cb;
- gpointer user_data;
-};
-
/* Stops reading and releases soup-transfer's ref. */
static void
soup_transfer_read_stop (SoupReader *r)
if (!r->err_tag)
return;
- g_source_remove (r->read_tag);
+ g_signal_handler_disconnect (r->sock, r->read_tag);
r->read_tag = 0;
- g_source_remove (r->err_tag);
+ g_signal_handler_disconnect (r->sock, r->err_tag);
r->err_tag = 0;
+ if (r->idle_tag) {
+ g_source_remove (r->idle_tag);
+ r->idle_tag = 0;
+ }
+
soup_transfer_read_unref (r);
}
return TRUE;
soup_transfer_read_stop (r);
- g_byte_array_free (r->recv_buf, r->callback_issued ? FALSE : TRUE);
+ if (r->body_buf)
+ g_byte_array_free (r->body_buf, TRUE);
+ if (r->meta_buf)
+ g_byte_array_free (r->meta_buf, TRUE);
+ g_object_unref (r->sock);
g_free (r);
return FALSE;
}
static void
issue_final_callback (SoupReader *r)
{
- SoupDataBuffer buf;
+ char *body;
+ guint len;
- /*
- * Null terminate
- */
- g_byte_array_append (r->recv_buf, "\0", 1);
-
- buf.owner = SOUP_BUFFER_SYSTEM_OWNED;
- buf.body = r->recv_buf->data;
- buf.length = r->recv_buf->len - 1;
-
- r->callback_issued = TRUE;
+ if (r->body_buf) {
+ /*
+ * Null terminate. FIXME
+ */
+ g_byte_array_append (r->body_buf, "\0", 1);
+
+ body = r->body_buf->data;
+ len = r->body_buf->len - 1;
+ g_byte_array_free (r->body_buf, FALSE);
+ r->body_buf = NULL;
+ } else {
+ body = NULL;
+ len = 0;
+ }
soup_transfer_read_ref (r);
soup_transfer_read_stop (r);
- (*r->read_done_cb) (&buf, r->user_data);
+ (*r->read_done_cb) (body, len, r->user_data);
soup_transfer_read_unref (r);
}
-static gboolean
-soup_transfer_read_error_cb (GIOChannel* iochannel,
- GIOCondition condition,
- SoupReader *r)
+static void
+reader_disconnected (SoupSocket *sock, SoupReader *r)
{
- gboolean body_started = r->recv_buf->len > r->header_len;
-
+ soup_transfer_read_ref (r);
soup_transfer_read_stop (r);
/*
* Closing the connection to signify EOF is valid if content length is
* unknown, but only if headers have been sent.
*/
- if (r->header_len && r->encoding == SOUP_TRANSFER_UNKNOWN)
+ if (r->state == SOUP_READER_STATE_READ_TO_EOF)
issue_final_callback (r);
- else
- (*r->error_cb) (body_started, r->user_data);
+ else {
+ (*r->error_cb) (r->state > SOUP_READER_STATE_HEADERS,
+ r->user_data);
+ }
- return FALSE;
+ soup_transfer_read_unref (r);
}
-static void
-remove_block_at_index (GByteArray *arr, gint offset, gint length)
+static gboolean
+soup_reader_read_metadata (SoupReader *r, const char *boundary, int boundary_len)
{
- gchar *data;
+ SoupSocketIOStatus status;
+ char read_buf[RESPONSE_BLOCK_SIZE];
+ guint nread;
+ gboolean done;
- g_return_if_fail (length != 0);
- g_assert (arr->len >= (guint) offset + length);
+ do {
+ status = soup_socket_read_until (r->sock, read_buf,
+ sizeof (read_buf),
+ boundary, boundary_len,
+ &nread, &done);
+ switch (status) {
+ case SOUP_SOCKET_OK:
+ g_byte_array_append (r->meta_buf, read_buf, nread);
+ break;
- data = &arr->data [offset];
+ case SOUP_SOCKET_ERROR:
+ case SOUP_SOCKET_EOF:
+ reader_disconnected (r->sock, r);
+ return FALSE;
- g_memmove (data,
- data + length,
- arr->len - offset - length);
+ case SOUP_SOCKET_WOULD_BLOCK:
+ return FALSE;
+ }
+ } while (!done);
- g_byte_array_set_size (arr, arr->len - length);
+ return TRUE;
}
-/* Parse chunk data in @arr. On return, *@datalen bytes of @arr contain
- * parsed data that can be returned to the caller. Return value is
- * TRUE if it parsed the last chunk, or FALSE if we need to read more
- * data.
- */
static gboolean
-decode_chunk (SoupTransferChunkState *s,
- GByteArray *arr,
- gint *datalen)
+soup_reader_read_body_chunk (SoupReader *r, guint *size)
{
- gboolean ret = FALSE;
+ SoupSocketIOStatus status;
+ char read_buf[RESPONSE_BLOCK_SIZE];
+ guint nread, len = sizeof (read_buf);
- *datalen = 0;
+ while (!size || *size > 0) {
+ if (size)
+ len = MIN (len, *size);
- while (TRUE) {
- gint new_len = 0;
- gint len = 0;
+ status = soup_socket_read (r->sock, read_buf, len, &nread);
- if (s->len) {
- /* We're in the middle of a chunk. If we don't
- * have the entire chunk and the trailing CRLF
- * yet, read more.
- */
- if (s->idx + s->len + 2 > arr->len)
+ switch (status) {
+ case SOUP_SOCKET_OK:
+ if (!nread)
break;
- /*
- * Increment datalen and s->idx, and remove
- * the trailing CRLF.
- */
- s->idx += s->len;
- *datalen += s->len;
- remove_block_at_index (arr, s->idx, 2);
-
- /*
- * Ready for the next chunk.
- */
- s->len = 0;
- }
-
- /*
- * We're at the start of a new chunk. If we don't have
- * the complete chunk header, wait for more.
- */
- len = soup_substring_index (&arr->data [s->idx],
- arr->len - s->idx,
- "\r\n");
- if (len < 0)
+ if (r->read_chunk_cb) {
+ r->read_chunk_cb (read_buf, nread,
+ r->user_data);
+ }
+ if (r->body_buf)
+ g_byte_array_append (r->body_buf, read_buf, nread);
+ if (size)
+ *size -= nread;
break;
- len += 2;
-
- new_len = strtol (&arr->data [s->idx], NULL, 16);
- g_assert (new_len >= 0);
- /*
- * If this is the final (zero-length) chunk, we need
- * to have all of the trailing entity headers as well.
- */
- if (new_len == 0) {
- len = soup_substring_index (&arr->data [s->idx],
- arr->len - s->idx,
- "\r\n\r\n");
- if (len < 0)
- break;
+ case SOUP_SOCKET_EOF:
+ if (!size)
+ return TRUE;
+ /* else fall through */
- /*
- * FIXME: Add entity headers we find here to
- * req->response_headers.
- */
+ case SOUP_SOCKET_ERROR:
+ reader_disconnected (r->sock, r);
+ return FALSE;
- len += 4;
- ret = TRUE;
+ case SOUP_SOCKET_WOULD_BLOCK:
+ return FALSE;
}
-
- /*
- * Remove chunk header and get ready for chunk data.
- */
- remove_block_at_index (arr, s->idx, len);
- s->len = new_len;
}
- return ret;
-}
-
-static void
-issue_chunk_callback (SoupReader *r, gchar *data, gint len)
-{
- /*
- * Call chunk callback. Pass len worth of data.
- */
- if (r->read_chunk_cb && len) {
- SoupDataBuffer buf = {
- SOUP_BUFFER_SYSTEM_OWNED,
- data,
- len
- };
-
- r->callback_issued = TRUE;
- (*r->read_chunk_cb) (&buf, r->user_data);
- }
+ return TRUE;
}
-static gboolean
-read_chunk (SoupReader *r)
-{
- SoupTransferChunkState *s = &r->chunk_state;
- GByteArray *arr = r->recv_buf;
- gboolean done;
- gint datalen;
-
- /*
- * Update datalen for any data read
- */
- done = decode_chunk (&r->chunk_state, r->recv_buf, &datalen);
- if (!datalen)
- return done;
-
- issue_chunk_callback (r, arr->data, s->idx);
-
- /*
- * If overwrite, remove already-processed data from start
- * of buffer
- */
- if (r->overwrite_chunks) {
- remove_block_at_index (arr, 0, s->idx);
-
- s->idx = 0;
- }
+#define SOUP_TRANSFER_EOL "\r\n"
+#define SOUP_TRANSFER_EOL_LEN 2
- return done;
-}
+#define SOUP_TRANSFER_DOUBLE_EOL "\r\n\r\n"
+#define SOUP_TRANSFER_DOUBLE_EOL_LEN 4
-static gboolean
-read_content_length (SoupReader *r)
+static void
+reader_read (SoupSocket *sock, SoupReader *r)
{
- GByteArray *arr = r->recv_buf;
+ soup_transfer_read_ref (r);
- if (arr->len) {
- issue_chunk_callback (r, arr->data, arr->len);
+ while (1) {
+ switch (r->state) {
+ case SOUP_READER_STATE_HEADERS:
+ if (!soup_reader_read_metadata (
+ r, SOUP_TRANSFER_DOUBLE_EOL,
+ SOUP_TRANSFER_DOUBLE_EOL_LEN))
+ goto out;
+
+ r->meta_buf->len -= SOUP_TRANSFER_DOUBLE_EOL_LEN;
+ if (r->headers_done_cb) {
+ (*r->headers_done_cb) (r->meta_buf->data,
+ r->meta_buf->len,
+ &r->encoding,
+ &r->read_length,
+ r->user_data);
+ }
+ g_byte_array_set_size (r->meta_buf, 0);
+
+ switch (r->encoding) {
+ case SOUP_TRANSFER_UNKNOWN:
+ r->state = SOUP_READER_STATE_READ_TO_EOF;
+ break;
+ case SOUP_TRANSFER_CONTENT_LENGTH:
+ r->state = SOUP_READER_STATE_CONTENT_LENGTH;
+ break;
+ case SOUP_TRANSFER_CHUNKED:
+ r->state = SOUP_READER_STATE_CHUNK_SIZE;
+ break;
+ }
+ break;
- /*
- * If overwrite, clear
- */
- if (r->overwrite_chunks) {
- r->content_length -= r->recv_buf->len;
- g_byte_array_set_size (arr, 0);
- }
- }
+ case SOUP_READER_STATE_READ_TO_EOF:
+ if (!soup_reader_read_body_chunk (r, NULL))
+ goto out;
- return r->content_length == arr->len;
-}
+ goto done;
+ break;
-static gboolean
-read_unknown (SoupReader *r)
-{
- GByteArray *arr = r->recv_buf;
+ case SOUP_READER_STATE_CONTENT_LENGTH:
+ if (!soup_reader_read_body_chunk (r, &r->read_length))
+ goto out;
- if (arr->len) {
- issue_chunk_callback (r, arr->data, arr->len);
+ goto done;
+ break;
- /*
- * If overwrite, clear
- */
- if (r->overwrite_chunks)
- g_byte_array_set_size (arr, 0);
- }
+ case SOUP_READER_STATE_CHUNK_SIZE:
+ if (!soup_reader_read_metadata (r, SOUP_TRANSFER_EOL,
+ SOUP_TRANSFER_EOL_LEN))
+ goto out;
- /*
- * Keep reading until we get a zero read or HUP.
- */
- return FALSE;
-}
+ r->read_length = strtoul (r->meta_buf->data, NULL, 16);
+ g_byte_array_set_size (r->meta_buf, 0);
-static gboolean
-soup_transfer_read_cb (GIOChannel *iochannel,
- GIOCondition condition,
- SoupReader *r)
-{
- gchar read_buf [RESPONSE_BLOCK_SIZE];
- gsize bytes_read = 0, total_read = 0;
- gboolean read_done = FALSE;
- GIOError error;
-
- READ_AGAIN:
- error = g_io_channel_read (iochannel,
- read_buf,
- sizeof (read_buf),
- &bytes_read);
-
- if (error == G_IO_ERROR_AGAIN) {
- if (total_read)
- goto PROCESS_READ;
- else return TRUE;
- }
+ if (r->read_length > 0)
+ r->state = SOUP_READER_STATE_CHUNK;
+ else
+ r->state = SOUP_READER_STATE_TRAILERS;
+ break;
- if (error != G_IO_ERROR_NONE) {
- if (total_read)
- goto PROCESS_READ;
- else {
- soup_transfer_read_error_cb (iochannel, G_IO_HUP, r);
- return FALSE;
- }
- }
+ case SOUP_READER_STATE_CHUNK:
+ if (!soup_reader_read_body_chunk (r, &r->read_length))
+ goto out;
- if (bytes_read) {
- DUMP_READ (read_buf, bytes_read);
+ r->state = SOUP_READER_STATE_BETWEEN_CHUNKS;
+ break;
- g_byte_array_append (r->recv_buf, read_buf, bytes_read);
- total_read += bytes_read;
+ case SOUP_READER_STATE_BETWEEN_CHUNKS:
+ if (!soup_reader_read_metadata (r, SOUP_TRANSFER_EOL,
+ SOUP_TRANSFER_EOL_LEN))
+ goto out;
- goto READ_AGAIN;
- }
+ g_byte_array_set_size (r->meta_buf, 0);
+ r->state = SOUP_READER_STATE_CHUNK_SIZE;
+ break;
- PROCESS_READ:
+ case SOUP_READER_STATE_TRAILERS:
+ if (!soup_reader_read_metadata (r, SOUP_TRANSFER_EOL,
+ SOUP_TRANSFER_EOL_LEN))
+ goto out;
- if (r->header_len == 0 && total_read == 0) {
- soup_transfer_read_error_cb (iochannel, G_IO_HUP, r);
- return FALSE;
- }
+ if (r->meta_buf->len == SOUP_TRANSFER_EOL_LEN)
+ goto done;
- if (r->header_len == 0) {
- gint index;
-
- index = soup_substring_index (r->recv_buf->data,
- r->recv_buf->len,
- "\r\n\r\n");
- if (index < 0)
- return TRUE;
- else
- index += 4;
-
- if (r->headers_done_cb) {
- GString str;
-
- str.len = index;
- str.str = alloca (index + 1);
- strncpy (str.str, r->recv_buf->data, index);
- str.str [index] = '\0';
-
- soup_transfer_read_ref (r);
- (*r->headers_done_cb) (&str,
- &r->encoding,
- &r->content_length,
- r->user_data);
- if (!soup_transfer_read_unref (r))
- return FALSE;
- }
+ /* FIXME: process trailers */
+ g_byte_array_set_size (r->meta_buf, 0);
+ break;
- remove_block_at_index (r->recv_buf, 0, index);
- r->header_len = index;
+ }
}
- if (total_read == 0)
- read_done = TRUE;
- else {
- soup_transfer_read_ref (r);
-
- switch (r->encoding) {
- case SOUP_TRANSFER_CHUNKED:
- read_done = read_chunk (r);
- break;
- case SOUP_TRANSFER_CONTENT_LENGTH:
- read_done = read_content_length (r);
- break;
- case SOUP_TRANSFER_UNKNOWN:
- read_done = read_unknown (r);
- break;
- }
+ done:
+ issue_final_callback (r);
- if (!soup_transfer_read_unref (r))
- return FALSE;
- }
+ out:
+ soup_transfer_read_unref (r);
+}
- if (!read_done) {
- total_read = 0;
- goto READ_AGAIN;
- }
+static gboolean
+idle_read (gpointer user_data)
+{
+ SoupReader *r = user_data;
- issue_final_callback (r);
+ r->idle_tag = 0;
+ reader_read (r->sock, r);
return FALSE;
}
* soup_transfer_read_cancel().
**/
SoupReader *
-soup_transfer_read (GIOChannel *chan,
+soup_transfer_read (SoupSocket *sock,
gboolean overwrite_chunks,
SoupReadHeadersDoneFn headers_done_cb,
SoupReadChunkFn read_chunk_cb,
g_assert (read_done_cb && error_cb);
reader = g_new0 (SoupReader, 1);
- reader->channel = chan;
- reader->overwrite_chunks = overwrite_chunks;
+ reader->sock = g_object_ref (sock);
reader->headers_done_cb = headers_done_cb;
reader->read_chunk_cb = read_chunk_cb;
reader->read_done_cb = read_done_cb;
reader->error_cb = error_cb;
reader->user_data = user_data;
- reader->recv_buf = g_byte_array_new ();
reader->encoding = SOUP_TRANSFER_UNKNOWN;
+ reader->meta_buf = g_byte_array_new ();
+ if (!overwrite_chunks)
+ reader->body_buf = g_byte_array_new ();
+
reader->read_tag =
- g_io_add_watch (chan,
- G_IO_IN,
- (GIOFunc) soup_transfer_read_cb,
- reader);
+ g_signal_connect (sock, "readable",
+ G_CALLBACK (reader_read), reader);
reader->err_tag =
- g_io_add_watch (chan,
- G_IO_HUP | G_IO_ERR | G_IO_NVAL,
- (GIOFunc) soup_transfer_read_error_cb,
- reader);
+ g_signal_connect (sock, "disconnected",
+ G_CALLBACK (reader_disconnected), reader);
+
+ reader->idle_tag = g_idle_add (idle_read, reader);
/* Initial ref_count is 2: one reference owned by
* soup-transfer and one by the caller.
return reader;
}
+
+struct _SoupWriter {
+ int ref_count;
+
+ SoupSocket *sock;
+ guint idle_tag;
+ guint write_tag;
+ guint err_tag;
+
+ SoupTransferEncoding encoding;
+ GByteArray *write_buf;
+
+ gboolean headers_done;
+ int chunk_cnt;
+
+ SoupWriteGetHeaderFn get_header_cb;
+ SoupWriteGetChunkFn get_chunk_cb;
+ SoupWriteDoneFn write_done_cb;
+ SoupWriteErrorFn error_cb;
+ gpointer user_data;
+};
+
static void
soup_transfer_write_stop (SoupWriter *w)
{
if (!w->err_tag)
return;
+ g_signal_handler_disconnect (w->sock, w->err_tag);
+ w->err_tag = 0;
+
if (w->write_tag) {
- g_source_remove (w->write_tag);
+ g_signal_handler_disconnect (w->sock, w->write_tag);
w->write_tag = 0;
}
- g_source_remove (w->err_tag);
- w->err_tag = 0;
+
+ if (w->idle_tag) {
+ g_source_remove (w->idle_tag);
+ w->idle_tag = 0;
+ }
/* Give up soup-transfer's ref */
soup_transfer_write_unref (w);
soup_transfer_write_stop (w);
g_byte_array_free (w->write_buf, TRUE);
+ g_object_unref (w->sock);
g_free (w);
return FALSE;
}
soup_transfer_write_unref (w);
}
-static gboolean
-soup_transfer_write_error_cb (GIOChannel* iochannel,
- GIOCondition condition,
- SoupWriter *w)
+static void
+writer_disconnected (SoupSocket *sock, SoupWriter *w)
{
soup_transfer_write_stop (w);
(*w->error_cb) (w->headers_done, w->user_data);
-
- return FALSE;
}
static gboolean
}
}
-#ifdef SIGPIPE
-# define IGNORE_PIPE(pipe_handler) pipe_handler = signal (SIGPIPE, SIG_IGN)
-# define RESTORE_PIPE(pipe_handler) signal (SIGPIPE, pipe_handler)
-#else
-# define IGNORE_PIPE(x)
-# define RESTORE_PIPE(x)
-#endif
-
-static gboolean
-soup_transfer_write_cb (GIOChannel* iochannel,
- GIOCondition condition,
- SoupWriter *w)
+static void
+writer_write (SoupSocket *sock, SoupWriter *w)
{
- GIOError error;
- gpointer pipe_handler;
- gsize bytes_written = 0;
+ SoupSocketIOStatus status;
+ guint bytes_written = 0;
- /*
- * Get the header and first data chunk (if available).
- */
+ /* Get the header and first data chunk (if available). */
if (w->get_header_cb) {
soup_transfer_write_ref (w);
- if (!get_header (w))
- return soup_transfer_write_unref (w);
+ if (!get_header (w)) {
+ soup_transfer_write_unref (w);
+ return;
+ }
if (w->get_chunk_cb)
get_next_chunk (w);
if (!soup_transfer_write_unref (w))
- return FALSE;
+ return;
}
WRITE_AGAIN:
while (w->write_buf->len) {
- IGNORE_PIPE (pipe_handler);
- error = g_io_channel_write (iochannel,
- w->write_buf->data,
- w->write_buf->len,
- &bytes_written);
- RESTORE_PIPE (pipe_handler);
-
- if (error == G_IO_ERROR_AGAIN)
- return TRUE;
-
- if (error != G_IO_ERROR_NONE) {
- soup_transfer_write_error_cb (iochannel, G_IO_HUP, w);
- return FALSE;
+ status = soup_socket_write (sock, w->write_buf->data,
+ w->write_buf->len, &bytes_written);
+
+ switch (status) {
+ case SOUP_SOCKET_EOF:
+ case SOUP_SOCKET_ERROR:
+ writer_disconnected (sock, w);
+ return;
+
+ case SOUP_SOCKET_WOULD_BLOCK:
+ return;
+
+ case SOUP_SOCKET_OK:
+ memmove (w->write_buf->data,
+ w->write_buf->data + bytes_written,
+ w->write_buf->len - bytes_written);
+ g_byte_array_set_size (w->write_buf,
+ w->write_buf->len - bytes_written);
+ break;
}
-
- if (!bytes_written)
- return TRUE;
-
- DUMP_WRITE (w->write_buf->data, bytes_written);
-
- remove_block_at_index (w->write_buf, 0, bytes_written);
}
- /*
- * When we exit the above block, we are certain that the headers have
+ /* When we exit the above block, we are certain that the headers have
* been written.
*/
w->headers_done = TRUE;
- /*
- * Get the next data chunk and try again, or quit if paused.
- */
+ /* Get the next data chunk and try again, or quit if paused. */
if (w->get_chunk_cb) {
soup_transfer_write_ref (w);
get_next_chunk (w);
if (!soup_transfer_write_unref (w))
- return TRUE;
+ return;
if (!w->write_tag)
- return TRUE;
+ return;
goto WRITE_AGAIN;
}
soup_transfer_write_stop (w);
(*w->write_done_cb) (w->user_data);
soup_transfer_write_unref (w);
+}
+
+static gboolean
+idle_write (gpointer user_data)
+{
+ SoupWriter *w = user_data;
+
+ w->idle_tag = 0;
+ writer_write (w->sock, w);
return FALSE;
}
static SoupWriter *
-create_writer (GIOChannel *chan,
+create_writer (SoupSocket *sock,
SoupTransferEncoding encoding,
SoupWriteDoneFn write_done_cb,
SoupWriteErrorFn error_cb,
g_assert (write_done_cb && error_cb);
writer = g_new0 (SoupWriter, 1);
- writer->channel = chan;
+ writer->sock = g_object_ref (sock);
writer->encoding = encoding;
writer->write_buf = g_byte_array_new ();
writer->write_done_cb = write_done_cb;
writer->user_data = user_data;
writer->write_tag =
- g_io_add_watch (chan,
- G_IO_OUT,
- (GIOFunc) soup_transfer_write_cb,
- writer);
+ g_signal_connect (sock, "writable",
+ G_CALLBACK (writer_write), writer);
writer->err_tag =
- g_io_add_watch (chan,
- G_IO_HUP | G_IO_ERR | G_IO_NVAL,
- (GIOFunc) soup_transfer_write_error_cb,
- writer);
+ g_signal_connect (sock, "disconnected",
+ G_CALLBACK (writer_disconnected), writer);
+
+ writer->idle_tag = g_idle_add (idle_write, writer);
/* As with SoupReader, one reference is owned by soup-transfer
* and one by the caller.
/**
* soup_transfer_write_simple:
- * @chan: the iochannel to write to
+ * @sock: the socket to write to
* @header: message headers (including trailing blank line)
* @src: buffer to write
* @write_done_cb: (mandatory) callback to call when the body has been
* @error_cb: (mandatory) callback to call when an error occurs
* @user_data: data to pass to the callbacks.
*
- * Attempts to write a single HTTP message to @chan using identity
+ * Attempts to write a single HTTP message to @sock using identity
* encoding and Content-Length.
*
* Unless the caller calls soup_transfer_write_cancel(), either
* soup_transfer_write_cancel().
**/
SoupWriter *
-soup_transfer_write_simple (GIOChannel *chan,
+soup_transfer_write_simple (SoupSocket *sock,
GString *header,
const SoupDataBuffer *src,
SoupWriteDoneFn write_done_cb,
{
SoupWriter *writer;
- writer = create_writer (chan,
+ writer = create_writer (sock,
SOUP_TRANSFER_CONTENT_LENGTH,
write_done_cb,
error_cb,
}
/**
- * soup_transfer_write_simple:
- * @chan: the iochannel to write to
+ * soup_transfer_write:
+ * @sock: the socket to write to
* @encoding: HTTP encoding mechanism to use.
* @get_header_cb: (mandatory) callback to call to get message headers
* @get_chunk_cb: (optional) callback to call to get body chunks
* @error_cb: (mandatory) callback to call when an error occurs
* @user_data: data to pass to the callbacks.
*
- * Attempts to write a single HTTP message to @chan using @encoding.
+ * Attempts to write a single HTTP message to @sock using @encoding.
*
* Unless the caller calls soup_transfer_write_cancel(), either
* @write_done_cb or @write_error_cb will eventually be called.
* soup_transfer_write_cancel().
**/
SoupWriter *
-soup_transfer_write (GIOChannel *chan,
+soup_transfer_write (SoupSocket *sock,
SoupTransferEncoding encoding,
SoupWriteGetHeaderFn get_header_cb,
SoupWriteGetChunkFn get_chunk_cb,
{
SoupWriter *writer;
- writer = create_writer (chan,
+ writer = create_writer (sock,
encoding,
write_done_cb,
error_cb,
g_return_if_fail (w != NULL);
if (w->write_tag) {
- g_source_remove (w->write_tag);
+ g_signal_handler_disconnect (w->sock, w->write_tag);
w->write_tag = 0;
}
+ if (w->idle_tag) {
+ g_source_remove (w->idle_tag);
+ w->idle_tag = 0;
+ }
}
void
if (!w->write_tag) {
w->write_tag =
- g_io_add_watch (w->channel,
- G_IO_OUT,
- (GIOFunc) soup_transfer_write_cb,
- w);
+ g_signal_connect (w->sock, "writable",
+ G_CALLBACK (writer_write), w);
}
+ if (!w->idle_tag)
+ w->idle_tag = g_idle_add (idle_write, w);
}
typedef struct _SoupReader SoupReader;
typedef struct _SoupWriter SoupWriter;
-typedef void (*SoupReadHeadersDoneFn) (const GString *headers,
+typedef void (*SoupReadHeadersDoneFn) (char *headers,
+ guint header_len,
SoupTransferEncoding *encoding,
- gint *content_len,
+ int *content_len,
gpointer user_data);
-typedef void (*SoupReadChunkFn) (const SoupDataBuffer *data,
- gpointer user_data);
+typedef void (*SoupReadChunkFn) (const char *chunk,
+ guint len,
+ gpointer user_data);
-typedef void (*SoupReadDoneFn) (const SoupDataBuffer *data,
- gpointer user_data);
+typedef void (*SoupReadDoneFn) (char *body,
+ guint len,
+ gpointer user_data);
typedef void (*SoupReadErrorFn) (gboolean headers_done, gpointer user_data);
-SoupReader *soup_transfer_read (GIOChannel *chan,
+SoupReader *soup_transfer_read (SoupSocket *sock,
gboolean overwrite_chunks,
SoupReadHeadersDoneFn headers_done_cb,
SoupReadChunkFn read_chunk_cb,
typedef void (*SoupWriteErrorFn) (gboolean headers_done, gpointer user_data);
-SoupWriter *soup_transfer_write_simple (GIOChannel *chan,
+SoupWriter *soup_transfer_write_simple (SoupSocket *sock,
GString *header,
const SoupDataBuffer *src,
SoupWriteDoneFn write_done_cb,
typedef SoupTransferDone (*SoupWriteGetChunkFn) (SoupDataBuffer *out_next,
gpointer user_data);
-SoupWriter *soup_transfer_write (GIOChannel *chan,
+SoupWriter *soup_transfer_write (SoupSocket *sock,
SoupTransferEncoding encoding,
SoupWriteGetHeaderFn get_header_cb,
SoupWriteGetChunkFn get_chunk_cb,
g_string_append_c (str, '/');
}
- if (uri->path)
+ if (uri->path && *uri->path)
append_uri_encoded (str, uri->path, "?");
else if (just_path)
g_string_append_c (str, '/');
Makefile.in
auth-test
get
+revserver
simple-httpd
simple-proxy
-timeserver
uri-parsing
LIBS = $(top_builddir)/libsoup/libsoup-$(SOUP_API_VERSION).la
-noinst_PROGRAMS = get timeserver simple-httpd simple-proxy
+noinst_PROGRAMS = get revserver simple-httpd simple-proxy
get_SOURCES = get.c
-timeserver_SOURCES = timeserver.c
+revserver_SOURCES = revserver.c
simple_httpd_SOURCES = simple-httpd.c
simple_proxy_SOURCES = simple-proxy.c
--- /dev/null
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libsoup/soup.h>
+
+static void rev_read (SoupSocket *sock, GString *buf);
+static void rev_write (SoupSocket *sock, GString *buf);
+
+gboolean nonblocking = TRUE;
+
+static void
+reverse (GString *buf)
+{
+ char tmp, *a, *b;
+
+ a = buf->str;
+ b = buf->str + buf->len - 1;
+
+ while (isspace ((unsigned char)*b) && b > a)
+ b--;
+
+ while (a < b) {
+ tmp = *a;
+ *a++ = *b;
+ *b-- = tmp;
+ }
+}
+
+static void
+rev_done (SoupSocket *sock, GString *buf)
+{
+ g_signal_handlers_disconnect_by_func (sock, rev_read, buf);
+ g_signal_handlers_disconnect_by_func (sock, rev_write, buf);
+ g_object_unref (sock);
+ g_string_free (buf, TRUE);
+}
+
+static void
+rev_write (SoupSocket *sock, GString *buf)
+{
+ SoupSocketIOStatus status;
+ guint nwrote;
+
+ do {
+ status = soup_socket_write (sock, buf->str, buf->len, &nwrote);
+ memmove (buf->str, buf->str + nwrote, buf->len - nwrote);
+ buf->len -= nwrote;
+ } while (status == SOUP_SOCKET_OK && buf->len);
+
+ switch (status) {
+ case SOUP_SOCKET_OK:
+ rev_read (sock, buf);
+ break;
+
+ case SOUP_SOCKET_WOULD_BLOCK:
+ g_assert (nonblocking == TRUE);
+ break;
+
+ default:
+ g_warning ("Socket error");
+ /* fall through */
+
+ case SOUP_SOCKET_EOF:
+ rev_done (sock, buf);
+ break;
+ }
+}
+
+static void
+rev_read (SoupSocket *sock, GString *buf)
+{
+ SoupSocketIOStatus status;
+ char tmp[10];
+ guint nread;
+ gboolean eol;
+
+ do {
+ status = soup_socket_read_until (sock, tmp, sizeof (tmp),
+ "\n", 1, &nread, &eol);
+ if (status == SOUP_SOCKET_OK)
+ g_string_append_len (buf, tmp, nread);
+ } while (status == SOUP_SOCKET_OK && !eol);
+
+ switch (status) {
+ case SOUP_SOCKET_OK:
+ reverse (buf);
+ rev_write (sock, buf);
+ break;
+
+ case SOUP_SOCKET_WOULD_BLOCK:
+ g_assert (nonblocking == TRUE);
+ break;
+
+ default:
+ g_warning ("Socket error");
+ /* fall through */
+
+ case SOUP_SOCKET_EOF:
+ rev_done (sock, buf);
+ break;
+ }
+}
+
+static void
+new_connection (SoupSocket *listener, SoupSocket *client, gpointer user_data)
+{
+ GString *buf;
+
+ g_object_ref (client);
+ buf = g_string_new (NULL);
+
+ if (nonblocking) {
+ g_signal_connect (client, "readable",
+ G_CALLBACK (rev_read), buf);
+ g_signal_connect (client, "writable",
+ G_CALLBACK (rev_write), buf);
+ } else
+ soup_socket_set_flag (client, SOUP_SOCKET_FLAG_NONBLOCKING, FALSE);
+
+ rev_read (client, buf);
+}
+
+int
+main (int argc, char **argv)
+{
+ SoupSocket *listener;
+ SoupAddressFamily family = SOUP_ADDRESS_FAMILY_IPV4;
+ guint port = SOUP_ADDRESS_ANY_PORT;
+ SoupAddress *addr;
+ GMainLoop *loop;
+ int opt;
+
+ g_type_init ();
+
+ while ((opt = getopt (argc, argv, "6bp:")) != -1) {
+ switch (opt) {
+ case '6':
+ family = SOUP_ADDRESS_FAMILY_IPV6;
+ break;
+ case 'b':
+ nonblocking = FALSE;
+ break;
+ case 'p':
+ port = atoi (optarg);
+ break;
+ default:
+ fprintf (stderr, "Usage: %s [-6] [-b] [-p port]\n",
+ argv[0]);
+ exit (1);
+ }
+ }
+
+ addr = soup_address_new_any (family, port);
+ if (!addr) {
+ fprintf (stderr, "Could not create listener address\n");
+ exit (1);
+ }
+
+ listener = soup_socket_server_new (addr, port,
+ new_connection, NULL);
+ g_object_unref (addr);
+ if (!listener) {
+ fprintf (stderr, "Could not create listening socket\n");
+ exit (1);
+ }
+ printf ("Listening on port %d\n",
+ soup_address_get_port (
+ soup_socket_get_local_address (listener)));
+
+ loop = g_main_loop_new (NULL, TRUE);
+ g_main_loop_run (loop);
+
+ g_object_unref (listener);
+ return 0;
+}
+++ /dev/null
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <libsoup/soup.h>
-
-static void
-got_address (SoupAddress *addr, SoupKnownErrorCode status, gpointer user_data)
-{
- SoupSocket *client = user_data;
- const char *name, *phys;
- time_t now;
- char *timebuf;
- GIOChannel *chan;
- gsize wrote;
-
- name = soup_address_get_name (addr);
- phys = soup_address_get_physical (addr);
-
- printf ("got connection from %s (%s) port %d\n",
- name ? name : "?", phys,
- soup_address_get_port (addr));
-
- now = time (NULL);
- timebuf = ctime (&now);
-
- chan = soup_socket_get_iochannel (client);
- g_io_channel_write (chan, timebuf, strlen (timebuf), &wrote);
- g_io_channel_unref (chan);
-
- g_object_unref (client);
-}
-
-static void
-new_connection (SoupSocket *listener, SoupSocket *client, gpointer user_data)
-{
- SoupAddress *addr;
-
- g_object_ref (client);
- addr = soup_socket_get_remote_address (client);
- soup_address_resolve (addr, got_address, client);
-}
-
-int
-main (int argc, char **argv)
-{
- SoupSocket *listener;
- SoupAddressFamily family;
- SoupAddress *addr;
- guint port;
- GMainLoop *loop;
-
- g_type_init ();
-
- if (argc >=2 && !strcmp (argv[1], "-6")) {
- family = SOUP_ADDRESS_FAMILY_IPV6;
- argc--;
- argv++;
- } else
- family = SOUP_ADDRESS_FAMILY_IPV4;
-
- if (argc > 2) {
- fprintf (stderr, "Usage: %s [-6] [port]\n", argv[0]);
- exit (1);
- }
-
- if (argc == 2)
- port = atoi (argv[1]);
- else
- port = SOUP_ADDRESS_ANY_PORT;
-
- addr = soup_address_new_any (SOUP_ADDRESS_FAMILY_IPV4, port);
- if (!addr) {
- fprintf (stderr, "Could not create listener address\n");
- exit (1);
- }
-
- listener = soup_socket_server_new (addr, port,
- new_connection, NULL);
- g_object_unref (addr);
- if (!listener) {
- fprintf (stderr, "Could not create listening socket\n");
- exit (1);
- }
- printf ("Listening on port %d\n",
- soup_address_get_port (
- soup_socket_get_local_address (listener)));
-
- loop = g_main_loop_new (NULL, TRUE);
- g_main_loop_run (loop);
-
- g_object_unref (listener);
- return 0;
-}