added qse_httpd_geterrnum()/qse_httpd_seterrnum().

added more error codes to httpd.
added qse_pio_gethandleasubi().
enhanced sample file handlers
enhanced qse_htre_completecontent()/qse_htre_discardcontent()/qse_htre_addcontent()/qse_htre_clear()
fixed many cgi handling bugs
This commit is contained in:
hyung-hwan 2012-02-10 13:58:23 +00:00
parent 29a218eeb5
commit 3df521f7a9
9 changed files with 510 additions and 245 deletions

View File

@ -132,6 +132,8 @@ enum qse_pio_errnum_t
QSE_PIO_ECHILD, /**< the child is not valid */
QSE_PIO_EINTR, /**< interrupted */
QSE_PIO_EPIPE, /**< broken pipe */
QSE_PIO_EACCES, /**< access denied */
QSE_PIO_ENOENT, /**< no such file */
QSE_PIO_ESUBSYS /**< subsystem(system call) error */
};
typedef enum qse_pio_errnum_t qse_pio_errnum_t;
@ -316,6 +318,16 @@ qse_pio_hnd_t qse_pio_gethandle (
qse_pio_hid_t hid /**< handle ID */
);
/**
* The qse_pio_gethandleasubi() function gets a pipe handle wrapped
* in the #qse_ubi_t type.
* @return pipe handle
*/
qse_ubi_t qse_pio_gethandleasubi (
qse_pio_t* pio, /**< pio object */
qse_pio_hid_t hid /**< handle ID */
);
/**
* The qse_pio_getchild() function gets a process handle.
* @return process handle

View File

@ -126,12 +126,6 @@ struct qse_htre_t
#define qse_htre_setsmessagefromxstr(re,v) \
qse_htre_setstrfromxstr((re),qse_htre_getsmessage(re),(v))
/* NOTE: setcontent() doesn't execute concb. use this with care */
#define qse_htre_setcontentfromcstr(re,v) \
qse_htre_setstrfromcstr((re),qse_htre_getcontent(re),(v))
#define qse_htre_setcontentfromxstr(re,v) \
qse_htre_setstrfromxstr((re),qse_htre_getcontent(re),(v))
typedef int (*qse_htre_header_walker_t) (
qse_htre_t* re,
const qse_mchar_t* key,

View File

@ -34,13 +34,15 @@ enum qse_httpd_errnum_t
QSE_HTTPD_ENOERR,
QSE_HTTPD_ENOMEM,
QSE_HTTPD_EINVAL,
QSE_HTTPD_ENOENT,
QSE_HTTPD_EACCES,
QSE_HTTPD_EINTERN,
QSE_HTTPD_EIOMUX,
QSE_HTTPD_ESUBSYS,
QSE_HTTPD_ESOCKET,
QSE_HTTPD_EDISCON, /* client disconnnected */
QSE_HTTPD_EBADREQ, /* bad request */
QSE_HTTPD_ETASK,
QSE_HTTPD_ECOMCBS
QSE_HTTPD_ETASK
};
typedef enum qse_httpd_errnum_t qse_httpd_errnum_t;
@ -184,6 +186,15 @@ void qse_httpd_close (
qse_httpd_t* httpd
);
qse_httpd_errnum_t qse_httpd_geterrnum (
qse_httpd_t* httpd
);
void qse_httpd_seterrnum (
qse_httpd_t* httpd,
qse_httpd_errnum_t errnum
);
int qse_httpd_getoption (
qse_httpd_t* httpd
);
@ -214,11 +225,6 @@ int qse_httpd_addlistener (
const qse_char_t* uri
);
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

View File

