From 1ab75927d2b300ca31e5e413fb4813f5da65a6b3 Mon Sep 17 00:00:00 2001 From: hyung-hwan Date: Thu, 9 Feb 2012 12:54:00 +0000 Subject: [PATCH] fixed various bugs in handling cgi --- qse/include/qse/net/htre.h | 25 +- qse/include/qse/net/httpd.h | 67 ++--- qse/lib/net/htrd.c | 22 +- qse/lib/net/htre.c | 22 +- qse/lib/net/httpd.c | 538 ++++++++---------------------------- qse/lib/net/httpd_task.c | 362 +++++++++++++++--------- qse/samples/net/http01.c | 114 +++++++- 7 files changed, 542 insertions(+), 608 deletions(-) diff --git a/qse/include/qse/net/htre.h b/qse/include/qse/net/htre.h index 6a2ce277..5463ff70 100644 --- a/qse/include/qse/net/htre.h +++ b/qse/include/qse/net/htre.h @@ -28,7 +28,7 @@ /* header and contents of request/response */ typedef struct qse_htre_t qse_htre_t; -enum qse_htre_flag_t +enum qse_htre_state_t { QSE_HTRE_DISCARDED = (1 << 0), /** content has been discarded */ QSE_HTRE_COMPLETED = (1 << 1) /** complete content has been seen */ @@ -72,8 +72,8 @@ struct qse_htre_t qse_htre_concb_t concb; void* concb_ctx; - /* ORed of qse_htre_flag_t */ - int flags; + /* ORed of qse_htre_state_t */ + int state; }; #define qse_htre_getversion(re) (&((re)->version)) @@ -169,7 +169,7 @@ int qse_htre_setstrfromxstr ( ); const qse_mchar_t* qse_htre_getheaderval ( - qse_htre_t* re, + const qse_htre_t* re, const qse_mchar_t* key ); @@ -179,12 +179,27 @@ int qse_htre_walkheaders ( void* ctx ); +/** + * The qse_htre_addcontent() function adds a content semgnet pointed to by + * @a ptr of @a len bytes to the content buffer. If @a re is already completed + * or discarded, this function returns 0 without adding the segment to the + * content buffer. + * @return 1 on success, -1 on failure, 0 if adding is skipped. + */ int qse_htre_addcontent ( qse_htre_t* re, const qse_mchar_t* ptr, qse_size_t len ); +void qse_htre_completecontent ( + qse_htre_t* re +); + +void qse_htre_discardcontent ( + qse_htre_t* re +); + void qse_htre_unsetconcb ( qse_htre_t* re ); @@ -196,7 +211,7 @@ void qse_htre_setconcb ( ); const qse_mchar_t* qse_htre_getqmethodname ( - qse_htre_t* re + const qse_htre_t* re ); #ifdef __cplusplus diff --git a/qse/include/qse/net/httpd.h b/qse/include/qse/net/httpd.h index f6ee8108..f089972b 100644 --- a/qse/include/qse/net/httpd.h +++ b/qse/include/qse/net/httpd.h @@ -24,6 +24,7 @@ #include #include #include +#include typedef struct qse_httpd_t qse_httpd_t; typedef struct qse_httpd_client_t qse_httpd_client_t; @@ -45,24 +46,36 @@ typedef enum qse_httpd_errnum_t qse_httpd_errnum_t; enum qse_httpd_option_t { - QSE_HTTPD_CGINOCLOEXEC = (1 << 0) + QSE_HTTPD_CGIERRTONUL = (1 << 0), + QSE_HTTPD_CGINOCLOEXEC = (1 << 1) }; typedef struct qse_httpd_cbs_t qse_httpd_cbs_t; struct qse_httpd_cbs_t { + struct + { + int (*readable) (qse_httpd_t* httpd, qse_ubi_t handle, qse_ntoff_t timeout); + int (*writable) (qse_httpd_t* httpd, qse_ubi_t handle, qse_ntoff_t timeout); + } mux; + + struct + { + int (*executable) (qse_httpd_t* httpd, const qse_mchar_t* path); + } path; + struct { /* action */ - int (*recv) (qse_httpd_t* httpd, + qse_ssize_t (*recv) (qse_httpd_t* httpd, qse_httpd_client_t* client, qse_mchar_t* buf, qse_size_t bufsize); - int (*send) (qse_httpd_t* httpd, + qse_ssize_t (*send) (qse_httpd_t* httpd, qse_httpd_client_t* client, const qse_mchar_t* buf, qse_size_t bufsize); - int (*sendfile) (qse_httpd_t* httpd, + qse_ssize_t (*sendfile) (qse_httpd_t* httpd, qse_httpd_client_t* client, qse_ubi_t handle, qse_foff_t* offset, qse_size_t count); @@ -74,7 +87,7 @@ struct qse_httpd_cbs_t qse_httpd_t* httpd, qse_httpd_client_t* client); /* optional */ } client; - + int (*peek_request) ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req); int (*handle_request) ( @@ -108,9 +121,11 @@ typedef int (*qse_httpd_task_main_t) ( enum qse_httpd_task_trigger_mask_t { QSE_HTTPD_TASK_TRIGGER_READ = (1 << 0), - QSE_HTTPD_TASK_TRIGGER_WRITE = (1 << 1), - QSE_HTTPD_TASK_TRIGGER_READABLE = (1 << 2), - QSE_HTTPD_TASK_TRIGGER_WRITABLE = (1 << 3) + QSE_HTTPD_TASK_TRIGGER_RELAY = (1 << 1), + QSE_HTTPD_TASK_TRIGGER_WRITE = (1 << 2), + QSE_HTTPD_TASK_TRIGGER_READABLE = (1 << 3), + QSE_HTTPD_TASK_TRIGGER_RELAYABLE = (1 << 4), + QSE_HTTPD_TASK_TRIGGER_WRITABLE = (1 << 5) }; struct qse_httpd_task_t @@ -123,7 +138,7 @@ struct qse_httpd_task_t qse_httpd_task_main_t main; int trigger_mask; - qse_ubi_t trigger[2]; + qse_ubi_t trigger[3]; void* ctx; }; @@ -158,28 +173,12 @@ void qse_httpd_setoption ( int option ); -const qse_httpd_cbs_t* qse_httpd_getcbs ( - qse_httpd_t* httpd -); - -void qse_httpd_setcbs ( - qse_httpd_t* httpd, - qse_httpd_cbs_t* cbs -); - /** * The qse_httpd_loop() function starts a httpd server loop. - * If @a threaded is non-zero, it creates a separate output thread. - * If no thread support is available, it is ignored. - * - * @note - * In the future, the @a threaded parameter will be extended to - * specify the number of output threads. */ int qse_httpd_loop ( qse_httpd_t* httpd, - qse_httpd_cbs_t* cbs, - int threaded + qse_httpd_cbs_t* cbs ); /** @@ -195,7 +194,6 @@ int qse_httpd_addlistener ( const qse_char_t* uri ); - void qse_httpd_markbadclient ( qse_httpd_t* httpd, qse_httpd_client_t* client @@ -206,6 +204,11 @@ void qse_httpd_discardcontent ( qse_htre_t* req ); +void qse_httpd_completecontent ( + qse_httpd_t* httpd, + qse_htre_t* req +); + #define qse_httpd_gettaskxtn(httpd,task) ((void*)(task+1)) qse_httpd_task_t* qse_httpd_entask ( @@ -272,14 +275,14 @@ qse_httpd_task_t* qse_httpd_entaskerror ( qse_httpd_client_t* client, const qse_httpd_task_t* task, int code, - const qse_htre_t* req + qse_htre_t* req ); qse_httpd_task_t* qse_httpd_entaskcontinue ( qse_httpd_t* httpd, qse_httpd_client_t* client, const qse_httpd_task_t* task, - const qse_htre_t* req + qse_htre_t* req ); qse_httpd_task_t* qse_httpd_entaskpath ( @@ -287,7 +290,7 @@ qse_httpd_task_t* qse_httpd_entaskpath ( qse_httpd_client_t* client, const qse_httpd_task_t* pred, const qse_mchar_t* name, - const qse_htre_t* req + qse_htre_t* req ); qse_httpd_task_t* qse_httpd_entaskcgi ( @@ -295,7 +298,7 @@ qse_httpd_task_t* qse_httpd_entaskcgi ( qse_httpd_client_t* client, const qse_httpd_task_t* pred, const qse_mchar_t* path, - const qse_htre_t* req + qse_htre_t* req ); qse_httpd_task_t* qse_httpd_entasknph ( @@ -303,7 +306,7 @@ qse_httpd_task_t* qse_httpd_entasknph ( qse_httpd_client_t* client, const qse_httpd_task_t* pred, const qse_mchar_t* path, - const qse_htre_t* req + qse_htre_t* req ); /* -------------------------------------------- */ diff --git a/qse/lib/net/htrd.c b/qse/lib/net/htrd.c index 54d7e5fd..82aa7950 100644 --- a/qse/lib/net/htrd.c +++ b/qse/lib/net/htrd.c @@ -101,6 +101,9 @@ static QSE_INLINE int push_content ( htrd->errnum = QSE_HTRD_ENOMEM; return -1; } + + /* qse_htre_addcontent() returns 1 on full success and 0 if adding is + * skipped. i treat both as success */ return 0; } @@ -1101,16 +1104,16 @@ int qse_htrd_feed (qse_htrd_t* htrd, const qse_mchar_t* req, qse_size_t len) * reading CGI outputs. So it comes with * awkwardity described above. */ - if (!(htrd->re.flags & QSE_HTRE_DISCARDED) && - push_content (htrd, ptr, end - ptr) <= -1) return -1; + if (push_content (htrd, ptr, end - ptr) <= -1) return -1; /* this jump is only to invoke the peek * callback. this function should not be fed * more. */ /* i don't really know if it is really completed * with content. QSE_HTRD_PEEKONLY is not compatible - * with the completed flag. */ - htrd->re.flags &= QSE_HTRE_COMPLETED; + * with the completed state. anyway, let me complete + * it. */ + qse_htre_completecontent (&htrd->re); goto feedme_more; } @@ -1166,8 +1169,7 @@ int qse_htrd_feed (qse_htrd_t* htrd, const qse_mchar_t* req, qse_size_t len) if (avail < htrd->fed.s.need) { /* the data is not as large as needed */ - if (!(htrd->re.flags & QSE_HTRE_DISCARDED) && - push_content (htrd, ptr, avail) <= -1) return -1; + if (push_content (htrd, ptr, avail) <= -1) return -1; htrd->fed.s.need -= avail; /* we didn't get a complete content yet */ goto feedme_more; @@ -1175,8 +1177,7 @@ int qse_htrd_feed (qse_htrd_t* htrd, const qse_mchar_t* req, qse_size_t len) else { /* we got all or more than needed */ - if (!(htrd->re.flags & QSE_HTRE_DISCARDED) && - push_content (htrd, ptr, htrd->fed.s.need) <= -1) return -1; + if (push_content (htrd, ptr, htrd->fed.s.need) <= -1) return -1; ptr += htrd->fed.s.need; htrd->fed.s.need = 0; } @@ -1226,6 +1227,9 @@ int qse_htrd_feed (qse_htrd_t* htrd, const qse_mchar_t* req, qse_size_t len) } } + /* the content has been received fully */ + qse_htre_completecontent (&htrd->re); + if (header_completed_during_this_feed && htrd->recbs->peek) { /* the peek handler has not been executed. @@ -1234,7 +1238,6 @@ int qse_htrd_feed (qse_htrd_t* htrd, const qse_mchar_t* req, qse_size_t len) * plus complete content body and the header * of the next request. */ int n; - htrd->re.flags |= QSE_HTRE_COMPLETED; htrd->errnum = QSE_HTRD_ENOERR; n = htrd->recbs->peek (htrd, &htrd->re); if (n <= -1) @@ -1252,7 +1255,6 @@ int qse_htrd_feed (qse_htrd_t* htrd, const qse_mchar_t* req, qse_size_t len) if (htrd->recbs->handle) { int n; - htrd->re.flags |= QSE_HTRE_COMPLETED; htrd->errnum = QSE_HTRD_ENOERR; n = htrd->recbs->handle (htrd, &htrd->re); if (n <= -1) diff --git a/qse/lib/net/htre.c b/qse/lib/net/htre.c index fdadc5d1..541769a0 100644 --- a/qse/lib/net/htre.c +++ b/qse/lib/net/htre.c @@ -59,7 +59,7 @@ void qse_htre_clear (qse_htre_t* re) qse_mbs_clear (&re->content); qse_mbs_clear (&re->qpath_or_smesg); qse_mbs_clear (&re->qparam); - re->flags = 0; + re->state = 0; } int qse_htre_setstrfromcstr ( @@ -75,7 +75,7 @@ int qse_htre_setstrfromxstr ( } const qse_mchar_t* qse_htre_getheaderval ( - qse_htre_t* re, const qse_mchar_t* name) + const qse_htre_t* re, const qse_mchar_t* name) { qse_htb_pair_t* pair; pair = qse_htb_search (&re->hdrtab, name, qse_mbslen(name)); @@ -117,11 +117,25 @@ int qse_htre_walkheaders ( int qse_htre_addcontent ( qse_htre_t* re, const qse_mchar_t* ptr, qse_size_t len) { + if (re->state & (QSE_HTRE_COMPLETED | QSE_HTRE_DISCARDED)) return 0; + /* if the callback is set, the content goes to the callback. */ if (re->concb) return re->concb (re, ptr, len, re->concb_ctx); /* if the callback is not set, the contents goes to the internal buffer */ if (qse_mbs_ncat (&re->content, ptr, len) == (qse_size_t)-1) return -1; - return 0; + + return 1; /* added successfully */ +} + +void qse_htre_completecontent (qse_htre_t* re) +{ + re->state |= QSE_HTRE_COMPLETED; +} + +void qse_htre_discardcontent (qse_htre_t* re) +{ + re->state |= QSE_HTRE_DISCARDED; + qse_mbs_clear (&re->content); } void qse_htre_unsetconcb (qse_htre_t* re) @@ -136,7 +150,7 @@ void qse_htre_setconcb (qse_htre_t* re, qse_htre_concb_t concb, void* ctx) re->concb_ctx = ctx; } -const qse_mchar_t* qse_htre_getqmethodname (qse_htre_t* re) +const qse_mchar_t* qse_htre_getqmethodname (const qse_htre_t* re) { return qse_gethttpmethodname (re->qmethod_or_sstatus); } diff --git a/qse/lib/net/httpd.c b/qse/lib/net/httpd.c index cfd5b739..e687fd0f 100644 --- a/qse/lib/net/httpd.c +++ b/qse/lib/net/httpd.c @@ -35,14 +35,6 @@ #include #include -#if defined(HAVE_PTHREAD) -#include -#endif - -#if 0 -#include -#endif - #include typedef struct htrd_xtn_t htrd_xtn_t; @@ -90,19 +82,12 @@ int qse_httpd_init (qse_httpd_t* httpd, qse_mmgr_t* mmgr) httpd->mmgr = mmgr; httpd->listener.max = -1; -#if defined(HAVE_PTHREAD) - pthread_mutex_init (&httpd->listener.mutex, QSE_NULL); -#endif - return 0; } void qse_httpd_fini (qse_httpd_t* httpd) { /* TODO */ -#if defined(HAVE_PTHREAD) - pthread_mutex_destroy (&httpd->listener.mutex); -#endif free_listener_list (httpd, httpd->listener.list); httpd->listener.list = QSE_NULL; } @@ -122,16 +107,6 @@ void qse_httpd_setoption (qse_httpd_t* httpd, int option) httpd->option = option; } -const qse_httpd_cbs_t* qse_httpd_getcbs (qse_httpd_t* httpd) -{ - return httpd->cbs; -} - -void qse_httpd_setcbs (qse_httpd_t* httpd, qse_httpd_cbs_t* cbs) -{ - httpd->cbs = cbs; -} - QSE_INLINE void* qse_httpd_allocmem (qse_httpd_t* httpd, qse_size_t size) { void* ptr = QSE_MMGR_ALLOC (httpd->mmgr, size); @@ -152,7 +127,7 @@ QSE_INLINE void qse_httpd_freemem (qse_httpd_t* httpd, void* ptr) QSE_MMGR_FREE (httpd->mmgr, ptr); } -static qse_httpd_task_t* enqueue_task_unlocked ( +static qse_httpd_task_t* enqueue_task ( qse_httpd_t* httpd, qse_httpd_client_t* client, const qse_httpd_task_t* pred, const qse_httpd_task_t* task, qse_size_t xtnsize) @@ -215,30 +190,7 @@ static qse_httpd_task_t* enqueue_task_unlocked ( return &node->task; } -static qse_httpd_task_t* enqueue_task_locked ( - qse_httpd_t* httpd, qse_httpd_client_t* client, - const qse_httpd_task_t* pred, const qse_httpd_task_t* task, - qse_size_t xtnsize) -{ -#if defined(HAVE_PTHREAD) - if (httpd->threaded) - { - qse_httpd_task_t* ret; - pthread_mutex_lock (&client->task.mutex); - ret = enqueue_task_unlocked (httpd, client, pred, task, xtnsize); - pthread_mutex_unlock (&client->task.mutex); - return ret; - } - else - { -#endif - return enqueue_task_unlocked (httpd, client, pred, task, xtnsize); -#if defined(HAVE_PTHREAD) - } -#endif -} - -static QSE_INLINE int dequeue_task_unlocked ( +static QSE_INLINE int dequeue_task ( qse_httpd_t* httpd, qse_httpd_client_t* client) { task_queue_node_t* node; @@ -265,35 +217,10 @@ static QSE_INLINE int dequeue_task_unlocked ( return 0; } -static int dequeue_task_locked (qse_httpd_t* httpd, qse_httpd_client_t* client) +static QSE_INLINE void purge_tasks ( + qse_httpd_t* httpd, qse_httpd_client_t* client) { -#if defined(HAVE_PTHREAD) - if (httpd->threaded) - { - int ret; - pthread_mutex_lock (&client->task.mutex); - ret = dequeue_task_unlocked (httpd, client); - pthread_mutex_unlock (&client->task.mutex); - return ret; - } - else - { -#endif - return dequeue_task_unlocked (httpd, client); -#if defined(HAVE_PTHREAD) - } -#endif -} - -static void purge_tasks_locked (qse_httpd_t* httpd, qse_httpd_client_t* client) -{ -#if defined(HAVE_PTHREAD) - if (httpd->threaded) pthread_mutex_lock (&client->task.mutex); -#endif - while (dequeue_task_unlocked (httpd, client) == 0); -#if defined(HAVE_PTHREAD) - if (httpd->threaded) pthread_mutex_unlock (&client->task.mutex); -#endif + while (dequeue_task (httpd, client) == 0); } static int htrd_peek_request (qse_htrd_t* htrd, qse_htre_t* req) @@ -458,11 +385,7 @@ static void delete_from_client_array (qse_httpd_t* httpd, int fd) client_array_t* array = &httpd->client.array; if (array->data[fd].htrd) { - purge_tasks_locked (httpd, &array->data[fd]); -#if defined(HAVE_PTHREAD) - if (httpd->threaded) - pthread_mutex_destroy (&array->data[fd].task.mutex); -#endif + purge_tasks (httpd, &array->data[fd]); qse_htrd_close (array->data[fd].htrd); array->data[fd].htrd = QSE_NULL; @@ -538,11 +461,6 @@ static qse_httpd_client_t* insert_into_client_array ( array->data[fd].local_addr = client->local_addr; array->data[fd].remote_addr = client->remote_addr; -#if defined(HAVE_PTHREAD) - if (httpd->threaded) - pthread_mutex_init (&array->data[fd].task.mutex, QSE_NULL); -#endif - xtn = (htrd_xtn_t*)qse_htrd_getxtn (array->data[fd].htrd); xtn->client_index = fd; xtn->httpd = httpd; @@ -600,13 +518,7 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: too many client?\n")); flag = fcntl (clibuf.handle.i, F_GETFD); if (flag >= 0) fcntl (clibuf.handle.i, F_SETFD, flag | FD_CLOEXEC); -#if defined(HAVE_PTHREAD) - if (httpd->threaded) pthread_mutex_lock (&httpd->client.mutex); -#endif client = insert_into_client_array (httpd, &clibuf); -#if defined(HAVE_PTHREAD) - if (httpd->threaded) pthread_mutex_unlock (&httpd->client.mutex); -#endif if (client == QSE_NULL) { @@ -638,109 +550,102 @@ 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_httpd_t* httpd, fd_set* r, fd_set* w) { - /* 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; + client_array_t* ca; + qse_httpd_client_t* client; - int fd, max = -1; - client_array_t* ca = &httpd->client.array; + ca = &httpd->client.array; - 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; - } - else - { - 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 - } + /* qse_http_loop() needs to monitor listner handles + * to handle a new client connection. */ + max = httpd->listener.max; + *r = httpd->listener.set; FD_ZERO (w); for (fd = 0; fd < ca->capa; fd++) { - if (ca->data[fd].htrd) + client = &ca->data[fd]; + + if (!client->htrd) continue; + + if (client->bad) { - if (!ca->data[fd].bad) - { - if (for_rdwr) - { - /* 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; - } + /* add a client-side handle to the write set + * if the client is already marked bad */ + FD_SET (client->handle.i, w); + if (client->handle.i > max) max = client->handle.i; + } + else + { + /* add a client-side handle to the read set */ + FD_SET (client->handle.i, r); + if (client->handle.i > max) max = client->handle.i; +qse_printf (QSE_T(">>>>ADDING CLIENT HANDLE %d\n"), client->handle.i); - if (!httpd->threaded || !for_rdwr) + /* trigger[0] is a handle to monitor to check + * if there is data avaiable to read to write back to + * the client. qse_httpd_loop() needs to monitor + * trigger handles. + * + * trigger[1] is a user-defined handle to monitor to + * check if httpd can post data to. but this is not + * a client-side handle. + */ + if (client->task.queue.head) + { + qse_httpd_task_t* task = &client->task.queue.head->task; + int has_trigger = 0; + + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) { - /* trigger[0] is a handle to monitor to check - * if there is data avaiable to read 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. - * - * trigger[1] is a user-defined handle to monitor to - * check if httpd can post data to. but this is not - * a client-side handle. - */ - if (ca->data[fd].task.queue.head) + /* if a trigger is available, add it to the read set also. */ +qse_printf (QSE_T(">>>>%s ADDING TRIGGER[0] %d\n"), + (task->trigger[0].i == client->handle.i? QSE_T("NOT"): QSE_T("")), + task->trigger[0].i); + if (task->trigger[0].i != client->handle.i) { - qse_httpd_task_t* task = &ca->data[fd].task.queue.head->task; - if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) - { - /* if a trigger is available, add it to the read set also. */ -qse_printf (QSE_T(">>>>ADDING TRIGGER[0] %d\n"), task->trigger[0].i); - FD_SET (task->trigger[0].i, r); - if (task->trigger[0].i > max) max = task->trigger[0].i; - } - if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE) - { - /* if a trigger is available, add it to the read set also. */ -qse_printf (QSE_T(">>>>ADDING TRIGGER[1] %d\n"), task->trigger[1].i); - FD_SET (task->trigger[1].i, w); - if (task->trigger[1].i > max) max = task->trigger[1].i; - } + FD_SET (task->trigger[0].i, r); + if (task->trigger[0].i > max) max = task->trigger[0].i; } + has_trigger = 1; + } + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) + { +qse_printf (QSE_T(">>>>%s ADDING TRIGGER[1] %d\n"), + (task->trigger[1].i == client->handle.i? QSE_T("NOT"): QSE_T("")), + task->trigger[1].i); + if (task->trigger[1].i != client->handle.i) + { + FD_SET (task->trigger[1].i, r); + if (task->trigger[1].i > max) max = task->trigger[1].i; + } + has_trigger = 1; + } + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE) + { + /* if a trigger is available, add it to the read set also. */ +qse_printf (QSE_T(">>>>ADDING TRIGGER[2] %d\n"), task->trigger[2].i); + FD_SET (task->trigger[2].i, w); + if (task->trigger[2].i > max) max = task->trigger[2].i; + has_trigger = 1; } - } - if (ca->data[fd].bad || - (ca->data[fd].task.queue.head && !(ca->data[fd].task.queue.head->task.trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ))) - { - /* 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; + if (!has_trigger) + { + /* there is a task to perform. but no triggers + * were specified. if the client-side handle is + * available for writing, arrange to perform the + * task in the main loop by adding the client-side + * handle to the write set. */ +qse_printf (QSE_T(">>>>ADDING CLIENT CONNECTION %d TO WRITE\n"), client->handle.i); + FD_SET (client->handle.i, w); + if (client->handle.i > max) max = client->handle.i; + } } } + } return max; @@ -758,144 +663,16 @@ static void perform_task (qse_httpd_t* httpd, qse_httpd_client_t* client) n = node->task.main (httpd, client, &node->task); if (n <= -1) { - dequeue_task_locked (httpd, client); + dequeue_task (httpd, client); /*shutdown (client->handle.i, SHUT_RDWR);*/ client->bad = 1; } else if (n == 0) { - dequeue_task_locked (httpd, client); + dequeue_task (httpd, client); } } -#if defined(HAVE_PTHREAD) -static void* response_thread (void* arg) -{ - qse_httpd_t* httpd = (qse_httpd_t*)arg; - - while (!httpd->stopreq) - { - int n, max, fd; - fd_set r, w; - struct timeval tv; - - pthread_mutex_lock (&httpd->client.mutex); - max = make_fd_set_from_client_array (httpd, &r, &w, 0); - pthread_mutex_unlock (&httpd->client.mutex); - - while (max == -1 && !httpd->stopreq) - { - struct timeval now; - struct timespec timeout; - - pthread_mutex_lock (&httpd->client.mutex); - - gettimeofday (&now, QSE_NULL); - timeout.tv_sec = now.tv_sec + 1; - timeout.tv_nsec = now.tv_usec * 1000; - - 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); - } - - if (httpd->stopreq) break; - - tv.tv_sec = 1; - tv.tv_usec = 0; - - n = select (max + 1, &r, &w, QSE_NULL, &tv); - if (n <= -1) - { - /*if (errno == EINTR) continue; */ -qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure - %hs\n"), strerror(errno)); - /* break; */ - continue; - } - if (n == 0) - { - 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 (client->bad) - { - /*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.head) - { - qse_httpd_task_t* task; - int perform = 0; - - task = &client->task.queue.head->task; - - task->trigger_mask &= - ~(QSE_HTTPD_TASK_TRIGGER_READABLE | - QSE_HTTPD_TASK_TRIGGER_WRITABLE); - - if (!(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) && - !(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE)) - { - /* no trigger set. set the flag to - * non-readable and non-writable */ - perform = 1; - } - else - { - if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) && - FD_ISSET(task->trigger[0].i, &r)) - { - /* set the flag to readable */ - task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_READABLE; - perform = 1; - } - - if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE) && - FD_ISSET(task->trigger[1].i, &w)) - { - /* set the flag to writable */ - task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITABLE; - perform = 1; - } - } - - if (perform) - { - tv.tv_sec = 0; - tv.tv_usec = 0; - FD_ZERO (&w); - FD_SET (client->handle.i, &w); - 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); - } - } - } - } - } - - pthread_exit (QSE_NULL); - return QSE_NULL; -} -#endif - static int read_from_client (qse_httpd_t* httpd, qse_httpd_client_t* client) { qse_mchar_t buf[1024]; @@ -938,6 +715,7 @@ qse_fprintf (QSE_STDERR, QSE_T("Debug: connection closed %d\n"), client->handle. qse_fprintf (QSE_STDERR, QSE_T("Debug: read from a client %d\n"), client->handle.i); httpd->errnum = QSE_HTTPD_ENOERR; +qse_printf (QSE_T("!!!!!FEEDING [%.*hs]\n"), (int)m, buf); if (qse_htrd_feed (client->htrd, buf, m) <= -1) { if (httpd->errnum == QSE_HTTPD_ENOERR) @@ -955,13 +733,8 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: http error while processing \n")); return 0; } -int qse_httpd_loop (qse_httpd_t* httpd, qse_httpd_cbs_t* cbs, int threaded) +int qse_httpd_loop (qse_httpd_t* httpd, qse_httpd_cbs_t* cbs) { -#if defined(HAVE_PTHREAD) - pthread_t response_thread_id; -#endif - - httpd->threaded = 0; httpd->stopreq = 0; httpd->cbs = cbs; @@ -984,43 +757,6 @@ int qse_httpd_loop (qse_httpd_t* httpd, qse_httpd_cbs_t* cbs, int threaded) init_client_array (httpd); -#if defined(HAVE_PTHREAD) - /* start the response sender as a thread */ - if (threaded) - { - if (QSE_PIPE(httpd->client.pfd) == 0) - { - 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); - - /* set this before creating a thread - * because this is accessed in a thread. - * if i set this after pthread_create, a thread - * function may still see 0. */ - httpd->threaded = 1; - - if (pthread_create ( - &response_thread_id, QSE_NULL, - response_thread, httpd) != 0) - { - httpd->threaded = 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]); - } - } - } -#endif - while (!httpd->stopreq) { int n, max, fd; @@ -1031,14 +767,7 @@ int qse_httpd_loop (qse_httpd_t* httpd, qse_httpd_cbs_t* cbs, int threaded) tv.tv_sec = 1; tv.tv_usec = 0; -#if defined(HAVE_PTHREAD) - if (httpd->threaded) pthread_mutex_lock (&httpd->client.mutex); -#endif - max = make_fd_set_from_client_array (httpd, &r, &w, 1); -#if defined(HAVE_PTHREAD) - if (httpd->threaded) pthread_mutex_unlock (&httpd->client.mutex); -#endif - + max = make_fd_set_from_client_array (httpd, &r, &w); n = select (max + 1, &r, &w, QSE_NULL, &tv); if (n <= -1) { @@ -1090,9 +819,7 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); } else { - /*pthread_mutex_lock (&httpd->client.mutex);*/ delete_from_client_array (httpd, fd); - /*pthread_mutex_unlock (&httpd->client.mutex);*/ continue; /* don't need to go to the writing part */ } } @@ -1104,9 +831,7 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); if (client->bad) { /*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.head) { @@ -1116,9 +841,11 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); task = &client->task.queue.head->task; task->trigger_mask &= ~(QSE_HTTPD_TASK_TRIGGER_READABLE | + QSE_HTTPD_TASK_TRIGGER_RELAYABLE | QSE_HTTPD_TASK_TRIGGER_WRITABLE); if (!(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) && + !(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) && !(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE)) { /* no trigger set. set the flag to @@ -1130,15 +857,20 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) && FD_ISSET(task->trigger[0].i, &r)) { - /* set the flag to readable */ task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_READABLE; perform = 1; } - - if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE) && - FD_ISSET(task->trigger[1].i, &w)) + + if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) && + FD_ISSET(task->trigger[1].i, &r)) + { + task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_RELAYABLE; + perform = 1; + } + + if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) && + FD_ISSET(task->trigger[2].i, &w)) { - /* set the flag to writable */ task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITABLE; perform = 1; } @@ -1146,15 +878,11 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); if (perform) { - tv.tv_sec = 0; - tv.tv_usec = 0; - FD_ZERO (&w); - FD_SET (client->handle.i, &w); - n = select (client->handle.i + 1, QSE_NULL, &w, QSE_NULL, &tv); - - /* TODO: logging if n == -1 */ - - if (n > 0 && FD_ISSET(client->handle.i, &w)) + /* TODO: error handling -> writable() returns <= -1 */ + /* TODO: though the client side is not writable, can't i still exeucte the task? + * if the task needs to transfer anything yet.. it can do that. + * i probably need a new trigger type??? */ + if (httpd->cbs->mux.writable (httpd, client->handle, 0) >= 1) perform_task (httpd, client); } } @@ -1162,17 +890,6 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); } } -#if defined(HAVE_PTHREAD) - if (httpd->threaded) - { - 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 - fini_client_array (httpd); deactivate_listeners (httpd); return 0; @@ -1370,34 +1087,20 @@ static int delete_listeners (qse_httpd_t* httpd, const qse_char_t* uri) int qse_httpd_addlistener (qse_httpd_t* httpd, const qse_char_t* uri) { -#if defined(HAVE_PTHREAD) - int n; - pthread_mutex_lock (&httpd->listener.mutex); - n = add_listener (httpd, uri); - pthread_mutex_unlock (&httpd->listener.mutex); - return n; -#else return add_listener (httpd, uri); -#endif } #if 0 int qse_httpd_dellistener (qse_httpd_t* httpd, const qse_char_t* uri) { - int n; - pthread_mutex_lock (&httpd->listener.mutex); - n = delete_listeners (httpd, uri); - pthread_mutex_unlock (&httpd->listener.mutex); - return n; + return delete_listeners (httpd, uri); } void qse_httpd_clearlisteners (qse_httpd_t* httpd) { - pthread_mutex_lock (&httpd->listener.mutex); deactivate_listeners (httpd); free_listener_list (httpd, httpd->listener.list); httpd->listener.list = QSE_NULL; - pthread_mutex_unlock (&httpd->listener.mutex); } #endif @@ -1407,19 +1110,8 @@ qse_httpd_task_t* qse_httpd_entask ( qse_size_t xtnsize) { qse_httpd_task_t* ret; - ret = enqueue_task_locked (httpd, client, pred, task, xtnsize); + ret = enqueue_task (httpd, client, pred, task, xtnsize); if (ret == QSE_NULL) client->bad = 1; /* mark this client bad */ -#if defined(HAVE_PTHREAD) - 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; } @@ -1433,10 +1125,12 @@ void qse_httpd_markbadclient (qse_httpd_t* httpd, qse_httpd_client_t* client) void qse_httpd_discardcontent (qse_httpd_t* httpd, qse_htre_t* req) { - req->flags |= QSE_HTRE_DISCARDED; - /* clear the content buffer in case it has received contents - * partially already */ - qse_mbs_clear (&req->content); + qse_htre_discardcontent (req); +} + +void qse_httpd_completecontent (qse_httpd_t* httpd, qse_htre_t* req) +{ + qse_htre_completecontent (req); } #endif diff --git a/qse/lib/net/httpd_task.c b/qse/lib/net/httpd_task.c index 761bcd72..bc741b65 100644 --- a/qse/lib/net/httpd_task.c +++ b/qse/lib/net/httpd_task.c @@ -391,7 +391,7 @@ static qse_httpd_task_t* entask_error ( qse_httpd_task_t* qse_httpd_entaskerror ( qse_httpd_t* httpd, qse_httpd_client_t* client, - const qse_httpd_task_t* task, int code, const qse_htre_t* req) + const qse_httpd_task_t* task, int code, qse_htre_t* req) { return entask_error (httpd, client, task, code, qse_htre_getversion(req), req->attr.keepalive); } @@ -399,9 +399,9 @@ qse_httpd_task_t* qse_httpd_entaskerror ( /*------------------------------------------------------------------------*/ qse_httpd_task_t* qse_httpd_entaskcontinue ( qse_httpd_t* httpd, qse_httpd_client_t* client, - const qse_httpd_task_t* task, const qse_htre_t* req) + const qse_httpd_task_t* task, qse_htre_t* req) { - qse_http_version_t* version = qse_htre_getversion(req); + const qse_http_version_t* version = qse_htre_getversion(req); return qse_httpd_entaskformat ( httpd, client, task, QSE_MT("HTTP/%d.%d 100 Continue\r\n\r\n"), @@ -866,13 +866,17 @@ static int task_init_path ( } static QSE_INLINE int task_main_path_file ( - qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task, qse_foff_t filesize) + qse_httpd_t* httpd, qse_httpd_client_t* client, + qse_httpd_task_t* task, qse_foff_t filesize) { task_path_t* data = (task_path_t*)task->ctx; qse_httpd_task_t* x = task; qse_ubi_t handle; int oflags; +/* TODO: if you should deal with files on a network-mounted drive, + setting a trigger or non-blocking I/O are needed. */ + /* when it comes to the file size, using fstat after opening * can be more accurate. but this function uses information * set into the task context before the call to this function */ @@ -886,7 +890,8 @@ qse_printf (QSE_T("opening file %hs\n"), data->name); handle.i = QSE_OPEN (data->name, oflags, 0); if (handle.i <= -1) { - x = entask_error (httpd, client, x, 404, &data->version, data->keepalive); + x = entask_error ( + httpd, client, x, 404, &data->version, data->keepalive); goto no_file_send; } oflags = QSE_FCNTL (handle.i, F_GETFD, 0); @@ -907,7 +912,8 @@ qse_printf (QSE_T("opening file %hs\n"), data->name); if (data->range.from >= filesize) { - x = entask_error (httpd, client, x, 416, &data->version, data->keepalive); + x = entask_error ( + httpd, client, x, 416, &data->version, data->keepalive); goto no_file_send; } @@ -1104,7 +1110,7 @@ qse_httpd_task_t* qse_httpd_entaskpath ( qse_httpd_client_t* client, const qse_httpd_task_t* pred, const qse_mchar_t* name, - const qse_htre_t* req) + qse_htre_t* req) { qse_httpd_task_t task; task_path_t data; @@ -1143,7 +1149,7 @@ typedef struct task_cgi_arg_t task_cgi_arg_t; struct task_cgi_arg_t { const qse_mchar_t* path; - const qse_htre_t* req; + qse_htre_t* req; int nph; }; @@ -1270,7 +1276,7 @@ static int cgi_htrd_peek_request (qse_htrd_t* htrd, qse_htre_t* req) } else { - qse_mchar_t* location; + const qse_mchar_t* location; qse_mchar_t buf[128]; location = qse_htre_getheaderval (req, QSE_MT("Location")); @@ -1364,11 +1370,10 @@ static qse_htrd_recbs_t cgi_htrd_cbs = static qse_env_t* makecgienv ( qse_httpd_t* httpd, qse_httpd_client_t* client, - const qse_htre_t* req, const qse_mchar_t* path) + const qse_htre_t* req, const qse_mchar_t* path, qse_size_t content_length) { /* TODO: error check */ qse_env_t* env; - cgi_req_hdr_ctx_t ctx; env = qse_env_open (httpd->mmgr, 0, 0); if (env == QSE_NULL) goto oops; @@ -1404,13 +1409,10 @@ qse_mbsxncpy (tmp, QSE_COUNTOF(tmp), qse_htre_getqpathptr(req), qse_htre_getqpat qse_env_insertmbs ( env, QSE_MT("REQUEST_METHOD"), qse_htre_getqmethodname(req)); - - if (req->attr.content_length_set) { qse_mchar_t tmp[64]; qse_fmtuintmaxtombs ( - tmp, QSE_COUNTOF(tmp), - req->attr.content_length, 10, + tmp, QSE_COUNTOF(tmp), content_length, 10, -1, QSE_MT('\0'), QSE_NULL); qse_env_insertmbs (env, QSE_MT("CONTENT_LENGTH"), tmp); } @@ -1507,6 +1509,9 @@ static int cgi_snatch_content ( { task_cgi_t* cgi = (task_cgi_t*)ctx; +if (ptr) qse_printf (QSE_T("!!!SNATCHING [%.*hs]\n"), len, ptr); +else qse_printf (QSE_T("!!!SNATCHING DONE\n")); + if (ptr == QSE_NULL) { /* request ended. this could be a real end or @@ -1514,19 +1519,20 @@ static int cgi_snatch_content ( QSE_ASSERT (len == 0); cgi->req = QSE_NULL; } - else + else if (!cgi->reqfwderr) { /* push the contents to the own buffer */ if (qse_mbs_ncat (cgi->reqcon, ptr, len) == (qse_size_t)-1) { return -1; } +qse_printf (QSE_T("!!!SNACHED [%.*hs]\n"), len, ptr); } return 0; } -static int cgi_forward_content (qse_httpd_task_t* task) +static void cgi_forward_content (qse_httpd_t* httpd, qse_httpd_task_t* task) { task_cgi_t* cgi = (task_cgi_t*)task->ctx; @@ -1534,50 +1540,71 @@ static int cgi_forward_content (qse_httpd_task_t* task) if (QSE_MBS_LEN(cgi->reqcon) > 0) { - qse_ssize_t n; - - if (!cgi->reqfwderr) + if (cgi->reqfwderr) { -qse_printf (QSE_T("@@@@@@@WRITING %d bytes TO CGI\n"), (int)QSE_MBS_LEN(cgi->reqcon)); - n = qse_pio_write ( - cgi->pio, QSE_PIO_IN, - QSE_MBS_PTR(cgi->reqcon), - QSE_MBS_LEN(cgi->reqcon) - ); + qse_mbs_clear (cgi->reqcon); + } + else + { + qse_ubi_t handle; + qse_ssize_t n; + + /* TODO: handle = qse_pio_getubihandle(); */ + handle.i = qse_pio_gethandle (cgi->pio, QSE_PIO_IN); + + n = httpd->cbs->mux.writable (httpd, handle, 0); + if (n >= 1) + { + /* writable */ +qse_printf (QSE_T("@@@@@@@@@@WRITING[%.*hs]\n"), + (int)QSE_MBS_LEN(cgi->reqcon), + QSE_MBS_PTR(cgi->reqcon)); + n = qse_pio_write ( + cgi->pio, QSE_PIO_IN, + QSE_MBS_PTR(cgi->reqcon), + QSE_MBS_LEN(cgi->reqcon) + ); +/* TODO: improve performance.. instead of copying the remaing part to the head all the time.. +grow the buffer to a certain limit. */ + if (n > 0) qse_mbs_del (cgi->reqcon, 0, n); + } + if (n <= -1) { qse_printf (QSE_T("@@@@@@@@WRITE TO CGI FAILED\n")); /* TODO: logging ... */ cgi->reqfwderr = 1; - if (cgi->req) cgi->req->flags |= QSE_HTRE_DISCARDED; + qse_mbs_clear (cgi->reqcon); + if (cgi->req) qse_htre_discardcontent (cgi->req); } } - -/* TODO: performance improvement... -deleting keeps on moving contents to the head... -can't we grow the buffer to a certain limit? -if the limit is reached, copy the tail to the head... */ - /* can write return 0? */ - qse_mbs_del (cgi->reqcon, 0, n); } else if (cgi->req == QSE_NULL) { /* no more request content */ qse_printf (QSE_T("@@@@@@@@NOTHING MORE TO WRITE TO CGI\n")); task->trigger_mask &= - ~(QSE_HTTPD_TASK_TRIGGER_WRITE | - QSE_HTTPD_TASK_TRIGGER_WRITABLE); + ~(QSE_HTTPD_TASK_TRIGGER_RELAY | + QSE_HTTPD_TASK_TRIGGER_RELAYABLE); } - - return 0; } - static int task_init_cgi ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { - task_cgi_t* cgi = (task_cgi_t*)qse_httpd_gettaskxtn (httpd, task); - task_cgi_arg_t* arg = (task_cgi_arg_t*)task->ctx; + task_cgi_t* cgi; + task_cgi_arg_t* arg; + qse_size_t content_length; + qse_size_t len; + const qse_mchar_t* ptr; + + cgi = (task_cgi_t*)qse_httpd_gettaskxtn (httpd, task); + arg = (task_cgi_arg_t*)task->ctx; + +/* TODO: can content length be a different type??? + * maybe qse_uintmax_t.... it thinks the data size can be larger than the max pointer size + * qse_htre_t and qse_htrd_t also needs changes to support it + */ QSE_MEMSET (cgi, 0, QSE_SIZEOF(*cgi)); qse_mbscpy ((qse_mchar_t*)(cgi + 1), arg->path); @@ -1586,41 +1613,100 @@ static int task_init_cgi ( cgi->keepalive = arg->req->attr.keepalive; cgi->nph = arg->nph; - if (!(arg->req->flags & QSE_HTRE_DISCARDED)) + if (arg->req->state & QSE_HTRE_DISCARDED) { - /* CGI entasking is invoked probably from the peek handler - * that was triggered after the request header is received - * in principle. In that case, arrange to forward content - * bypassing the buffer in the request object itself. */ - const qse_mchar_t* ptr = qse_htre_getcontentptr(arg->req); - qse_size_t len = qse_htre_getcontentlen(arg->req); +qse_printf (QSE_T("XXXXXXXXXXXXXXXXX\n")); + content_length = 0; + goto done; + } + len = qse_htre_getcontentlen(arg->req); + if ((arg->req->state & QSE_HTRE_COMPLETED) && len <= 0) + { +qse_printf (QSE_T("YYYYYYYYYYYYYYYYy\n")); + /* the content part is completed and no content + * in the content buffer. there is nothing to forward */ + content_length = 0; + goto done; + } + + if (!(arg->req->state & QSE_HTRE_COMPLETED) && + !arg->req->attr.content_length_set) + { +qse_printf (QSE_T("ZZZZZZZZZZZZZZZ\n")); + /* if the request is not completed and doesn't have + * content-length set, it's not really possible to + * pass the content. this function, however, allows + * such a request to entask a cgi script dropping the + * content */ + qse_htre_discardcontent (arg->req); + content_length = 0; + } + else + { /* create a buffer to hold request content from the client * and copy content received already */ cgi->reqcon = qse_mbs_open (httpd->mmgr, 0, (len < 512? 512: len)); - if (cgi->reqcon == QSE_NULL || - qse_mbs_ncpy (cgi->reqcon, ptr, len) == (qse_size_t)-1) + if (cgi->reqcon == QSE_NULL) goto oops; + + ptr = qse_htre_getcontentptr(arg->req); + if (qse_mbs_ncpy (cgi->reqcon, ptr, len) == (qse_size_t)-1) { - cgi->init_failed = 1; + qse_mbs_close (cgi->reqcon); + cgi->reqcon = QSE_NULL; + goto oops; } - else if (!(arg->req->flags & QSE_HTRE_COMPLETED)) + + if (arg->req->state & QSE_HTRE_COMPLETED) { + /* no furthur forwarding is needed. + * even a chunked request entaksed when completed + * should reach here. if content-length is set + * the length should match len. */ + QSE_ASSERT (len > 0); + QSE_ASSERT (!arg->req->attr.content_length_set || + (arg->req->attr.content_length_set && arg->req->attr.content_length == len)); +qse_printf (QSE_T("HHHHHHHHHHHHHHHHhh %d\n"), (int)len); + content_length = len; + } + else + { + /* CGI entasking is invoked probably from the peek handler + * that was triggered after the request header is received. + * you can know this because the request is not completed. + * In this case, arrange to forward content + * bypassing the buffer in the request object itself. */ + /* TODO: callback chain instead of a single pointer??? -if the request is already set up with a callback, something will go wrong. - */ + if the request is already set up with a callback, something will go wrong. +*/ /* set up a callback to be called when the request content - * is fed to the htrd reader */ - cgi->req = (qse_htre_t*)arg->req; + * is fed to the htrd reader. qse_htre_addcontent() called + * by htrd invokes this callback. */ + cgi->req = arg->req; qse_htre_setconcb (cgi->req, cgi_snatch_content, cgi); + + QSE_ASSERT (arg->req->attr.content_length_set); + content_length = arg->req->attr.content_length; +qse_printf (QSE_T("TTTTTTTTTTTTTTTTTTTT %d\n"), (int)content_length); } } - - if (!cgi->init_failed) - { - cgi->env = makecgienv (httpd, client, arg->req, arg->path); - if (cgi->env == QSE_NULL) cgi->init_failed = 1; - } +done: + cgi->env = makecgienv (httpd, client, arg->req, arg->path, content_length); + if (cgi->env == QSE_NULL) goto oops; + + /* i don't set triggers yet. triggers will be set task_main_cgi(). + * this way, task_main_cgi() is called regardless of data availability */ + + task->ctx = cgi; + return 0; + +oops: + /* since a new task can't be added in the initializer, + * i mark that initialization failed and let task_main_cgi() + * add an error task */ + cgi->init_failed = 1; task->ctx = cgi; return 0; } @@ -1659,9 +1745,9 @@ static int task_main_cgi_5 ( QSE_ASSERT (cgi->pio != QSE_NULL); - if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE) { - if (cgi_forward_content (task) <= -1) return -1; + cgi_forward_content (httpd, task); /* if forwarding didn't finish, something is not really right... * so long as the output from CGI is finished, no more forwarding * is performed */ @@ -1692,9 +1778,9 @@ static int task_main_cgi_4 ( QSE_ASSERT (cgi->pio != QSE_NULL); - if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE) { - if (cgi_forward_content (task) <= -1) return -1; + cgi_forward_content (httpd, task); } if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READABLE) @@ -1744,6 +1830,8 @@ static int task_main_cgi_4 ( cgi->buf[cgi->buflen++] = QSE_MT('\n'); task->main = task_main_cgi_5; + /* ok to chain-call since this task is called + * if the client-side is writable */ return task_main_cgi_5 (httpd, client, task); } @@ -1781,6 +1869,8 @@ static int task_main_cgi_4 ( ~(QSE_HTTPD_TASK_TRIGGER_READ | QSE_HTTPD_TASK_TRIGGER_READABLE); task->main = task_main_cgi_5; + /* ok to chain-call since this task is called + * if the client-side is writable */ return task_main_cgi_5 (httpd, client, task); } @@ -1796,24 +1886,25 @@ static int task_main_cgi_4 ( return -1; } - #if 0 - qse_printf (QSE_T("CGI_4 SEND [%.*hs]\n"), (int)cgi->buflen, cgi->buf); - #endif +#if 0 +qse_printf (QSE_T("CGI_4 SEND [%.*hs]\n"), (int)cgi->buflen, cgi->buf); +#endif httpd->errnum = QSE_HTTPD_ENOERR; n = httpd->cbs->client.send (httpd, client, cgi->buf, cgi->buflen); if (n <= -1) { /* can't return internal server error any more... */ /* TODO: logging ... */ + qse_printf (QSE_T("CGI SEND FAILURE\n")); return -1; } 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 +#if 0 +qse_printf (QSE_T("CGI SEND DONE\n")); +#endif } return 1; @@ -1829,9 +1920,9 @@ static int task_main_cgi_3 ( qse_ssize_t n; qse_size_t count; - if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE) { - if (cgi_forward_content (task) <= -1) return -1; + cgi_forward_content (httpd, task); } if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READABLE) @@ -1843,7 +1934,11 @@ qse_printf (QSE_T("[cgi_3 sending %d bytes]\n"), (int)count); httpd->errnum = QSE_HTTPD_ENOERR; n = httpd->cbs->client.send (httpd, client, cgi->res_ptr, count); - if (n <= -1) return -1; + if (n <= -1) + { +qse_printf (QSE_T("[cgi-3 send failure....\n")); + return -1; + } cgi->res_left -= n; if (cgi->res_left <= 0) @@ -1877,10 +1972,10 @@ static int task_main_cgi_2 ( QSE_ASSERT (cgi->pio != QSE_NULL); qse_printf (QSE_T("[cgi_2 ]\n")); - if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE) { qse_printf (QSE_T("[cgi_2 write]\n")); - if (cgi_forward_content (task) <= -1) return -1; + cgi_forward_content (httpd, task); } if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READABLE) @@ -1896,17 +1991,15 @@ qse_printf (QSE_T("[cgi_2 read]\n")); { /* can't return internal server error any more... */ /* TODO: logging ... */ - return -1; + goto oops; } if (n == 0) { /* end of output from cgi before it has seen a header. * the cgi script must be crooked. */ /* TODO: logging */ - task->trigger_mask &= - ~(QSE_HTTPD_TASK_TRIGGER_READ | - QSE_HTTPD_TASK_TRIGGER_READABLE); - return -1; +qse_printf (QSE_T("#####PREMATURE EOF FROM CHILD\n")); + goto oops; } cgi->buflen += n; @@ -1914,7 +2007,8 @@ qse_printf (QSE_T("[cgi_2 read]\n")); if (qse_htrd_feed (cgi->htrd, cgi->buf, cgi->buflen) <= -1) { /* TODO: logging */ - return -1; +qse_printf (QSE_T("#####INVALID HEADER FROM FROM CHILD [%.*hs]\n"), (int)cgi->buflen, cgi->buf); + goto oops; } cgi->buflen = 0; @@ -1932,13 +2026,18 @@ qse_printf (QSE_T("[cgi_2 read]\n")); */ if (cgi->disconnect && - qse_httpd_entaskdisconnect (httpd, client, task) == QSE_NULL) return -1; + qse_httpd_entaskdisconnect (httpd, client, task) == QSE_NULL) + { + goto oops; + } cgi->res_ptr = QSE_MBS_PTR(cgi->res); cgi->res_left = QSE_MBS_LEN(cgi->res); qse_printf (QSE_T("TRAILING DATA=[%.*hs]\n"), (int)QSE_MBS_LEN(cgi->res), QSE_MBS_PTR(cgi->res)); task->main = task_main_cgi_3; + /* ok to chain-call since this task is called + * only if the client-side is writable */ return task_main_cgi_3 (httpd, client, task); } @@ -1946,6 +2045,9 @@ qse_printf (QSE_T("TRAILING DATA=[%.*hs]\n"), (int)QSE_MBS_LEN(cgi->res), QSE_MB /* complete headers not seen yet. i need to be called again */ return 1; + +oops: + return (entask_error (httpd, client, task, 500, &cgi->version, cgi->keepalive) == QSE_NULL)? -1: 0; } static int task_main_cgi ( @@ -1953,23 +2055,17 @@ static int task_main_cgi ( { task_cgi_t* cgi = (task_cgi_t*)task->ctx; int pio_options; - int error_code = 500; if (cgi->init_failed) goto oops; - if (QSE_ACCESS (cgi->path, X_OK) == -1) - { - error_code = (errno == EACCES)? 403: 404; - goto oops; - } - if (cgi->nph) { /* i cannot know how long the content will be * since i don't parse the header. so i have to close * the connection regardless of content-length or transfer-encoding * in the actual header. */ - if (qse_httpd_entaskdisconnect (httpd, client, task) == QSE_NULL) return -1; + if (qse_httpd_entaskdisconnect ( + httpd, client, task) == QSE_NULL) goto oops; } else { @@ -1990,8 +2086,11 @@ static int task_main_cgi ( if (cgi->res == QSE_NULL) goto oops; } - pio_options = QSE_PIO_READOUT | QSE_PIO_WRITEIN | - QSE_PIO_ERRTONUL | QSE_PIO_MBSCMD; + pio_options = QSE_PIO_READOUT | QSE_PIO_WRITEIN | QSE_PIO_MBSCMD; + if (httpd->option & QSE_HTTPD_CGIERRTONUL) + pio_options |= QSE_PIO_ERRTONUL; + else + pio_options |= QSE_PIO_ERRTOOUT; if (httpd->option & QSE_HTTPD_CGINOCLOEXEC) pio_options |= QSE_PIO_NOCLOEXEC; @@ -2010,8 +2109,24 @@ HANDLE for win32??? task->trigger[0].i = qse_pio_gethandle (cgi->pio, QSE_PIO_OUT); if (cgi->reqcon) { + /* not meaningful to check writability to the child process + * in the main loop. it is checked in cgi_forward_content(). + * so the following 2 lines are commented out. task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITE; - task->trigger[1].i = qse_pio_gethandle (cgi->pio, QSE_PIO_IN); + task->trigger[1].i = qse_pio_gethandle (cgi->pio, QSE_PIO_IN);*/ + + task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_RELAY; + task->trigger[1].i = client->handle.i; + } + + if (cgi->reqcon) + { + /* since i didn't set triggers in the initializer (task_init_cgi()), + * it is possible that some contents has been read in already, + * forward them first. cgi_forward_content() is called after + * triggers are added above because cgi_forwrad_content() + * manipulates triggers when a forwarding error occurs. */ + cgi_forward_content (httpd, task); } task->main = cgi->nph? task_main_cgi_4: task_main_cgi_2; @@ -2032,26 +2147,34 @@ oops: cgi->htrd = QSE_NULL; } - return (entask_error (httpd, client, task, error_code, &cgi->version, cgi->keepalive) == QSE_NULL)? -1: 0; + return (entask_error (httpd, client, task, 500, &cgi->version, cgi->keepalive) == QSE_NULL)? -1: 0; } /* TODO: global option or individual paramter for max cgi lifetime * non-blocking pio read ... */ -qse_httpd_task_t* qse_httpd_entaskcgi ( - qse_httpd_t* httpd, - qse_httpd_client_t* client, - const qse_httpd_task_t* pred, - const qse_mchar_t* path, - const qse_htre_t* req) +static QSE_INLINE qse_httpd_task_t* entask_cgi ( + qse_httpd_t* httpd, qse_httpd_client_t* client, + const qse_httpd_task_t* pred, const qse_mchar_t* path, + qse_htre_t* req, int nph) { qse_httpd_task_t task; task_cgi_arg_t arg; + int x; + +/* TODO: NEED TO CHECK IF it's a regular file and executable?? +directory may be treated as executable??? +*/ + x = httpd->cbs->path.executable (httpd, path); + if (x == 0) + return qse_httpd_entaskerror (httpd, client, pred, 403, req); + else if (x <= -1) + return qse_httpd_entaskerror (httpd, client, pred, 404, req); arg.path = path; arg.req = req; - arg.nph = 0; + arg.nph = nph; QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); task.init = task_init_cgi; @@ -2065,33 +2188,20 @@ qse_httpd_task_t* qse_httpd_entaskcgi ( ); } +qse_httpd_task_t* qse_httpd_entaskcgi ( + qse_httpd_t* httpd, qse_httpd_client_t* client, + const qse_httpd_task_t* pred, const qse_mchar_t* path, qse_htre_t* req) +{ + return entask_cgi (httpd, client, pred, path, req, 0); +} + qse_httpd_task_t* qse_httpd_entasknph ( - qse_httpd_t* httpd, - qse_httpd_client_t* client, - const qse_httpd_task_t* pred, - const qse_mchar_t* path, - const qse_htre_t* req) + qse_httpd_t* httpd, qse_httpd_client_t* client, + const qse_httpd_task_t* pred, const qse_mchar_t* path, qse_htre_t* req) { - qse_httpd_task_t task; - task_cgi_arg_t arg; - - arg.path = path; - arg.req = req; - arg.nph = 1; - - QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); - task.init = task_init_cgi; - task.fini = task_fini_cgi; - task.main = task_main_cgi; - task.ctx = &arg; - - return qse_httpd_entask ( - httpd, client, pred, &task, - QSE_SIZEOF(task_cgi_t) + ((qse_mbslen(path) + 1) * QSE_SIZEOF(*path)) - ); + return entask_cgi (httpd, client, pred, path, req, 1); } - /*------------------------------------------------------------------------*/ #if 0 diff --git a/qse/samples/net/http01.c b/qse/samples/net/http01.c index 47b105ff..e9f612fa 100644 --- a/qse/samples/net/http01.c +++ b/qse/samples/net/http01.c @@ -10,6 +10,9 @@ #include #if defined(_WIN32) # include +#else +# include +# include #endif #include @@ -24,7 +27,6 @@ #if defined(HAVE_SYS_SENDFILE_H) # include #endif -#include #if defined(HAVE_SENDFILE) && defined(HAVE_SENDFILE64) # if !defined(_LP64) && (QSE_SIZEOF_VOID_P<8) && defined(HAVE_SENDFILE64) @@ -179,6 +181,55 @@ static void fini_xtn_ssl (httpd_xtn_t* xtn) CRYPTO_cleanup_all_ex_data (); } +/* ------------------------------------------------------------------- */ + +static int mux_readable (qse_httpd_t* httpd, qse_ubi_t handle, qse_ntoff_t msec) +{ + fd_set r; + struct timeval tv, * tvp; + + if (msec >= 0) + { + tv.tv_sec = (msec / 1000); + tv.tv_usec = ((msec % 1000) * 1000); + tvp = &tv; + } + else tvp = QSE_NULL; + + FD_ZERO (&r); + FD_SET (handle.i, &r); + + return select (handle.i + 1, &r, QSE_NULL, QSE_NULL, tvp); +} + +static int mux_writable (qse_httpd_t* httpd, qse_ubi_t handle, qse_ntoff_t msec) +{ + fd_set w; + struct timeval tv, * tvp; + + if (msec >= 0) + { + tv.tv_sec = (msec / 1000); + tv.tv_usec = ((msec % 1000) * 1000); + tvp = &tv; + } + else tvp = QSE_NULL; + + FD_ZERO (&w); + FD_SET (handle.i, &w); + + return select (handle.i + 1, QSE_NULL, &w, QSE_NULL, tvp); +} + +/* ------------------------------------------------------------------- */ + +static int path_executable (qse_httpd_t* httpd, const qse_mchar_t* path) +{ + if (access (path, X_OK) == -1) + return (errno == EACCES)? 0 /*no*/: -1 /*error*/; + return 1; /* yes */ +} + /* ------------------------------------------------------------------- */ static qse_ssize_t client_recv ( qse_httpd_t* httpd, qse_httpd_client_t* client, @@ -300,13 +351,13 @@ static int process_request ( content_received = (qse_htre_getcontentlen(req) > 0); qse_printf (QSE_T("================================\n")); -qse_printf (QSE_T("[%lu] %hs REQUEST ==> [%hs] version[%d.%d] method[%d]\n"), +qse_printf (QSE_T("[%lu] %hs REQUEST ==> [%hs] version[%d.%d] method[%hs]\n"), (unsigned long)time(NULL), (peek? QSE_MT("PEEK"): QSE_MT("HANDLE")), qse_htre_getqpathptr(req), qse_htre_getmajorversion(req), qse_htre_getminorversion(req), - method + qse_htre_getqmethodname(req) ); if (qse_htre_getqparamlen(req) > 0) qse_printf (QSE_T("PARAMS ==> [%hs]\n"), qse_htre_getqparamptr(req)); qse_htb_walk (&req->hdrtab, walk, QSE_NULL); @@ -364,9 +415,44 @@ if (qse_htre_getcontentlen(req) > 0) if (peek) { /* cgi */ - task = qse_httpd_entaskcgi ( - httpd, client, QSE_NULL, qpath, req); - if (task == QSE_NULL) goto oops; + if (req->attr.chunked) + { +qse_printf (QSE_T("chunked cgi... delaying until contents are received\n")); + #if 0 + req->attr.keepalive = 0; + task = qse_httpd_entaskerror ( + httpd, client, QSE_NULL, 411, req); + /* 411 can't keep alive */ + if (task) qse_httpd_entaskdisconnect (httpd, client, QSE_NULL); + #endif + } + else if (method == QSE_HTTP_POST && + !req->attr.content_length_set) + { + req->attr.keepalive = 0; + task = qse_httpd_entaskerror ( + httpd, client, QSE_NULL, 411, req); + /* 411 can't keep alive */ + if (task) qse_httpd_entaskdisconnect (httpd, client, QSE_NULL); + } + else + { + task = qse_httpd_entaskcgi ( + httpd, client, QSE_NULL, qpath, req); + if (task == QSE_NULL) goto oops; + } + } + else + { + /* to support the chunked request, + * i need to wait until it's compelted and invoke cgi */ + if (req->attr.chunked) + { +qse_printf (QSE_T("Entasking chunked CGI...\n")); + task = qse_httpd_entaskcgi ( + httpd, client, QSE_NULL, qpath, req); + if (task == QSE_NULL) goto oops; + } } return 0; } @@ -446,9 +532,18 @@ int list_directory (qse_httpd_t* httpd, const qse_mchar_t* path) static qse_httpd_cbs_t httpd_cbs = { + /* multiplexer */ + { mux_readable, mux_writable }, + + /* path operation */ + { path_executable }, + /* client connection */ - { client_recv, client_send, client_sendfile, - client_accepted, client_closed }, + { client_recv, + client_send, + client_sendfile, + client_accepted, + client_closed }, /* http request */ peek_request, @@ -508,7 +603,8 @@ int httpd_main (int argc, qse_char_t* argv[]) signal (SIGINT, sigint); signal (SIGPIPE, SIG_IGN); - ret = qse_httpd_loop (httpd, &httpd_cbs, 0); + qse_httpd_setoption (httpd, QSE_HTTPD_CGIERRTONUL); + ret = qse_httpd_loop (httpd, &httpd_cbs); signal (SIGINT, SIG_DFL); signal (SIGPIPE, SIG_DFL);