Travaux Pratiques - serveur HTTP - version 100% Amiga
Le plus petit serveur HTTP de pages statiques, httpd, pour daemon HTTP.

Petit programme

Il devrait compiler avec le SASC 6.58 et le netinclude de l'AmigaOS4.0


/* a very little web server */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/bsdsocket.h>

#define BACKLOG 10
#define PORT 8080

struct sockaddr_in server_addr;
struct sockaddr_in client_addr;

typedef long ssize_t ;

struct Library *SocketBase ;


int loop ;

int CXBRK(void) { return 0; } /* Disable SAS/Lattice Ctrl-C handling */
void chkabort(void) { return; } /* really */
 
const char *get_mime_type(const char *filename)
{
    const char *ext = strrchr(filename, '.');

    if (!ext) return "application/octet-stream";

    ext++; // skip the dot

    if (strcmp(ext, "html") == 0) return "text/html";
    if (strcmp(ext, "htm") == 0)  return "text/html";
    if (strcmp(ext, "css") == 0)  return "text/css";
    if (strcmp(ext, "js") == 0)   return "application/javascript";
    if (strcmp(ext, "png") == 0)  return "image/png";
    if (strcmp(ext, "jpeg") == 0) return "image/jpeg";
    if (strcmp(ext, "jpg") == 0)  return "image/jpeg";
    if (strcmp(ext, "gif") == 0)  return "image/gif";
    if (strcmp(ext, "svg") == 0)  return "image/svg+xml";
    if (strcmp(ext, "txt") == 0)  return "text/plain";

    return "application/octet-stream";
}

int send_file(int client_fd, const char *filename, const char *content, const char *status)
{
  int err = -1 ;
  FILE *file ;
  long file_size ;

  printf("file \"%s\" %s\n", filename, content) ;


  file = fopen(filename, "rb");
  if (file != NULL)
  {
    char header[1024];
    char buffer[4096];
    size_t bytes_read;

    fseek(file, 0, SEEK_END) ;
    file_size = ftell(file) ;
    fseek(file, 0, SEEK_SET) ;

    sprintf(header,
            "HTTP/1.1 %s\r\n"
            "Content-Type: %s\r\n"
            "Content-Length: %ld\r\n"
            "\r\n",
            status,
            content,
            file_size);

    send(client_fd, header, strlen(header), 0) ;
    while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0)
    {
      send(client_fd, buffer, bytes_read, 0);
    }

    err = 0 ;

    fclose(file);
  }

  return err ;
}

