www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - I need review this web server

reply Eungkyu Song <eungkyu sparcs.kaist.ac.kr> writes:
I wrote a very simple web server for testing d language. (in linux)
It seems to work well. but a chunk of memory leeks per connection
especially in thread mode.

please help me.
i need review for the code style too.

below is the code

Makefile:
--------------------------------------------------------
TARGET = httpd httpd-inetd httpd-thread

all: $(TARGET)

httpd: httpd.d
        dmd -ofhttpd httpd.d

httpd-inetd: httpd.d
        dmd -ofhttpd-inetd -version=inetd httpd.d

httpd-thread: httpd.d
        dmd -ofhttpd-thread -version=thread httpd.d

clean:
        rm -f *.o $(TARGET)
--------------------------------------------------------
httpd.d:
--------------------------------------------------------
import std.socket;
import std.socketstream;
import std.stream;
import std.string;
import std.file;
import std.thread;

alias char[][char[]] map;

const char[] SERVER = "Dhttpd";
const char[] SERVER_ROOT = "/home/eungkyu/public_html";
const char[] DEFAULT = "index.html";
const char[] MIME_DEFAULT = "text/plain";
const int PORT = 8888;

char[][int] code_string;
char[][int] error;
map mime_type;

extern (C) {
    alias void function(int) sighandler_t;
    const int SIGPIPE = 13;
    const sighandler_t SIG_IGN = cast(sighandler_t) 2;
    sighandler_t signal(int signum, sighandler_t handler);

    void handle_signal(int signo)
    {
	/* do nothing */
    }

    alias long time_t;
    time_t time(time_t* t);
    size_t strftime(char* s, size_t max, in char* format, in void* tm);
    void* localtime(time_t* timep);
}

void init()
{
    code_string[200] = "OK";
    code_string[400] = "Bad Request";
    code_string[403] = "Forbidden";
    code_string[404] = "Not Found";
    code_string[501] = "Not Implemented";

    error[400] = "error/400.html";
    error[403] = "error/403.html";
    error[404] = "error/404.html";
    error[501] = "error/501.html";

    mime_type["html"] = "text/html";
    mime_type["htm"] = "text/html";
    mime_type["gif"] = "image/gif";
    mime_type["jpg"] = "image/jpeg";
    mime_type["jpeg"] = "image/jpeg";
    mime_type["png"] = "image/png";
    mime_type["txt"] = "text/plain";

    /* why ignoring or not touching the SIGPIPE cause segfault */
    //signal(SIGPIPE, &SIG_IGN);
    signal(SIGPIPE, &handle_signal);
    chdir(SERVER_ROOT);
}

Socket init_listener(int port)
{
    Socket listener = new TcpSocket;
    listener.bind(new InternetAddress(port));
    listener.listen(10);

    return listener;
}

map parse_header(Stream input)
{
    map header;

    char[] line, name, value;
    while ((line = input.readLine()) != null) {
    	if (find(" \t", line[0]) == 0 && name != null) {
    	    header[name] ~= stripl(line);
    	    continue;
	}
    	int delim = find(line, ':');
	if (delim == -1) {
	    name = null;
	    continue;
	}
    	name = line[0 .. delim];
    	value = stripl(line[delim + 1 .. line.length]);
    	header[name] = value;
    }

    return header;
}

void print_header(Stream output, map header)
{
    foreach(char[] name; header.keys)
    	output.writeLine(name ~ ": " ~ header[name]);
}

char[] get_path(char[] location)
{
    char[] path = "." ~ location;

    while (path.length > 0 && path[path.length - 1] == '/')
    	path.length = path.length - 1;

    return path;
}

char[] get_mime_type(char[] path)
{
    if (path == null)
    	return MIME_DEFAULT;

    int extloc = rfind(path, '.');
    if (extloc == -1)
    	return MIME_DEFAULT;

    char[] ext = path[extloc + 1 .. path.length];
    if (mime_type[ext] == null)
    	return MIME_DEFAULT;

    return mime_type[ext];
}

char[] get_date_string()
{
    char[50] tmp;
    time_t t;
    time(&t);
    strftime(tmp, 50, "%a, %d %b %Y %H:%M:%S %z", localtime(&t));

    /* return toString(tmp) doesn't work */
    return toString(tmp).dup;
}

