On Fri, Apr 16, 2021 at 12:21:56PM +0200, Claudio Jeker wrote:
> This diff changes the http module to support keep-alive.
> It splits requests (for a resource) from connections (to a server).
> When a request is received the code tries to first use a IDLE connection,
> if none is around a new connection is started (unless there are too many
> connections inflight).
> 
> Idle connections are kept for 10sec and closed after that time. For
> rpki-client this should work well since the RRDP exchange will be a burtst
> of requests (one after another). There is only one server that is
> connected twice during the run (one server hosting 2 repos).
> 
> The benefit of using keep-alive is less CPU time wasted on constant TLS
> handshakes. I did not notice any speed improvements.
> 
> After fixing most issues in the http module the last few days this is less
> urgent to go in. Still sending it out so people can play with it.

Updated diff to apply after all the KNF commits.

-- 
:wq Claudio

Index: http.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/http.c,v
retrieving revision 1.31
diff -u -p -r1.31 http.c
--- http.c      19 Apr 2021 17:04:35 -0000      1.31
+++ http.c      20 Apr 2021 08:06:56 -0000
@@ -48,6 +48,7 @@
 #include <sys/queue.h>
 #include <sys/socket.h>
 
+#include <assert.h>
 #include <ctype.h>
 #include <err.h>
 #include <errno.h>
@@ -66,25 +67,31 @@
 
 #include "extern.h"
 
