Hi everybody,

attached is a patch that implements a rather naive HTTP-timeout solution.
Problem:
The current http-module will not disconnect clients, thus resources (e.g. sockets) are not reclaimed.
Approach of the solution:
* each new client registers itself in a dictionary of active clients upon creation.
* the server thread checks after a certain timeout each client in the dictionary whether or not the connection has been timed out.
* 2 kinds of timeout are supported:
(1) CONNECTION: the time between creation of the client and the first input.
(2) IDLE: the time between subsequent data transfers.
* clients that are waiting for an answer (e.g. clients in the request_is_being_handled and sending_reply state) are not disconnected.
* each data transfer resets the client's timestamp.
* the configuration of the timeout-values is done in the http.h file, which I know is not a very sane approach.


Please have a look at the patch. If anyone comes up with a better solution or finds errors/mistakes: I am happy to learn :))).

Mit freundlichen Gruessen/Best regards

David Schmitz

Softwareentwicklung

-----------------------------------------------------------------

Wapme Systems AG

Vogelsanger Weg 80

40470 D�sseldorf

Tel.: + 49 -211-7 48 45 - 2708

Fax: + 49 -211-80-6-06-2801

E-Mail: [EMAIL PROTECTED]

Internet: http://www.wapme-systems.de

Index: gwlib/http.c
===================================================================
RCS file: /home/cvs/gateway/gwlib/http.c,v
retrieving revision 1.211
diff -u -p -b -r1.211 http.c
--- gwlib/http.c        15 Nov 2003 13:14:23 -0000      1.211
+++ gwlib/http.c        24 Nov 2003 10:34:18 -0000
@@ -95,11 +95,13 @@
 
 
 /*
- * Default port to connect to for HTTP connections.
+* Default port to connect to for HTTP connections.
  */
 enum { HTTP_PORT = 80,
        HTTPS_PORT = 443 };
 
+static Dict *active_clients;
+static Mutex *active_clients_lock;
 
 /*
  * Status of this module.
@@ -174,7 +176,6 @@ static int parse_http_version(Octstr *ve
     return 1;
 }
 
-
 /***********************************************************************
  * Proxy support.
  */
@@ -1693,6 +1694,15 @@ static void client_shutdown(void)
     fdset_destroy(client_fdset);
 }
 
+Octstr *pointer_to_octstr(void *p) {
+    Octstr *tmp_oct;
+    char *tmp = (char *) gw_malloc(sizeof(char) * 255);
+    sprintf(tmp, "%ld", p);
+    tmp_oct = octstr_create(tmp);
+    gw_free(tmp);
+    return tmp_oct;
+};
+
 
 /***********************************************************************
  * HTTP server interface.
@@ -1710,7 +1720,8 @@ struct HTTPClient {
         reading_request_line,
         reading_request,
         request_is_being_handled,
-        sending_reply
+        sending_reply,
+        to_be_destroyed
     } state;
     int method;  /* HTTP_METHOD_ value */
     Octstr *url;
@@ -1718,11 +1729,17 @@ struct HTTPClient {
     int persistent_conn;
     unsigned long conn_time; /* store time for timeouting */
     HTTPEntity *request;
+    Counter *ref_counter;
 };
 
-
+/*
+ * creates a new HTTPClient.
+ * Furthermore the client is registered in the active-clients dictionary.
+ */
 static HTTPClient *client_create(int port, Connection *conn, Octstr *ip)
 {
+    Octstr *key;
+    
     HTTPClient *p;
     
 #ifdef HAVE_LIBSSL
@@ -1743,18 +1760,29 @@ static HTTPClient *client_create(int por
     p->persistent_conn = 1;
     p->conn_time = time(NULL);
     p->request = NULL;
+    p->ref_counter = counter_create();
+
+    key = pointer_to_octstr(p);
+    dict_put(active_clients, key, p);
+    octstr_destroy(key);
     return p;
 }
 
-
-static void client_destroy(void *client)
-{
+/*
+ * frees resources allocated for a client.
+ */
+static void client_no_lock_destroy(void *client) {
     HTTPClient *p;
     
     if (client == NULL)
        return;
-
     p = client;
+
+    if (counter_value(p->ref_counter) > 0) {
+        debug("", 0, "Cannot kill client! Client %p is still referenced.", client);
+        return;
+    };
+
     debug("gwlib.http", 0, "HTTP: Destroying HTTPClient area %p.", p);
     gw_assert_allocated(p, __FILE__, __LINE__, __func__);
     debug("gwlib.http", 0, "HTTP: Destroying HTTPClient for `%s'.",
@@ -1763,17 +1791,41 @@ static void client_destroy(void *client)
     octstr_destroy(p->ip);
     octstr_destroy(p->url);
     entity_destroy(p->request);
+    counter_destroy(p->ref_counter);
     gw_free(p);
-}
+    p = NULL;
+};
 
+/*
+ * destroys the client and removes it from the active-clients-dictionary.
+ */
+static void client_destroy(void *client) {
+    Octstr *key;
+    mutex_lock(active_clients_lock);
+    key = pointer_to_octstr(client);
+    if (dict_remove(active_clients, key) == NULL) {
+        octstr_destroy(key);
+        mutex_unlock(active_clients_lock);
+        return;
+    };
+    octstr_destroy(key);
+    client_no_lock_destroy(client);
+    mutex_unlock(active_clients_lock);
+};
 
 static void client_reset(HTTPClient *p)
 {
+    Octstr *key;
+    key = pointer_to_octstr(p);
+    
     debug("gwlib.http", 0, "HTTP: Resetting HTTPClient for `%s'.",
          octstr_get_cstr(p->ip));
     p->state = reading_request_line;
     p->conn_time = time(NULL);
     gw_assert(p->request == NULL);
+    counter_set(p->ref_counter, 0L);
+    dict_put(active_clients, key, p);
+    octstr_destroy(key);
 }
 
 
@@ -1982,24 +2034,36 @@ error:
     return -1;
 }
 
-
+/*
+ * Callback routine for http-requests.
+ * Implements a state machine:
+ * reading_request_line -> reading_request 
+ * -> request_is_being_handled -> sending_reply
+ *
+ * To avoid the destruction of active clients waiting for a reply,
+ * those clients are removed from the active-clients dictionary
+ * upon a reading_request -> request_is_being_handled transition.
+ */
 static void receive_request(Connection *conn, void *data)
 {
     HTTPClient *client;
     Octstr *line;
+    Octstr *key;
     int ret;
 
+    client = data;
     if (run_status != running) {
        conn_unregister(conn);
        return;
     }
-
-    client = data;
-    
     for (;;) {
+        debug("",0,"loop start: %p", client);
+            
        switch (client->state) {
        case reading_request_line:
            line = conn_read_line(conn);
+                client->conn_time = time(NULL);
+                /*debug("", 0, "reading_request_line");*/
            if (line == NULL) {
                if (conn_eof(conn) || conn_read_error(conn))
                    goto error;
@@ -2008,8 +2072,10 @@ static void receive_request(Connection *
            ret = parse_request_line(&client->method, &client->url,
                                      &client->use_version_1_0, line);
            octstr_destroy(line);
-           if (ret == -1)
+                if (ret == -1) {
                goto error;
+                }
+                
            /*
             * RFC2616 (4.3) says we should read a message body if there
             * is one, even on GET requests.
@@ -2019,10 +2085,15 @@ static void receive_request(Connection *
            break;
            
        case reading_request:
+                /*debug("", 0, "reading_request");*/
            ret = entity_read(client->request, conn);
-           if (ret < 0)
+                client->conn_time = time(NULL);
+                if (ret < 0) {
                goto error;
+                }
            if (ret == 0) {
+                    /*dict_remove(active_clients, pointer_to_octstr(client));*/
+                    counter_increase(client->ref_counter);
                client->state = request_is_being_handled;
                conn_unregister(conn);
                port_put_request(client);
@@ -2030,10 +2101,13 @@ static void receive_request(Connection *
            return;
 
        case sending_reply:
-           if (conn_outbuf_len(conn) > 0)
+                /*debug("", 0, "sending_reply");*/
+                if (conn_outbuf_len(conn) > 0) {
                return;
+                }
            /* Reply has been sent completely */
            if (!client->persistent_conn) {
+                    counter_decrease(client->ref_counter);
                 conn_unregister(conn);
                client_destroy(client);
                return;
@@ -2043,8 +2117,9 @@ static void receive_request(Connection *
            break;
 
        default:
-           panic(0, "Internal error: HTTPClient state is wrong.");
+                return;
        }
+        debug("",0,"loop end");
     }
     
 error:
@@ -2058,12 +2133,112 @@ struct server {
     int ssl;
 };
 
+/* check_timeout. Checks for timedout connections.
+ * each active clients (determined by using the active_clients-dictionary)
+ * is handled based on its state. Depending on the state different timeout-
+ * thresholds are used.
+ *
+ * NOTE: Some state transitions happen elsewhere in this module. 
+ * 
+ * state                    | timeout used          | action
+ * """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+ * reading_request_line     | CONNECTION_TIME_OUT   | - the client has not submitted a
+ *                                                    request within the 
timeout-period.
+ *                                                    this is used for first-time 
+ *                                                    communication only.
+ *                                                    - unregister the client.
+ *                                                    STATE = to_be_destroyed.
+ * reading_request          | IDLE_TIME_OUT         | - the communication has 
started, but 
+ *                                                    there has not been any input 
within the
+ *                                                    timeout period.
+ *                                                    - unregister the client.
+ *                                                    STATE = to_be_destroyed.
+ * request_is_being_handled | none                  | - A valid request has been 
submitted,
+ *                                                    thus the client will not be 
interrupted.
+ *                                                    STATE = 
request_is_being_handled.
+ * sending_reply            | none                  | - An answer is being sent to 
the client,
+ *                                                    thus the client will not be 
interrupted.
+ *                                                    STATE = sending_reply.
+ * to_be_destroyed          | none                  | - The client has already been 
unregistered and
+ *                                                    is now queued for destruction.
+ *                                                    - the client is removed from 
the list of active
+ *                                                    clients and subsequently 
destroyed.
+ */
+static void check_timeout(void) 
+{
+    List *keys;
+    int i;
+    HTTPClient *tmp_client;
+    time_t now;
+    double timediff;
+
+    debug("", 0, "Checking timeout...");
+    /*
+     * for each active client check status.
+     * - based on status check either CONNECTION or IDLE timeout.
+     * - if the client is timedout, then unregister the callback and
+     * - set the client's state to "to_be_destroyed"
+     */
+    mutex_lock(active_clients_lock);
+    now = time(NULL);
+    keys = dict_keys(active_clients);
+    for (i = 0; i < list_len(keys); i++) {
+        tmp_client = dict_get(active_clients, list_get(keys, i));
+        /*debug("", 0, "sleep now %p", tmp_client);*/
+        if (tmp_client == NULL)
+            continue;
+        /*gwthread_sleep(10);
+        debug("", 0, "awake now %p", tmp_client);*/
+        
+        debug("", 0, "Client:   %p", tmp_client);
+        debug("", 0, "Num ref:  %ld", counter_value(tmp_client->ref_counter));
+
+        if (counter_value(tmp_client->ref_counter) > 0)
+            continue;
+        timediff = difftime(now, tmp_client->conn_time);
+
+        switch (tmp_client->state) {
+            case reading_request_line:
+                debug("", 0, "State:   reading_request_line");
+                if (timediff > CONNECTION_TIME_OUT) {
+                    debug("", 0, "CONNECT TIMEOUT");
+                    tmp_client->state = to_be_destroyed;
+                    conn_unregister(tmp_client->conn);
+                }
+                break;
+            case reading_request:
+                debug("", 0, "State:   reading_request");
+                if (timediff > IDLE_TIME_OUT) {
+                    debug("", 0, "IDLE TIMEOUT");
+                    tmp_client->state = to_be_destroyed;
+                    conn_unregister(tmp_client->conn);
+                }
+                break;
+            case request_is_being_handled:
+                debug("", 0, "State:   request_is_being_handled [ignored]");
+                break;
+            case sending_reply:
+                debug("", 0, "State:   sending_reply [ignored]");
+                break;
+            case to_be_destroyed:
+                debug("", 0, "State:   to_be_destroyed");
+                dict_remove(active_clients, list_get(keys, i));
+                client_no_lock_destroy(tmp_client);
+                break;
+            default:
+                panic(0, "Invalid state %d", tmp_client->state);
+        };
+    }
+    mutex_unlock(active_clients_lock);
+    debug("", 0, "timeout check done!");
+};
 
 static void server_thread(void *dummy)
 {
     struct pollfd tab[MAX_SERVERS];
     int ports[MAX_SERVERS];
     int ssl[MAX_SERVERS];
+
     long i, j, n, fd;
     int *portno;
     struct server *p;
@@ -2090,10 +2265,21 @@ static void server_thread(void *dummy)
             gw_free(p);
         }
 
-        if ((ret = gwthread_poll(tab, n, -1.0)) == -1) {
+        /* poll returns -1 on error and 0 on timeout. we'll handle both */
+        if ((ret = gwthread_poll(tab, n, MIN_TIME_OUT)) < 1) {
+            switch(ret) {
+            case 0: /* timeout */
+                    /*debug("", 0, "check tout");*/
+                    check_timeout();
+                break;
+            case -1: /* error */
             if (errno != EINTR) /* a signal was caught during poll() function */
                 warning(0, "HTTP: gwthread_poll failed.");
             continue;
+                break;
+            default: /* should not happen */
+                panic(0, "This is not supposed to happen!");
+            }
         }
 
         for (i = 0; i < n; ++i) {
@@ -2178,7 +2364,6 @@ static void start_server_thread(void)
     }
 }
 
-
 int http_open_port_if(int port, int ssl, Octstr *interface)
 {
     struct server *p;
@@ -2288,8 +2473,8 @@ HTTPClient *http_accept_request(int port
                                List **cgivars)
 {
     HTTPClient *client;
-
     client = port_get_request(port);
+
     if (client == NULL) {
        debug("gwlib.http", 0, "HTTP: No clients with requests, quitting.");
        return NULL;
@@ -2314,7 +2499,6 @@ HTTPClient *http_accept_request(int port
     client->request->body = NULL;
     entity_destroy(client->request);
     client->request = NULL;
-
     return client;
 }
 
@@ -2359,6 +2543,7 @@ void http_send_reply(HTTPClient *client,
     if (ret == 0) { 
         /* HTTP/1.0 or 1.1, hence keep-alive or keep-alive */
         if (!client->persistent_conn) {
+            counter_decrease(client->ref_counter);
             client_destroy(client);     
         } else {
             /* XXX mark this HTTPClient in the keep-alive cleaner thread */
@@ -2373,6 +2558,7 @@ void http_send_reply(HTTPClient *client,
     }
     /* error while sending response */
     else {     
+        counter_decrease(client->ref_counter);
         client_destroy(client);
     }
 }
@@ -3179,6 +3365,9 @@ void http_init(void)
 {
     gw_assert(run_status == limbo);
 
+    active_clients = dict_create(1024, NULL);
+    active_clients_lock = mutex_create();
+
 #ifdef HAVE_LIBSSL
     openssl_init_locks();
     conn_init_ssl();
@@ -3195,6 +3384,23 @@ void http_init(void)
     run_status = running;
 }
 
+void purge_active_clients() 
+{
+    List *keys;
+    int i;
+    HTTPClient *tmp_client;
+
+    debug("", 0, "Purging clients...");
+    keys = dict_keys(active_clients);
+    for (i = 0; i < list_len(keys); ++i) {
+        tmp_client = dict_get(active_clients, list_get(keys, i));       
+        if (tmp_client != NULL) {
+            conn_flush_real(tmp_client->conn, 2.0);
+            conn_unregister(tmp_client->conn);
+        }
+        client_destroy(tmp_client);
+    }   
+};
 
 void http_shutdown(void)
 {
@@ -3203,10 +3409,13 @@ void http_shutdown(void)
 
     run_status = terminating;
 
+    mutex_destroy(active_clients_lock);
     conn_pool_shutdown();
     port_shutdown();
     client_shutdown();
     server_shutdown();
+    purge_active_clients();
+    dict_destroy(active_clients);
     proxy_shutdown();
 #ifdef HAVE_LIBSSL
     openssl_shutdown_locks();
Index: gwlib/http.h
===================================================================
RCS file: /home/cvs/gateway/gwlib/http.h,v
retrieving revision 1.60
diff -u -p -b -r1.60 http.h
--- gwlib/http.h        15 Nov 2003 13:14:23 -0000      1.60
+++ gwlib/http.h        24 Nov 2003 10:34:18 -0000
@@ -194,13 +194,21 @@ typedef struct {
        Octstr *value;
 } HTTPCGIVar;
 
+/*
+ * Connection resp. flush timeout in seconds. 
+ */
+enum {
+    MIN_TIME_OUT=10,
+    CONNECTION_TIME_OUT=50,
+    IDLE_TIME_OUT=10,
+    FLUSH_TIME_OUT=2
+};
 
 /*
  * Initialization function. This MUST be called before any other function
  * declared in this header file.
  */
 void http_init(void);
-
 
 /*
  * Shutdown function. This MUST be called when no other function
Index: gwlib/conn.c
===================================================================
RCS file: /home/cvs/gateway/gwlib/conn.c,v
retrieving revision 1.67
diff -u -p -b -r1.67 conn.c
--- gwlib/conn.c        15 Nov 2003 13:14:23 -0000      1.67
+++ gwlib/conn.c        24 Nov 2003 10:34:18 -0000
@@ -1058,7 +1058,8 @@ int conn_wait(Connection *conn, double s
     return 0;
 }
 
-int conn_flush(Connection *conn)
+
+int conn_flush_real(Connection *conn, double tout_seconds)
 {
     int ret;
     int revents;
@@ -1075,7 +1076,7 @@ int conn_flush(Connection *conn)
         fd = conn->fd;
 
         unlock_out(conn);
-        revents = gwthread_pollfd(fd, POLLOUT, -1.0);
+        revents = gwthread_pollfd(fd, POLLOUT, tout_seconds);
 
         /* Note: Make sure we have the "out" lock when
          * going through the loop again, because the 
@@ -1113,6 +1114,7 @@ int conn_flush(Connection *conn)
 
     return 0;
 }
+
 
 int conn_write(Connection *conn, Octstr *data)
 {
Index: gwlib/conn.h
===================================================================
RCS file: /home/cvs/gateway/gwlib/conn.h,v
retrieving revision 1.26
diff -u -p -b -r1.26 conn.h
--- gwlib/conn.h        15 Nov 2003 13:14:23 -0000      1.26
+++ gwlib/conn.h        24 Nov 2003 10:34:18 -0000
@@ -220,7 +220,8 @@ int conn_wait(Connection *conn, double s
  * is done, or until the thread is interrupted or woken up.  Return 0
  * if it worked, 1 if there was an interruption, or -1 if the connection
  * is broken. */
-int conn_flush(Connection *conn);
+int conn_flush_real(Connection *conn, double tout_seconds);
+#define conn_flush(conn) conn_flush_real(conn, -1.0)
 
 /* Output functions.  Each of these takes an open connection and some
  * data, formats the data and queues it for sending.  It may also

Reply via email to