Usually WWW involves HTML. HTML (Hyper-Text Markup Language) is a way to write documents with embedded pictures and links to other pages. These links are normally displayed underlined and if you click them your WWW- browser will load whatever document that link leads to.
We inherit Stdio.Port into this program so we can bind a TCP socket to accept incoming connection. A socket is simply a number to separate communications to and from different programs on the same computer.#!/usr/local/bin/pike
/* A very small httpd capable of fetching files only. * Written by Fredrik Hübinette as a demonstration of Pike. */
inherit Stdio.Port;
Next are some constants that will affect how uHTTPD will operate. This uses the preprocessor directive #define. The preprocessor is the first stage in the compiling process and can make textual processing of the code before it is compiled. As an example, after the first define below, all occurrences of 'BLOCK' will be replaced with 16060.
A port is a destination for a TCP connection. It is simply a number on the local computer. 1905 is not the standard port for HTTP connections though, which means that if you want to access this WWW server from a browser you need to specify the port like this: http://my.host.my.domain:1905//* Amount of data moved in one operation */
#define BLOCK 16060
/* Where do we have the html files ? */
#define BASE "/usr/local/html/"
/* File to return when we can't find the file requested */
#define NOFILE "/user/local/html/nofile.html"
/* Port to open */
#define PORT 1905
Next we declare a class called output_class. Later we will clone one instance of this class for each incoming HTTP connection.
Our new class inherits Stdio.File twice. To be able to separate them they are then named 'socket' and 'file'.class output_class
{
inherit Stdio.File : socket;
inherit Stdio.File : file;
Then there is a global variable called offset which is initialized to zero. (Each instance of this class will have its own instance of this variable, so it is not truly global, but...) Note that the initialization is done when the class is cloned (or instantiated if you prefer C++ terminology).int offset=0;
Next we define the function write_callback(). Later the program will go into a 'waiting' state, until something is received to process, or until there is buffer space available to write output to. When that happens a callback will be called to do this. The write_callback() is called when there is buffer space available. In the following lines 'void' means that it does not return a value. Write callback will be used further down as a callback and will be called whenever there is room in the socket output buffer.
The following line means: call seek in the inherited program 'file'.void write_callback()
{
int written;
string data;
Move the file pointer to the where we want to the position we want to read from. The file pointer is simply a location in the file, usually it is where the last read() ended and the next will begin. seek() can move this pointer to where we want it though.file::seek(offset);
Read BLOCK (16060) bytes from the file. If there are less that that left to read only that many bytes will be returned.data=file::read(BLOCK);
If we managed to read something...if(strlen(data))
{
... we try to write it to the socket.written=socket::write(data);
Update offset if we managed to write to the socket without errors.if(written >= 0)
{
offset+=written;
return;
}
If something went wrong during writing, or there was nothing left to read we destruct this instance of this class.werror("Error: "+socket::errno()+".\n");
}
That was the end of write_callback()destruct(this_object());
}
Next we need a variable to buffer the input received in. We initialize it to an empty string.
And then we define the function that will be called when there is something in the socket input buffer. The first argument 'id' is declared as mixed, which means that it can contain any type of value. The second argument is the contents of the input buffer.string input="";
Append data to the string input. Then we check if we have received a a complete line yet. If so we parse this and start outputting the file.void read_callback(mixed id,string data)
{
string cmd;
input+=data;
This sscanf is pretty complicated, but in essence it means: put the first word in 'input' in 'cmd' and the second in 'input' and return 2 if successful, 0 otherwise.if(sscanf(input,"%s %s%*[\012\015 \t]",cmd,input)>2)
{
If the first word isn't GET print an error message and terminate this instance of the program. (and thus the connection)if(cmd!="GET")
{
werror("Only method GET is supported.\n");
destruct(this_object());
return;
}
Remove the leading slash.sscanf(input,"%*[/]%s",input);
Combine the requested file with the base of the HTML tree, this gives us a full filename beginning with a slash. The HTML tree is the directory on the server in which the HTML files are located. Normally all files in this directory can be accessed by anybody by using a WWW browser. So if a user requests 'index.html' then that file name is first added to BASE (/home/hubbe/www/html/ in this case) and if that file exists it will be returned to the browser.input=BASE+combine_path("/",input);
Try opening the file in read-only mode. If this fails, try opening NOFILE instead. Opening the file will enable us to read it later.if(!file::open(input,"r"))
{
If this fails too. Write an error message and destruct this object.if(!file::open(NOFILE,"r"))
{
Ok, now we set up the socket so we can write the data back.werror("Couldn't find default file.\n");
destruct(this_object());
return;
}
}
Set the buffer size to 64 kilobytes.socket::set_buffer(65536,"w");
Make it so that write_callback is called when it is time to write more data to the socket.socket::set_nonblocking(0,write_callback,0);
Jump-start the writing.write_callback();
That was the end of read_callback().}
}
This function is called if the connection is closed while we are reading from the socket.
This function is called when the program is instantiated. It is used to set up data the way we want it. Extra arguments to clone() will be sent to this function. In this case it is the object representing the new connection.void selfdestruct() { destruct(this_object()); }
We insert the data from the file f into 'socket'.void create(object f)
{
socket::assign(f);
Then we set up the callback functions and sets the file nonblocking. Nonblocking mode means that read() and write() will rather return that wait for I/O to finish. Then we sit back and wait for read_callback to be called.socket::set_nonblocking(read_callback,0,selfdestruct);
End of create()}
End of the new class.};
Next we define the function called when someone connects.
This creates a local variable of type 'object'. An object variable can contain a clone of any program. Pike does not consider clones of different programs different types. This also means that function calls to objects have to be resolved at run time.void accept_callback()
{
object tmp_output;
The function accept clones a Stdio.File and makes this equal to the newly connected socket.tmp_output=accept();
If it failed we just return.if(!tmp_output) return;
Otherwise we clone an instance of 'output_class' and let it take care of the connection. Each clone of output_class will have its own set of global variables, which will enable many connections to be active at the same time without data being mixed up. Note that the programs will not actually run simultaneously though.output_class(tmp_output);
Destruct the object returned by accept(), output_class has already copied the contents of this object.destruct(tmp_output);
Then there is main, the function that gets it all started.}
Write an encouraging message to stderr.int main(int argc, array(string) argv)
{
werror("Starting minimal httpd\n");
Bind PORT and set it up to call accept_callback as soon as someone connects to it. If the bind() fails we write an error message and return the 17 to indicate failure.if(!bind(PORT, accept_callback))
{
werror("Failed to open socket (already bound?)\n");
return 17;
}
If everything went ok, we return -17, any negative value returned by main() means that the program WON'T exit, it will hang around waiting for events instead. (like someone connecting)return - 17; /* Keep going */
That's it, this simple program can be used as the basis for a simple WWW-server. Note that today most WWW servers are very complicated programs, and the above program can never replace a modern WWW server. However, it is very fast if you only want a couple of web pages and have a slow machine available for the server.}