-#define HTTP_USER_AGENT        "OpenBSD rpki-client"
-#define HTTP_BUF_SIZE  (32 * 1024)
-#define MAX_CONNECTIONS        12
-
-#define WANT_POLLIN    1
-#define WANT_POLLOUT   2
+#define HTTP_USER_AGENT                "OpenBSD rpki-client"
+#define HTTP_BUF_SIZE          (32 * 1024)
+#define HTTP_IDLE_TIMEOUT      10
+#define MAX_CONNECTIONS                64
+#define NPFDS                  (MAX_CONNECTIONS + 1)
+
+enum res {
+       DONE,
+       WANT_POLLIN,
+       WANT_POLLOUT,
+};
 
 enum http_state {
        STATE_FREE,
-       STATE_INIT,
        STATE_CONNECT,
        STATE_TLSCONNECT,
        STATE_REQUEST,
        STATE_RESPONSE_STATUS,
        STATE_RESPONSE_HEADER,
        STATE_RESPONSE_DATA,
-       STATE_RESPONSE_CHUNKED,
+       STATE_RESPONSE_CHUNKED_HEADER,
+       STATE_RESPONSE_CHUNKED_TRAILER,
        STATE_WRITE_DATA,
-       STATE_DONE,
+       STATE_IDLE,
+       STATE_CLOSE,
 };
 
 struct http_proxy {
@@ -94,50 +101,108 @@ struct http_proxy {
 };
 
 struct http_connection {
-       char                    *url;
+       LIST_ENTRY(http_connection)     entry;
        char                    *host;
        char                    *port;
-       const char              *path;  /* points into url */
-       char                    *modified_since;
        char                    *last_modified;
+       char                    *redir_uri;
+       struct http_request     *req;
+       struct pollfd           *pfd;
        struct addrinfo         *res0;
        struct addrinfo         *res;
        struct tls              *tls;
        char                    *buf;
        size_t                  bufsz;
        size_t                  bufpos;
-       size_t                  id;
        off_t                   iosz;
+       time_t                  idle_time;
        int                     status;
-       int                     redirect_loop;
        int                     fd;
-       int                     outfd;
+       int                     chunked;
+       int                     keep_alive;
        short                   events;
-       short                   chunked;
        enum http_state         state;
 };
 
-struct msgbuf msgq;
-struct sockaddr_storage http_bindaddr;
-struct tls_config *tls_config;
-uint8_t *tls_ca_mem;
-size_t tls_ca_size;
+LIST_HEAD(http_conn_list, http_connection);
 
+struct http_request {
+       TAILQ_ENTRY(http_request)       entry;
+       char                    *uri;
+       char                    *modified_since;
+       char                    *host;
+       char                    *port;
+       const char              *path;  /* points into uri */
+       size_t                   id;
+       int                      outfd;
+       int                      redirect_loop;
+};
+
+TAILQ_HEAD(http_req_queue, http_request);
+
+static struct http_conn_list   active = LIST_HEAD_INITIALIZER(active);
+static struct http_conn_list   idle = LIST_HEAD_INITIALIZER(idle);
+static struct http_req_queue   queue = TAILQ_HEAD_INITIALIZER(queue);
+static size_t http_conn_count;
+
+static struct msgbuf msgq;
+static struct sockaddr_storage http_bindaddr;
+static struct tls_config *tls_config;
+static uint8_t *tls_ca_mem;
+static size_t tls_ca_size;
+
+/* HTTP request API */
+static void    http_req_new(size_t, char *, char *, int);
+static void    http_req_free(struct http_request *);
+static void    http_req_done(size_t, enum http_result, const char *);
+static void    http_req_fail(size_t);
+static int     http_req_schedule(struct http_request *);
+
+/* HTTP connection API */
+static void    http_new(struct http_request *);
 static void    http_free(struct http_connection *);
 
-static int     http_tls_handshake(struct http_connection *);
-static int     http_write(struct http_connection *);
+static enum res http_done(struct http_connection *, enum http_result);
+static enum res http_failed(struct http_connection *);
+
+/* HTTP connection FSM functions */
+static void    http_do(struct http_connection *,
+                   enum res (*)(struct http_connection *));
+
+/* These functions can be used with http_do() */
+static enum res        http_connect(struct http_connection *);
+static enum res        http_request(struct http_connection *);
+static enum res        http_close(struct http_connection *);
+static enum res http_handle(struct http_connection *);
+
+/* Internal state functions used by the above functions */
+static enum res        http_finish_connect(struct http_connection *);
+static enum res        http_tls_connect(struct http_connection *);
+static enum res        http_tls_handshake(struct http_connection *);
+static enum res        http_read(struct http_connection *);
+static enum res        http_write(struct http_connection *);
+static enum res        data_write(struct http_connection *);
+
+static time_t
+getmonotime(void)
+{
+       struct timespec ts;
+
+       if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
+               err(1, "clock_gettime");
+       return (ts.tv_sec);
+}
 
 /*
  * Return a string that can be used in error message to identify the
  * connection.
  */
 static const char *
-http_info(const char *url)
+http_info(const char *uri)
 {
        static char buf[80];
 
-       if (strnvis(buf, url, sizeof buf, VIS_SAFE) >= (int)sizeof buf) {
+       if (strnvis(buf, uri, sizeof buf, VIS_SAFE) >= (int)sizeof buf) {
                /* overflow, add indicator */
                memcpy(buf + sizeof buf - 4, "...", 4);
        }
@@ -212,6 +277,11 @@ url_encode(const char *path)
        return (epath);
 }
 
+/*
+ * Parse a URI and split it up into host, port and path.
+ * Does some basic URI validation. Both host and port need to be freed
+ * by the caller whereas path points into the uri.
+ */
 static int
 http_parse_uri(char *uri, char **ohost, char **oport, char **opath)
 {
@@ -269,8 +339,12 @@ http_parse_uri(char *uri, char **ohost, 
        return 0;
 }
 
+/*
+ * Lookup the IP addresses for host:port.
+ * Returns 0 on success and -1 on failure.
+ */
 static int
-http_resolv(struct http_connection *conn, const char *host, const char *port)
+http_resolv(struct addrinfo **res, const char *host, const char *port)
 {
        struct addrinfo hints;
        int error;
@@ -278,13 +352,13 @@ http_resolv(struct http_connection *conn
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = PF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
-       error = getaddrinfo(host, port, &hints, &conn->res0);
+       error = getaddrinfo(host, port, &hints, res);
        /*
         * If the services file is corrupt/missing, fall back
         * on our hard-coded defines.
         */
        if (error == EAI_SERVICE)
-               error = getaddrinfo(host, "443", &hints, &conn->res0);
+               error = getaddrinfo(host, "443", &hints, res);
        if (error != 0) {
                warnx("%s: %s", host, gai_strerror(error));
                return -1;
@@ -293,8 +367,61 @@ http_resolv(struct http_connection *conn
        return 0;
 }
 
+/*
+ * Create and queue a new request.
+ */
+static void
+http_req_new(size_t id, char *uri, char *modified_since, int outfd)
+{
+       struct http_request *req;
+       char *host, *port, *path;
+
+       if (http_parse_uri(uri, &host, &port, &path) == -1) {
+               free(uri);
+               free(modified_since);
+               close(outfd);
+               http_req_fail(id);
+               return;
+       }
+
+       if ((req = calloc(1, sizeof(*req))) == NULL)
+               err(1, NULL);
+
+       req->id = id;
+       req->outfd = outfd;
+       req->host = host;
+       req->port = port;
+       req->path = path;
+       req->uri = uri;
+       req->modified_since = modified_since;
+
+       TAILQ_INSERT_TAIL(&queue, req, entry);
+}
+
+/*
+ * Free a request, request is not allowed to be on the req queue.
+ */
 static void
-http_done(size_t id, enum http_result res, const char *last_modified)
+http_req_free(struct http_request *req)
+{
+       if (req == NULL)
+               return;
+
+       free(req->host);
+       free(req->port);
+       /* no need to free req->path it points into req->uri */
+       free(req->uri);
+       free(req->modified_since);
+
+       if (req->outfd != -1)
+               close(req->outfd);
+}
+
+/*
+ * Enqueue request response
+ */
+static void
+http_req_done(size_t id, enum http_result res, const char *last_modified)
 {
        struct ibuf *b;
 
@@ -306,8 +433,11 @@ http_done(size_t id, enum http_result re
        ibuf_close(&msgq, b);
 }
 
+/*
+ * Enqueue request failure response
+ */
 static void
-http_fail(size_t id)
+http_req_fail(size_t id)
 {
        struct ibuf *b;
        enum http_result res = HTTP_FAILED;
@@ -320,53 +450,101 @@ http_fail(size_t id)
        ibuf_close(&msgq, b);
 }
 
-static struct http_connection *
-http_new(size_t id, char *uri, char *modified_since, int outfd)
+/*
+ * Schedule new requests until maximum number of connections is reached.
+ * Try to reuse an idle connection if one exists that matches host and port.
+ */
+static int
+http_req_schedule(struct http_request *req)
 {
        struct http_connection *conn;
-       char *host, *port, *path;
 
-       if (http_parse_uri(uri, &host, &port, &path) == -1) {
-               free(uri);
-               free(modified_since);
-               close(outfd);
-               http_fail(id);
-               return NULL;
+       TAILQ_REMOVE(&queue, req, entry);
+
+       /* check list of idle connections first */
+       LIST_FOREACH(conn, &idle, entry) {
+               if (strcmp(conn->host, req->host) != 0)
+                       continue;
+               if (strcmp(conn->port, req->port) != 0)
+                       continue;
+
+               LIST_REMOVE(conn, entry);
+               LIST_INSERT_HEAD(&active, conn, entry);
+
+               /* use established connection */
+               conn->req = req;
+               conn->idle_time = 0;
+
+               /* start request */
+               http_do(conn, http_request);
+               if (conn->state == STATE_FREE)
+                       http_free(conn);
+               return 1;
        }
 
+       if (http_conn_count < MAX_CONNECTIONS) {
+               http_new(req);
+               return 1;
+       }
+
+       /* no more slots free, requeue */
+       TAILQ_INSERT_HEAD(&queue, req, entry);
+       return 0;
+}
+
+/*
+ * Create a new HTTP connection which will be used for the HTTP request req.
+ * On errors a req faulure is issued and both connection and request are freed.
+ */ 
+static void
+http_new(struct http_request *req)
+{
+       struct http_connection *conn;
+
        if ((conn = calloc(1, sizeof(*conn))) == NULL)
                err(1, NULL);
 
-       conn->id = id;
        conn->fd = -1;
-       conn->outfd = outfd;
-       conn->host = host;
-       conn->port = port;
-       conn->path = path;
-       conn->url = uri;
-       conn->modified_since = modified_since;
-       conn->state = STATE_INIT;
+       conn->req = req;
+       if ((conn->host = strdup(req->host)) == NULL)
+               err(1, NULL);
+       if ((conn->port = strdup(req->port)) == NULL)
+               err(1, NULL);
+
+       LIST_INSERT_HEAD(&active, conn, entry);
+       http_conn_count++;
 
        /* TODO proxy support (overload of host and port) */
 
-       if (http_resolv(conn, host, port) == -1) {
-               http_fail(conn->id);
+       if (http_resolv(&conn->res0, conn->host, conn->port) == -1) {
+               http_req_fail(req->id);
                http_free(conn);
-               return NULL;
+               return;
        }
 
-       return conn;
+       /* connect and start request */
+       http_do(conn, http_connect);
+       if (conn->state == STATE_FREE)
+               http_free(conn);
 }
 
+/*
+ * Free a no longer active connection, releasing all memory and closing
+ * any open file descriptor.
+ */
 static void
 http_free(struct http_connection *conn)
 {
-       free(conn->url);
+       assert(conn->state == STATE_FREE);
+
+       LIST_REMOVE(conn, entry);
+       http_conn_count--;
+
+       http_req_free(conn->req);
        free(conn->host);
        free(conn->port);
-       /* no need to free conn->path it points into conn->url */
-       free(conn->modified_since);
        free(conn->last_modified);
+       free(conn->redir_uri);
        free(conn->buf);
 
        if (conn->res0 != NULL)
@@ -376,12 +554,91 @@ http_free(struct http_connection *conn)
 
        if (conn->fd != -1)
                close(conn->fd);
-       close(conn->outfd);
        free(conn);
 }
 
+/*
+ * Called when a request on this connection is finished.
+ * Move connection into idle state and onto idle queue.
+ * If there is a request connected to it send back a response
+ * with http_result res, else ignore the res.
+ */
+static enum res
+http_done(struct http_connection *conn, enum http_result res)
+{
+       assert(conn->bufpos == 0);
+       assert(conn->iosz == 0);
+       assert(conn->chunked == 0);
+       assert(conn->redir_uri == NULL);
 
-static int
+       conn->state = STATE_IDLE;
+       conn->idle_time = getmonotime() + HTTP_IDLE_TIMEOUT;
+
+       if (conn->req) {
+               http_req_done(conn->req->id, res, conn->last_modified);
+               http_req_free(conn->req);
+               conn->req = NULL;
+       }
+
+       if (!conn->keep_alive)
+               return http_close(conn);
+
+       LIST_REMOVE(conn, entry);
+       LIST_INSERT_HEAD(&idle, conn, entry);
+
+       /* reset status and keep-alive for good measures */
+       conn->status = 0;
+       conn->keep_alive = 0;
+
+       return WANT_POLLIN;
+}
+
+/*
+ * Called in case of error, moves connection into free state.
+ * This will skip proper shutdown of the TLS session.
+ * If a request is pending fail and free the request.
+ */
+static enum res
+http_failed(struct http_connection *conn)
+{
+       conn->state = STATE_FREE;
+
+       if (conn->req) {
+               http_req_fail(conn->req->id);
+               http_req_free(conn->req);
+               conn->req = NULL;
+       }
+
+       return DONE;
+}
+
+/*
+ * Call the function f and update the connection events based
+ * on the return value.
+ */
+static void
+http_do(struct http_connection *conn, enum res (*f)(struct http_connection *))
+{
+       switch (f(conn)) {
+       case DONE:
+               conn->events = 0;
+               break;
+       case WANT_POLLIN:
+               conn->events = POLLIN;
+               break;
+       case WANT_POLLOUT:
+               conn->events = POLLOUT;
+               break;
+       default:
+               errx(1, "%s: unexpected function return",
+                   http_info(conn->host));
+       }
+}
+
+/*
+ * Connection successfully establish, initiate TLS handshake.
+ */
+static enum res
 http_connect_done(struct http_connection *conn)
 {
        freeaddrinfo(conn->res0);
@@ -394,21 +651,20 @@ http_connect_done(struct http_connection
                proxy_connect(conn->fd, sslhost, proxy_credentials); */
 #endif
 
-       return 0;
+       return http_tls_connect(conn);
 }
 
-static int
+/*
+ * Start an asynchronous connect.
+ */
+static enum res
 http_connect(struct http_connection *conn)
 {
        const char *cause = NULL;
 
+       assert(conn->fd == -1); 
        conn->state = STATE_CONNECT;
 
-       if (conn->fd != -1) {
-               close(conn->fd);
-               conn->fd = -1;
-       }
-
        /* start the loop below with first or next address */
        if (conn->res == NULL)
                conn->res = conn->res0;
@@ -457,17 +713,17 @@ http_connect(struct http_connection *con
 
        if (conn->fd == -1) {
                if (cause != NULL)
-                       warn("%s: %s", http_info(conn->url), cause);
-               freeaddrinfo(conn->res0);
-               conn->res0 = NULL;
-               conn->res = NULL;
-               return -1;
+                       warn("%s: %s", http_info(conn->req->uri), cause);
+               return http_failed(conn);
        }
 
        return http_connect_done(conn);
 }
 
-static int
+/*
+ * Called once an asynchronus connect request finished.
+ */
+static enum res
 http_finish_connect(struct http_connection *conn)
 {
        int error = 0;
@@ -475,61 +731,84 @@ http_finish_connect(struct http_connecti
 
        len = sizeof(error);
        if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) {
-               warn("%s: getsockopt SO_ERROR", http_info(conn->url));
-               /* connection will be closed by http_connect() */
-               return -1;
+               warn("%s: getsockopt SO_ERROR", http_info(conn->req->uri));
+               goto fail;
        }
        if (error != 0) {
                errno = error;
-               warn("%s: connect", http_info(conn->url));
-               return -1;
+               warn("%s: connect", http_info(conn->req->uri));
+               goto fail;
        }
 
        return http_connect_done(conn);
+
+fail:
+       close(conn->fd);
+       conn->fd = -1;
+
+       return http_connect(conn);
 }
 
-static int
+/*
+ * Initiate TLS session on a new connection.
+ */
+static enum res
 http_tls_connect(struct http_connection *conn)
 {
+       assert(conn->state == STATE_CONNECT);
+       conn->state = STATE_TLSCONNECT;
+
        if ((conn->tls = tls_client()) == NULL) {
                warn("tls_client");
-               return -1;
+               return http_failed(conn);
        }
        if (tls_configure(conn->tls, tls_config) == -1) {
-               warnx("%s: TLS configuration: %s\n", http_info(conn->url),
+               warnx("%s: TLS configuration: %s\n", http_info(conn->req->uri),
                    tls_error(conn->tls));
-               return -1;
+               return http_failed(conn);
        }
        if (tls_connect_socket(conn->tls, conn->fd, conn->host) == -1) {
-               warnx("%s: TLS connect: %s\n", http_info(conn->url),
+               warnx("%s: TLS connect: %s\n", http_info(conn->req->uri),
                    tls_error(conn->tls));
-               return -1;
+               return http_failed(conn);
        }
+
        return http_tls_handshake(conn);
 }
 
-static int
+/*
+ * Do the tls_handshake and then send out the HTTP request.
+ */
+static enum res
 http_tls_handshake(struct http_connection *conn)
 {
        switch (tls_handshake(conn->tls)) {
-       case 0:
-               return 0;
+       case -1:
+               warnx("%s: TLS handshake: %s", http_info(conn->req->uri),
+                   tls_error(conn->tls));
+               return http_failed(conn);
        case TLS_WANT_POLLIN:
                return WANT_POLLIN;
        case TLS_WANT_POLLOUT:
                return WANT_POLLOUT;
        }
-       warnx("%s: TLS handshake: %s", http_info(conn->url),
-           tls_error(conn->tls));
-       return -1;
+
+       /* ready to send request */
+       return http_request(conn);
 }
 
-static int
+/*
+ * Build the HTTP request and send it out.
+ */
+static enum res
 http_request(struct http_connection *conn)
 {
        char *host, *epath, *modified_since;
        int r, with_port = 0;
 
+       assert(conn->state == STATE_IDLE || conn->state == STATE_TLSCONNECT);
+       conn->state = STATE_REQUEST;
+
        /* TODO adjust request for HTTP proxy setups */
 
        /*
@@ -537,7 +816,7 @@ http_request(struct http_connection *con
         * the default. Some broken HTTP servers get confused if you explicitly
         * send them the port number.
         */
-       if (conn->port && strcmp(conn->port, "443") != 0)
+       if (strcmp(conn->port, "443") != 0)
                with_port = 1;
 
        /* Construct the Host header from host and port info */
@@ -555,12 +834,12 @@ http_request(struct http_connection *con
        /*
         * Construct and send the request. Proxy requests don't want leading /.
         */
-       epath = url_encode(conn->path);
+       epath = url_encode(conn->req->path);
 
        modified_since = NULL;
-       if (conn->modified_since) {
+       if (conn->req->modified_since != NULL) {
                if (asprintf(&modified_since, "If-Modified-Since: %s\r\n",
-                   conn->modified_since) == -1)
+                   conn->req->modified_since) == -1)
                        err(1, NULL);
        }
 
@@ -568,7 +847,7 @@ http_request(struct http_connection *con
        conn->bufpos = 0;
        if ((r = asprintf(&conn->buf,
            "GET /%s HTTP/1.1\r\n"
-           "Connection: close\r\n"
+           "Connection: keep-alive\r\n"
            "User-Agent: " HTTP_USER_AGENT "\r\n"
            "Host: %s\r\n%s\r\n",
            epath, host,
@@ -580,9 +859,15 @@ http_request(struct http_connection *con
        free(host);
        free(modified_since);
 
-       return WANT_POLLOUT;
+       return http_write(conn);
 }
 
+/*
+ * Parse the HTTP status line.
+ * Return 0 for status codes 200, 301-304, 307-308.
+ * Failure codes and other errors return -1.
+ * The redirect loop limit is enforced here.
+ */
 static int
 http_parse_status(struct http_connection *conn, char *buf)
 {
@@ -593,7 +878,7 @@ http_parse_status(struct http_connection
 
        cp = strchr(buf, ' ');
        if (cp == NULL) {
-               warnx("Improper response from %s", http_info(conn->url));
+               warnx("Improper response from %s", http_info(conn->host));
                return -1;
        } else
                cp++;
@@ -602,7 +887,8 @@ http_parse_status(struct http_connection
        status = strtonum(ststr, 200, 599, &errstr);
        if (errstr != NULL) {
                strnvis(gerror, cp, sizeof gerror, VIS_SAFE);
-               warnx("Error retrieving %s: %s", http_info(conn->url), gerror);
+               warnx("Error retrieving %s: %s", http_info(conn->host),
+                   gerror);
                return -1;
        }
 
@@ -612,9 +898,9 @@ http_parse_status(struct http_connection
        case 303:
        case 307:
        case 308:
-               if (conn->redirect_loop++ > 10) {
+               if (conn->req->redirect_loop++ > 10) {
                        warnx("%s: Too many redirections requested",
-                           http_info(conn->url));
+                           http_info(conn->host));
                        return -1;
                }
                /* FALLTHROUGH */
@@ -624,13 +910,17 @@ http_parse_status(struct http_connection
                break;
        default:
                strnvis(gerror, cp, sizeof gerror, VIS_SAFE);
-               warnx("Error retrieving %s: %s", http_info(conn->url), gerror);
-               break;
+               warnx("Error retrieving %s: %s", http_info(conn->host),
+                   gerror);
+               return -1;
        }
 
        return 0;
 }
 
+/*
+ * Returns true if the connection status is any of the redirect codes.
+ */
 static inline int
 http_isredirect(struct http_connection *conn)
 {
@@ -640,44 +930,29 @@ http_isredirect(struct http_connection *
        return 0;
 }
 
-static int
-http_redirect(struct http_connection *conn, char *uri)
+static void
+http_redirect(struct http_connection *conn)
 {
-       char *host, *port, *path;
+       char *uri, *mod_since = NULL;
+       int outfd;
 
-       logx("redirect to %s", http_info(uri));
+       /* move uri and fd out for new request */
+       outfd = conn->req->outfd;
+       conn->req->outfd = -1;
 
-       if (http_parse_uri(uri, &host, &port, &path) == -1) {
-               free(uri);
-               return -1;
-       }
+       uri = conn->redir_uri;
+       conn->redir_uri = NULL;
 
-       free(conn->url);
-       conn->url = uri;
-       free(conn->host);
-       conn->host = host;
-       free(conn->port);
-       conn->port = port;
-       conn->path = path;
-       /* keep modified_since since that is part of the request */
-       free(conn->last_modified);
-       conn->last_modified = NULL;
-       free(conn->buf);
-       conn->buf = NULL;
-       conn->bufpos = 0;
-       conn->bufsz = 0;
-       tls_close(conn->tls);
-       tls_free(conn->tls);
-       conn->tls = NULL;
-       close(conn->fd);
-       conn->state = STATE_INIT;
-
-       /* TODO proxy support (overload of host and port) */
+       if (conn->req->modified_since)
+               if ((mod_since = strdup(conn->req->modified_since)) == NULL)
+                       err(1, NULL);
 
-       if (http_resolv(conn, host, port) == -1)
-               return -1;
+       logx("redirect to %s", http_info(uri));
+       http_req_new(conn->req->id, uri, mod_since, outfd);     
 
-       return -2;
+       /* clear request before moving connection to idle */
+       http_req_free(conn->req);
+       conn->req = NULL;
 }
 
 static int
@@ -685,6 +960,7 @@ http_parse_header(struct http_connection
 {
 #define CONTENTLEN "Content-Length: "
 #define LOCATION "Location: "
+#define CONNECTION "Connection: "
 #define TRANSFER_ENCODING "Transfer-Encoding: "
 #define LAST_MODIFIED "Last-Modified: "
        const char *errstr;
@@ -703,7 +979,7 @@ http_parse_header(struct http_connection
                conn->iosz = strtonum(cp, 0, LLONG_MAX, &errstr);
                if (errstr != NULL) {
                        warnx("Content-Length of %s is %s",
-                           http_info(conn->url), errstr);
+                           http_info(conn->req->uri), errstr);
                        return -1;
                }
        } else if (http_isredirect(conn) &&
@@ -719,7 +995,7 @@ http_parse_header(struct http_connection
                                locbase = NULL;
                                cp++;
                        } else {
-                               locbase = strdup(conn->path);
+                               locbase = strdup(conn->req->path);
                                if (locbase == NULL)
                                        err(1, NULL);
                                loctail = strchr(locbase, '#');
@@ -737,9 +1013,8 @@ http_parse_header(struct http_connection
                        }
                        /* Construct URL from relative redirect */
                        if (asprintf(&redirurl, "%.*s/%s%s",
-                           (int)(conn->path - conn->url), conn->url,
-                           locbase ? locbase : "",
-                           cp) == -1)
+                           (int)(conn->req->path - conn->req->uri),
+                           conn->req->uri, locbase ? locbase : "", cp) == -1)
                                err(1, "Cannot build redirect URL");
                        free(locbase);
                } else if ((redirurl = strdup(cp)) == NULL)
@@ -747,13 +1022,18 @@ http_parse_header(struct http_connection
                loctail = strchr(redirurl, '#');
                if (loctail != NULL)
                        *loctail = '\0';
-               return http_redirect(conn, redirurl);
+               conn->redir_uri = redirurl;
        } else if (strncasecmp(cp, TRANSFER_ENCODING,
            sizeof(TRANSFER_ENCODING) - 1) == 0) {
                cp += sizeof(TRANSFER_ENCODING) - 1;
                cp[strcspn(cp, " \t")] = '\0';
                if (strcasecmp(cp, "chunked") == 0)
                        conn->chunked = 1;
+       } else if (strncasecmp(cp, CONNECTION, sizeof(CONNECTION) - 1) == 0) {
+               cp += sizeof(CONNECTION) - 1;
+               cp[strcspn(cp, " \t")] = '\0';
+               if (strcasecmp(cp, "keep-alive") == 0)
+                       conn->keep_alive = 1;
        } else if (strncasecmp(cp, LAST_MODIFIED,
            sizeof(LAST_MODIFIED) - 1) == 0) {
                cp += sizeof(LAST_MODIFIED) - 1;
@@ -764,6 +1044,12 @@ http_parse_header(struct http_connection
        return 1;
 }
 
+/*
+ * Return one line from the HTTP response.
+ * The line returned has any possible '\r' and '\n' at the end stripped.
+ * The buffer is advanced to the start of the next line.
+ * If there is currently no full line in the buffer NULL is returned.
+ */ 
 static char *
 http_get_line(struct http_connection *conn)
 {
@@ -788,6 +1074,12 @@ http_get_line(struct http_connection *co
        return line;
 }
 
+/*
+ * Parse the header between data chunks during chunked transfers.
+ * Returns 0 if a new chunk size could be correctly read.
+ * Returns 1 for the empty trailer lines.
+ * If the chuck size could not be converted properly -1 is returned.
+ */
 static int
 http_parse_chunked(struct http_connection *conn, char *buf)
 {
@@ -795,7 +1087,7 @@ http_parse_chunked(struct http_connectio
        char *end;
        unsigned long chunksize;
 
-       /* ignore empty lines, used between chunk and next header */
+       /* empty lines are used as trailer */
        if (*header == '\0')
                return 1;
 
@@ -804,22 +1096,14 @@ http_parse_chunked(struct http_connectio
        errno = 0;
        chunksize = strtoul(header, &end, 16);
        if (header[0] == '\0' || *end != '\0' || (errno == ERANGE &&
-           chunksize == ULONG_MAX) || chunksize > INT_MAX) {
-               warnx("%s: Invalid chunk size", http_info(conn->url));
+           chunksize == ULONG_MAX) || chunksize > INT_MAX)
                return -1;
-       }
-       conn->iosz = chunksize;
-
-       if (conn->iosz == 0) {
-               http_done(conn->id, HTTP_OK, conn->last_modified);
-               conn->state = STATE_DONE;
-               return 0;
-       }
 
-       return 1;
+       conn->iosz = chunksize;
+       return 0;
 }
 
-static int
+static enum res
 http_read(struct http_connection *conn)
 {
        ssize_t s;
@@ -830,9 +1114,9 @@ read_more:
        s = tls_read(conn->tls, conn->buf + conn->bufpos,
            conn->bufsz - conn->bufpos);
        if (s == -1) {
-               warn("%s: TLS read: %s", http_info(conn->url),
+               warn("%s: TLS read: %s", http_info(conn->host),
                    tls_error(conn->tls));
-               return -1;
+               return http_failed(conn);
        } else if (s == TLS_WANT_POLLIN) {
                return WANT_POLLIN;
        } else if (s == TLS_WANT_POLLOUT) {
@@ -840,9 +1124,10 @@ read_more:
        }
 
        if (s == 0 && conn->bufpos == 0) {
-               warnx("%s: short read, connection closed",
-                   http_info(conn->url));
-               return -1;
+               if (conn->req)
+                       warnx("%s: short read, connection closed",
+                           http_info(conn->req->uri));
+               return http_failed(conn);
        }
 
        conn->bufpos += s;
@@ -855,7 +1140,7 @@ again:
                        goto read_more;
                if (http_parse_status(conn, buf) == -1) {
                        free(buf);
-                       return -1;
+                       return http_failed(conn);
                }
                free(buf);
                conn->state = STATE_RESPONSE_HEADER;
@@ -871,86 +1156,149 @@ again:
 
                        rv = http_parse_header(conn, buf);
                        free(buf);
+
                        if (rv == -1)
-                               return -1;
-                       if (rv == -2)   /* redirect */
-                               return 0;
-                       if (rv == 0)
+                               return http_failed(conn);
+                       if (rv ==  0)
                                done = 1;
                }
 
                /* Check status header and decide what to do next */
-               if (conn->status == 200) {
+               if (conn->status == 200 || http_isredirect(conn)) {
+                       if (http_isredirect(conn))
+                               http_redirect(conn);
+
                        if (conn->chunked)
-                               conn->state = STATE_RESPONSE_CHUNKED;
+                               conn->state = STATE_RESPONSE_CHUNKED_HEADER;
                        else
                                conn->state = STATE_RESPONSE_DATA;
                        goto again;
                } else if (conn->status == 304) {
-                       http_done(conn->id, HTTP_NOT_MOD, conn->last_modified);
-               } else {
-                       http_done(conn->id, HTTP_FAILED, conn->last_modified);
+                       return http_done(conn, HTTP_NOT_MOD);
                }
-
-               conn->state = STATE_DONE;
-               return 0;
+               
+               return http_failed(conn);
        case STATE_RESPONSE_DATA:
-               if (conn->bufpos == conn->bufsz ||
-                   conn->iosz <= (off_t)conn->bufpos)
-                       return 0;
-               goto read_more;
-       case STATE_RESPONSE_CHUNKED:
-               while (conn->iosz == 0) {
-                       buf = http_get_line(conn);
-                       if (buf == NULL)
-                               goto read_more;
-                       switch (http_parse_chunked(conn, buf)) {
-                       case -1:
-                               free(buf);
-                               return -1;
-                       case 0:
-                               free(buf);
-                               return 0;
+               if (conn->bufpos != conn->bufsz &&
+                   conn->iosz > (off_t)conn->bufpos)
+                       goto read_more;
+
+               /* got a full buffer full of data */
+               if (conn->req == NULL) {
+                       /*
+                        * After redirects all data needs to be discarded.
+                        */
+                       if (conn->iosz < (off_t)conn->bufpos) {
+                               conn->bufpos  -= conn->iosz;
+                               conn->iosz = 0;
+                       } else {
+                               conn->iosz  -= conn->bufpos;
+                               conn->bufpos = 0;
                        }
+                       if (conn->chunked)
+                               conn->state = STATE_RESPONSE_CHUNKED_TRAILER;
+                       else
+                               conn->state = STATE_RESPONSE_DATA;
+                       goto read_more;
+               }
+
+               conn->state = STATE_WRITE_DATA;
+               return WANT_POLLOUT;
+       case STATE_RESPONSE_CHUNKED_HEADER:
+               assert(conn->iosz == 0);
+
+               buf = http_get_line(conn);
+               if (buf == NULL)
+                       goto read_more;
+               if (http_parse_chunked(conn, buf) != 0) {
+                       warnx("%s: bad chunk encoding", http_info(conn->host));
+                       free(buf);
+                       return http_failed(conn);
+               }
+
+               /*
+                * check if transfer is done, in which case the last trailer
+                * still needs to be processed.
+                */
+               if (conn->iosz == 0) {
+                       conn->chunked = 0;
+                       conn->state = STATE_RESPONSE_CHUNKED_TRAILER;
+                       goto again;
+               }
+
+               conn->state = STATE_RESPONSE_DATA;
+               goto again;
+       case STATE_RESPONSE_CHUNKED_TRAILER:
+               buf = http_get_line(conn);
+               if (buf == NULL)
+                       goto read_more;
+               if (http_parse_chunked(conn, buf) != 1) {
+                       warnx("%s: bad chunk encoding", http_info(conn->host));
                        free(buf);
+                       return http_failed(conn);
                }
+               free(buf);
 
-               if (conn->bufpos == conn->bufsz ||
-                   conn->iosz <= (off_t)conn->bufpos)
-                       return 0;
-               goto read_more;
+               /* if chunked got cleared then the transfer is over */
+               if (conn->chunked == 0)
+                       return http_done(conn, HTTP_OK);
+
+               conn->state = STATE_RESPONSE_CHUNKED_HEADER;
+               goto again;
        default:
                errx(1, "unexpected http state");
        }
 }
 
-static int
+/*
+ * Send out the HTTP request. When done, replace buffer with the read buffer.
+ */
+static enum res
 http_write(struct http_connection *conn)
 {
        ssize_t s;
 
-       s = tls_write(conn->tls, conn->buf + conn->bufpos,
-           conn->bufsz - conn->bufpos);
-       if (s == -1) {
-               warnx("%s: TLS write: %s", http_info(conn->url),
-                   tls_error(conn->tls));
-               return -1;
-       } else if (s == TLS_WANT_POLLIN) {
-               return WANT_POLLIN;
-       } else if (s == TLS_WANT_POLLOUT) {
-               return WANT_POLLOUT;
+       assert(conn->state == STATE_REQUEST);
+
+       while (conn->bufpos < conn->bufsz) {
+               s = tls_write(conn->tls, conn->buf + conn->bufpos,
+                   conn->bufsz - conn->bufpos);
+               if (s == -1) {
+                       warnx("%s: TLS write: %s", http_info(conn->host),
+                           tls_error(conn->tls));
+                       return http_failed(conn);
+               } else if (s == TLS_WANT_POLLIN) {
+                       return WANT_POLLIN;
+               } else if (s == TLS_WANT_POLLOUT) {
+                       return WANT_POLLOUT;
+               }
+
+               conn->bufpos += s;
        }
 
-       conn->bufpos += s;
-       if (conn->bufpos == conn->bufsz)
-               return 0;
+       /* done writing, first thing we need the status */
+       conn->state = STATE_RESPONSE_STATUS;
 
-       return WANT_POLLOUT;
+       /* free write buffer and allocate the read buffer */
+       free(conn->buf);
+       conn->bufpos = 0;
+       conn->bufsz = HTTP_BUF_SIZE;
+       if ((conn->buf = malloc(conn->bufsz)) == NULL)
+               err(1, NULL);
+
+       return http_read(conn);
 }
 
-static int
+/*
+ * Properly shutdown the TLS session else move connection into free state.
+ */
+static enum res
 http_close(struct http_connection *conn)
 {
+       assert(conn->state == STATE_IDLE || conn->state == STATE_CLOSE);
+
+       conn->state = STATE_CLOSE;
+
        if (conn->tls != NULL) {
                switch (tls_close(conn->tls)) {
                case TLS_WANT_POLLIN:
@@ -963,22 +1311,30 @@ http_close(struct http_connection *conn)
                }
        }
 
-       return -1;
+       conn->state = STATE_FREE;
+       return DONE;
 }
 
-static int
+/*
+ * Write data into provided file descriptor. If all data got written
+ * the connection may change into idle state.
+ */
+static enum res
 data_write(struct http_connection *conn)
 {
        ssize_t s;
        size_t bsz = conn->bufpos;
 
+       assert(conn->state == STATE_WRITE_DATA);
+
        if (conn->iosz < (off_t)bsz)
                bsz = conn->iosz;
 
-       s = write(conn->outfd, conn->buf, bsz);
+       s = write(conn->req->outfd, conn->buf, bsz);
+
        if (s == -1) {
-               warn("%s: data write", http_info(conn->url));
-               return -1;
+               warn("%s: data write", http_info(conn->req->uri));
+               return http_failed(conn);
        }
 
        conn->bufpos -= s;
@@ -986,16 +1342,13 @@ data_write(struct http_connection *conn)
        memmove(conn->buf, conn->buf + s, conn->bufpos);
 
        /* check if regular file transfer is finished */
-       if (!conn->chunked && conn->iosz == 0) {
-               http_done(conn->id, HTTP_OK, conn->last_modified);
-               conn->state = STATE_DONE;
-               return 0;
-       }
+       if (!conn->chunked && conn->iosz == 0)
+               return http_done(conn, HTTP_OK);
 
        /* all data written, switch back to read */
        if (conn->bufpos == 0 || conn->iosz == 0) {
                if (conn->chunked)
-                       conn->state = STATE_RESPONSE_CHUNKED;
+                       conn->state = STATE_RESPONSE_CHUNKED_TRAILER;
                else
                        conn->state = STATE_RESPONSE_DATA;
                return http_read(conn);
@@ -1011,17 +1364,14 @@ data_write(struct http_connection *conn)
  * If 0 is returned this stage is finished and the protocol should move
  * to the next stage by calling http_nextstep(). On error return -1.
  */
-static int
-http_handle(struct http_connection *conn, int events)
+static enum res
+http_handle(struct http_connection *conn)
 {
+       assert (conn->pfd != NULL && conn->pfd->revents != 0);
+
        switch (conn->state) {
-       case STATE_INIT:
-               return http_connect(conn);
        case STATE_CONNECT:
-               if (http_finish_connect(conn) == -1)
-                       /* something went wrong, try other host */
-                       return http_connect(conn);
-               return 0;
+               return http_finish_connect(conn);
        case STATE_TLSCONNECT:
                return http_tls_handshake(conn);
        case STATE_REQUEST:
@@ -1029,12 +1379,16 @@ http_handle(struct http_connection *conn
        case STATE_RESPONSE_STATUS:
        case STATE_RESPONSE_HEADER:
        case STATE_RESPONSE_DATA:
-       case STATE_RESPONSE_CHUNKED:
+       case STATE_RESPONSE_CHUNKED_HEADER:
+       case STATE_RESPONSE_CHUNKED_TRAILER:
                return http_read(conn);
        case STATE_WRITE_DATA:
                return data_write(conn);
-       case STATE_DONE:
+       case STATE_CLOSE:
                return http_close(conn);
+       case STATE_IDLE:
+               conn->state = STATE_RESPONSE_HEADER;
+               return http_read(conn);
        case STATE_FREE:
                errx(1, "bad http state");
        }
@@ -1042,94 +1396,15 @@ http_handle(struct http_connection *conn
 }
 
 /*
- * Move the state machine forward until IO needs to happen.
- * Returns either WANT_POLLIN or WANT_POLLOUT or -1 on error.
+ * Initialisation done before pledge() call to load certificates.
  */
-static int
-http_nextstep(struct http_connection *conn)
-{
-       int r;
-
-       switch (conn->state) {
-       case STATE_INIT:
-               return http_connect(conn);
-       case STATE_CONNECT:
-               conn->state = STATE_TLSCONNECT;
-               r = http_tls_connect(conn);
-               if (r != 0)
-                       return r;
-               /* FALLTHROUGH */
-       case STATE_TLSCONNECT:
-               conn->state = STATE_REQUEST;
-               return http_request(conn);
-       case STATE_REQUEST:
-               conn->state = STATE_RESPONSE_STATUS;
-               free(conn->buf);
-               /* allocate the read buffer */
-               if ((conn->buf = malloc(HTTP_BUF_SIZE)) == NULL)
-                       err(1, NULL);
-               conn->bufpos = 0;
-               conn->bufsz = HTTP_BUF_SIZE;
-               return http_read(conn);
-       case STATE_RESPONSE_DATA:
-       case STATE_RESPONSE_CHUNKED:
-               conn->state = STATE_WRITE_DATA;
-               return WANT_POLLOUT;
-       case STATE_DONE:
-               return http_close(conn);
-       case STATE_RESPONSE_STATUS:
-       case STATE_RESPONSE_HEADER:
-       case STATE_WRITE_DATA:
-       case STATE_FREE:
-               errx(1, "bad http state");
-       }
-       errx(1, "unknown http state");
-}
-
-static int
-http_do(struct http_connection *conn, int events)
-{
-       switch (http_handle(conn, events)) {
-       case -1:
-               /* connection failure */
-               if (conn->state != STATE_DONE)
-                       http_fail(conn->id);
-               http_free(conn);
-               return -1;
-       case 0:
-               switch (http_nextstep(conn)) {
-               case WANT_POLLIN:
-                       conn->events = POLLIN;
-                       break;
-               case WANT_POLLOUT:
-                       conn->events = POLLOUT;
-                       break;
-               case -1:
-                       if (conn->state != STATE_DONE)
-                               http_fail(conn->id);
-                       http_free(conn);
-                       return -1;
-               case 0:
-                       errx(1, "%s: http_nextstep returned 0, state %d",
-                           http_info(conn->url), conn->state);
-               }
-               break;
-       case WANT_POLLIN:
-               conn->events = POLLIN;
-               break;
-       case WANT_POLLOUT:
-               conn->events = POLLOUT;
-               break;
-       }
-       return 0;
-}
-
 static void
 http_setup(void)
 {
        tls_config = tls_config_new();
        if (tls_config == NULL)
                errx(1, "tls config failed");
+
 #if 0
        /* TODO Should we allow extra protos and ciphers? */
        if (tls_config_set_protocols(tls_config, TLS_PROTOCOLS_ALL) == -1)
@@ -1148,14 +1423,14 @@ http_setup(void)
        tls_config_set_ca_mem(tls_config, tls_ca_mem, tls_ca_size);
 
        /* TODO initalize proxy settings */
-
 }
 
 void
 proc_http(char *bind_addr, int fd)
 {
-       struct http_connection *http_conns[MAX_CONNECTIONS];
-       struct pollfd pfds[MAX_CONNECTIONS + 1];
+       struct pollfd pfds[NPFDS];
+       struct http_connection *conn, *nc;
+       struct http_request *req, *nr;
 
        if (bind_addr != NULL) {
                struct addrinfo hints, *res;
@@ -1174,44 +1449,59 @@ proc_http(char *bind_addr, int fd)
        if (pledge("stdio inet dns recvfd", NULL) == -1)
                err(1, "pledge");
 
-       memset(&http_conns, 0, sizeof(http_conns));
        memset(&pfds, 0, sizeof(pfds));
-       pfds[MAX_CONNECTIONS].fd = fd;
 
        msgbuf_init(&msgq);
        msgq.fd = fd;
 
        for (;;) {
-               int active_connections = 0;
+               time_t now;
+               int timeout;
                size_t i;
 
-               for (i = 0; i < MAX_CONNECTIONS; i++) {
-                       struct http_connection *conn = http_conns[i];
+               pfds[0].fd = fd;
+               pfds[0].events = POLLIN;
+               if (msgq.queued)
+                       pfds[0].events |= POLLOUT;
 
-                       if (conn == NULL) {
-                               pfds[i].fd = -1;
-                               continue;
-                       }
+               i = 1;
+               timeout = INFTIM;
+               now = getmonotime();
+               LIST_FOREACH(conn, &active, entry) {
                        if (conn->state == STATE_WRITE_DATA)
-                               pfds[i].fd = conn->outfd;
+                               pfds[i].fd = conn->req->outfd;
                        else
                                pfds[i].fd = conn->fd;
 
                        pfds[i].events = conn->events;
-                       active_connections++;
+                       conn->pfd = &pfds[i];
+                       i++;
+                       if (i > NPFDS)
+                               errx(1, "too many connections");
+               }
+               LIST_FOREACH(conn, &idle, entry) {
+                       if (conn->idle_time <= now)
+                               timeout = 0;
+                       else {
+                               int diff = conn->idle_time - now; 
+                               diff *= 1000;
+                               if (timeout == INFTIM || diff < timeout)
+                                       timeout = diff;
+                       }
+                       pfds[i].fd = conn->fd;
+                       pfds[i].events = POLLIN;
+                       conn->pfd = &pfds[i];
+                       i++;
+                       if (i > NPFDS)
+                               errx(1, "too many connections");
                }
-               pfds[MAX_CONNECTIONS].events = 0;
-               if (active_connections < MAX_CONNECTIONS)
-                       pfds[MAX_CONNECTIONS].events |= POLLIN;
-               if (msgq.queued)
-                       pfds[MAX_CONNECTIONS].events |= POLLOUT;
 
-               if (poll(pfds, sizeof(pfds) / sizeof(pfds[0]), INFTIM) == -1)
+               if (poll(pfds, i, timeout) == -1)
                        err(1, "poll");
 
-               if (pfds[MAX_CONNECTIONS].revents & POLLHUP)
+               if (pfds[0].revents & POLLHUP)
                        break;
-               if (pfds[MAX_CONNECTIONS].revents & POLLOUT) {
+               if (pfds[0].revents & POLLOUT) {
                        switch (msgbuf_write(&msgq)) {
                        case 0:
                                errx(1, "write: connection closed");
@@ -1219,24 +1509,7 @@ proc_http(char *bind_addr, int fd)
                                err(1, "write");
                        }
                }
-
-               /* process active http requests */
-               for (i = 0; i < MAX_CONNECTIONS; i++) {
-                       struct http_connection *conn = http_conns[i];
-
-                       if (conn == NULL)
-                               continue;
-                       /* event not ready */
-                       if (pfds[i].revents == 0)
-                               continue;
-
-                       if (http_do(conn, pfds[i].revents) == -1)
-                               http_conns[i] = NULL;
-               }
-
-               /* process new requests last */
-               if (pfds[MAX_CONNECTIONS].revents & POLLIN) {
-                       struct http_connection *h;
+               if (pfds[0].revents & POLLIN) {
                        size_t id;
                        int outfd;
                        char *uri;
@@ -1246,18 +1519,36 @@ proc_http(char *bind_addr, int fd)
                        io_str_read(fd, &uri);
                        io_str_read(fd, &mod);
 
-                       h = http_new(id, uri, mod, outfd);
-                       if (h != NULL) {
-                               for (i = 0; i < MAX_CONNECTIONS; i++) {
-                                       if (http_conns[i] != NULL)
-                                               continue;
-                                       http_conns[i] = h;
-                                       if (http_do(h, 0) == -1)
-                                               http_conns[i] = NULL;
-                                       break;
-                               }
-                       }
+                       /* queue up new requests */
+                       http_req_new(id, uri, mod, outfd);
+               }
+
+               now = getmonotime();
+               /* process idle connections */
+               LIST_FOREACH_SAFE(conn, &idle, entry, nc) {
+                       if (conn->pfd != NULL && conn->pfd->revents != 0)
+                               http_do(conn, http_handle);
+                       else if (conn->idle_time <= now)
+                               http_do(conn, http_close);
+
+                       if (conn->state == STATE_FREE)
+                               http_free(conn);
                }
+
+               /* then active http requests */
+               LIST_FOREACH_SAFE(conn, &active, entry, nc) {
+                       /* check if event is ready */
+                       if (conn->pfd != NULL && conn->pfd->revents != 0)
+                               http_do(conn, http_handle);
+
+                       if (conn->state == STATE_FREE)
+                               http_free(conn);
+               }
+
+
+               TAILQ_FOREACH_SAFE(req, &queue, entry, nr)
+                       if (!http_req_schedule(req))
+                               break;
        }
 
        exit(0);

Reply via email to