• Please review our updated Terms and Rules here

give my XT web server a workout...

Mike Chambers

Veteran Member
Joined
Sep 2, 2006
Messages
2,621
i wanted to get some more experience with WATTCP, so i spent a few hours making this simple HTTP server for DOS in Turbo C++. i've got it going on my 5160 with a full mirror of The Art of Assembly.

http://home.rubbermallet.org/

i'll probably keep it up and logging the connections for a few days, and see whether or not it explodes. i've got it set to handle up to 5 simultaneous connections. seems reasonable for the hardware it's on. in the mean time i'm going to see what i can do about improving the buffering sytem to get some more speed.

if anybody wants a copy of the EXE/source i'd provide it. it's pretty small, about 5 KB of code. i don't plan to spend a lot of time on this, since i have other projects i need to finish first but this seemed a good waste of an evening.
 
It's running. Are you single threaded at the moment?

no, it's multi threaded and can handle as many connections as WATTCP has memory for. i have #define for the max connection limit when compiling.

i'm posting the code below.. i am going to add support for name-based virtual hosts, directory listings, resuming downloads, and the POST method so that a site's content can be changed without taking the server offline. i should be able to get that done tonight, then i will call it finished.

i was also going to add buffering to read more than one packet at a time from the disk. maybe 8 KB or so. 8 times fewer hard drive accesses should make a pretty good difference in speed on an 8088 with a 24 year old MFM drive! :p

Code:
#include <stdio.h>
#include <dos.h>
#include <tcp.h>
#include <string.h>

#define PORT_HTTP 80
#define MAXCLIENTS 5

struct structhttp {
	char filename[64];
	FILE *filehand;
	tcp_Socket sock;
	sockaddr peer;
	char laststate;
	char gotreq;
	unsigned long filesize;
	unsigned long curpos;
} static http[MAXCLIENTS];

FILE *logfile;
struct time timeval;
struct date dateval;
char *reqstring[2] = { "GET ", "POST " };
char msg[256], msgout[4096];
int msglen;

void resetsocket(int socknum) {
	sock_abort(&http[socknum].sock); //it can't hurt, right?
	memset(&http[socknum].filename, 0, 64);
	if (http[socknum].filehand!=NULL) fclose(http[socknum].filehand);
	http[socknum].laststate = 0;
	http[socknum].filesize = 0;
	http[socknum].curpos = 0;
	http[socknum].gotreq = 0;
	tcp_listen(&http[socknum].sock, PORT_HTTP, 0, 0, NULL, 0);
}

void sendfile(int socknum) {
	int remain;
	remain = sock_rbleft(&http[socknum].sock) & 0xFFF; //4 KB max buffer
	if (!remain) return;
	msglen = fread(&msgout[0], 1, remain, http[socknum].filehand);
	if (!msglen) { sock_close(&http[socknum].sock); return; }

	sock_write(&http[socknum].sock, &msgout[0], msglen);
	sock_flush(&http[socknum].sock);
	//if (ftell(http[socknum].filehand)==http[socknum].filesize)
	//	sock_close(&http[socknum].sock);
}

void startsend(int socknum) {
	http[socknum].filehand = fopen(&http[socknum].filename[0], "rb");
	if (http[socknum].filehand==NULL) {
			msglen = sprintf(&msgout[0], "HTTP/1.0 404 File Not Found\r\n\r\n");
			sock_write(&http[socknum].sock, &msgout[0], msglen);
			msglen = sprintf(&msgout[0], "<b><h1>404 File Not Found</h1></b><p>The requested file does not exist on this server:\r\n<b>%s</b>\r\n", &http[socknum].filename[0]);
			sock_write(&http[socknum].sock, &msgout[0], msglen);
			sock_flush(&http[socknum].sock);
			sock_close(&http[socknum].sock);
			return;
	}
	fseek(http[socknum].filehand, 0, SEEK_END);
	http[socknum].filesize = ftell(http[socknum].filehand);
	fseek(http[socknum].filehand, 0, SEEK_SET);
	msglen = sprintf(&msgout[0], "HTTP/1.0 200 OK\r\n\r\n");
	sock_write(&http[socknum].sock, &msgout[0], msglen);
	sock_flush(&http[socknum].sock);
	http[socknum].laststate = 2;
}

int getreqtype(char *request) {
	int i, pos;
	char cc1, cc2;

	for (i=0; i<2; i++) {
		pos = 0;
		while (1) {
			cc1 = reqstring[i][pos];
			cc2 = request[pos++];
			if ((cc2>96) && (cc2<123)) cc2 -= 32; //convert lowercase to uppercase
			if ((cc1 != cc2) || (cc1==32)) break;
		}
		if (cc1==32) return(i);
	}
	return(i);
}

void parsefilename(int socknum, char *reqline) {
	int srcpos, dstpos = 0;
	char cc;
	for (srcpos=5; srcpos<68; srcpos++) {
		cc = reqline[srcpos];
		if (cc==32) cc = 0;
		if (cc==47) cc = 92; //convert forward slashes to back slashes
		http[socknum].filename[dstpos++] = cc;
		if (!cc) {
			if ((http[socknum].filename[dstpos-2]==92) || (dstpos==1)) {
				sprintf(&http[socknum].filename[0], "%sindex.htm", &http[socknum].filename[0]);
			}
			return;
		}
	}
}