@ -288,6 +288,35 @@ oops:
return -1;
}
static int assert_executable (qse_pio_t* pio, const qse_mchar_t* path)
{
qse_lstat_t st;
if (QSE_ACCESS(path, X_OK) <= -1)
{
if (errno == EACCES) pio->errnum = QSE_PIO_EACCES;
else if (errno == ENOENT) pio->errnum = QSE_PIO_ENOENT;
else if (errno == ENOMEM) pio->errnum = QSE_PIO_ENOMEM;
return -1;
}
if (QSE_LSTAT(path, &st) <= -1)
{
if (errno == EACCES) pio->errnum = QSE_PIO_EACCES;
else if (errno == ENOENT) pio->errnum = QSE_PIO_ENOENT;
else if (errno == ENOMEM) pio->errnum = QSE_PIO_ENOMEM;
return -1;
}
if (!S_ISREG(st.st_mode))
{
pio->errnum = QSE_PIO_EACCES;
return -1;
}
return 0;
}
static QSE_INLINE int is_fd_valid (int fd)
{
return fcntl (fd, F_GETFD) != -1 || errno != EBADF;
@ -572,7 +601,26 @@ int qse_pio_init (
);
QSE_MMGR_FREE (mmgr, dupcmd);
if (x == FALSE) goto oops;
if (x == FALSE)
{
DWORD e = GetLastError ();
switch (e)
{
case ERROR_ACCESS_DENIED:
pio->errnum = QSE_PIO_EACCES;
break;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
pio->errnum = QSE_PIO_ENOENT;
break;
case ERROR_NOT_ENOUGH_MEMORY:
case ERROR_OUTOFMEMORY:
pio->errnum = QSE_PIO_ENOMEM;
break;
}
}
}
if (windevnul != INVALID_HANDLE_VALUE)
@ -871,6 +919,8 @@ int qse_pio_init (
cmd_file
);
/* TODO: translate error code ... */
QSE_MMGR_FREE (mmgr, cmd_line);
cmd_line = QSE_NULL;
@ -994,6 +1044,16 @@ int qse_pio_init (
}
if (make_param (pio, cmd, flags, &param) <= -1) goto oops;
/* check if the command(the command requested or /bin/sh) is
* exectuable to return an error without trying to execute it
* though this check alone isn't sufficient */
if (assert_executable (pio, param.argv[0]) <= -1)
{
free_param (pio, &param);
goto oops;
}
spawn_ret = posix_spawn(
&pid, param.argv[0], &fa, QSE_NULL, param.argv,
(env? qse_env_getarr(env): environ));
@ -1050,8 +1110,23 @@ int qse_pio_init (
goto oops;
}
if (make_param (pio, cmd, flags, &param) <= -1) goto oops;
/* check if the command(the command requested or /bin/sh) is
* exectuable to return an error without trying to execute it
* though this check alone isn't sufficient */
if (assert_executable (pio, param.argv[0]) <= -1)
{
free_param (pio, &param);
goto oops;
}
pid = QSE_FORK();
if (pid <= -1) goto oops;
if (pid <= -1)
{
free_param (pio, &param);
goto oops;
}
if (pid == 0)
{
@ -1145,7 +1220,7 @@ int qse_pio_init (
if (flags & QSE_PIO_DROPOUT) QSE_CLOSE(1);
if (flags & QSE_PIO_DROPERR) QSE_CLOSE(2);
if (make_param (pio, cmd, flags, &param) <= -1) goto child_oops;
/*if (make_param (pio, cmd, flags, &param) <= -1) goto child_oops;*/
QSE_EXECVE (param.argv[0], param.argv, (env? qse_env_getarr(env): environ));
free_param (pio, &param);
@ -1155,6 +1230,7 @@ int qse_pio_init (
}
/* parent */
free_param (pio, &param);
pio->child = pid;
if (flags & QSE_PIO_WRITEIN)
@ -1337,6 +1413,8 @@ const qse_char_t* qse_pio_geterrmsg (qse_pio_t* pio)
QSE_T("child process not valid"),
QSE_T("interruped"),
QSE_T("broken pipe"),
QSE_T("access denied"),
QSE_T("no such file"),
QSE_T("systeam call error"),
QSE_T("unknown error")
};
@ -1362,6 +1440,23 @@ qse_pio_hnd_t qse_pio_gethandle (qse_pio_t* pio, qse_pio_hid_t hid)
return pio->pin[hid].handle;
}
qse_ubi_t qse_pio_gethandleasubi (qse_pio_t* pio, qse_pio_hid_t hid)
{
qse_ubi_t handle;
#if defined(_WIN32)
handle.ptr = pio->pin[hid].handle;
#elif defined(__OS2__)
handle.ul = pio->pin[hid].handle;
#elif defined(__DOS__)
handle.i = pio->pin[hid].handle;
#else
handle.i = pio->pin[hid].handle;
#endif
return handle;
}
qse_pio_pid_t qse_pio_getchild (qse_pio_t* pio)
{
return pio->child;

View File

@ -45,11 +45,15 @@ void qse_htre_fini (qse_htre_t* re)
void qse_htre_clear (qse_htre_t* re)
{
if (!(re->state & QSE_HTRE_COMPLETED) &&
!(re->state & QSE_HTRE_DISCARDED))
{
if (re->concb)
{
re->concb (re, QSE_NULL, 0, re->concb_ctx); /* indicate end of content */
qse_htre_unsetconcb (re);
}
}
QSE_MEMSET (&re->version, 0, QSE_SIZEOF(re->version));
QSE_MEMSET (&re->attr, 0, QSE_SIZEOF(re->attr));
@ -117,25 +121,73 @@ 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;
/* see comments in qse_htre_discardcontent() */
if (re->state & (QSE_HTRE_COMPLETED | QSE_HTRE_DISCARDED)) return 0; /* skipped */
if (re->concb)
{
/* if the callback is set, the content goes to the callback. */
if (re->concb) return re->concb (re, ptr, len, re->concb_ctx);
if (re->concb (re, ptr, len, re->concb_ctx) <= -1) return -1;
}
else
{
/* 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;
if (qse_mbs_ncat (&re->content, ptr, len) == (qse_size_t)-1)
return -1;
}
return 1; /* added successfully */
}
void qse_htre_completecontent (qse_htre_t* re)
{
/* see comments in qse_htre_discardcontent() */
if (!(re->state & QSE_HTRE_COMPLETED) &&
!(re->state & QSE_HTRE_DISCARDED))
{
re->state |= QSE_HTRE_COMPLETED;
if (re->concb)
{
/* indicate end of content */
re->concb (re, QSE_NULL, 0, re->concb_ctx);
}
}
}
void qse_htre_discardcontent (qse_htre_t* re)
{
/* you can't discard this if it's completed.
* you can't complete this if it's discarded
* you can't add contents to this if it's completed or discarded
*/
if (!(re->state & QSE_HTRE_COMPLETED) &&
!(re->state & QSE_HTRE_DISCARDED))
{
re->state |= QSE_HTRE_DISCARDED;
/* qse_htre_addcontent()...
* qse_thre_setconcb()...
* qse_htre_discardcontent()... <-- POINT A.
*
* at point A, the content must contain something
* and concb is also set. for simplicity,
* clear the content buffer and invoke the callback
*
* likewise, you may produce many weird combinations
* of these functions. however, these functions are
* designed to serve a certain usage pattern not including
* weird combinations.
*/
qse_mbs_clear (&re->content);
if (re->concb)
{
/* indicate end of content */
re->concb (re, QSE_NULL, 0, re->concb_ctx);
}
}
}
void qse_htre_unsetconcb (qse_htre_t* re)

View File

@ -97,6 +97,16 @@ void qse_httpd_stop (qse_httpd_t* httpd)
httpd->stopreq = 1;
}
qse_httpd_errnum_t qse_httpd_geterrnum (qse_httpd_t* httpd)
{
return httpd->errnum;
}
void qse_httpd_seterrnum (qse_httpd_t* httpd, qse_httpd_errnum_t errnum)
{
httpd->errnum = errnum;
}
int qse_httpd_getoption (qse_httpd_t* httpd)
{
return httpd->option;
@ -675,7 +685,7 @@ static void perform_task (qse_httpd_t* httpd, qse_httpd_client_t* client)
static int read_from_client (qse_httpd_t* httpd, qse_httpd_client_t* client)
{
qse_mchar_t buf[1024];
qse_mchar_t buf[2048]; /* TODO: adjust this buffer size */
qse_ssize_t m;
QSE_ASSERT (httpd->cbs->client.recv != QSE_NULL);
@ -803,41 +813,30 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n"));
QSE_ASSERT (httpd->cbs->client.accepted != QSE_NULL);
int x = httpd->cbs->client.accepted (httpd, client); /* is this correct???? what if ssl handshaking got stalled because writing failed in SSL_accept()? */
if (x >= 1) client->ready = 1;
else if (x <= -1) goto bad_client;
}
else
{
if (read_from_client (httpd, client) <= -1)
{
bad_client:
if (httpd->threaded)
{
/* let the writing part handle it,
* probably in the next iteration */
qse_httpd_markbadclient (httpd, client);
shutdown (client->handle.i, SHUT_RDWR);
}
else
else if (x <= -1)
{
delete_from_client_array (httpd, fd);
continue; /* don't need to go to the writing part */
continue;
}
}
else
{
// TODO: any way to suspend read while a request is being processed???
if (read_from_client (httpd, client) <= -1)
{
delete_from_client_array (httpd, fd);
continue;
}
}
}
if (!httpd->threaded)
{
if (client->bad)
{
/*shutdown (client->handle.i, SHUT_RDWR);*/
delete_from_client_array (httpd, fd);
}
else if (client->task.queue.head)
/* perform a client task enqued to a client */
if (client->task.queue.head)
{
qse_httpd_task_t* task;
int perform = 0;
qse_printf (QSE_T(".....CLIENT %d HAS TASK\n"), fd);
task = &client->task.queue.head->task;
task->trigger_mask &=
~(QSE_HTTPD_TASK_TRIGGER_READABLE |
@ -848,17 +847,25 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n"));
!(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) &&
!(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE))
{
qse_printf (QSE_T(".....NO TRIGGER ACTION....\n"));
/* no trigger set. set the flag to
* non-readable and non-writable */
perform = 1;
}
else
{
if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ)
qse_printf (QSE_T(".....CLIENT %d HAS READ TREIGGER %d\n"), fd, task->trigger[0].i);
if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY)
qse_printf (QSE_T(".....CLIENT %d HAS RELAY TREIGGER %d\n"), fd, task->trigger[1].i);
if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE)
qse_printf (QSE_T(".....CLIENT %d HAS WRITE TREIGGER %d\n"), fd, task->trigger[2].i);
if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) &&
FD_ISSET(task->trigger[0].i, &r))
{
task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_READABLE;
perform = 1;
qse_printf (QSE_T(".....TRIGGER READABLE....\n"));
}
if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) &&
@ -866,13 +873,15 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n"));
{
task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_RELAYABLE;
perform = 1;
qse_printf (QSE_T(".....TRIGGER RELAYABLE....\n"));
}
if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) &&
if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE) &&
FD_ISSET(task->trigger[2].i, &w))
{
task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITABLE;
perform = 1;
qse_printf (QSE_T(".....TRIGGER WRITABLE....\n"));
}
}
@ -883,6 +892,7 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n"));
* 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);
}
}
@ -1109,18 +1119,7 @@ qse_httpd_task_t* qse_httpd_entask (
const qse_httpd_task_t* pred, const qse_httpd_task_t* task,
qse_size_t xtnsize)
{
qse_httpd_task_t* ret;
ret = enqueue_task (httpd, client, pred, task, xtnsize);
if (ret == QSE_NULL) client->bad = 1; /* mark this client bad */
return ret;
}
void qse_httpd_markbadclient (qse_httpd_t* httpd, qse_httpd_client_t* client)
{
/* mark that something is wrong in processing requests from this client.
* this client could be bad... or the system could encounter some errors
* like memory allocation failure */
client->bad = 1;
return enqueue_task (httpd, client, pred, task, xtnsize);
}
void qse_httpd_discardcontent (qse_httpd_t* httpd, qse_htre_t* req)

View File

@ -30,10 +30,6 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#if defined(HAVE_PTHREAD)
# include <pthread.h>
#endif
#ifndef SHUT_RDWR
# define SHUT_RDWR 2
#endif
@ -64,17 +60,14 @@ struct qse_httpd_client_t
qse_ubi_t handle2;
int ready;
int bad;
int secure;
int bad;
sockaddr_t local_addr;
sockaddr_t remote_addr;
qse_htrd_t* htrd;
struct
{
#if defined(HAVE_PTHREAD)
pthread_mutex_t mutex;
#endif
struct
{
int count;
@ -120,23 +113,13 @@ struct qse_httpd_t
int option;
int stopreq;
int threaded;
struct
{
#if defined(HAVE_PTHREAD)
int pfd[2];
pthread_mutex_t mutex;
pthread_cond_t cond;
#endif
client_array_t array;
} client;
struct
{
#if defined(HAVE_PTHREAD)
pthread_mutex_t mutex;
#endif
listener_t* list;
fd_set set;
int max;

View File

@ -272,7 +272,8 @@ qse_httpd_task_t* qse_httpd_entaskformat (
/* vsnprintf returns the number of characters that would
* have been written not including the terminating '\0'
* if the _data buffer were large enough */
buf = (qse_mchar_t*) qse_httpd_allocmem (httpd, (bytes_req + 1) * QSE_SIZEOF(*buf));
buf = (qse_mchar_t*) qse_httpd_allocmem (
httpd, (bytes_req + 1) * QSE_SIZEOF(*buf));
if (buf == QSE_NULL) return QSE_NULL;
va_start (ap, fmt);
@ -287,7 +288,6 @@ qse_httpd_task_t* qse_httpd_entaskformat (
{
/* something got wrong ... */
qse_httpd_freemem (httpd, buf);
httpd->errnum = QSE_HTTPD_EINTERN;
return QSE_NULL;
}
@ -1116,17 +1116,17 @@ qse_httpd_task_t* qse_httpd_entaskpath (
{
qse_httpd_task_t task;
task_path_t data;
const qse_mchar_t* range;
const qse_mchar_t* tmp;
QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
data.name = name;
data.version = *qse_htre_getversion(req);
data.keepalive = req->attr.keepalive;
range = qse_htre_getheaderval(req, QSE_MT("Range"));
if (range)
tmp = qse_htre_getheaderval(req, QSE_MT("Range"));
if (tmp)
{
if (qse_parsehttprange (range, &data.range) <= -1)
if (qse_parsehttprange (tmp, &data.range) <= -1)
{
return entask_error (httpd, client, pred, 416, &data.version, data.keepalive);
}
@ -1196,7 +1196,7 @@ static int task_main_fseg (
httpd, client, ctx->handle, &ctx->offset, count);
if (n <= -1)
{
// HANDLE EGAIN specially???
/* HANDLE EGAIN specially??? */
return -1; /* TODO: any logging */
}
@ -1268,17 +1268,17 @@ static QSE_INLINE int task_main_file (
/* 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 */
qse_printf (QSE_T("opening file %hs\n"), file->path);
httpd->errnum = QSE_HTTPD_ENOERR;
if (httpd->cbs->file.ropen (httpd, file->path, &handle, &filesize) <= -1)
{
/* TODO: depending on the error type, either 404 or 403??? */
int http_errnum;
http_errnum = (httpd->errnum == QSE_HTTPD_ENOENT)? 404:
(httpd->errnum == QSE_HTTPD_EACCES)? 403: 500;
x = entask_error (
httpd, client, x, 404, &file->version, file->keepalive);
httpd, client, x, http_errnum,
&file->version, file->keepalive);
goto no_file_send;
}
fileopen = 1;
@ -1414,17 +1414,17 @@ qse_httpd_task_t* qse_httpd_entaskfile (
{
qse_httpd_task_t task;
task_file_t data;
const qse_mchar_t* range;
const qse_mchar_t* tmp;
QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
data.path = path;
data.version = *qse_htre_getversion(req);
data.keepalive = req->attr.keepalive;
range = qse_htre_getheaderval(req, QSE_MT("Range"));
if (range)
tmp = qse_htre_getheaderval(req, QSE_MT("Range"));
if (tmp)
{
if (qse_parsehttprange (range, &data.range) <= -1)
if (qse_parsehttprange (tmp, &data.range) <= -1)
{
return qse_httpd_entaskerror (httpd, client, pred, 416, req);
}
@ -1434,6 +1434,14 @@ qse_httpd_task_t* qse_httpd_entaskfile (
data.range.type = QSE_HTTP_RANGE_NONE;
}
/*
TODO: If-Modified-Since...
tmp = qse_htre_getheaderval(req, QSE_MT("If-Modified-Since"));
if (tmp)
{
}
*/
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_file;
task.main = task_main_file;
@ -1463,12 +1471,13 @@ struct task_cgi_t
int keepalive; /* taken from the request */
int nph;
qse_env_t* env;
qse_pio_t* pio;
qse_htrd_t* htrd;
qse_env_t* env;
qse_pio_t pio;
int pio_inited;
qse_htre_t* req; /* original request associated with this */
qse_mbs_t* reqcon; /* content from the request */
qse_mbs_t* reqfwdbuf; /* content from the request */
int reqfwderr;
qse_mbs_t* res;
@ -1807,89 +1816,136 @@ oops:
static int cgi_snatch_content (
qse_htre_t* req, const qse_mchar_t* ptr, qse_size_t len, void* ctx)
{
task_cgi_t* cgi = (task_cgi_t*)ctx;
qse_httpd_task_t* task;
task_cgi_t* cgi;
task = (qse_httpd_task_t*)ctx;
cgi = (task_cgi_t*)task->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
* abortion for an error */
/*
* this callback is called with ptr of QSE_NULL
* when the request is completed or discarded.
* and this indicates that there's nothing more to read
* from the client side. this can happen when the end of
* a request is seen or when an error occurs
*/
QSE_ASSERT (len == 0);
/* mark the there's nothing to read form the client side */
cgi->req = QSE_NULL;
/* TODO: probably need to add a write trigger....
until cgi->reqcon is emptied...
cannot clear triogters since there are jobs depending on readbility or realyableilityy
*/
/* since there is no more to read from the client side.
* the relay trigger is not needed any more. */
task->trigger_mask &=
~(QSE_HTTPD_TASK_TRIGGER_RELAY |
QSE_HTTPD_TASK_TRIGGER_RELAYABLE);
if (QSE_MBS_LEN(cgi->reqfwdbuf) > 0 && cgi->pio_inited &&
!(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE))
{
/* there's nothing more to read from the client side.
* there's something to forward in the forwarding buffer.
* but no write trigger is set. add the write trigger
* for task invocation. */
task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
task->trigger_mask &= ~QSE_HTTPD_TASK_TRIGGER_WRITABLE;
task->trigger[2] = qse_pio_gethandleasubi (&cgi->pio, QSE_PIO_IN);
}
}
else if (!cgi->reqfwderr)
{
/* push the contents to the own buffer */
if (qse_mbs_ncat (cgi->reqcon, ptr, len) == (qse_size_t)-1)
/* we can write to the child process if a forwarding error
* didn't occur previously. we store data from the client side
* to the forwaring buffer only if there's no such previous
* error. if an error occurred, we simply drop the data. */
if (qse_mbs_ncat (cgi->reqfwdbuf, ptr, len) == (qse_size_t)-1)
{
return -1;
}
qse_printf (QSE_T("!!!SNACHED [%.*hs]\n"), len, ptr);
qse_printf (QSE_T("!!!SNATCHED [%.*hs]\n"), len, ptr);
}
return 0;
}
static void cgi_forward_content (qse_httpd_t* httpd, qse_httpd_task_t* task)
static void cgi_forward_content (
qse_httpd_t* httpd, qse_httpd_task_t* task, int writable)
{
task_cgi_t* cgi = (task_cgi_t*)task->ctx;
QSE_ASSERT (cgi->reqcon != QSE_NULL);
QSE_ASSERT (cgi->reqfwdbuf != QSE_NULL);
if (QSE_MBS_LEN(cgi->reqcon) > 0)
if (QSE_MBS_LEN(cgi->reqfwdbuf) > 0)
{
/* there is something to forward in the forwarding buffer. */
if (cgi->reqfwderr)
{
qse_mbs_clear (cgi->reqcon);
/* a forwarding error has occurred previously.
* clear the forwarding buffer */
qse_printf (QSE_T("FORWARD: CLEARING REQCON FOR ERROR\n"));
qse_mbs_clear (cgi->reqfwdbuf);
}
else
{
qse_ubi_t handle;
/* normal forwarding */
qse_ssize_t n;
/* TODO: handle = qse_pio_getubihandle(); */
handle.i = qse_pio_gethandle (cgi->pio, QSE_PIO_IN);
if (writable) goto forward;
n = httpd->cbs->mux.writable (httpd, handle, 0);
n = httpd->cbs->mux.writable (
httpd, qse_pio_gethandleasubi (&cgi->pio, QSE_PIO_IN), 0);
if (n == 0) qse_printf (QSE_T("FORWARD: @@@@@@@@@NOT WRITABLE\n"));
if (n >= 1)
{
forward:
/* writable */
qse_printf (QSE_T("@@@@@@@@@@WRITING[%.*hs]\n"),
(int)QSE_MBS_LEN(cgi->reqcon),
QSE_MBS_PTR(cgi->reqcon));
qse_printf (QSE_T("FORWARD: @@@@@@@@@@WRITING[%.*hs]\n"),
(int)QSE_MBS_LEN(cgi->reqfwdbuf),
QSE_MBS_PTR(cgi->reqfwdbuf));
n = qse_pio_write (
cgi->pio, QSE_PIO_IN,
QSE_MBS_PTR(cgi->reqcon),
QSE_MBS_LEN(cgi->reqcon)
&cgi->pio, QSE_PIO_IN,
QSE_MBS_PTR(cgi->reqfwdbuf),
QSE_MBS_LEN(cgi->reqfwdbuf)
);
/* 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);
/* 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->reqfwdbuf, 0, n);
}
if (n <= -1)
{
qse_printf (QSE_T("@@@@@@@@WRITE TO CGI FAILED\n"));
qse_printf (QSE_T("FORWARD: @@@@@@@@WRITE TO CGI FAILED\n"));
/* TODO: logging ... */
cgi->reqfwderr = 1;
qse_mbs_clear (cgi->reqcon);
if (cgi->req) qse_htre_discardcontent (cgi->req);
qse_mbs_clear (cgi->reqfwdbuf);
if (cgi->req)
{
qse_htre_discardcontent (cgi->req);
/* NOTE: cgi->req may be set to QSE_NULL
* in cgi_snatch_content() triggered by
* qse_htre_discardcontent() */
}
}
}
}
else if (cgi->req == QSE_NULL)
{
/* no more request content */
qse_printf (QSE_T("@@@@@@@@NOTHING MORE TO WRITE TO CGI\n"));
/* there is nothing to read from the client side and
* there is nothing more to forward in the forwarding buffer.
* clear the relay and write triggers.
*/
qse_printf (QSE_T("FORWARD: @@@@@@@@NOTHING MORE TO WRITE TO CGI\n"));
task->trigger_mask &=
~(QSE_HTTPD_TASK_TRIGGER_RELAY |
QSE_HTTPD_TASK_TRIGGER_RELAYABLE);
QSE_HTTPD_TASK_TRIGGER_RELAYABLE |
QSE_HTTPD_TASK_TRIGGER_WRITE |
QSE_HTTPD_TASK_TRIGGER_WRITABLE);
}
}
@ -1950,14 +2006,14 @@ qse_printf (QSE_T("ZZZZZZZZZZZZZZZ\n"));
{
/* 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) goto oops;
cgi->reqfwdbuf = qse_mbs_open (httpd->mmgr, 0, (len < 512? 512: len));
if (cgi->reqfwdbuf == QSE_NULL) goto oops;
ptr = qse_htre_getcontentptr(arg->req);
if (qse_mbs_ncpy (cgi->reqcon, ptr, len) == (qse_size_t)-1)
if (qse_mbs_ncpy (cgi->reqfwdbuf, ptr, len) == (qse_size_t)-1)
{
qse_mbs_close (cgi->reqcon);
cgi->reqcon = QSE_NULL;
qse_mbs_close (cgi->reqfwdbuf);
cgi->reqfwdbuf = QSE_NULL;
goto oops;
}
@ -1985,10 +2041,10 @@ qse_printf (QSE_T("HHHHHHHHHHHHHHHHhh %d\n"), (int)len);
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. qse_htre_addcontent() called
* by htrd invokes this callback. */
* is fed to the htrd reader. qse_htre_addcontent() that
* htrd calls invokes this callback. */
cgi->req = arg->req;
qse_htre_setconcb (cgi->req, cgi_snatch_content, cgi);
qse_htre_setconcb (cgi->req, cgi_snatch_content, task);
QSE_ASSERT (arg->req->attr.content_length_set);
content_length = arg->req->attr.content_length;
@ -2020,16 +2076,16 @@ static void task_fini_cgi (
{
task_cgi_t* cgi = (task_cgi_t*)task->ctx;
if (cgi->env) qse_env_close (cgi->env);
if (cgi->pio)
if (cgi->pio_inited)
{
/* kill cgi in case it is still alive.
* qse_pio_wait() in qse_pio_close() can block. */
qse_pio_kill (cgi->pio);
qse_pio_close (cgi->pio);
qse_pio_kill (&cgi->pio);
qse_pio_close (&cgi->pio);
}
if (cgi->res) qse_mbs_close (cgi->res);
if (cgi->htrd) qse_htrd_close (cgi->htrd);
if (cgi->reqcon) qse_mbs_close (cgi->reqcon);
if (cgi->reqfwdbuf) qse_mbs_close (cgi->reqfwdbuf);
if (cgi->req)
{
/* this task is destroyed but the request
@ -2047,17 +2103,20 @@ static int task_main_cgi_5 (
task_cgi_t* cgi = (task_cgi_t*)task->ctx;
qse_ssize_t n;
QSE_ASSERT (cgi->pio != QSE_NULL);
QSE_ASSERT (cgi->pio_inited);
if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE)
{
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 */
cgi_forward_content (httpd, task, 0);
}
else if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE)
{
cgi_forward_content (httpd, task, 1);
}
qse_printf (QSE_T("task_main_cgi_5\n"));
if (cgi->buflen > 0)
{
/* TODO: check if cgi outputs more than content-length if it is set... */
httpd->errnum = QSE_HTTPD_ENOERR;
n = httpd->cbs->client.send (httpd, client, cgi->buf, cgi->buflen);
@ -2070,8 +2129,13 @@ qse_printf (QSE_T("task_main_cgi_5\n"));
QSE_MEMCPY (&cgi->buf[0], &cgi->buf[n], cgi->buflen - n);
cgi->buflen -= n;
}
return (cgi->buflen > 0)? 1: 0;
/* if forwarding didn't finish, something is not really right...
* so long as the output from CGI is finished, no more forwarding
* is performed */
return (cgi->buflen > 0 || cgi->req ||
(cgi->reqfwdbuf && QSE_MBS_LEN(cgi->reqfwdbuf) > 0))? 1: 0;
}
static int task_main_cgi_4 (
@ -2080,11 +2144,15 @@ static int task_main_cgi_4 (
task_cgi_t* cgi = (task_cgi_t*)task->ctx;
qse_ssize_t n;
QSE_ASSERT (cgi->pio != QSE_NULL);
QSE_ASSERT (cgi->pio_inited);
if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE)
{
cgi_forward_content (httpd, task);
cgi_forward_content (httpd, task, 0);
}
else if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE)
{
cgi_forward_content (httpd, task, 1);
}
if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READABLE)
@ -2110,7 +2178,7 @@ static int task_main_cgi_4 (
/* <- can i make it non-block?? or use select??? pio_tryread()? */
n = qse_pio_read (
cgi->pio, QSE_PIO_OUT,
&cgi->pio, QSE_PIO_OUT,
&cgi->buf[cgi->buflen + QSE_SIZEOF(chunklen) - 1],
count - extra
);
@ -2157,7 +2225,7 @@ static int task_main_cgi_4 (
{
qse_printf (QSE_T("READING IN NON-CHUNKED MODE...\n"));
n = qse_pio_read (
cgi->pio, QSE_PIO_OUT,
&cgi->pio, QSE_PIO_OUT,
&cgi->buf[cgi->buflen],
QSE_SIZEOF(cgi->buf) - cgi->buflen
);
@ -2226,7 +2294,11 @@ static int task_main_cgi_3 (
if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE)
{
cgi_forward_content (httpd, task);
cgi_forward_content (httpd, task, 0);
}
else if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE)
{
cgi_forward_content (httpd, task, 1);
}
if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READABLE)
@ -2273,13 +2345,17 @@ static int task_main_cgi_2 (
task_cgi_t* cgi = (task_cgi_t*)task->ctx;
qse_ssize_t n;
QSE_ASSERT (cgi->pio != QSE_NULL);
QSE_ASSERT (cgi->pio_inited);
qse_printf (QSE_T("[cgi_2 ]\n"));
if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE)
{
qse_printf (QSE_T("[cgi_2 write]\n"));
cgi_forward_content (httpd, task);
cgi_forward_content (httpd, task, 0);
}
else if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE)
{
cgi_forward_content (httpd, task, 1);
}
if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READABLE)
@ -2287,7 +2363,7 @@ qse_printf (QSE_T("[cgi_2 write]\n"));
qse_printf (QSE_T("[cgi_2 read]\n"));
/* <- can i make it non-block?? or use select??? pio_tryread()? */
n = qse_pio_read (
cgi->pio, QSE_PIO_OUT,
&cgi->pio, QSE_PIO_OUT,
&cgi->buf[cgi->buflen],
QSE_SIZEOF(cgi->buf) - cgi->buflen
);
@ -2359,6 +2435,7 @@ static int task_main_cgi (
{
task_cgi_t* cgi = (task_cgi_t*)task->ctx;
int pio_options;
int http_errnum = 500;
if (cgi->init_failed) goto oops;
@ -2398,39 +2475,67 @@ static int task_main_cgi (
if (httpd->option & QSE_HTTPD_CGINOCLOEXEC)
pio_options |= QSE_PIO_NOCLOEXEC;
cgi->pio = qse_pio_open (
httpd->mmgr, 0, (const qse_char_t*)cgi->path,
cgi->env, pio_options
);
if (cgi->pio == QSE_NULL) goto oops;
/* TODO: use a different field for different OS???
HANDLE for win32???
*/
/* set the trigger that the main loop can use this
* handle for multiplexing */
task->trigger_mask = QSE_HTTPD_TASK_TRIGGER_READ;
task->trigger[0].i = qse_pio_gethandle (cgi->pio, QSE_PIO_OUT);
if (cgi->reqcon)
if (qse_pio_init (
&cgi->pio, httpd->mmgr, (const qse_char_t*)cgi->path,
cgi->env, pio_options) <= -1)
{
/* 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);*/
qse_pio_errnum_t errnum;
errnum = qse_pio_geterrnum (&cgi->pio);
if (errnum == QSE_PIO_ENOENT) http_errnum = 404;
else if (errnum == QSE_PIO_EACCES) http_errnum = 403;
goto oops;
}
cgi->pio_inited = 1;
/* set the trigger that the main loop can use this
* handle for multiplexing
*
* it the output from the child is available, this task
* writes it back to the client. so add a trigger for
* checking the data availability from the child process */
task->trigger_mask = QSE_HTTPD_TASK_TRIGGER_READ;
task->trigger[0] = qse_pio_gethandleasubi (&cgi->pio, QSE_PIO_OUT);
if (cgi->reqfwdbuf)
{
/* the existence of the forwarding buffer leads to a trigger
* for checking data availiability from the client side. */
if (cgi->req)
{
/* there are still things to forward from the client-side.
* i can rely on this relay trigger for task invocation. */
task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_RELAY;
task->trigger[1].i = client->handle.i;
}
if (cgi->reqcon)
else if (QSE_MBS_LEN(cgi->reqfwdbuf) > 0)
{
/* 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);
/* there's nothing more to read from the client side but
* some contents are already read into the forwarding buffer.
* this is possible because the main loop can still read
* between the initializer function (task_init_cgi()) and
* this function. so let's forward it initially. */
qse_printf (QSE_T("FORWARDING INITIAL PART OF CONTENT...\n"));
cgi_forward_content (httpd, task, 0);
/* if the initial forwarding clears the forwarding
* buffer, there is nothing more to forward.
* (nothing more to read from the client side, nothing
* left in the forwarding buffer). if not, this task should
* still be invoked for forwarding.
*/
if (QSE_MBS_LEN(cgi->reqfwdbuf) > 0)
{
/* since the buffer is not cleared, this task needs
* a trigger for invocation. ask the main loop to
* invoke this task so long as it is able to write
* to the child process */
task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
task->trigger[2] = qse_pio_gethandleasubi (&cgi->pio, QSE_PIO_IN);
}
}
}
task->main = cgi->nph? task_main_cgi_4: task_main_cgi_2;
@ -2451,7 +2556,9 @@ oops:
cgi->htrd = QSE_NULL;
}
return (entask_error (httpd, client, task, 500, &cgi->version, cgi->keepalive) == QSE_NULL)? -1: 0;
return (entask_error (
httpd, client, task, http_errnum,
&cgi->version, cgi->keepalive) == QSE_NULL)? -1: 0;
}
/* TODO: global option or individual paramter for max cgi lifetime

View File

@ -249,7 +249,13 @@ static int file_ropen (
qse_printf (QSE_T("opening file [%hs] for reading\n"), path);
fd = open (path, flags, 0);
if (fd <= -1) return -1;
if (fd <= -1)
{
qse_httpd_seterrnum (httpd,
(errno == ENOENT? QSE_HTTPD_ENOENT:
errno == EACCES? QSE_HTTPD_EACCES: QSE_HTTPD_ESUBSYS));
return -1;
}
flags = fcntl (fd, F_GETFD);
if (flags >= 0) fcntl (fd, F_SETFD, flags | FD_CLOEXEC);
@ -257,12 +263,16 @@ qse_printf (QSE_T("opening file [%hs] for reading\n"), path);
/* TODO: fstat64??? */
if (fstat (fd, &st) <= -1)
{
qse_httpd_seterrnum (httpd,
(errno == ENOENT? QSE_HTTPD_ENOENT:
errno == EACCES? QSE_HTTPD_EACCES: QSE_HTTPD_ESUBSYS));
close (fd);
return -1;
}
if (S_ISDIR(st.st_mode))
if (!S_ISREG(st.st_mode))
{
qse_httpd_seterrnum (httpd, QSE_HTTPD_EACCES);
close (fd);
return -1;
}
@ -287,7 +297,13 @@ static int file_wopen (
qse_printf (QSE_T("opening file [%hs] for writing\n"), path);
fd = open (path, flags, 0644);
if (fd <= -1) return -1;
if (fd <= -1)
{
qse_httpd_seterrnum (httpd,
(errno == ENOENT? QSE_HTTPD_ENOENT:
errno == EACCES? QSE_HTTPD_EACCES: QSE_HTTPD_ESUBSYS));
return -1;
}
handle->i = fd;
return 0;
@ -488,6 +504,7 @@ if (qse_htre_getcontentlen(req) > 0)
qse_printf (QSE_T("CONTENT after discard = [%.*S]\n"), (int)qse_htre_getcontentlen(req), qse_htre_getcontentptr(req));
}
if (method == QSE_HTTP_GET || method == QSE_HTTP_POST)
{
const qse_mchar_t* qpath = qse_htre_getqpathptr(req);
@ -583,7 +600,7 @@ qse_printf (QSE_T("Entasking chunked CGI...\n"));
oops:
/*qse_httpd_markbadclient (httpd, client);*/
return 0; /* TODO: return failure??? */
return -1;
}
static int peek_request (