diff --git a/qse/lib/net/httpd.c b/qse/lib/net/httpd.c index b69ffefe..562367ff 100644 --- a/qse/lib/net/httpd.c +++ b/qse/lib/net/httpd.c @@ -25,6 +25,7 @@ #include "httpd.h" #include "../cmn/mem.h" +#include "../cmn/syscall.h" #include #include #include @@ -627,67 +628,93 @@ httpd->cbs.on_error (httpd, l).... */ static int make_fd_set_from_client_array ( qse_httpd_t* httpd, fd_set* r, fd_set* w, int for_rdwr) { + /* qse_http_loop() sets for_rdwr to true. + * response_thread() sets for_rdwr to false. + * + * qse_http_loop() + * - accepts a new client connection + * - reads a client request + * - writes back a response to a client request if not threaded. + * + * response_thread() + * - writes back a response to a client request if threaded. + */ + int fd, max = -1; client_array_t* ca = &httpd->client.array; - if (r && for_rdwr) + if (for_rdwr) { + /* qse_http_loop() needs to monitor listner handles + * to handle a new client connection. */ max = httpd->listener.max; *r = httpd->listener.set; } - if (w) + else { - FD_ZERO (w); + FD_ZERO (r); + +#if defined(HAVE_PTHREAD) + /* select() in response_thread() needs to be aborted + * if it's blocking on a fd_set previously composed + * when a new task is enqueued. it can select() on new + * fd_set quickly. + */ + QSE_ASSERT (httpd->threaded); + FD_SET (httpd->client.pfd[0], r); + max = httpd->client.pfd[0]; +#endif } + FD_ZERO (w); for (fd = 0; fd < ca->capa; fd++) { if (ca->data[fd].htrd) { - if (r && !ca->data[fd].bad) + if (!ca->data[fd].bad) { if (for_rdwr) { - /* add a client-side handle to the read set */ + /* add a client-side handle to the read set + * only for qse_httpd_loop(). */ FD_SET (ca->data[fd].handle.i, r); if (ca->data[fd].handle.i > max) max = ca->data[fd].handle.i; } - if (ca->data[fd].task.queue.head && - ca->data[fd].task.queue.head->task.trigger.i >= 0) + if (!httpd->threaded || !for_rdwr) { - /* if a trigger is available, add it to the read set also */ - FD_SET (ca->data[fd].task.queue.head->task.trigger.i, r); - if (ca->data[fd].task.queue.head->task.trigger.i > max) - max = ca->data[fd].task.queue.head->task.trigger.i; + /* a trigger is a handle to monitor to check + * if there is data avaiable to write back to the client. + * if it is not threaded, qse_httpd_loop() needs to + * monitor trigger handles. if it is threaded, + * response_thread() needs to monitor these handles */ + + if (ca->data[fd].task.queue.head && + ca->data[fd].task.queue.head->task.trigger.i >= 0) + { + /* if a trigger is available, add it to the read set also. */ + FD_SET (ca->data[fd].task.queue.head->task.trigger.i, r); + if (ca->data[fd].task.queue.head->task.trigger.i > max) + max = ca->data[fd].task.queue.head->task.trigger.i; + } } } -#if 0 - if (w && (ca->data[fd].task.queue.count > 0 || ca->data[fd].bad)) + + if (ca->data[fd].bad || + (ca->data[fd].task.queue.head && + ca->data[fd].task.queue.head->task.trigger.i <= -1)) { - /* add it to the set if it has a response to send */ + /* add a client-side handle to the write set + * if the client is already marked bad or + * the current task enqueued didn't specify a trigger. + * + * if the task doesn't have a trigger, i perform + * the task so long as the client side-handle is + * available for writing in the main loop. + */ FD_SET (ca->data[fd].handle.i, w); if (ca->data[fd].handle.i > max) max = ca->data[fd].handle.i; } -#endif - if (w) - { - if (ca->data[fd].bad || - (ca->data[fd].task.queue.head && - ca->data[fd].task.queue.head->task.trigger.i <= -1)) - { - /* add a client-side handle to the write set - * if the client is already marked bad or - * the current task enqueued didn't specify a trigger. - * - * if the task doesn't have a trigger, i perform - * the task so long as the client side-handle is - * available for writing in the main loop. - */ - FD_SET (ca->data[fd].handle.i, w); - if (ca->data[fd].handle.i > max) max = ca->data[fd].handle.i; - } - } } } @@ -727,9 +754,6 @@ static void* response_thread (void* arg) fd_set r, w; struct timeval tv; - tv.tv_sec = 1; - tv.tv_usec = 0; - pthread_mutex_lock (&httpd->client.mutex); max = make_fd_set_from_client_array (httpd, &r, &w, 0); pthread_mutex_unlock (&httpd->client.mutex); @@ -742,10 +766,11 @@ static void* response_thread (void* arg) pthread_mutex_lock (&httpd->client.mutex); gettimeofday (&now, QSE_NULL); - timeout.tv_sec = now.tv_sec + 2; + timeout.tv_sec = now.tv_sec + 1; timeout.tv_nsec = now.tv_usec * 1000; - pthread_cond_timedwait (&httpd->client.cond, &httpd->client.mutex, &timeout); + pthread_cond_timedwait ( + &httpd->client.cond, &httpd->client.mutex, &timeout); max = make_fd_set_from_client_array (httpd, &r, &w, 0); pthread_mutex_unlock (&httpd->client.mutex); @@ -753,6 +778,9 @@ static void* response_thread (void* arg) if (httpd->stopreq) break; + tv.tv_sec = 1; + tv.tv_usec = 0; + n = select (max + 1, &r, &w, QSE_NULL, &tv); if (n <= -1) { @@ -766,29 +794,18 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure - %hs\n"), strerr continue; } + if (FD_ISSET (httpd->client.pfd[0], &r)) + { + qse_mchar_t dummy; + QSE_READ (httpd->client.pfd[0], &dummy, 1); + } + for (fd = 0; fd < httpd->client.array.capa; fd++) { qse_httpd_client_t* client = &httpd->client.array.data[fd]; if (!client->htrd) continue; - /* ---------------------------------------------- */ -#if 0 - if (FD_ISSET(client->handle.i, &w)) - { - if (client->bad) - { - } - else if (client->task.queue.count > 0) - { - if (client->task.queue.head->task.trigger.i <= -1 || - FD_ISSET(client->task.queue.head->task.trigger.i, &r)) - { - perform_task (httpd, client); - } - } - } -#endif if (client->bad) { /*shutdown (client->handle.i, SHUT_RDWR);*/ @@ -796,7 +813,7 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure - %hs\n"), strerr delete_from_client_array (httpd, fd); pthread_mutex_unlock (&httpd->client.mutex); } - else + else if (client->task.queue.head) { if (client->task.queue.head->task.trigger.i <= -1 || FD_ISSET(client->task.queue.head->task.trigger.i, &r)) @@ -805,12 +822,13 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure - %hs\n"), strerr tv.tv_usec = 0; FD_ZERO (&w); FD_SET (client->handle.i, &w); - n = select (max + 1, QSE_NULL, &w, QSE_NULL, &tv); + n = select (client->handle.i + 1, QSE_NULL, &w, QSE_NULL, &tv); if (n > 0 && FD_ISSET(client->handle.i, &w)) + { perform_task (httpd, client); + } } } - /* ---------------------------------------------- */ } } @@ -903,17 +921,30 @@ int qse_httpd_loop (qse_httpd_t* httpd, int threaded) /* start the response sender as a thread */ if (threaded) { - pthread_mutex_init (&httpd->client.mutex, QSE_NULL); - pthread_cond_init (&httpd->client.cond, QSE_NULL); - - if (pthread_create ( - &response_thread_id, QSE_NULL, - response_thread, httpd) != 0) + if (QSE_PIPE(httpd->client.pfd) == 0) { - pthread_cond_destroy (&httpd->client.cond); - pthread_mutex_destroy (&httpd->client.mutex); + int i; + for (i = 0; i < 2; i++) + { + int flags = QSE_FCNTL (httpd->client.pfd[i], F_GETFD, 0); + if (flags >= 0) + QSE_FCNTL (httpd->client.pfd[i], F_SETFD, flags | FD_CLOEXEC); + } + + pthread_mutex_init (&httpd->client.mutex, QSE_NULL); + pthread_cond_init (&httpd->client.cond, QSE_NULL); + + if (pthread_create ( + &response_thread_id, QSE_NULL, + response_thread, httpd) != 0) + { + pthread_cond_destroy (&httpd->client.cond); + pthread_mutex_destroy (&httpd->client.mutex); + QSE_CLOSE (httpd->client.pfd[1]); + QSE_CLOSE (httpd->client.pfd[0]); + } + else httpd->threaded = 1; } - else httpd->threaded = 1; } #endif @@ -935,7 +966,6 @@ int qse_httpd_loop (qse_httpd_t* httpd, int threaded) if (httpd->threaded) pthread_mutex_unlock (&httpd->client.mutex); #endif - /*n = select (max + 1, &r, &w, QSE_NULL, &tv);*/ n = select (max + 1, &r, &w, QSE_NULL, &tv); if (n <= -1) { @@ -984,31 +1014,6 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); } - /*----------------------------------------------------------*/ -#if 0 - if (!httpd->threaded && FD_ISSET(client->handle.i, &w)) - { - /* output is handled in the main loop if and - * only if it is not threaded */ - if (client->bad) - { - /*send (client->handle, i, "INTERNAL SERVER ERROR..", ...);*/ - /*shutdown (client->handle.i, SHUT_RDWR);*/ - - /*pthread_mutex_lock (&httpd->client.mutex);*/ - delete_from_client_array (httpd, fd); - /*pthread_mutex_unlock (&httpd->client.mutex);*/ - } - else if (client->task.queue.count > 0) - { - if (client->task.queue.head->task.trigger.i <= -1 || - FD_ISSET(client->task.queue.head->task.trigger.i, &r)) - { - perform_task (httpd, client); - } - } - } -#endif if (!httpd->threaded) { if (client->bad) @@ -1018,7 +1023,7 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); delete_from_client_array (httpd, fd); /*pthread_mutex_unlock (&httpd->client.mutex);*/ } - else if (client->task.queue.count > 0) + else if (client->task.queue.head) { if (client->task.queue.head->task.trigger.i <= -1 || FD_ISSET(client->task.queue.head->task.trigger.i, &r)) @@ -1027,7 +1032,7 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); tv.tv_usec = 0; FD_ZERO (&w); FD_SET (client->handle.i, &w); - n = select (max + 1, QSE_NULL, &w, QSE_NULL, &tv); + n = select (client->handle.i + 1, QSE_NULL, &w, QSE_NULL, &tv); /* TODO: logging if n == -1 */ @@ -1036,8 +1041,6 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); } } } - - /*----------------------------------------------------------*/ } } @@ -1047,6 +1050,8 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); pthread_join (response_thread_id, QSE_NULL); pthread_cond_destroy (&httpd->client.cond); pthread_mutex_destroy (&httpd->client.mutex); + QSE_CLOSE (httpd->client.pfd[1]); + QSE_CLOSE (httpd->client.pfd[0]); } #endif @@ -1287,7 +1292,15 @@ qse_httpd_task_t* qse_httpd_entask ( ret = enqueue_task_locked (httpd, client, pred, task, xtnsize); if (ret == QSE_NULL) client->bad = 1; /* mark this client bad */ #if defined(HAVE_PTHREAD) - else if (httpd->threaded) pthread_cond_signal (&httpd->client.cond); + else if (httpd->threaded) + { + static qse_byte_t dummy = 0x01; + /* write to the pipe to wake up select() in + * the response thread if it was blocking. */ + QSE_WRITE (httpd->client.pfd[1], &dummy, 1); + + pthread_cond_signal (&httpd->client.cond); + } #endif return ret; } diff --git a/qse/lib/net/httpd.h b/qse/lib/net/httpd.h index 2e4605c3..8b3ce9f3 100644 --- a/qse/lib/net/httpd.h +++ b/qse/lib/net/httpd.h @@ -114,11 +114,13 @@ struct qse_httpd_t int option; int stopreq; + int threaded; struct { #if defined(HAVE_PTHREAD) + int pfd[2]; pthread_mutex_t mutex; pthread_cond_t cond; #endif @@ -149,6 +151,7 @@ void qse_httpd_fini ( qse_httpd_t* httpd ); + #ifdef __cplusplus } #endif diff --git a/qse/lib/net/httpd_task.c b/qse/lib/net/httpd_task.c index f4343b5a..f3b59db8 100644 --- a/qse/lib/net/httpd_task.c +++ b/qse/lib/net/httpd_task.c @@ -1622,7 +1622,9 @@ qse_printf (QSE_T("CGI FUCKED UP...RETURNING TOO MUCH DATA\n")); return -1; } +#if 0 qse_printf (QSE_T("CGI SEND [%.*hs]\n"), (int)cgi->buflen, cgi->buf); +#endif n = send (client->handle.i, cgi->buf, cgi->buflen, 0); if (n <= -1) { @@ -1634,7 +1636,9 @@ qse_printf (QSE_T("CGI SEND [%.*hs]\n"), (int)cgi->buflen, cgi->buf); QSE_MEMCPY (&cgi->buf[0], &cgi->buf[n], cgi->buflen - n); cgi->buflen -= n; +#if 0 qse_printf (QSE_T("CGI SEND DONE\n")); +#endif return 1; } diff --git a/qse/samples/net/http01.c b/qse/samples/net/http01.c index d4bbb70e..7d0f0ef6 100644 --- a/qse/samples/net/http01.c +++ b/qse/samples/net/http01.c @@ -37,7 +37,8 @@ static int handle_request ( #endif qse_printf (QSE_T("================================\n")); -qse_printf (QSE_T("REQUEST ==> [%hs] version[%d.%d] method[%d]\n"), +qse_printf (QSE_T("[%lu] REQUEST ==> [%hs] version[%d.%d] method[%d]\n"), + (unsigned long)time(NULL), qse_htre_getqpathptr(req), qse_htre_getmajorversion(req), qse_htre_getminorversion(req),