
\subsubsection{Idea}

The goal is to implement a web server that lets the client navigate
the server's file system. It should respond in two different ways. It
should send the contents of the file if a file is requested. If a
directory is requested, it should send a directory listing to the
client so that the user can navigate the file system. Figure
\ref{fig:gswebsrv} shows the server display a page with {\tt w3m.}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{figure}
\begin{boxedminipage}{\textwidth.}
{\tt
\begin{verbatim}
Directory listing of /home/gnustep/gswebsrv/

Up one level.

 -  GNUmakefile                 2003-07-08 16:53:23 +0200              159
 -  GNUmakefile~                2003-07-08 16:52:40 +0200              156
 -  gswebsrv.m                  2003-07-09 18:39:03 +0200             6565
 -  gswebsrv.m~                 2003-07-09 12:52:37 +0200             2417
[+] obj                         2003-07-08 17:58:17 +0200               39
[+] shared_obj                  2003-07-08 17:58:17 +0200             4096
\end{verbatim}
}
\end{boxedminipage}
\caption{\label{fig:gswebsrv}
Example web server session with {\tt w3m.}
}
\end{figure}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsubsection{Preliminaries}

We'll be using {\tt xinetd} to handle requests to the web server. This
way we don't have to worry about implementing a complete daemon that
forks a child process for every request etc., {\tt xinetd} will handle
all of this for us. This is the configuration that we use.

\begin{verbatim}
service gswebsrv
{
        port            = 7788
        socket_type     = stream        
        wait            = no
        user            = wwwrun
        group           = nogroup
        server          = /usr/sbin/gswebsrv.sh
}
\end{verbatim}

It is basically self-explanatory. We specify user and group for the
server as well as the port. The entry ``server'' points to a shell
script ({\tt bash}) that performs some initialization. It sets the
requisite GNUstep variables, the user and the time zone and invokes
the actual server that will handle the request. It is important to set
these variables correctly, or else warnings by GNUstep will be output
to the web server. Needless to say the home directory of {\tt wwwrun}
has to exist for this to work properly.

\begin{verbatim}
#! /bin/sh
#

export LOGNAME=wwwrun
export HOME=/home/wwwrun
export TZ=Europe/Berlin

GNUSTEP_SYSTEM_ROOT=/usr/GNUstep
export GNUSTEP_SYSTEM_ROOT
. $GNUSTEP_SYSTEM_ROOT/System/Makefiles/GNUstep.sh

/home/gnustep/gswebsrv/shared_obj/ix86/linux-gnu/gnu-gnu-gnu/gswebsrv
\end{verbatim} %%$

Given this setup, {\tt xinetd} will listen on port {\tt 7788}, fork a
process for every request, connect the socket to the standard input
and output of the process and invoke the shell script, which in turn
sets the necessary variables and starts the actual server. The server
only has to read the request from the standard input and produce a
response on the standard output.

\subsubsection{Implementation}

HTTP knows many different status codes, of which we'll be using quite
a few, mostly to signal errors. The server sends a request followed by
a set of headers, which we'll ignore in our server. The client sends a
status code, followed by a set of headers and the response data.

These are the codes that our server will produce:
\begin{itemize}
\item 220 OK
\item 400 Bad Request
\item 403 Forbidden
\item 404 Not Found
\item 501 Not implemented
\item 505 HTTP Version Not Supported
\end{itemize}

We could have done with fewer codes, but we use them here to
illustrate the problem of writing a web server. All these codes with
the exception of the first one are error codes, so we start with a
routine that handles errors. It outputs the error code followed by a
detailed description of the problem, and exits the server (recall that
there is one server process for every request). The routine constructs
the code description from the status code itself and the description
and stores it in the string {\tt msg}. These are the entries shown in
the list above. Subsequently the routine builds the body of the
response, whose title contains the code description. The body lists
the description and any explanatory details we might have supplied. It
includes the process name and the host name so that both can be
identified easily during debugging. The last step is to output the
status code, the content type of the message body, the HTML body
itself and exit the server.

\begin{lstlisting}{}
#include <Foundation/Foundation.h>
#include <fcntl.h>

