fixed various bugs in handling cgi

This commit is contained in:
hyung-hwan 2012-02-09 12:54:00 +00:00
parent 517107ab79
commit 1ab75927d2
7 changed files with 542 additions and 608 deletions

View File

@ -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

View File

@ -24,6 +24,7 @@
#include <qse/types.h>
#include <qse/macros.h>
#include <qse/net/htre.h>
#include <qse/cmn/time.h>
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
);
/* -------------------------------------------- */

View File

@ -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)

View File

@ -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);
}

View File

@ -35,14 +35,6 @@
#include <errno.h>
#include <sys/time.h>
#if defined(HAVE_PTHREAD)
#include <pthread.h>
#endif
#if 0
#include <openssl.h>
#endif
#include <qse/cmn/stdio.h>
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

View File

@ -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

View File

@ -10,6 +10,9 @@
#include <locale.h>
#if defined(_WIN32)
# include <windows.h>
#else
# include <unistd.h>
# include <errno.h>
#endif
#include <openssl/ssl.h>
@ -24,7 +27,6 @@
#if defined(HAVE_SYS_SENDFILE_H)
# include <sys/sendfile.h>
#endif
#include <unistd.h>
#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);