From 6d5196ec11a0e771000e325b34606e0d16057d78 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Tue, 19 Aug 2003 19:04:09 +0000 Subject: [PATCH] New API for doing socket IO. Works both synchronously and asynchronously, * 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) --- ChangeLog | 32 +- libsoup/soup-connection.c | 16 +- libsoup/soup-connection.h | 2 +- libsoup/soup-error.h | 14 +- libsoup/soup-message.c | 17 +- libsoup/soup-queue.c | 51 ++-- libsoup/soup-server.c | 186 ++++-------- libsoup/soup-socket.c | 380 ++++++++++++++++++++--- libsoup/soup-socket.h | 40 +++ libsoup/soup-transfer.c | 750 +++++++++++++++++++--------------------------- libsoup/soup-transfer.h | 21 +- libsoup/soup-uri.c | 2 +- tests/.cvsignore | 2 +- tests/Makefile.am | 4 +- tests/revserver.c | 182 +++++++++++ tests/timeserver.c | 98 ------ 16 files changed, 1031 insertions(+), 766 deletions(-) create mode 100644 tests/revserver.c delete mode 100644 tests/timeserver.c diff --git a/ChangeLog b/ChangeLog index 0dd77bf..086cfcb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,8 +1,36 @@ 2003-08-19 Dan Winship + * 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 + * 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. diff --git a/libsoup/soup-connection.c b/libsoup/soup-connection.c index d5f1f19..20d104c 100644 --- a/libsoup/soup-connection.c +++ b/libsoup/soup-connection.c @@ -155,23 +155,17 @@ soup_connection_is_connected (SoupConnection *conn) } /** - * 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; } diff --git a/libsoup/soup-connection.h b/libsoup/soup-connection.h index 70d4d4c..8d19722 100644 --- a/libsoup/soup-connection.h +++ b/libsoup/soup-connection.h @@ -41,7 +41,7 @@ void soup_connection_start_ssl (SoupConnection *conn); 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); diff --git a/libsoup/soup-error.h b/libsoup/soup-error.h index 51803b6..717e7fa 100644 --- a/libsoup/soup-error.h +++ b/libsoup/soup-error.h @@ -37,13 +37,13 @@ typedef enum { 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 diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c index e64ae89..b477063 100644 --- a/libsoup/soup-message.c +++ b/libsoup/soup-message.c @@ -144,16 +144,15 @@ soup_message_copy (SoupMessage *req) } 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 @@ -553,14 +552,14 @@ requeue_read_error (gboolean body_started, gpointer user_data) } 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); diff --git a/libsoup/soup-queue.c b/libsoup/soup-queue.c index 575b7fa..9672883 100644 --- a/libsoup/soup-queue.c +++ b/libsoup/soup-queue.c @@ -116,9 +116,10 @@ soup_queue_error_cb (gboolean body_started, gpointer user_data) } 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; @@ -127,12 +128,11 @@ soup_queue_read_headers_cb (const GString *headers, 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 " @@ -210,14 +210,15 @@ soup_queue_read_headers_cb (const GString *headers, } 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); @@ -225,8 +226,9 @@ soup_queue_read_chunk_cb (const SoupDataBuffer *data, } 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; @@ -240,21 +242,21 @@ soup_queue_read_done_cb (const SoupDataBuffer *data, 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, @@ -450,11 +452,11 @@ soup_queue_write_done_cb (gpointer user_data) 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; @@ -481,7 +483,7 @@ start_request (SoupContext *ctx, SoupMessage *req) } 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, @@ -491,7 +493,7 @@ start_request (SoupContext *ctx, SoupMessage *req) 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, @@ -581,7 +583,8 @@ soup_queue_connect_cb (SoupContext *ctx, 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; diff --git a/libsoup/soup-server.c b/libsoup/soup-server.c index 3a2aa51..9490459 100644 --- a/libsoup/soup-server.c +++ b/libsoup/soup-server.c @@ -13,17 +13,9 @@ #include #endif -#ifdef HAVE_SYS_FILIO_H -#include -#endif - -#ifdef HAVE_SYS_IOCTL_H -#include -#endif - -#include #include #include +#include #include #include "soup-server.h" @@ -42,6 +34,7 @@ struct SoupServerPrivate { GMainLoop *loop; SoupSocket *listen_sock; + GSList *client_socks; GHashTable *handlers; /* KEY: path, VALUE: SoupServerHandler */ SoupServerHandler *default_handler; @@ -54,7 +47,6 @@ struct SoupServerMessage { gboolean finished; }; - static void init (GObject *object) { @@ -94,6 +86,14 @@ finalize (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); @@ -189,14 +189,7 @@ free_chunk (gpointer chunk, gpointer notused) 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) @@ -223,7 +216,7 @@ check_close_connection (SoupMessage *msg) } return close_connection; -} /* check_close_connection */ +} static void destroy_message (SoupMessage *msg) @@ -233,35 +226,15 @@ 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) { @@ -269,11 +242,10 @@ destroy_message (SoupMessage *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 @@ -354,7 +326,6 @@ static void issue_bad_request (SoupMessage *msg) { GString *header; - GIOChannel *channel; set_response_error (msg, SOUP_ERROR_BAD_REQUEST, NULL, NULL); @@ -362,9 +333,8 @@ issue_bad_request (SoupMessage *msg) 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, @@ -373,17 +343,17 @@ issue_bad_request (SoupMessage *msg) } 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, @@ -495,7 +465,8 @@ read_headers_cb (const GString *headers, static void call_handler (SoupMessage *req, - const SoupDataBuffer *req_data, + char *body, + guint len, const char *handler_path) { SoupServer *server = req->priv->server; @@ -504,9 +475,9 @@ call_handler (SoupMessage *req, 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; @@ -625,19 +596,18 @@ get_chunk_cb (SoupDataBuffer *out_next, gpointer user_data) } 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; @@ -648,7 +618,7 @@ read_done_cb (const SoupDataBuffer *data, encoding = SOUP_TRANSFER_CHUNKED; req->priv->write_tag = - soup_transfer_write (channel, + soup_transfer_write (server_sock, encoding, get_header_cb, get_chunk_cb, @@ -667,7 +637,7 @@ read_done_cb (const SoupDataBuffer *data, 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, @@ -678,82 +648,44 @@ read_done_cb (const SoupDataBuffer *data, 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 diff --git a/libsoup/soup-socket.c b/libsoup/soup-socket.c index 6c24e8f..3014974 100644 --- a/libsoup/soup-socket.c +++ b/libsoup/soup-socket.c @@ -2,10 +2,7 @@ /* * 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 @@ -14,6 +11,7 @@ #include #include +#include #include #include @@ -30,6 +28,9 @@ static GObjectClass *parent_class; enum { CONNECT_RESULT, + READABLE, + WRITABLE, + DISCONNECTED, NEW_CONNECTION, LAST_SIGNAL }; @@ -43,6 +44,9 @@ struct SoupSocketPrivate { guint watch; gboolean server, ssl; + + guint read_tag, write_tag, error_tag; + GByteArray *read_buf; }; static void @@ -93,6 +97,30 @@ class_init (GObjectClass *object_class) 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), @@ -118,31 +146,38 @@ soup_socket_new (void) 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; } } @@ -234,8 +269,8 @@ soup_socket_connect (SoupSocket *sock, SoupAddress *remote_addr) 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); @@ -285,18 +320,19 @@ listen_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data) 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); @@ -336,8 +372,8 @@ soup_socket_listen (SoupSocket *sock, SoupAddress *local_addr) 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) @@ -476,10 +512,53 @@ soup_socket_get_iochannel (SoupSocket *sock) 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) @@ -514,3 +593,234 @@ soup_socket_get_remote_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; +} diff --git a/libsoup/soup-socket.h b/libsoup/soup-socket.h index 036d52f..45300a2 100644 --- a/libsoup/soup-socket.h +++ b/libsoup/soup-socket.h @@ -30,13 +30,25 @@ typedef struct { /* 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); @@ -44,6 +56,8 @@ gboolean soup_socket_listen (SoupSocket *sock, 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, @@ -68,4 +82,30 @@ GIOChannel *soup_socket_get_iochannel (SoupSocket *sock); 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 */ diff --git a/libsoup/soup-transfer.c b/libsoup/soup-transfer.c index 19ec04d..bcd81c6 100644 --- a/libsoup/soup-transfer.c +++ b/libsoup/soup-transfer.c @@ -22,67 +22,30 @@ #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; @@ -91,26 +54,6 @@ struct _SoupReader { 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) @@ -118,11 +61,16 @@ 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); } @@ -140,7 +88,11 @@ soup_transfer_read_unref (SoupReader *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; } @@ -173,343 +125,242 @@ soup_transfer_read_set_callbacks (SoupReader *r, 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; } @@ -536,7 +387,7 @@ soup_transfer_read_cb (GIOChannel *iochannel, * 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, @@ -549,27 +400,27 @@ soup_transfer_read (GIOChannel *chan, 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. @@ -579,18 +430,46 @@ soup_transfer_read (GIOChannel *chan, 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); @@ -611,6 +490,7 @@ soup_transfer_write_unref (SoupWriter *w) soup_transfer_write_stop (w); g_byte_array_free (w->write_buf, TRUE); + g_object_unref (w->sock); g_free (w); return FALSE; } @@ -622,15 +502,11 @@ soup_transfer_write_cancel (SoupWriter *w) 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 @@ -698,81 +574,66 @@ get_next_chunk (SoupWriter *w) } } -#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; } @@ -781,11 +642,20 @@ soup_transfer_write_cb (GIOChannel* iochannel, 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, @@ -796,7 +666,7 @@ create_writer (GIOChannel *chan, 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; @@ -804,16 +674,14 @@ create_writer (GIOChannel *chan, 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. @@ -825,7 +693,7 @@ create_writer (GIOChannel *chan, /** * 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 @@ -833,7 +701,7 @@ create_writer (GIOChannel *chan, * @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 @@ -844,7 +712,7 @@ create_writer (GIOChannel *chan, * 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, @@ -853,7 +721,7 @@ soup_transfer_write_simple (GIOChannel *chan, { SoupWriter *writer; - writer = create_writer (chan, + writer = create_writer (sock, SOUP_TRANSFER_CONTENT_LENGTH, write_done_cb, error_cb, @@ -875,8 +743,8 @@ soup_transfer_write_simple (GIOChannel *chan, } /** - * 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 @@ -885,7 +753,7 @@ soup_transfer_write_simple (GIOChannel *chan, * @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. @@ -895,7 +763,7 @@ soup_transfer_write_simple (GIOChannel *chan, * 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, @@ -905,7 +773,7 @@ soup_transfer_write (GIOChannel *chan, { SoupWriter *writer; - writer = create_writer (chan, + writer = create_writer (sock, encoding, write_done_cb, error_cb, @@ -923,9 +791,13 @@ soup_transfer_write_pause (SoupWriter *w) 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 @@ -935,9 +807,9 @@ soup_transfer_write_unpause (SoupWriter *w) 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); } diff --git a/libsoup/soup-transfer.h b/libsoup/soup-transfer.h index d09749b..068529a 100644 --- a/libsoup/soup-transfer.h +++ b/libsoup/soup-transfer.h @@ -29,20 +29,23 @@ typedef enum { 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, @@ -66,7 +69,7 @@ typedef void (*SoupWriteDoneFn) (gpointer user_data); 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, @@ -79,7 +82,7 @@ typedef void (*SoupWriteGetHeaderFn) (GString **out_hdr, 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, diff --git a/libsoup/soup-uri.c b/libsoup/soup-uri.c index 4282ea1..c934942 100644 --- a/libsoup/soup-uri.c +++ b/libsoup/soup-uri.c @@ -298,7 +298,7 @@ soup_uri_to_string (const SoupUri *uri, gboolean just_path) 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, '/'); diff --git a/tests/.cvsignore b/tests/.cvsignore index 14ba20c..be4f123 100644 --- a/tests/.cvsignore +++ b/tests/.cvsignore @@ -2,7 +2,7 @@ Makefile Makefile.in auth-test get +revserver simple-httpd simple-proxy -timeserver uri-parsing diff --git a/tests/Makefile.am b/tests/Makefile.am index 7419ee0..aa8dcc5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -4,10 +4,10 @@ INCLUDES = \ 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 diff --git a/tests/revserver.c b/tests/revserver.c new file mode 100644 index 0000000..839603a --- /dev/null +++ b/tests/revserver.c @@ -0,0 +1,182 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include + +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; +} diff --git a/tests/timeserver.c b/tests/timeserver.c deleted file mode 100644 index 766c322..0000000 --- a/tests/timeserver.c +++ /dev/null @@ -1,98 +0,0 @@ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include -#include -#include -#include - -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; -} -- 2.7.4