void ErrmsgAndExit(int code, NSString *desc, NSString *detail)
{
    NSProcessInfo *pinfo = [NSProcessInfo processInfo];

    NSString *msg = 
        [NSString stringWithFormat:@"%d %@", code, desc];
    NSString *body = 
        [NSString stringWithFormat:@"<HTML><HEAD>\n"
                  @"<TITLE>%@</TITLE>\n"
                  @"</HEAD><BODY BGCOLOR=white>\n"
                  @"<H1>%@</H1>\n"
                  @"%@\n"
                  @"<HR>\n"
                  @"<ADDRESS>%@ at %@</ADDRESS>\n"
                  @"</BODY></HTML>\n",
                  msg, desc, detail,
                  [pinfo processName], [pinfo hostName]];


    printf("HTTP/1.1 %s\r\n", [msg cString]);
    printf("Content-type: text/html\r\n\n");
    printf("%s", [body cString]);

    exit(1);
}    
\end{lstlisting}

The routine {\tt main} must process the two types of requests. The
very first thing it needs to do is to read the request from the
standard input. For this purpose we obtain the appropriate file handle
and read any data that might be available. We need to know how many
bytes have been read, so that we can convert the contents of the data
object into a string.

\begin{lstlisting}{}
int main(int argc, char** argv, char **env)
{
    NSAutoreleasePool *pool = [NSAutoreleasePool new];

    NSData *reqData =
        [[NSFileHandle fileHandleWithStandardInput]
            availableData];

    unsigned reqLength = [reqData length];
    const char *reqBytes = [reqData bytes];
\end{lstlisting}

We convert the data to a string and split the request into lines. We
are only interested in the first line (the request itself), but we
could conceivably expand the server to do additional processing on the
headers that are stored in the array {\tt lines}.

\begin{lstlisting}{}
    NSArray *lines;
    NSString *firstLine;

    lines = [[NSString stringWithCString:reqBytes length:reqLength] 
                componentsSeparatedByString:@"\n"];
    firstLine = [lines objectAtIndex:0];
\end{lstlisting}

A good request line should look like this:
\begin{center}
{\tt GET /path/to/file HTTP/1.1}
\end{center}
Hence we need to split it into fields so that we can check the
components of the request and deliver the result. We use code that is
also present in the {\tt df} recipe, i.e. we obtain a character set
that contains white space and a scanner whose data source is the first
line we have read. We scan fields until the scanner reaches the end of
the line.

\begin{lstlisting}{}
    NSCharacterSet *space =
        [NSCharacterSet whitespaceAndNewlineCharacterSet];
    NSScanner *scn = 
        [NSScanner scannerWithString:firstLine];
    NSMutableArray *fields =
        [NSMutableArray arrayWithCapacity:3];
    NSString *field;

    while([scn isAtEnd]==NO){
        [scn scanCharactersFromSet:space  
             intoString:NULL];
        [scn scanUpToCharactersFromSet:space
             intoString:&field];
        [fields addObject:field];
    }
\end{lstlisting}

A series of validity checks follows. There must be three fields: the
request, the entity requested and the version of the protocol.

\begin{lstlisting}{}
    if([fields count]!=3){
        ErrmsgAndExit(400, @"Bad Request",
                      @"Format is: GET <path> <version>.");
    }
\end{lstlisting}

Our server only responds to {\tt GET} requests, upon receipt of which
it produces the contents of the file or a directory browser.

\begin{lstlisting}{}
    if([[fields objectAtIndex:0] isEqualToString:@"GET"]==NO){
        ErrmsgAndExit(501, @"Not Implemented",
                      @"Use GET only.");
    }
\end{lstlisting}

We will be picky about the protocol that we support, it must be either
HTTP 1.0 or HTTP 1.1.

\begin{lstlisting}{}
    field = [fields objectAtIndex:2];
    if([field isEqualToString:@"HTTP/1.0"]==NO && 
       [field isEqualToString:@"HTTP/1.1"]==NO){
        ErrmsgAndExit(505, @"HTTP Version Not Supported",
                      @"Use HTTP/1.0 or HTTP/1.1.");
    }
\end{lstlisting}

This ends the series of request checks. We now know that we have a
good request. However, it is entirely possible that the client has
requested a file or a directory that does not exist. We get the
default file manager and ask it to check the path from the request. We
return ``Not Found'' if there is no file or directory at that path.

\begin{lstlisting}{}
    NSString *path = [fields objectAtIndex:1];
    NSFileManager *fm = [NSFileManager defaultManager];
    BOOL isDir;
    
    if([fm fileExistsAtPath:path isDirectory:&isDir]==NO){
        NSString *detail = 
            [NSString stringWithFormat:@"The requested URL "
                      @"%@ was not found on this server.", path];
        ErrmsgAndExit(404, @"Not Found", detail);
    }
\end{lstlisting}

The last check is to determine whether we can read the file or
directory. 

The method {\tt isReadableFileAtPath} uses the {\tt access(2)} call
and works on files and directories. We send an error message if we
cannot access the requested entity.

\begin{lstlisting}{}
    // "access" call  works on files and directories
    if([fm isReadableFileAtPath:path]==NO){
        NSString *detail = 
            [NSString stringWithFormat:@"You don't have "
                      @"permission to accesss %@ "
                      @"on this server.", path];
        ErrmsgAndExit(403, @"Forbidden", detail);
    }
\end{lstlisting}

We can send a {\tt 220 OK} response now that the request and the
requested entity have been verified.

\begin{lstlisting}{}
    printf("HTTP/1.1 220 OK\r\n");
\end{lstlisting}

Start with the easy part, i.e. serving files. We use a minimal set of
headers, namely the content type and the content length. The content
type is {\tt application/octet-stream} and the content length is
obtained from the file manager.

\begin{lstlisting}{}
    if(isDir==NO){
        NSDictionary *attrib =
            [fm fileAttributesAtPath:path traverseLink:YES];

        printf("Content-type: application/octet-stream\r\n");
        printf("Content-length: %d\r\n\n",
               [[attrib objectForKey:NSFileSize] intValue]);
        fflush(stdout);
\end{lstlisting}

It remains to send the contents of the file. We could read the entire
file into a data object and send the file all at once, but this could
result in a server process requesting a lot of memory. Instead we
choose to serve the file in chunks of 64K.

We obtain a file handle for reading the file and the file handle for
standard output (an effort was made to use foundation objects rather
than system calls). We read one chunk after another and output the
current chunk immediately. We close the file when there are no more
data.

\begin{lstlisting}{}
        #define CHUNK_SIZE (1<<16)
        NSFileHandle *reader =
            [NSFileHandle fileHandleForReadingAtPath:path];
        NSFileHandle *writer =
            [NSFileHandle fileHandleWithStandardOutput];
        NSData *chunk;

        while(chunk=[reader readDataOfLength:CHUNK_SIZE],
              [chunk length]>0){
            [writer writeData:chunk];
        }

        [reader closeFile];
    }
\end{lstlisting}

That's it for the case of serving files. Producing a browsable
directory listing requires a bit more effort. We start by ensuring
that the path ends in a path delimiter and append one if this is not
the case.

\begin{lstlisting}{}
    else{
        if([path hasSuffix:@"/"]==NO){
            path = [path stringByAppendingString:@"/"];
        }
\end{lstlisting}

We start by constructing the title of the HTML document that we will
serve, and output the content type and the beginning of the document
(title, open body tag, background color).

\begin{lstlisting}{}
        NSString *title =
            [NSString stringWithFormat:@"Directory listing "
                      @"of %@", path];
        
        printf("Content-type: text/html\r\n\n");

        printf("<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n", 
               [title cString]);
        printf("<BODY BGCOLOR=white>\n");
\end{lstlisting}

We wish to have a certain feature to simplify navigation. There should
be a header that displays the current directory in such a manner that
directories that are higher up in the tree are clickable, e.g. if the
path is {\tt /path/to/subdirectory/}, then both {\tt path} and {\tt to}
should be clickable and take the user to {\tt /path} and {\tt
/path/to}, respectively.

The first step is to split the path into components.

\begin{lstlisting}{}
        NSArray *compArray = [path pathComponents];
        int cind, cmax = [compArray count];
        printf("<H1>Directory listing of /");
\end{lstlisting}

A path like {\tt /path/to/subdirectory/} yields five components, the
first and last of which are slashes; the root directory yields a
single component. We process the inner components of the path,
e.g. {\tt path}, {\tt to} and {\tt subdirectory}. We construct the
subpaths for each inner component excluding the last one, giving {\tt
/path} and {\tt /path/to}. We output an anchor for each component. The
anchor points to the complete path and contains the last component of
the subpath for display. The last component of the complete path is
not displayed in this manner because it points to the directory being
displayed (that's why we have {\tt cind<cmax-2} rather than {\tt
cind<cmax-1}.)

\begin{lstlisting}{}
        for(cind=1; cind<cmax-2; cind++){
            NSString *subPath = 
                [[compArray subarrayWithRange:NSMakeRange(1, cind)]
                    componentsJoinedByString:@"/"];
            printf("<A HREF=/%s>%s/</A>", 
                   [subPath cString], 
                   [[compArray objectAtIndex:cind] cString]);
        }
\end{lstlisting}

It remains to display the last component, which is not
clickable. There is no last component when we browse the root
directory. This concludes the construction of the navigable header.

\begin{lstlisting}{}
        if(cmax>1){
          printf("%s/", [[compArray objectAtIndex:cind] cString]);
        }
        printf("</H1>\n");
\end{lstlisting}

The user must be able to ascend the directory tree after he has
descended it in search of some file or directory. We output an anchor
for this purpose. If we are not browsing the root directory, then
output an anchor with the title {\tt Up one level}, which points to
the parent directory.

\begin{lstlisting}{}
        if([path isEqualToString:@"/"]==NO){
            printf("<A HREF=%s>Up one level.</A><P>\n",
                   [[path stringByDeletingLastPathComponent]
                       cString]);
        }
\end{lstlisting}

We are now ready to enumerate the contents of the directory. Start by
obtaining the contents of the directory (which do not include {\tt
``.''} and {\tt ``..''}, by the way). Sort them alphabetically, but
ignoring case, and fetch the enumerator of the sorted array. The
string {\tt item} will hold a single entry.

\begin{lstlisting}{}
        NSEnumerator *dirContentEnum =
            [[[fm directoryContentsAtPath:path]
                 sortedArrayUsingSelector:
                     @selector(caseInsensitiveCompare:)]
                objectEnumerator];
        NSString *item;
\end{lstlisting}

We produce one line of output for each item and iterate over the items
with the enumerator. We construct the full path to each item and check
whether it is a directory or not.

\begin{lstlisting}{}
        printf("<PRE>\n");
        while((item = [dirContentEnum nextObject])!=nil){
            NSString *fullPath =
                [path stringByAppendingString:item];

            BOOL itemIsDir;
            [fm fileExistsAtPath:fullPath isDirectory:&itemIsDir];
\end{lstlisting}

The anchor for the item points to the full path and lists the item,
i.e. the last component of the full path. We mark directories with the
string {\tt ``[+]''}, chosen because it resembles the icon that is
used for directories by some graphical browsers.

\begin{lstlisting}{}
            NSString 
                *fmt = @"<A HREF=%@>%@ %@</A>",
                *anchor =
                [NSString stringWithFormat:fmt, fullPath,
                          (itemIsDir==YES ? @"[+]" : @" - "),
                          item];
            printf("%s", [anchor cString]);
\end{lstlisting}

We pad the line up to column sixty, so that the modification time and
the size of the file line up properly when we output them, which we'll
do next.

\begin{lstlisting}{}
            int ilen = 3+1+[item length];
            while(ilen++<60){
                putchar(' ');
            };
\end{lstlisting}

We obtain the attributes of the current item from the file manager and
extract the modification date and the size. By the way, this is where
the variable {\tt TZ} from the shell script comes into play. We output
the date and the file size. This ends the current iteration of the
loop. Note that the entire listing will be displayed as-is, because it
is bracketed by {\tt PRE} tags.

\begin{lstlisting}{}
            NSDictionary *itemAttrib =
                [fm fileAttributesAtPath:fullPath traverseLink:NO];
            NSString *dateStr = 
                [[itemAttrib fileModificationDate] description];
            printf("%s %16d\n", [dateStr cString],
                   [[itemAttrib objectForKey:NSFileSize] intValue]);
        }
        printf("</PRE>\n");
\end{lstlisting}

The last step is to close the {\tt BODY} and {\tt HTML} tags, flush
buffers, and exit the server. Easy, wasn't it?

\begin{lstlisting}{}
        printf("</BODY></HTML>\n");

        fflush(stdout);
    }

    [pool release];
    exit(0);
}
\end{lstlisting}