void response(Stream output, int code, char[] path = null)
{
    map header;

    header["Server"] = SERVER;
    header["Connection"] = "closed";
    header["Date"] = get_date_string();

    if (code >= 400 && code < 600 && path == null)
    	path = error[code];

    if (path != null) {
	header["Content-Type"] = get_mime_type(path);
	header["Content-Length"] = toString(getSize(path));
    }

    try {
	output.printf("HTTP/1.0 %i %.*s\r\n", code, code_string[code]);
	print_header(output, header);
	output.writeString("\r\n");

	if (path != null) {
	    File file = new File(path, FileMode.In);
	    output.copyFrom(file, file.size);
	    file.close();
	}
    }
    catch (WriteError) {
	/* client closed the connection. just ignore it */
    }
}

void process(Stream output, char[][] method, map header)
{
    if (method.length == 0)
    	return response(output, 400);

    if (method[0] != "GET")
    	return response(output, 501);

    if (method[1][0] != '/')
    	return response(output, 400);

    char[] path = get_path(method[1]);

    while (exists(path) == 1) {
    	if (isdir(path) == 0)
	    return response(output, 200, path);

    	if (exists(path ~ "/" ~ DEFAULT) == 0) {
	    return response(output, 403);
	}

	path ~= "/" ~ DEFAULT;
    }

    return response(output, 404);
}

int run(void* data)
{
    Stream[] stream = *(cast(Stream[]*) data);
    char[][] method = split(stream[0].readLine());
    map header = parse_header(stream[0]);
    process(stream[1], method, header);

    stream[0].close();
    stream[1].close();

    return 0;
}

int main(char[][] args)
{
    init();

    version (inetd) {
	Stream[] stream;
	stream.length = 2;

	stream[0] = stdin;
	stream[1] = stdout;

	run(&stream);
    }
    else {
	Socket listener = init_listener(PORT);

	while (1) {
	    Stream[] stream;
	    stream.length = 2;

	    SocketStream sockstream = new SocketStream(listener.accept());
	    stream[0] = sockstream;
	    stream[1] = sockstream;

	    version (thread) {
		Thread runner = new Thread(&run, &stream);
		runner.start();
	    }
	    else {
		run(&stream);
	    }

	}
    }

    return 0;
}
--------------------------------------------------------
Jun 12 2004
parent reply "Bruno A. Costa" <bruno codata.com.br> writes:
Hi,

the code does not compile. I am using dmd-0.92 with Linux:

$ make httpd
dmd -ofhttpd httpd.d
httpd.d(200): e2ir: cannot cast from void* to Stream []
make: ** [httpd] Erro 1

The error occurs in function  run(void* data)

Bruno.


Eungkyu Song wrote:

 I wrote a very simple web server for testing d language. (in linux)
 It seems to work well. but a chunk of memory leeks per connection
 especially in thread mode.
 
 please help me.
 i need review for the code style too.
 
 below is the code
 
Jun 14 2004
parent J C Calvarese <jcc7 cox.net> writes:
Bruno A. Costa wrote:
 Hi,
 
 the code does not compile. I am using dmd-0.92 with Linux:
Hmmm. It compiles fine for me using DMD 0.92 on WinXP (once I add ws2_32.lib). I don't have Linux installed, so I can't check whether it'd compile for me on Linux. It could be a bug in the Linux version of the compiler.
 
 $ make httpd
 dmd -ofhttpd httpd.d
 httpd.d(200): e2ir: cannot cast from void* to Stream []
 make: ** [httpd] Erro 1
 
 The error occurs in function  run(void* data)
 
 Bruno.
 
 
 Eungkyu Song wrote:
 
 
I wrote a very simple web server for testing d language. (in linux)
It seems to work well. but a chunk of memory leeks per connection
especially in thread mode.
I don't claim to be an expert on garbage collection (that's an understatement), but have you tried invoking a full collection at a strategic time? import std.gc; ... fullCollect();
please help me.
i need review for the code style too.

below is the code
-- Justin (a/k/a jcc7) http://jcc_7.tripod.com/d/
Jun 14 2004