void waitheader(int socknum) {
	if (sock_rbused(&http[socknum].sock)==0) return;
	msglen = sock_gets(&http[socknum].sock, &msg[0], 256);
	if (msglen==0) {
		http[socknum].laststate = 2;
		return;
	}

	if (!http[socknum].gotreq) {
		if (getreqtype(&msg[0])>0) {
			msglen = sprintf(&msgout[0], "HTTP/1.0 405 Method Not Allowed\r\n\r\n<b><h1>405 Method Not Allowed</h1></b><p>Invalid request method!\r\n");
			sock_write(&http[socknum].sock, &msgout[0], msglen);
			sock_flush(&http[socknum].sock);
			sock_close(&http[socknum].sock);
			return;
		}
		http[socknum].gotreq = 1;
		parsefilename(socknum, &msg[0]);
		inet_ntoa(&msg[0], http[socknum].peer.s_ip);
		getdate(&dateval); gettime(&timeval);
		sprintf(&msgout[0], "[%02u-%02u-%04u %02u:%02u:%02u] %s GET \\%s\r\n", dateval.da_mon, dateval.da_day, dateval.da_year, timeval.ti_hour, timeval.ti_min, timeval.ti_sec, &msg[0], &http[socknum].filename[0]);
		printf("%s", &msgout[0]);
		if (logfile!=NULL) fprintf("%s", &msgout[0]);
		startsend(socknum);
	}
}

int main(int argc, char *argv[]) {
	int i;

	printf("HTTP Server for DOS v1.0 (c)2011 Mike Chambers\r\n\r\n");
	printf("Initializing TCP/IP... ");
	sock_init();
	printf("OK!\r\n");

	for (i=1; i<argc; i++)
		if (strcmpi(argv[i], "-log")==0) {
			printf("Logging enabled, will be written to HTTPDLOG.TXT.\r\n");
			logfile = fopen("httpdlog.txt", "w");
		}

	for (i=0; i<MAXCLIENTS; i++) {
		resetsocket(i);
	}
	printf("Listening for connections on port %u...\r\n", PORT_HTTP);

	while (!kbhit()) {
		for (i=0; i<MAXCLIENTS; i++) {
			tcp_tick(&http[i].sock);
			if (sock_established(&http[i].sock)) {
				switch (http[i].laststate) {
					case 0: //this is a new connection
						getpeername(&http[i].sock, &http[i].peer, (int *)NULL);
						sock_mode(&http[i].sock, TCP_MODE_ASCII);
						http[i].laststate = 1;
						waitheader(i); break;
					case 1: //we're waiting on the request header
						waitheader(i); break;
					case 2: //data transfer in progress
						sendfile(i); break;
					default: break;
				}
			} else if (http[i].laststate>0) resetsocket(i);
		}
	}

	if (logfile!=NULL) fclose(logfile);

	return(0);
}

so glad i'm not limited to QuickBASIC any more.. eww
 
Very nice Sir :) I am browsing around..

Seems to work nicely, I'm browsing from Israel while on business this week.
Kipp

No problem here. The longer pages take several seconds to load but I wouldn't expect lightning speeds from a 5160.

thanks to all you guys for poking around on it. :D

imagine.. a huge data center running nothing but XTs! who needs an 8 core Xeon?

EDIT: i just realized, since it's actually serving the site out of the same directory as as .EXE file, you can just download it from the XT itself:

http://home.rubbermallet.org/httpd.exe
 
Last edited:
i was also going to add buffering to read more than one packet at a time from the disk. maybe 8 KB or so. 8 times fewer hard drive accesses should make a pretty good difference in speed on an 8088 with a 24 year old MFM drive!

Did you actually mean you were, but aren't. Or will you actually do that?
 
Later versions of DOS have a read-ahead configurable via BUFFERS config.sys (buffers=64,16 would provide 32K cache and 8K read-ahead). I can't remember which version of DOS the second parameter was introduced with though.
 
The site looks wonderful in Lynx. I'm currently using lynx to visit the site because I can so easily redefine the environment variable http_proxy to make the browser go via the Tor proxy network - and the reason is that Fortinet is currently classifying your site as pornography! Our FortiGuard firewall subscribes to the Fortinet filtering and it blocks your site. No worries though, I have pestered them to reclassify (with an added sharp note of "who comes up with this classifying, anyway?"), and usually they'll fix it within hours.
 
The site looks wonderful in Lynx. I'm currently using lynx to visit the site because I can so easily redefine the environment variable http_proxy to make the browser go via the Tor proxy network - and the reason is that Fortinet is currently classifying your site as pornography! Our FortiGuard firewall subscribes to the Fortinet filtering and it blocks your site. No worries though, I have pestered them to reclassify (with an added sharp note of "who comes up with this classifying, anyway?"), and usually they'll fix it within hours.

wow, that's.... strange. lol.
 
Later versions of DOS have a read-ahead configurable via BUFFERS config.sys (buffers=64,16 would provide 32K cache and 8K read-ahead). I can't remember which version of DOS the second parameter was introduced with though.

that wouldn't really do the trick for this, because it will be reading different parts of totally different files when theres more than 1 user on it. so it would keep reloading the buffer with stuff that isn't going to be read next and slow it all down.

i need to make a large buffer separate for each connected client.
 
It's still worth doing though to try to keep the FAT cached (since DOS 2 the FAT has been treated like any other sector, i.e. not cached!).
 
Nicely done! And it's fast - I haven't seen such a fast web server on this class hardware before.
 
Back
Top