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_ECHILD, /**< the child is not valid */
QSE_PIO_EINTR, /**< interrupted */ QSE_PIO_EINTR, /**< interrupted */
QSE_PIO_EPIPE, /**< broken pipe */ QSE_PIO_EPIPE, /**< broken pipe */
QSE_PIO_EACCES, /**< access denied */
QSE_PIO_ENOENT, /**< no such file */
QSE_PIO_ESUBSYS /**< subsystem(system call) error */ QSE_PIO_ESUBSYS /**< subsystem(system call) error */
}; };
typedef enum qse_pio_errnum_t qse_pio_errnum_t; 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 */ 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. * The qse_pio_getchild() function gets a process handle.
* @return process handle * @return process handle

View File

@ -126,12 +126,6 @@ struct qse_htre_t
#define qse_htre_setsmessagefromxstr(re,v) \ #define qse_htre_setsmessagefromxstr(re,v) \
qse_htre_setstrfromxstr((re),qse_htre_getsmessage(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) ( typedef int (*qse_htre_header_walker_t) (
qse_htre_t* re, qse_htre_t* re,
const qse_mchar_t* key, const qse_mchar_t* key,

View File

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

View File

@ -288,6 +288,35 @@ oops:
return -1; 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) static QSE_INLINE int is_fd_valid (int fd)
{ {
return fcntl (fd, F_GETFD) != -1 || errno != EBADF; return fcntl (fd, F_GETFD) != -1 || errno != EBADF;
@ -572,7 +601,26 @@ int qse_pio_init (
); );
QSE_MMGR_FREE (mmgr, dupcmd); 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) if (windevnul != INVALID_HANDLE_VALUE)
@ -871,6 +919,8 @@ int qse_pio_init (
cmd_file cmd_file
); );
/* TODO: translate error code ... */
QSE_MMGR_FREE (mmgr, cmd_line); QSE_MMGR_FREE (mmgr, cmd_line);
cmd_line = QSE_NULL; cmd_line = QSE_NULL;
@ -994,6 +1044,16 @@ int qse_pio_init (
} }
if (make_param (pio, cmd, flags, &param) <= -1) 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;
}
spawn_ret = posix_spawn( spawn_ret = posix_spawn(
&pid, param.argv[0], &fa, QSE_NULL, param.argv, &pid, param.argv[0], &fa, QSE_NULL, param.argv,
(env? qse_env_getarr(env): environ)); (env? qse_env_getarr(env): environ));
@ -1050,8 +1110,23 @@ int qse_pio_init (
goto oops; 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(); pid = QSE_FORK();
if (pid <= -1) goto oops; if (pid <= -1)
{
free_param (pio, &param);
goto oops;
}
if (pid == 0) if (pid == 0)
{ {
@ -1145,7 +1220,7 @@ int qse_pio_init (
if (flags & QSE_PIO_DROPOUT) QSE_CLOSE(1); if (flags & QSE_PIO_DROPOUT) QSE_CLOSE(1);
if (flags & QSE_PIO_DROPERR) QSE_CLOSE(2); 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)); QSE_EXECVE (param.argv[0], param.argv, (env? qse_env_getarr(env): environ));
free_param (pio, &param); free_param (pio, &param);
@ -1155,6 +1230,7 @@ int qse_pio_init (
} }
/* parent */ /* parent */
free_param (pio, &param);
pio->child = pid; pio->child = pid;
if (flags & QSE_PIO_WRITEIN) 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("child process not valid"),
QSE_T("interruped"), QSE_T("interruped"),
QSE_T("broken pipe"), QSE_T("broken pipe"),
QSE_T("access denied"),
QSE_T("no such file"),
QSE_T("systeam call error"), QSE_T("systeam call error"),
QSE_T("unknown 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; 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) qse_pio_pid_t qse_pio_getchild (qse_pio_t* pio)
{ {
return pio->child; return pio->child;

View File

@ -45,10 +45,14 @@ void qse_htre_fini (qse_htre_t* re)
void qse_htre_clear (qse_htre_t* re) void qse_htre_clear (qse_htre_t* re)
{ {
if (re->concb) if (!(re->state & QSE_HTRE_COMPLETED) &&
!(re->state & QSE_HTRE_DISCARDED))
{ {
re->concb (re, QSE_NULL, 0, re->concb_ctx); /* indicate end of content */ if (re->concb)
qse_htre_unsetconcb (re); {
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->version, 0, QSE_SIZEOF(re->version));
@ -117,25 +121,73 @@ int qse_htre_walkheaders (
int qse_htre_addcontent ( int qse_htre_addcontent (
qse_htre_t* re, const qse_mchar_t* ptr, qse_size_t len) 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 the callback is set, the content goes to the callback. */ if (re->state & (QSE_HTRE_COMPLETED | QSE_HTRE_DISCARDED)) return 0; /* skipped */
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 (re->concb)
if (qse_mbs_ncat (&re->content, ptr, len) == (qse_size_t)-1) return -1; {
/* if the callback is set, the content goes to the callback. */
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;
}
return 1; /* added successfully */ return 1; /* added successfully */
} }
void qse_htre_completecontent (qse_htre_t* re) void qse_htre_completecontent (qse_htre_t* re)
{ {
re->state |= QSE_HTRE_COMPLETED; /* 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) void qse_htre_discardcontent (qse_htre_t* re)
{ {
re->state |= QSE_HTRE_DISCARDED; /* you can't discard this if it's completed.
qse_mbs_clear (&re->content); * 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) 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; 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) int qse_httpd_getoption (qse_httpd_t* httpd)
{ {
return httpd->option; return httpd->option;
@ -665,7 +675,7 @@ static void perform_task (qse_httpd_t* httpd, qse_httpd_client_t* client)
{ {
dequeue_task (httpd, client); dequeue_task (httpd, client);
/*shutdown (client->handle.i, SHUT_RDWR);*/ /*shutdown (client->handle.i, SHUT_RDWR);*/
client->bad = 1; client->bad = 1;
} }
else if (n == 0) else if (n == 0)
{ {
@ -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) 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_ssize_t m;
QSE_ASSERT (httpd->cbs->client.recv != QSE_NULL); QSE_ASSERT (httpd->cbs->client.recv != QSE_NULL);
@ -803,87 +813,87 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n"));
QSE_ASSERT (httpd->cbs->client.accepted != QSE_NULL); 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()? */ 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; if (x >= 1) client->ready = 1;
else if (x <= -1) goto bad_client; else if (x <= -1)
{
delete_from_client_array (httpd, fd);
continue;
}
} }
else else
{ {
// TODO: any way to suspend read while a request is being processed???
if (read_from_client (httpd, client) <= -1) if (read_from_client (httpd, client) <= -1)
{ {
bad_client: delete_from_client_array (httpd, fd);
if (httpd->threaded) continue;
{
/* let the writing part handle it,
* probably in the next iteration */
qse_httpd_markbadclient (httpd, client);
shutdown (client->handle.i, SHUT_RDWR);
}
else
{
delete_from_client_array (httpd, fd);
continue; /* don't need to go to the writing part */
}
} }
} }
} }
if (!httpd->threaded) /* perform a client task enqued to a client */
if (client->task.queue.head)
{ {
if (client->bad) 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 |
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))
{ {
/*shutdown (client->handle.i, SHUT_RDWR);*/ qse_printf (QSE_T(".....NO TRIGGER ACTION....\n"));
delete_from_client_array (httpd, fd); /* no trigger set. set the flag to
* non-readable and non-writable */
perform = 1;
} }
else if (client->task.queue.head) else
{ {
qse_httpd_task_t* task; if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ)
int perform = 0; 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)
task = &client->task.queue.head->task; qse_printf (QSE_T(".....CLIENT %d HAS RELAY TREIGGER %d\n"), fd, task->trigger[1].i);
task->trigger_mask &= if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE)
~(QSE_HTTPD_TASK_TRIGGER_READABLE | qse_printf (QSE_T(".....CLIENT %d HAS WRITE TREIGGER %d\n"), fd, task->trigger[2].i);
QSE_HTTPD_TASK_TRIGGER_RELAYABLE | if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) &&
QSE_HTTPD_TASK_TRIGGER_WRITABLE); FD_ISSET(task->trigger[0].i, &r))
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 task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_READABLE;
* non-readable and non-writable */
perform = 1; perform = 1;
qse_printf (QSE_T(".....TRIGGER READABLE....\n"));
} }
else
{
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;
}
if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) && if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) &&
FD_ISSET(task->trigger[1].i, &r)) 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))
{
task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITABLE;
perform = 1;
}
}
if (perform)
{ {
/* TODO: error handling -> writable() returns <= -1 */ task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_RELAYABLE;
/* TODO: though the client side is not writable, can't i still exeucte the task? perform = 1;
* if the task needs to transfer anything yet.. it can do that. qse_printf (QSE_T(".....TRIGGER RELAYABLE....\n"));
* i probably need a new trigger type??? */ }
if (httpd->cbs->mux.writable (httpd, client->handle, 0) >= 1)
perform_task (httpd, client); 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"));
}
}
if (perform)
{
/* 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);
} }
} }
} }
@ -1109,18 +1119,7 @@ qse_httpd_task_t* qse_httpd_entask (
const qse_httpd_task_t* pred, const qse_httpd_task_t* task, const qse_httpd_task_t* pred, const qse_httpd_task_t* task,
qse_size_t xtnsize) qse_size_t xtnsize)
{ {
qse_httpd_task_t* ret; return enqueue_task (httpd, client, pred, task, xtnsize);
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;
} }
void qse_httpd_discardcontent (qse_httpd_t* httpd, qse_htre_t* req) void qse_httpd_discardcontent (qse_httpd_t* httpd, qse_htre_t* req)

View File

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

View File

@ -262,7 +262,7 @@ qse_httpd_task_t* qse_httpd_entaskformat (
capa = capa * 2; capa = capa * 2;
buf = (qse_mchar_t*) qse_httpd_allocmem (httpd, (capa + 1) * QSE_SIZEOF(*buf)); buf = (qse_mchar_t*) qse_httpd_allocmem (httpd, (capa + 1) * QSE_SIZEOF(*buf));
if (buf == QSE_NULL) return QSE_NULL; if (buf == QSE_NULL) return QSE_NULL;
} }
else break; else break;
} }
@ -272,7 +272,8 @@ qse_httpd_task_t* qse_httpd_entaskformat (
/* vsnprintf returns the number of characters that would /* vsnprintf returns the number of characters that would
* have been written not including the terminating '\0' * have been written not including the terminating '\0'
* if the _data buffer were large enough */ * 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; if (buf == QSE_NULL) return QSE_NULL;
va_start (ap, fmt); va_start (ap, fmt);
@ -287,7 +288,6 @@ qse_httpd_task_t* qse_httpd_entaskformat (
{ {
/* something got wrong ... */ /* something got wrong ... */
qse_httpd_freemem (httpd, buf); qse_httpd_freemem (httpd, buf);
httpd->errnum = QSE_HTTPD_EINTERN; httpd->errnum = QSE_HTTPD_EINTERN;
return QSE_NULL; return QSE_NULL;
} }
@ -1116,17 +1116,17 @@ qse_httpd_task_t* qse_httpd_entaskpath (
{ {
qse_httpd_task_t task; qse_httpd_task_t task;
task_path_t data; task_path_t data;
const qse_mchar_t* range; const qse_mchar_t* tmp;
QSE_MEMSET (&data, 0, QSE_SIZEOF(data)); QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
data.name = name; data.name = name;
data.version = *qse_htre_getversion(req); data.version = *qse_htre_getversion(req);
data.keepalive = req->attr.keepalive; data.keepalive = req->attr.keepalive;
range = qse_htre_getheaderval(req, QSE_MT("Range")); tmp = qse_htre_getheaderval(req, QSE_MT("Range"));
if (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); 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); httpd, client, ctx->handle, &ctx->offset, count);
if (n <= -1) if (n <= -1)
{ {
// HANDLE EGAIN specially??? /* HANDLE EGAIN specially??? */
return -1; /* TODO: any logging */ 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, /* TODO: if you should deal with files on a network-mounted drive,
setting a trigger or non-blocking I/O are needed. */ 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); 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) 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 ( x = entask_error (
httpd, client, x, 404, &file->version, file->keepalive); httpd, client, x, http_errnum,
&file->version, file->keepalive);
goto no_file_send; goto no_file_send;
} }
fileopen = 1; fileopen = 1;
@ -1414,17 +1414,17 @@ qse_httpd_task_t* qse_httpd_entaskfile (
{ {
qse_httpd_task_t task; qse_httpd_task_t task;
task_file_t data; task_file_t data;
const qse_mchar_t* range; const qse_mchar_t* tmp;
QSE_MEMSET (&data, 0, QSE_SIZEOF(data)); QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
data.path = path; data.path = path;
data.version = *qse_htre_getversion(req); data.version = *qse_htre_getversion(req);
data.keepalive = req->attr.keepalive; data.keepalive = req->attr.keepalive;
range = qse_htre_getheaderval(req, QSE_MT("Range")); tmp = qse_htre_getheaderval(req, QSE_MT("Range"));
if (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); return qse_httpd_entaskerror (httpd, client, pred, 416, req);
} }
@ -1433,6 +1433,14 @@ qse_httpd_task_t* qse_httpd_entaskfile (
{ {
data.range.type = QSE_HTTP_RANGE_NONE; 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)); QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_file; task.init = task_init_file;
@ -1463,12 +1471,13 @@ struct task_cgi_t
int keepalive; /* taken from the request */ int keepalive; /* taken from the request */
int nph; int nph;
qse_env_t* env;
qse_pio_t* pio;
qse_htrd_t* htrd; 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_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; int reqfwderr;
qse_mbs_t* res; qse_mbs_t* res;
@ -1807,89 +1816,136 @@ oops:
static int cgi_snatch_content ( static int cgi_snatch_content (
qse_htre_t* req, const qse_mchar_t* ptr, qse_size_t len, void* ctx) 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); if (ptr) qse_printf (QSE_T("!!!SNATCHING [%.*hs]\n"), len, ptr);
else qse_printf (QSE_T("!!!SNATCHING DONE\n")); else qse_printf (QSE_T("!!!SNATCHING DONE\n"));
if (ptr == QSE_NULL) 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); QSE_ASSERT (len == 0);
cgi->req = QSE_NULL;
/* TODO: probably need to add a write trigger.... /* mark the there's nothing to read form the client side */
until cgi->reqcon is emptied... cgi->req = QSE_NULL;
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) else if (!cgi->reqfwderr)
{ {
/* push the contents to the own buffer */ /* we can write to the child process if a forwarding error
if (qse_mbs_ncat (cgi->reqcon, ptr, len) == (qse_size_t)-1) * 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; return -1;
} }
qse_printf (QSE_T("!!!SNACHED [%.*hs]\n"), len, ptr); qse_printf (QSE_T("!!!SNATCHED [%.*hs]\n"), len, ptr);
} }
return 0; 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; 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) 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 else
{ {
qse_ubi_t handle; /* normal forwarding */
qse_ssize_t n; qse_ssize_t n;
/* TODO: handle = qse_pio_getubihandle(); */ if (writable) goto forward;
handle.i = qse_pio_gethandle (cgi->pio, QSE_PIO_IN);
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) if (n >= 1)
{ {
forward:
/* writable */ /* writable */
qse_printf (QSE_T("@@@@@@@@@@WRITING[%.*hs]\n"), qse_printf (QSE_T("FORWARD: @@@@@@@@@@WRITING[%.*hs]\n"),
(int)QSE_MBS_LEN(cgi->reqcon), (int)QSE_MBS_LEN(cgi->reqfwdbuf),
QSE_MBS_PTR(cgi->reqcon)); QSE_MBS_PTR(cgi->reqfwdbuf));
n = qse_pio_write ( n = qse_pio_write (
cgi->pio, QSE_PIO_IN, &cgi->pio, QSE_PIO_IN,
QSE_MBS_PTR(cgi->reqcon), QSE_MBS_PTR(cgi->reqfwdbuf),
QSE_MBS_LEN(cgi->reqcon) QSE_MBS_LEN(cgi->reqfwdbuf)
); );
/* TODO: improve performance.. instead of copying the remaing part to the head all the time.. /* TODO: improve performance.. instead of copying the remaing part
grow the buffer to a certain limit. */ 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 > 0) qse_mbs_del (cgi->reqfwdbuf, 0, n);
} }
if (n <= -1) if (n <= -1)
{ {
qse_printf (QSE_T("@@@@@@@@WRITE TO CGI FAILED\n")); qse_printf (QSE_T("FORWARD: @@@@@@@@WRITE TO CGI FAILED\n"));
/* TODO: logging ... */ /* TODO: logging ... */
cgi->reqfwderr = 1; cgi->reqfwderr = 1;
qse_mbs_clear (cgi->reqcon); qse_mbs_clear (cgi->reqfwdbuf);
if (cgi->req) qse_htre_discardcontent (cgi->req); 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) else if (cgi->req == QSE_NULL)
{ {
/* no more request content */ /* there is nothing to read from the client side and
qse_printf (QSE_T("@@@@@@@@NOTHING MORE TO WRITE TO CGI\n")); * 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 &= task->trigger_mask &=
~(QSE_HTTPD_TASK_TRIGGER_RELAY | ~(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 /* create a buffer to hold request content from the client
* and copy content received already */ * and copy content received already */
cgi->reqcon = qse_mbs_open (httpd->mmgr, 0, (len < 512? 512: len)); cgi->reqfwdbuf = qse_mbs_open (httpd->mmgr, 0, (len < 512? 512: len));
if (cgi->reqcon == QSE_NULL) goto oops; if (cgi->reqfwdbuf == QSE_NULL) goto oops;
ptr = qse_htre_getcontentptr(arg->req); 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); qse_mbs_close (cgi->reqfwdbuf);
cgi->reqcon = QSE_NULL; cgi->reqfwdbuf = QSE_NULL;
goto oops; 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. 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 /* set up a callback to be called when the request content
* is fed to the htrd reader. qse_htre_addcontent() called * is fed to the htrd reader. qse_htre_addcontent() that
* by htrd invokes this callback. */ * htrd calls invokes this callback. */
cgi->req = arg->req; 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); QSE_ASSERT (arg->req->attr.content_length_set);
content_length = arg->req->attr.content_length; 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; task_cgi_t* cgi = (task_cgi_t*)task->ctx;
if (cgi->env) qse_env_close (cgi->env); if (cgi->env) qse_env_close (cgi->env);
if (cgi->pio) if (cgi->pio_inited)
{ {
/* kill cgi in case it is still alive. /* kill cgi in case it is still alive.
* qse_pio_wait() in qse_pio_close() can block. */ * qse_pio_wait() in qse_pio_close() can block. */
qse_pio_kill (cgi->pio); qse_pio_kill (&cgi->pio);
qse_pio_close (cgi->pio); qse_pio_close (&cgi->pio);
} }
if (cgi->res) qse_mbs_close (cgi->res); if (cgi->res) qse_mbs_close (cgi->res);
if (cgi->htrd) qse_htrd_close (cgi->htrd); 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) if (cgi->req)
{ {
/* this task is destroyed but the request /* this task is destroyed but the request
@ -2047,31 +2103,39 @@ static int task_main_cgi_5 (
task_cgi_t* cgi = (task_cgi_t*)task->ctx; task_cgi_t* cgi = (task_cgi_t*)task->ctx;
qse_ssize_t n; qse_ssize_t n;
QSE_ASSERT (cgi->pio != QSE_NULL); QSE_ASSERT (cgi->pio_inited);
if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE) if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE)
{ {
cgi_forward_content (httpd, task); cgi_forward_content (httpd, task, 0);
/* if forwarding didn't finish, something is not really right... }
* so long as the output from CGI is finished, no more forwarding else if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE)
* is performed */ {
cgi_forward_content (httpd, task, 1);
} }
qse_printf (QSE_T("task_main_cgi_5\n")); qse_printf (QSE_T("task_main_cgi_5\n"));
/* TODO: check if cgi outputs more than content-length if it is set... */ if (cgi->buflen > 0)
httpd->errnum = QSE_HTTPD_ENOERR;
n = httpd->cbs->client.send (httpd, client, cgi->buf, cgi->buflen);
if (n <= -1)
{ {
/* 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);
if (n <= -1)
{
/* can't return internal server error any more... */ /* can't return internal server error any more... */
/* TODO: logging ... */ /* TODO: logging ... */
return -1; return -1;
}
QSE_MEMCPY (&cgi->buf[0], &cgi->buf[n], cgi->buflen - n);
cgi->buflen -= n;
} }
QSE_MEMCPY (&cgi->buf[0], &cgi->buf[n], cgi->buflen - n); /* if forwarding didn't finish, something is not really right...
cgi->buflen -= n; * so long as the output from CGI is finished, no more forwarding
* is performed */
return (cgi->buflen > 0)? 1: 0; return (cgi->buflen > 0 || cgi->req ||
(cgi->reqfwdbuf && QSE_MBS_LEN(cgi->reqfwdbuf) > 0))? 1: 0;
} }
static int task_main_cgi_4 ( 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; task_cgi_t* cgi = (task_cgi_t*)task->ctx;
qse_ssize_t n; qse_ssize_t n;
QSE_ASSERT (cgi->pio != QSE_NULL); QSE_ASSERT (cgi->pio_inited);
if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE) 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) 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()? */ /* <- can i make it non-block?? or use select??? pio_tryread()? */
n = qse_pio_read ( n = qse_pio_read (
cgi->pio, QSE_PIO_OUT, &cgi->pio, QSE_PIO_OUT,
&cgi->buf[cgi->buflen + QSE_SIZEOF(chunklen) - 1], &cgi->buf[cgi->buflen + QSE_SIZEOF(chunklen) - 1],
count - extra count - extra
); );
@ -2157,7 +2225,7 @@ static int task_main_cgi_4 (
{ {
qse_printf (QSE_T("READING IN NON-CHUNKED MODE...\n")); qse_printf (QSE_T("READING IN NON-CHUNKED MODE...\n"));
n = qse_pio_read ( n = qse_pio_read (
cgi->pio, QSE_PIO_OUT, &cgi->pio, QSE_PIO_OUT,
&cgi->buf[cgi->buflen], &cgi->buf[cgi->buflen],
QSE_SIZEOF(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) 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) 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; task_cgi_t* cgi = (task_cgi_t*)task->ctx;
qse_ssize_t n; qse_ssize_t n;
QSE_ASSERT (cgi->pio != QSE_NULL); QSE_ASSERT (cgi->pio_inited);
qse_printf (QSE_T("[cgi_2 ]\n")); qse_printf (QSE_T("[cgi_2 ]\n"));
if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE) if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE)
{ {
qse_printf (QSE_T("[cgi_2 write]\n")); 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) 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")); qse_printf (QSE_T("[cgi_2 read]\n"));
/* <- can i make it non-block?? or use select??? pio_tryread()? */ /* <- can i make it non-block?? or use select??? pio_tryread()? */
n = qse_pio_read ( n = qse_pio_read (
cgi->pio, QSE_PIO_OUT, &cgi->pio, QSE_PIO_OUT,
&cgi->buf[cgi->buflen], &cgi->buf[cgi->buflen],
QSE_SIZEOF(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; task_cgi_t* cgi = (task_cgi_t*)task->ctx;
int pio_options; int pio_options;
int http_errnum = 500;
if (cgi->init_failed) goto oops; if (cgi->init_failed) goto oops;
@ -2398,39 +2475,67 @@ static int task_main_cgi (
if (httpd->option & QSE_HTTPD_CGINOCLOEXEC) if (httpd->option & QSE_HTTPD_CGINOCLOEXEC)
pio_options |= QSE_PIO_NOCLOEXEC; pio_options |= QSE_PIO_NOCLOEXEC;
cgi->pio = qse_pio_open ( if (qse_pio_init (
httpd->mmgr, 0, (const qse_char_t*)cgi->path, &cgi->pio, httpd->mmgr, (const qse_char_t*)cgi->path,
cgi->env, pio_options cgi->env, pio_options) <= -1)
);
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)
{ {
/* not meaningful to check writability to the child process qse_pio_errnum_t errnum;
* 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_mask |= QSE_HTTPD_TASK_TRIGGER_RELAY; errnum = qse_pio_geterrnum (&cgi->pio);
task->trigger[1].i = client->handle.i;
if (errnum == QSE_PIO_ENOENT) http_errnum = 404;
else if (errnum == QSE_PIO_EACCES) http_errnum = 403;
goto oops;
} }
cgi->pio_inited = 1;
if (cgi->reqcon)
/* 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)
{ {
/* since i didn't set triggers in the initializer (task_init_cgi()), /* the existence of the forwarding buffer leads to a trigger
* it is possible that some contents has been read in already, * for checking data availiability from the client side. */
* forward them first. cgi_forward_content() is called after
* triggers are added above because cgi_forwrad_content() if (cgi->req)
* manipulates triggers when a forwarding error occurs. */ {
cgi_forward_content (httpd, task); /* 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;
}
else if (QSE_MBS_LEN(cgi->reqfwdbuf) > 0)
{
/* 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; task->main = cgi->nph? task_main_cgi_4: task_main_cgi_2;
@ -2451,7 +2556,9 @@ oops:
cgi->htrd = QSE_NULL; 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 /* 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); qse_printf (QSE_T("opening file [%hs] for reading\n"), path);
fd = open (path, flags, 0); 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); flags = fcntl (fd, F_GETFD);
if (flags >= 0) fcntl (fd, F_SETFD, flags | FD_CLOEXEC); 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??? */ /* TODO: fstat64??? */
if (fstat (fd, &st) <= -1) if (fstat (fd, &st) <= -1)
{ {
qse_httpd_seterrnum (httpd,
(errno == ENOENT? QSE_HTTPD_ENOENT:
errno == EACCES? QSE_HTTPD_EACCES: QSE_HTTPD_ESUBSYS));
close (fd); close (fd);
return -1; return -1;
} }
if (S_ISDIR(st.st_mode)) if (!S_ISREG(st.st_mode))
{ {
qse_httpd_seterrnum (httpd, QSE_HTTPD_EACCES);
close (fd); close (fd);
return -1; return -1;
} }
@ -287,7 +297,13 @@ static int file_wopen (
qse_printf (QSE_T("opening file [%hs] for writing\n"), path); qse_printf (QSE_T("opening file [%hs] for writing\n"), path);
fd = open (path, flags, 0644); 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; handle->i = fd;
return 0; 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)); 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) if (method == QSE_HTTP_GET || method == QSE_HTTP_POST)
{ {
const qse_mchar_t* qpath = qse_htre_getqpathptr(req); const qse_mchar_t* qpath = qse_htre_getqpathptr(req);
@ -583,7 +600,7 @@ qse_printf (QSE_T("Entasking chunked CGI...\n"));
oops: oops:
/*qse_httpd_markbadclient (httpd, client);*/ /*qse_httpd_markbadclient (httpd, client);*/
return 0; /* TODO: return failure??? */ return -1;
} }
static int peek_request ( static int peek_request (