int main()
{
  int error = 0 ;
  int fd;

  onbreak(NULL) ;

  SocketBase = OpenLibrary("bsdsocket.library", 0) ;
  if (SocketBase == NULL)
  {
    printf("no TCP/IP stack, no BSDsocket.library\n") ;
    exit(0) ;
  }
  else
  {
    printf("%s %ld.%ld\n", SocketBase->lib_Node.ln_Name, SocketBase->lib_Version, SocketBase->lib_Revision ) ;
  }

  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(PORT);
  server_addr.sin_addr.s_addr = INADDR_ANY;

  fd = socket(AF_INET, SOCK_STREAM, 0);
  if (fd < 0)
  {
    printf("ERROR socket %ld\n", Errno());
    error = EXIT_FAILURE ;
  }
  else
  {
    int opt = 1 ;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) ;

    printf("Socket creation successful\n");

    if (bind(fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
    {
      printf("ERROR bind %ld\n", Errno());
      error = EXIT_FAILURE ;
    }
    else
    {
      printf("Bind successful\n");

      if (listen(fd, BACKLOG) < 0) 
      {
        printf("ERROR listen %ld\n", Errno());
        error = EXIT_FAILURE ;
      }
      else
      {
        printf("Listening on port: %d\n", PORT);

        loop = 1 ;

        while (loop)
        {
          char filepath[2048];
          char method[8], path[1024], protocol[16];
          char buffer[4096];char header[1024];
          socklen_t addr_size = sizeof(client_addr);
          ssize_t bytes_received ;
          FILE *file ;
          long file_size ;
          char *file_content ;
          int client_fd ;

          if (SetSignal(0, 0) & SIGBREAKF_CTRL_C)
          {
            printf("BREAK***0\n") ;
            loop = 0 ;
          }

          client_fd = accept(fd, (struct sockaddr *)&client_addr, &addr_size);
          if (SetSignal(0, 0) & SIGBREAKF_CTRL_C)
          {
            loop = 0 ;
            CloseSocket(client_fd); client_fd = -1 ;
            CloseSocket(fd) ;
            // il y a un bug ici, le printf intercepte le Ctrl+C et sort par un violent exit(20)
            // laissant le socket fd occupé pour toujours
            printf("BREAK***1 (client_fd=%ld)\n", client_fd) ;
          }
          if (client_fd == -1)
          {
            
            printf("ERROR accept %ld\n", Errno());
          }
          else
          {
            printf("Accepted connection from %s:%d\n", Inet_NtoA(client_addr.sin_addr.s_addr), ntohs(client_addr.sin_port));

            bytes_received = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
            if (bytes_received > 0)
            {
              buffer[bytes_received] = '\0';
              printf("Request:\n%s\n", buffer);

              sscanf(buffer, "%s %s %s", method, path, protocol);
              printf("Requested Path: \"%s\" (\"%s\", \"%s\")\n", path, method, protocol);

              if (strcmp(path, "/") == 0)
                strcpy(filepath, /*"./public/"*/"index.html");
              else
                sprintf(filepath, /*"./public*/ "%s", path);

              if (filepath[0] == '/')
              {
                strcpy(path, &filepath[1]) ;
                strcpy(filepath, path) ;
              }

              if (send_file(client_fd, filepath, get_mime_type(filepath), "200 OK") != 0)
              {
                if (send_file(client_fd, "404.html", "text/html", "404 Not found") != 0)
                {
                  const char *body = "<html><head><style>body{font-family: Arial, sans-serif;"
                                      "background-color: #f4f4f4;color: #333;margin: 0;padding: 20px;display: flex;"
                                      "justify-content: center;align-items: center;height: 100vh;}</style></head>"
                                      "<body>404 Not Found</body></html>";                  char response[1024] ;
                  sprintf(response,
                          "HTTP/1.1 404 Not Found\r\n"
                          "Content-Type: text/html\r\n"
                          "Content-Length: %ld\r\n"
                          "\r\n"
                          "%s",
                          strlen(body), body) ;
                  send(client_fd, response, strlen(response), 0) ;
                }
              }
            }
           CloseSocket(client_fd);
         }
       }
       printf("bye !\n") ;
     }
    }
    CloseSocket(fd) ;
  }

  CloseLibrary(SocketBase) ;

  return 0;
}



Makefile SAS/C
Dans la user-startup ou la startup-sequence il devrait y avoir la définition des includes pour le SAS C à l'aide de la commande assign.
assign INCLUDE: dev:sc/include
ajouter
assign NETINCLUDE: dev:sdks/sdk_53.30/netinclude
Ce fameux netinclude se trouve dans SDK_Install\base.lha\Include\ du sdk_53.30.lha

Après avoir recopié le fichier source server.c dans un répertoire (nommé httpd), créé les répertoires obj et bin puis édité un fichier makefile comme suit:

SRC=
OBJ=obj
BIN=bin

target: $(BIN)/httpd

$(OBJ)/server.o: server.c
  sc INCLUDEDIR=NETINCLUDE: STACKCHECK STACKEXTEND OBJNAME $(OBJ)/server.o server.c

$(BIN)/httpd: $(OBJ)/server.o
  slink LIB:c.o,$(OBJ)/server.o TO $(BIN)/httpd LIB LIB:sc.lib NOICONS

De cette manière, le pré-compilateur ira chercher dans NETINCLUDE: puis dans INCLUDE: s'il ne trouve pas son include.
Et puis on ne perturbe pas ses includes préférés, puisqu'ils sont dans des répertoires distincts.
La première ligne signifie que l'on veut construire le programme httpd.
La commande sc compile le code source server.c avec les options qui vont bien, contrôle de le taille de pile et son extension s'il n'y en a pas assez.
La commande slink lie le petit segment de démarrage c.o avec les fonctions C standard de sc.lib pour générer le programme httpd.

Essai
Créer une page html index.html et placer-là dans le même répertoire que httpd.
Créer ensuite une page 404.html, sinon c'est le 404 interne qui s'affichera.
Lancer le programme httpd.
Il attend une demande de fichier par le port 8080. Prendre son navigateur favori et aller à http://localhost:8080 . le démon serveur répond en envoyant les pages statiques, qui sont en fait des fichiers stockés dans le répertoire.
Ces fichiers sont lus en mode binaire et découpés en tronçons de 4096 octets.

Conclusion
Un petit exemple, qui ne sert à rien si ce n'est comprendre comment fonctionne un serveur HTTP.
On voit surtout que le navigateur ne redemande jamais un élément qu'il a mis dans son cache.
Dans le navigateur ctrl+F5 redemande tous les éléments.