The Smallest Tcl Web Server

ChangLi: This example shows how simple to build a web server by Tcl with event-driven. It creates a web server with callback function and display "Hello World!". You can visit server at http://localhost:2068

proc webServer {chan addr port} {
    while {[gets $chan] ne ""} {}
    puts $chan "HTTP/1.1 200 OK\nConnection: close\nContent-Type: text/plain\n"
    puts $chan "Hello World!"
    close $chan
}

socket -server webServer 2068
vwait forever

AMG: To make this code work reliably with Firefox, and to make it work at all with Safari and Internet Explorer, I had to add code to read (and ignore) all request lines through the first blank line. The browsers were getting confused when the server responded and closed the connection before they could even send the request. I had to add "Connection: close" for similar reasons. Last, I removed an extra newline, since [puts] appends a newline automatically. The one newline that remains is there to put a blank line between the header and the body. Also I corrected the content-type from text/html to text/plain.

By the way, a nasty DoS vulnerability lurks. All a malicious (or broken) client has to do is connect and never send a blank line. It doesn't have to send anything at all, in fact. Until a blank is sent, the server won't accept connections from any other clients, let alone process them and respond to them. The solution involves [fileevent].


AMG: Here's the same code, modified to use [apply]:

socket -server {apply {{chan addr port} {
    while {[gets $chan] ne ""} {}
    puts $chan "HTTP/1.1 200 OK\nConnection: close\nContent-Type: text/plain\n"
    puts $chan "Hello World!"
    close $chan
}}} 2068
vwait forever

dzach: Looks like the [apply] version is about 10% slower than the [proc] one.

AMG: How are you measuring the timing? As for why [apply] would go slower, my guess is that [socket] evals its -server argument fresh each time without caching any bytecodes. I don't think that can be fixed without changing it to take a command prefix instead of a script prefix, which would be an incompatible change but only in obscure usage.

dzach: I had no idea how to time [apply] so I just wrapped the body of both versions in a [puts [time {<body>}] ] which of course is not very accurate but is accurate enough to show the difference. And since the body in both versions is identical, the difference might indeed be bytecode related.

AMG: Since the bodies are identical, you just ran the same [time] command twice, and you got a 10% variation? That doesn't say one version is faster than the other; as you say, the bodies of the two versions are identical, and you only tested the bodies. I'm afraid I'm confused.

dzach: Code says it better than words. Try this:

 proc webServer {chan addr port} {
   puts [time {
    while {[gets $chan] ne ""} {}
    puts $chan "HTTP/1.1 200 OK\nConnection: close\nContent-Type: text/plain\n"
    puts $chan "Hello World!"
    close $chan
   }]
 }
 socket -server webServer 2068
 vwait forever
 socket -server {apply {{chan addr port} {
   puts [time {
    while {[gets $chan] ne ""} {}
    puts $chan "HTTP/1.1 200 OK\nConnection: close\nContent-Type: text/plain\n"
    puts $chan "Hello World!"
    close $chan
   }]
 }}} 2068
 vwait forever

AMG: To the best of my knowledge, [time] does not cache the bytecode compilation of its script argument, same as [eval]. Therefore both should be equally slow. I can't explain why there would be a difference. Maybe it's random.

That's strange. I just tried to repeat your test on a 2.8GHz P4 with 1GiB RAM, Windows XP SP3, and Tcl 8.6b1.2, and I got heavily quantized results: 0 microseconds (most common), 16000 microseconds (second-most common), and 15000 microseconds (least common). I'll have to try again on a different computer.


AMG: This page used to be named with a trailing space. To see its history, visit [L1 ].