| < Day Day Up > |
|
Now, let's look at a simple HTTP server written in Tcl. The entire source listing for the Tcl simple HTTP server is provided in Listing 21.8. Let's now walk through this listing to understand the sample implementation.
The Tcl simple HTTP server is made up of five procedures, with a general design mimicking that of all other simple HTTP servers presented in this book. In this discussion, we cover each of the procedures in their order of use, which in this case means last procedure first (from Listing 21.8).
The http_server procedure is the main entry for the simple HTTP server (lines 125-134). The http_server procedure accepts a single argument, which is the port on which the server should become resident (bind, in essence). At line 128, the socket command is used with the -server option to create the server socket. Immediately following the -server option at line 128 is the procedure that will be called upon accept of a new client socket (http_accept). The last argument for the socket command is the port, which we'll be bound to. The new server socket is stored into servsock (which is not used again here). At line 132, we allow the Tcl event loop to take over by calling vwait on an unused variable.
The http_accept procedure (lines 83-119) handles a new client connection and determines the type of request being made from the client. The http_accept procedure (a callback for the socket function) accepts the new client socket (sock) and the address and port from which the new client connection came (addr and port). We emit a debug message to standard-out at line 85 and then configure the new client socket for line buffering using fconfigure (line 87). A line of text is then read from the client using the gets command and stored in line (line 90). Recall that an HTTP request is made up of the request type (GET, etc.), the object desired (file), and the HTTP version. These are separated by spaces, so we use the split command at line 93 to split up the request into the three separate elements. Lines 97-99 extract the newly split elements from the list and store them to the individual variables using the lindex command. At line 102, we test the request against 'get' (using string compare), and, if it matches (line 105), we call the handle_get_method procedure. Otherwise, an error message is emitted to standard-out (line 112) and to the client (line 113) using the puts command. Finally, at line 117, the client socket is closed using the close command.
The handle_get_method procedure (lines 49-77) handles the connection from an HTTP GET message perspective and is the meat of the simple HTTP server. The procedure accepts as arguments the client socket (sock) and the filename being requested (filename). The first step is cleaning up the filename that was requested by the Web client. We trim the ‘/' characters from the beginning and end of the filename using the string trim command (line 52). After this is complete, we check to see if the filename is now empty (for example, if a ‘/' was requested indicating the default file). If the filename is now zero length (as identified by string compare with a null string at line 55), we set the filename to the default file (index.html).
At line 62, we open the filename defined by the client using the open command. We then call define_content_type to determine the type of content being returned to the user (line 65). We pass the filename to define_content_type, as the content type is determined here by the file extension. We then emit the HTTP response header (line 70 using a call to emit_response_header) and then emit the contents of the file through the socket using the puts command at line 73. Note that we read the file anonymously using the read command and pass the data read immediately to the socket using the puts command. Finally, we close the file opened at line 62 with the close command at line 75.
The final two procedures to be discussed are support procedures called from the handle_get_method procedure. The first is emit_response_header (lines 32-43) and define_content_type (lines 4-26).
Procedure emit_response_header is used to emit the HTTP message response header to the client. It accepts as arguments the client socket and the content type. The puts command is used to write the response header to the client, and specifies a message header with status code (line 37) followed by a number of optional elements. An important element to notice is the content-type line (line 40), which tells the Web client how to render the associated message body. A blank line is also emitted (line 41), which is used to identify the end of the response message, and the beginning of the HTTP response.
Procedure define_content_type is used to identify the type of content being returned to the client, based upon the extension of the filename (lines 4-26). The filename is split into a list at line 7 using the split command, and the extension extracted from the list using lindex at line 10. A series of if commands are then performed (lines 14-24) to identify the supported content types (returned as strings). If an unsupported content type is found, the default is returned ('application/octet-stream').
Listing 21.8 Tcl simple HTTP server source.
1 # 2 # Determine the content type based upon the file extension 3 # 4 proc define_content_type { filename } { 5 6 # Split the filename into filename / extension 7 set fileparts [split $filename {"."}] 8 9 # Grab the extension part 10 set extension [lindex $fileparts 1] 11 12 # Test the extensions and return the appropriate 13 # content-type string 14 if { [string compare $extension "html"] == 0 } { 15 return "text/html" 16 } elseif { [string compare $extension "htm"] == 0 } { 17 return "text/html" 18 } elseif { [string compare $extension "txt"] == 0 } { 19 return "text/html" 20 } elseif { [string compare $extension "tcl"] == 0 } { 21 return "text/plain" 22 } else { 23 return "application/octet-stream" 24 } 25 26 } 27 28 29 # 30 # Emit the standard HTTP response message header 31 # 32 proc emit_response_header { sock content_type } { 33 34 # Emit the header through the socket with one 35 # blank line to identify the header / message 36 # separation. 37 puts $sock "HTTP/1.1 200 OK" 38 puts $sock "Server: TCL shttp" 39 puts $sock "Connection: close" 40 puts $sock "Content-Type: $content_type" 41 puts $sock "\n" 42 43 } 44 45 46 # 47 # HTTP Get Method Handler 48 # 49 proc handle_get_method { sock filename } { 50 51 # Trim leading and trailing '/' 52 set newfile [string trim $filename "/"] 53 54 # If new file is blank, replace with 'index.html' 55 if { [string compare $newfile "" ] == 0 } { 56 57 set newfile "index.html" 58 59 } 60 61 # Open the requested file 62 set f [open $newfile r] 63 64 # Call define_content_type and get the content type 65 set ct [ define_content_type $filename ] 66 67 puts "The content type is $ct" 68 69 # Emit the response header 70 emit_response_header $sock $ct 71 72 # Emit the contents of the file 73 puts $sock [read $f] 74 75 close $f 76 77 } 78 79 80 # 81 # HTTP Connection Handler 82 # 83 proc http_accept { sock addr port } { 84 85 puts "Handling new connection from $addr port $port" 86 87 fconfigure $sock -buffering line 88 89 # Grab a line from the client 90 gets $sock line 91 92 # Split the command out into request / file / version 93 set message [split $line {" "}] 94 95 # Split out the elements of the HTTP request into 96 # separate strings. 97 set request [ lindex $message 0 ] 98 set filename [ lindex $message 1 ] 99 set version [ lindex $message 2 ] 100 101 # Test the request 102 set get_test [string compare -nocase $request "get"] 103 104 # If 'GET', then call the get method handler 105 if { $get_test == 0 } { 106 107 handle_get_method $sock $filename 108 109 } else { 110 111 # Emit the unimplemented error 112 puts "Unknown method $request" 113 puts $sock "HTTP/1.0 501 Unimplemented Method" 114 115 } 116 117 close $sock 118 119 } 120 121 122 # 123 # Initialization function for the simple HTTP server 124 # 125 proc http_server { port } { 126 127 # Create the TCP Server using the user-defined port 128 set servsock [ socket -server http_accept $port ] 129 130 # Wait indefinitely (allow the event-loop to take 131 # over) 132 vwait forever 133 134 }
The simple Tcl HTTP server is started as shown in Listing 21.9. In this listing, we simply call the http_server procedure (line 7) and specify the port number on which we want the server to bind.
Listing 21.9 Starting the Tcl simple HTTP server.
1 # 2 # Start the HTTP server on port 80 3 # 4 5 puts "Starting the HTTP server on port 80" 6 7 http_server 80
| < Day Day Up > |
|