Commit dda6a533 authored by deuce's avatar deuce
Browse files

Add in an output thread which handles chunking. This should improve JS

output, and prevent gobs 'o errors.
parent babcc9d4
......@@ -610,6 +610,10 @@ void sbbs_read_ini(
web->options
=iniGetBitField(list,section,strOptions,web_options
,BBS_OPT_NO_HOST_LOOKUP | WEB_OPT_HTTP_LOGGING);
web->outbuf_highwater_mark
=iniGetShortInt(list,section,"OutbufHighwaterMark",1024);
web->outbuf_drain_timeout
=iniGetShortInt(list,section,"OutbufDrainTimeout",10);
web->bind_retry_count=iniGetInteger(list,section,strBindRetryCount,global->bind_retry_count);
web->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,global->bind_retry_delay);
......@@ -1110,6 +1114,10 @@ BOOL sbbs_write_ini(
iniRemoveValue(lp,section,strBindRetryDelay);
else if(!iniSetInteger(lp,section,strBindRetryDelay,web->bind_retry_delay,&style))
break;
if(!iniSetShortInt(lp,section,"OutbufHighwaterMark",bbs->outbuf_highwater_mark,&style))
break;
if(!iniSetShortInt(lp,section,"OutbufDrainTimeout",bbs->outbuf_drain_timeout,&style))
break;
}
/***********************************************************************/
......
......@@ -82,6 +82,7 @@ static const char* unknown="<unknown>";
(Including terminator )*/
#define MAX_REDIR_LOOPS 20 /* Max. times to follow internal redirects for a single request */
#define MAX_POST_LEN 1048576 /* Max size of body for POSTS */
#define OUTBUF_LEN 20480 /* Size of output thread ring buffer */
enum {
CLEANUP_SSJS_TMP_FILE
......@@ -197,6 +198,10 @@ typedef struct {
js_branch_t js_branch;
subscan_t *subscan;
/* Ring Buffer Stuff */
RingBuf outbuf;
sem_t output_thread_terminated;
/* Client info */
client_t client;
} http_session_t;
......@@ -297,7 +302,7 @@ static BOOL js_setup(http_session_t* session);
static char *find_last_slash(char *str);
static BOOL check_extra_path(http_session_t * session);
static BOOL exec_ssjs(http_session_t* session, char* script);
static BOOL ssjs_send_headers(http_session_t* session);
static BOOL ssjs_send_headers(http_session_t* session, int chunked);
static time_t
sub_mkgmt(struct tm *tm)
......@@ -416,6 +421,22 @@ static int lprintf(int level, char *fmt, ...)
return(startup->lputs(startup->cbdata,level,sbuf));
}
static int writebuf(http_session_t *session, const char *buf, size_t len)
{
int sent=0;
int avail;
while(!terminate_server && sent < len) {
avail=RingBufFree(&session->outbuf);
if(!avail)
SLEEP(1);
if(avail > len-sent)
avail=len-sent;
sent=RingBufWrite(&(session->outbuf), ((char *)buf)+sent, avail);
}
return(sent);
}
static int sock_sendbuf(SOCKET sock, const char *buf, size_t len, BOOL *failed)
{
size_t sent=0;
......@@ -556,37 +577,12 @@ static void init_enviro(http_session_t *session) {
* Sends string str to socket sock... returns number of bytes written, or 0 on an error
* Can not close the socket since it can not set it to INVALID_SOCKET
*/
static int sockprint(SOCKET sock, const char *str)
static int bufprint(http_session_t *session, const char *str)
{
int len;
int result;
int written=0;
BOOL wr;
if(sock==INVALID_SOCKET)
return(0);
if(startup->options&WEB_OPT_DEBUG_TX)
lprintf(LOG_DEBUG,"%04d TX: %s", sock, str);
len=strlen(str);
while(socket_check(sock,NULL,&wr,startup->max_inactivity*1000) && wr && written<len) {
result=sendsocket(sock,str+written,len-written);
if(result==SOCKET_ERROR) {
if(ERROR_VALUE==ECONNRESET)
lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",sock);
else if(ERROR_VALUE==ECONNABORTED)
lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",sock);
else
lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
return(0);
}
written+=result;
}
if(written != len) {
lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
return(0);
}
return(len);
return(writebuf(session,str,len));
}
/**********************************************************/
......@@ -741,14 +737,20 @@ static void close_request(http_session_t * session)
int i;
if(session->req.write_chunked) {
sock_sendbuf(session->socket, "0\r\n",3,NULL);
while(RingBufFull(&session->outbuf))
SLEEP(1);
session->req.write_chunked=0;
writebuf(session,"0\r\n",3);
if(session->req.dynamic==IS_SSJS)
ssjs_send_headers(session);
ssjs_send_headers(session,FALSE);
else
/* Non-ssjs isn't capable of generating headers during execution */
sock_sendbuf(session->socket, newline,2,NULL);
writebuf(session, newline, 2);
}
/* Force the output thread to go NOW */
sem_post(&(session->outbuf.highwater_sem));
if(session->req.ld!=NULL) {
now=time(NULL);
localtime_r(&now,&session->req.ld->completed);
......@@ -886,7 +888,7 @@ static void safecat(char *dst, const char *append, size_t maxlen) {
/* Sends headers for the reply. */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
static BOOL send_headers(http_session_t *session, const char *status)
static BOOL send_headers(http_session_t *session, const char *status, int chunked)
{
int ret;
BOOL send_file=TRUE;
......@@ -985,13 +987,13 @@ static BOOL send_headers(http_session_t *session, const char *status)
safecat(headers,header,MAX_HEADERS_SIZE);
}
if(session->req.write_chunked) {
if(chunked) {
safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TRANSFER_ENCODING),"Chunked");
safecat(headers,header,MAX_HEADERS_SIZE);
}
/* DO NOT send a content-length for chunked */
if(session->req.keep_alive && session->req.dynamic!=IS_CGI && (!session->req.write_chunked)) {
if(session->req.keep_alive && session->req.dynamic!=IS_CGI && (!chunked)) {
if(ret) {
safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
safecat(headers,header,MAX_HEADERS_SIZE);
......@@ -1024,26 +1026,28 @@ static BOOL send_headers(http_session_t *session, const char *status)
}
safecat(headers,"",MAX_HEADERS_SIZE);
send_file = (sockprint(session->socket,headers) && send_file);
send_file = (bufprint(session,headers) && send_file);
FREE_AND_NULL(headers);
while(RingBufFull(&session->outbuf))
SLEEP(1);
session->req.write_chunked=chunked;
return(send_file);
}
static int sock_sendfile(SOCKET socket,char *path)
static int sock_sendfile(http_session_t *session,char *path)
{
int file;
long offset=0;
int ret=0;
int i;
char buf[2048]; /* Input buffer */
if(startup->options&WEB_OPT_DEBUG_TX)
lprintf(LOG_DEBUG,"%04d Sending %s",socket,path);
lprintf(LOG_DEBUG,"%04d Sending %s",session->socket,path);
if((file=open(path,O_RDONLY|O_BINARY))==-1)
lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",socket,errno,path);
lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",session->socket,errno,path);
else {
if((ret=sendfilesocket(socket, file, &offset, 0)) < 0) {
lprintf(LOG_DEBUG,"%04d !ERROR %d sending %s"
, socket, errno, path);
ret=0;
while((i=read(file, buf, sizeof(buf)))>0) {
writebuf(session,buf,i);
}
close(file);
}
......@@ -1086,7 +1090,7 @@ static void send_error(http_session_t * session, const char* message)
int snt=0;
lprintf(LOG_INFO,"%04d Sending generated error page",session->socket);
snt=sock_sendfile(session->socket,session->req.physical_path);
snt=sock_sendfile(session,session->req.physical_path);
if(snt<0)
snt=0;
if(session->req.ld!=NULL)
......@@ -1102,10 +1106,10 @@ static void send_error(http_session_t * session, const char* message)
if(!sent_ssjs) {
sprintf(session->req.physical_path,"%s%s.html",session->req.error_dir?session->req.error_dir:error_dir,error_code);
session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
send_headers(session,message);
send_headers(session,message,FALSE);
if(!stat(session->req.physical_path,&sb)) {
int snt=0;
snt=sock_sendfile(session->socket,session->req.physical_path);
snt=sock_sendfile(session,session->req.physical_path);
if(snt<0)
snt=0;
if(session->req.ld!=NULL)
......@@ -1121,7 +1125,7 @@ static void send_error(http_session_t * session, const char* message)
"please notify <a href=\"mailto:sysop@%s\">"
"%s</a></BODY></HTML>"
,error_code,error_code,error_code,scfg.sys_inetaddr,scfg.sys_op);
sockprint(session->socket,sbuf);
bufprint(session,sbuf);
if(session->req.ld!=NULL)
session->req.ld->size=strlen(sbuf);
}
......@@ -2329,6 +2333,7 @@ static BOOL exec_cgi(http_session_t *session)
str_list_t tmpbuf;
size_t tmpbuflen=0;
BOOL no_chunked=FALSE;
BOOL set_chunked=FALSE;
SAFECOPY(cmdline,session->req.physical_path);
......@@ -2433,16 +2438,10 @@ static BOOL exec_cgi(http_session_t *session)
int snt=0;
start=time(NULL);
if(session->req.method!=HTTP_HEAD) {
if(session->req.write_chunked) {
sprintf(header,"%X\r\n",i);
sock_sendbuf(session->socket,header,strlen(header),NULL);
}
snt=sock_sendbuf(session->socket,buf,i,NULL);
snt=writebuf(session,buf,i);
if(session->req.ld!=NULL) {
session->req.ld->size+=snt;
}
if(session->req.write_chunked)
sock_sendbuf(session->socket,newline,2,NULL);
}
}
else
......@@ -2509,13 +2508,13 @@ static BOOL exec_cgi(http_session_t *session)
else {
if(!no_chunked && session->http_ver>=HTTP_1_1) {
session->req.keep_alive=orig_keep;
session->req.write_chunked=TRUE;
set_chunked=TRUE;
}
if(got_valid_headers) {
session->req.dynamic=IS_CGI;
if(cgi_status[0]==0)
SAFECOPY(cgi_status,session->req.status);
send_headers(session,cgi_status);
send_headers(session,cgi_status,set_chunked);
}
else {
/* Invalid headers... send 'er all as plain-text */
......@@ -2533,34 +2532,22 @@ static BOOL exec_cgi(http_session_t *session)
/* Add the content-type header (REQUIRED) */
SAFEPRINTF2(content_type,"%s: %s",get_header(HEAD_TYPE),startup->default_cgi_content);
strListPush(&session->req.dynamic_heads,content_type);
send_headers(session,cgi_status);
send_headers(session,cgi_status,FALSE);
/* Now send the tmpbuf */
for(i=0; tmpbuf != NULL && tmpbuf[i] != NULL; i++) {
if(strlen(tmpbuf[i])>0) {
if(session->req.write_chunked) {
sprintf(header,"%X\r\n",strlen(tmpbuf[i]));
sock_sendbuf(session->socket,header,strlen(header),NULL);
}
snt=sock_sendbuf(session->socket,tmpbuf[i],strlen(tmpbuf[i]),NULL);
if(session->req.write_chunked)
sock_sendbuf(session->socket,newline,2,NULL);
snt=writebuf(session,tmpbuf[i],strlen(tmpbuf[i]));
if(session->req.ld!=NULL) {
session->req.ld->size+=snt;
}
}
}
if(strlen(fbuf)>0) {
if(session->req.write_chunked) {
sprintf(header,"%X\r\n",strlen(fbuf));
sock_sendbuf(session->socket,header,strlen(header),NULL);
}
snt=sock_sendbuf(session->socket,fbuf,strlen(fbuf),NULL);
snt=writebuf(session,fbuf,strlen(fbuf));
if(session->req.ld!=NULL && snt>0) {
session->req.ld->size+=snt;
}
if(session->req.write_chunked)
sock_sendbuf(session->socket,newline,2,NULL);
}
got_valid_headers=TRUE;
}
......@@ -2630,16 +2617,10 @@ static BOOL exec_cgi(http_session_t *session)
int snt=0;
start=time(NULL);
if(session->req.method!=HTTP_HEAD) {
if(session->req.write_chunked) {
sprintf(header,"%X\r\n",i);
sock_sendbuf(session->socket,header,strlen(header),NULL);
}
snt=sock_sendbuf(session->socket,buf,i,NULL);
snt=writebuf(session,buf,i);
if(session->req.ld!=NULL) {
session->req.ld->size+=snt;
}
if(session->req.write_chunked)
sock_sendbuf(session->socket,newline,2,NULL);
}
}
}
......@@ -2687,6 +2668,7 @@ static BOOL exec_cgi(http_session_t *session)
char *value=NULL;
time_t start;
BOOL no_chunked=FALSE;
int set_chunked=FALSE;
/* Win32-specific */
char* env_block;
......@@ -2881,24 +2863,18 @@ static BOOL exec_cgi(http_session_t *session)
session->req.dynamic=IS_CGI;
if(!no_chunked && session->http_ver>=HTTP_1_1) {
session->req.keep_alive=orig_keep;
session->req.write_chunked=TRUE;
set_chunked=TRUE;
}
strListPush(&session->req.dynamic_heads,content_type);
send_headers(session,cgi_status);
send_headers(session,cgi_status,set_chunked);
}
if(msglen) {
if(session->req.write_chunked) {
sprintf(header,"%X\r\n",msglen);
sock_sendbuf(session->socket,header,strlen(header),NULL);
}
lprintf(LOG_DEBUG,"%04d Sending %d bytes: %.*s"
,session->socket,msglen,msglen,buf);
wr=sock_sendbuf(session->socket,buf,msglen,NULL);
wr=writebuf(session,session->socket,buf,msglen,NULL);
/* log actual bytes sent */
if(session->req.ld!=NULL && wr>0)
session->req.ld->size+=wr;
if(session->req.write_chunked)
sock_sendbuf(session->socket,newline,2,NULL);
}
}
......@@ -3069,8 +3045,7 @@ js_writefunc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval,
if((!session->req.prev_write) && (!session->req.sent_headers)) {
if(session->http_ver>=HTTP_1_1 && session->req.keep_alive) {
session->req.write_chunked=TRUE;
if(!ssjs_send_headers(session))
if(!ssjs_send_headers(session,TRUE))
return(JS_FALSE);
}
else {
......@@ -3082,7 +3057,7 @@ js_writefunc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval,
JS_GetProperty(cx, reply, "fast", &val);
if(JSVAL_IS_BOOLEAN(val) && JSVAL_TO_BOOLEAN(val)) {
session->req.keep_alive=FALSE;
if(!ssjs_send_headers(session))
if(!ssjs_send_headers(session,FALSE))
return(JS_FALSE);
}
}
......@@ -3097,16 +3072,9 @@ js_writefunc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval,
continue;
if(session->req.sent_headers) {
if(session->req.method!=HTTP_HEAD && session->req.method!=HTTP_OPTIONS) {
if(session->req.write_chunked) {
char chstr[12];
sprintf(chstr,"%X\r\n", JS_GetStringLength(str)+(writeln?2:0));
sock_sendbuf(session->socket, chstr, strlen(chstr),NULL);
}
sock_sendbuf(session->socket, JS_GetStringBytes(str), JS_GetStringLength(str),NULL);
writebuf(session,JS_GetStringBytes(str), JS_GetStringLength(str));
if(writeln)
sock_sendbuf(session->socket, newline, 2,NULL);
if(session->req.write_chunked)
sock_sendbuf(session->socket, newline, 2,NULL);
}
}
else {
......@@ -3369,7 +3337,7 @@ static BOOL js_setup(http_session_t* session)
return(TRUE);
}
static BOOL ssjs_send_headers(http_session_t* session)
static BOOL ssjs_send_headers(http_session_t* session,int chunked)
{
jsval val;
JSObject* reply;
......@@ -3397,7 +3365,7 @@ static BOOL ssjs_send_headers(http_session_t* session)
}
JS_ClearScope(session->js_cx, headers);
}
return(send_headers(session,session->req.status));
return(send_headers(session,session->req.status,chunked));
}
static BOOL exec_ssjs(http_session_t* session, char* script) {
......@@ -3459,7 +3427,7 @@ static BOOL exec_ssjs(http_session_t* session, char* script) {
/* Read http_reply object */
if(!session->req.sent_headers) {
retval=ssjs_send_headers(session);
retval=ssjs_send_headers(session,FALSE);
}
/* Free up temporary resources here */
......@@ -3476,7 +3444,7 @@ static void respond(http_session_t * session)
BOOL send_file=TRUE;
if(session->req.method==HTTP_OPTIONS) {
send_headers(session,session->req.status);
send_headers(session,session->req.status,FALSE);
}
else {
if(session->req.dynamic==IS_CGI) {
......@@ -3498,7 +3466,7 @@ static void respond(http_session_t * session)
}
else {
session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
send_file=send_headers(session,session->req.status);
send_file=send_headers(session,session->req.status,FALSE);
}
}
if(session->req.method==HTTP_HEAD || session->req.method==HTTP_OPTIONS)
......@@ -3507,7 +3475,7 @@ static void respond(http_session_t * session)
int snt=0;
lprintf(LOG_INFO,"%04d Sending file: %s (%u bytes)"
,session->socket, session->req.physical_path, flength(session->req.physical_path));
snt=sock_sendfile(session->socket,session->req.physical_path);
snt=sock_sendfile(session,session->req.physical_path);
if(session->req.ld!=NULL) {
if(snt<0)
snt=0;
......@@ -3594,6 +3562,75 @@ int read_post_data(http_session_t * session)
return(TRUE);
}
void http_output_thread(void *arg)
{
http_session_t *session=(http_session_t *)arg;
RingBuf *obuf;
char buf[OUTBUF_LEN+12]; /* *MUST* be large enough to hold the buffer,
the size of the buffer in hex, and four extra bytes. */
char *bufdata;
int failed=0;
int len;
int avail;
int chunked;
int i;
obuf=&(session->outbuf);
thread_up(TRUE /* setuid */);
while(session->socket!=INVALID_SOCKET && !terminate_server) {
/* Wait for something to output in the RingBuffer */
if(sem_trywait_block(&obuf->sem,1000))
continue;
/* Check for spurious sem post... */
if(!RingBufFull(obuf))
continue;
/* Wait for full buffer or drain timeout */
if(obuf->highwater_mark)
sem_trywait_block(&obuf->highwater_sem,startup->outbuf_drain_timeout);
/*
* At this point, there's something to send and,
* if the highwater mark is set, the timeout has
* passed or we've hit highwater. Read ring buffer
* into linear buffer.
*/
len=avail=RingBufFull(obuf);
if(avail>sizeof(buf)-12)
len=avail=sizeof(buf);
/*
* Read the current value of write_chunked... since we wait until the
* ring buffer is empty before fiddling with it.
*/
chunked=session->req.write_chunked;
bufdata=buf;
if(chunked) {
i=sprintf(buf, "%X\r\n", avail);
bufdata+=i;
len+=i;
}
RingBufRead(obuf, bufdata, avail);
if(chunked) {
bufdata+=avail;
*(bufdata++)='\r';
*(bufdata++)='\n';
len+=2;
}
if(failed)
continue;
sock_sendbuf(session->socket, buf, len, &failed);
}
thread_down();
sem_post(&session->output_thread_terminated);
}
void http_session_thread(void* arg)
{
int i;
......@@ -3622,6 +3659,17 @@ void http_session_thread(void* arg)
thread_up(TRUE /* setuid */);
session.finished=FALSE;
/* Start up the output buffer */
if(RingBufInit(&(session.outbuf), OUTBUF_LEN)) {
lprintf(LOG_ERR,"%04d Canot create output ringbuffer!", session.socket);
close_socket(session.socket);
thread_down();
return;
}
sem_init(&session.output_thread_terminated,0,0);
_beginthread(http_output_thread, 0, &session);
sbbs_srand(); /* Seed random number generator */
if(startup->options&BBS_OPT_NO_HOST_LOOKUP)
......@@ -3644,6 +3692,9 @@ void http_session_thread(void* arg)
lprintf(LOG_INFO,"%04d HostAlias: %s", session.socket, host->h_aliases[i]);
if(trashcan(&scfg,host_name,"host")) {
close_socket(session.socket);
session.socket=INVALID_SOCKET;
sem_wait(&session.output_thread_terminated);
RingBufDispose(&session.outbuf);
lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in host.can: %s", session.socket, host_name);
thread_down();
return;
......@@ -3653,6 +3704,9 @@ void http_session_thread(void* arg)
/* host_ip wasn't defined in http_session_thread */
if(trashcan(&scfg,session.host_ip,"ip")) {
close_socket(session.socket);
session.socket=INVALID_SOCKET;
sem_wait(&session.output_thread_terminated);
RingBufDispose(&session.outbuf);
lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", session.socket, session.host_ip);
thread_down();
return;
......@@ -3762,6 +3816,9 @@ void http_session_thread(void* arg)
#endif
close_socket(session.socket);
session.socket=INVALID_SOCKET;
sem_wait(&session.output_thread_terminated);
RingBufDispose(&session.outbuf);
active_clients--;
update_clients();
......
......@@ -90,6 +90,8 @@ typedef struct {
uint bind_retry_count; /* Number of times to retry bind() calls */
uint bind_retry_delay; /* Time to wait between each bind() retry */
char default_cgi_content[128];
WORD outbuf_highwater_mark; /* output block size control */
WORD outbuf_drain_timeout;
/* JavaScript operating parameters */
js_startup_t js;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment