sanos home

Simple HTTP server sample for sanos


 

In order to demonstrate programming under sanos I have made a simple web server demo. This sample is deliberately kept simple and lacks many important features you might want from a real web server. It is a single threaded server with only minimal error checking and it only handles the most basic aspects of the HTTP protocol. You can take a look at the httpd server implementation in sanos for a more scalable and robust HTTP server implementation.

However, I hope this little sample web sever with only 200 lines of code demonstrates some of the the basic programming style used for programming sanos server applications. As can be seen from the source code below, it is fairly standard C code. The code is very similar to other POSIX applications. In fact, it can compile under Linux without any changes.

 

Compiling webserver.c under sanos using sanos SDK

 

The simplest way to try it out is to use the sanos SDK to compile webserver.c under sanos:

/$ cd /usr/src
/usr/src$ httpget www.jbox.dk /downloads/webserver.c webserver.c
Received 5808 bytes in 130 ms (44 KB/s)
/usr/src$ cc webserver.c
/usr/src$ webserver &
[process 18 started]
HTTP server listening on port 80
/usr/src$ _

 

Compiling webserver.c for sanos using Visual Studio

 

You can use the Visual Studio wizard to create a new sanos project with the webserver:

Add a new source file (webserver.c) to the project and paste the code below into it. Select the Release configuration and build the webserver and image projects. You should now have a webserver.flp boot image file in your release directory. This boot image contains a copy of sanos as well as your web server.

Copy this image to a floppy disk using the mkfloppy tool and boot the computer from the floppy. After booting sanos the webserver program executes. If you point your browser to the machine you can browser around in the sanos file system on the floppy. You now have a simple standalone web server on a floppy.

 

Source code for webserver.c

 

//
// webserver.c
//
// Simple HTTP server sample for sanos
//

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <dirent.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define SERVER "webserver/1.0"
#define PROTOCOL "HTTP/1.0"
#define RFC1123FMT "%a, %d %b %Y %H:%M:%S GMT"
#define PORT 80

char *get_mime_type(char *name)
{
  char *ext = strrchr(name, '.');
  if (!ext) return NULL;
  if (strcmp(ext, ".html") == 0 || strcmp(ext, ".htm") == 0) return "text/html";
  if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) return "image/jpeg";
  if (strcmp(ext, ".gif") == 0) return "image/gif";
  if (strcmp(ext, ".png") == 0) return "image/png";
  if (strcmp(ext, ".css") == 0) return "text/css";
  if (strcmp(ext, ".au") == 0) return "audio/basic";
  if (strcmp(ext, ".wav") == 0) return "audio/wav";
  if (strcmp(ext, ".avi") == 0) return "video/x-msvideo";
  if (strcmp(ext, ".mpeg") == 0 || strcmp(ext, ".mpg") == 0) return "video/mpeg";
  if (strcmp(ext, ".mp3") == 0) return "audio/mpeg";
  return NULL;
}

void send_headers(FILE *f, int status, char *title, char *extra, char *mime, 
                  int length, time_t date)
{
  time_t now;
  char timebuf[128];

  fprintf(f, "%s %d %s\r\n", PROTOCOL, status, title);
  fprintf(f, "Server: %s\r\n", SERVER);
  now = time(NULL);
  strftime(timebuf, sizeof(timebuf), RFC1123FMT, gmtime(&now));
  fprintf(f, "Date: %s\r\n", timebuf);
  if (extra) fprintf(f, "%s\r\n", extra);
  if (mime) fprintf(f, "Content-Type: %s\r\n", mime);
  if (length >= 0) fprintf(f, "Content-Length: %d\r\n", length);
  if (date != -1)
  {
    strftime(timebuf, sizeof(timebuf), RFC1123FMT, gmtime(&date));
    fprintf(f, "Last-Modified: %s\r\n", timebuf);
  }
  fprintf(f, "Connection: close\r\n");
  fprintf(f, "\r\n");
}

void send_error(FILE *f, int status, char *title, char *extra, char *text)
{
  send_headers(f, status, title, extra, "text/html", -1, -1);
  fprintf(f, "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\r\n", status, title);
  fprintf(f, "<BODY><H4>%d %s</H4>\r\n", status, title);
  fprintf(f, "%s\r\n", text);
  fprintf(f, "</BODY></HTML>\r\n");
}

void send_file(FILE *f, char *path, struct stat *statbuf)
{
  char data[4096];
  int n;

  FILE *file = fopen(path, "r");
  if (!file)
    send_error(f, 403, "Forbidden", NULL, "Access denied.");
  else
  {
    int length = S_ISREG(statbuf->st_mode) ? statbuf->st_size : -1;
    send_headers(f, 200, "OK", NULL, get_mime_type(path), length, statbuf->st_mtime);

    while ((n = fread(data, 1, sizeof(data), file)) > 0) fwrite(data, 1, n, f);
    fclose(file);
  }
}

int process(FILE *f)
{
  char buf[4096];
  char *method;
  char *path;
  char *protocol;
  struct stat statbuf;
  char pathbuf[4096];
  int len;

  if (!fgets(buf, sizeof(buf), f)) return -1;
  printf("URL: %s", buf);

  method = strtok(buf, " ");
  path = strtok(NULL, " ");
  protocol = strtok(NULL, "\r");
  if (!method || !path || !protocol) return -1;

  fseek(f, 0, SEEK_CUR); // Force change of stream direction

  if (strcasecmp(method, "GET") != 0)
    send_error(f, 501, "Not supported", NULL, "Method is not supported.");
  else if (stat(path, &statbuf) < 0)
    send_error(f, 404, "Not Found", NULL, "File not found.");
  else if (S_ISDIR(statbuf.st_mode))
  {
    len = strlen(path);
    if (len == 0 || path[len - 1] != '/')
    {
      snprintf(pathbuf, sizeof(pathbuf), "Location: %s/", path);
      send_error(f, 302, "Found", pathbuf, "Directories must end with a slash.");
    }
    else
    {
      snprintf(pathbuf, sizeof(pathbuf), "%sindex.html", path);
      if (stat(pathbuf, &statbuf) >= 0)
        send_file(f, pathbuf, &statbuf);
      else
      {
        DIR *dir;
        struct dirent *de;

        send_headers(f, 200, "OK", NULL, "text/html", -1, statbuf.st_mtime);
        fprintf(f, "<HTML><HEAD><TITLE>Index of %s</TITLE></HEAD>\r\n<BODY>", path);
        fprintf(f, "<H4>Index of %s</H4>\r\n<PRE>\n", path);
        fprintf(f, "Name                             Last Modified              Size\r\n");
        fprintf(f, "<HR>\r\n");
        if (len > 1) fprintf(f, "<A HREF=\"..\">..</A>\r\n");

        dir = opendir(path);
        while ((de = readdir(dir)) != NULL)
        {
          char timebuf[32];
          struct tm *tm;

          strcpy(pathbuf, path);
          strcat(pathbuf, de->d_name);

          stat(pathbuf, &statbuf);
          tm = gmtime(&statbuf.st_mtime);
          strftime(timebuf, sizeof(timebuf), "%d-%b-%Y %H:%M:%S", tm);

          fprintf(f, "<A HREF=\"%s%s\">", de->d_name, S_ISDIR(statbuf.st_mode) ? "/" : "");
          fprintf(f, "%s%s", de->d_name, S_ISDIR(statbuf.st_mode) ? "/</A>" : "</A> ");
          if (strlen(de->d_name) < 32) fprintf(f, "%*s", 32 - strlen(de->d_name), "");
          if (S_ISDIR(statbuf.st_mode))
            fprintf(f, "%s\r\n", timebuf);
          else
            fprintf(f, "%s %10d\r\n", timebuf, statbuf.st_size);
        }
        closedir(dir);

        fprintf(f, "</PRE>\r\n<HR>\r\n<ADDRESS>%s</ADDRESS>\r\n</BODY></HTML>\r\n", SERVER);
      }
    }
  }
  else
    send_file(f, path, &statbuf);

  return 0;
}

int main(int argc, char *argv[])
{
  int sock;
  struct sockaddr_in sin;

  sock = socket(AF_INET, SOCK_STREAM, 0);

  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = INADDR_ANY;
  sin.sin_port = htons(PORT);
  bind(sock, (struct sockaddr *) &sin, sizeof(sin));

  listen(sock, 5);
  printf("HTTP server listening on port %d\n", PORT);

  while (1)
  {
    int s;
    FILE *f;

    s = accept(sock, NULL, NULL);
    if (s < 0) break;

    f = fdopen(s, "a+");
    process(f);
    fclose(f);
  }

  close(sock);
  return 0;
}