Menu

HTTP-Server

Kalle
:::php
<?v1
/* 
  Simple HTTP-Server
  This V1 program will create a multithreaded HTTP-Server on localhost:8080 (optional with TLS/SSL)
  with document path of current working directory and optional arguments.

  Feel free to enhance!

  Usage: v1 webserver.v1 [docPath] [startURL]
*/

error_reporting (0); // 1 to show all warnings

const SERVER_HOST = "localhost"; // Host or IP address
const SERVER_PORT = 8080; // HTTP port
const SERVER_SSL_PORT = 0; // 443 to use https:// or 0 not using SSL
const CERT_DIR = "cert"; // Directory of SSL certificates (PEM format)
const MAX_THREADS = 10; // Max. parallel requests
const RECV_TIMEOUT = 30000; // Milliseconds a client should send data
const HTTP_DATE_FORMAT = "%D, %d %M %Y %H:%i:%s GMT";

if (SERVER_SSL_PORT!=0)
  dl ("ssl");

mimeTypeList = array (
  "css" => "text/css",
  "js" => "application/x-javascript",
  "jpg" => "image/jpeg",
  "jpeg" => "image/jpeg",
  "png" => "image/png",
  "gif" => "image/jpeg",
  "svg" => "image/svg+xml",
  "exe" => "application/octet-stream",
  "pdf" => "application/pdf",
  "doc" => "application/msword",
  "docx" => "application/msword",
  "ico" => "image/x-icon",
  "mp4" =>"video/mp4",    
  "xml" => "text/xml",
  "txt" => "text/plain",
  "sh" => "text/plain",
  "bat" => "text/plain"
); 


fExitAll = false;
docPath = getcwd ();
if (isset (argv[2]))
  docPath = argv[2];

realDocPath = realpath (docPath);
print ("Document path is ".realDocPath);

serverCertList = array ();


function maintenanceThread () {
  fFirstStart = true;
  while (true) {
    sleep (500);
    // Do some maintenance
    if (fFirstStart && isset (argv[3])) {
      // Start Webbrowser URL
      print ("Start ".argv[3]);
      shellexec (argv[3]); 
      fFirstStart = false;
    }
  }
}

function servernameCallback (servername) {
  global serverCertList;
  // print ("Callback ".servername);
  if (!isset (serverCertList[servername])) {
    ctx = SSL_CTX_new ();
    if (SSL_CTX_load_cert (ctx, CERT_DIR."/".servername.".crt", CERT_DIR."/".servername.".key")) {
      serverCertList[servername]=ctx;
    }
    else {
      print ("Cannot load SSL certificate ", CERT_DIR."/".servername.".crt");
    }
  }
  return serverCertList[servername];
}


function clientWrite (&client, &str, ssl=null) {
  if (ssl)
    return SSL_write (ssl, str);
  return fwrite (client, str);
}

function clientWriteLen (&client, &str, len, ssl=null) {
  if (ssl)
    return SSL_write (ssl, str, len);
  return fwrite (client, str, len);
}

function clientReadln (&client, &str, ssl=null) {
  if (ssl)
    return SSL_readln (ssl, str);
  return freadln (client, str);
}

function workerThread (client, ssl=null) {
  global docPath, realDocPath, mimeTypeList;

  if (ssl) {
    SSL_set_fd (ssl, client);
    if (!SSL_accept (ssl)) {
      // SSL problems
      SSL_free (ssl);
      fclose (client);
      return; 
    }
  }

  lineIdx = 0, contentLength=0, headerList=array ();

  // Read the HTTP headers
  line = "";
  while (clientReadln (client, line, ssl)) {
    if (lineIdx==0) {
      assign (method, uri, protocol) = explode (" ", line);
    }
    else {
      if ((p=strpos (line, ":"))!==false) {
        header = strtoupper (trim (substr(line,0,p)));
        value = trim (substr(line,p+1));
        if (header=="CONTENT-LENGTH")
            contentLength = value;
        headerList[header]=value;
      }
    }
    if (empty (line))
      break;
    lineIdx++;
  }

  if (!isset (uri)) {
    // No URI given, close connection and finish thread
    if (ssl)
      SSL_free (ssl);
    fclose (client);
    return;
  }

  // Decode URI and get path
  uri = url_decode (uri);
  queryString = "";
  p = strpos (uri, '?');
  if (p!==false) {
    queryString = substr (uri, p+1, strlen (uri)-p-1);
    path = substr (uri, 0, p);
  }
  else {
    path = uri;
  }

  fFound = false;

  // Check for auto index files
  if (!is_file (docPath.path)) {
    autoIndexList = array ("index.html", "index.v1", "index.htm");
    foreach (autoIndexList as autoFile) {
        path2=path."/".autoFile;
        if (is_file (docPath.path2)) {
            path = path."/".autoFile;
        fFound = true;
            break;
        }
    }
  }
  else {
    fFound = true;
  }

  // Handle the filename
  filename = docPath."/".path;
  fileSize = 0;
  if (fFound) {
    fileSize = filesize (filename);
  }
  // Check if filename is outside docPath for security reasons
  realFilename = realpath (filename);
  if (fFound && fileSize>0 && strpos (realFilename, realDocPath)!==0) {
    // Send HTTP status forbidden
    clientWrite (client, "HTTP/1.1 403 Forbidden\r\nServer: V1 HTTP-Server\r\nConnection:close\r\nContent-Type: text/html\r\n\r\nPath points outside home directory.", ssl);    
  }
  else
  if (fFound) {
    // Get MIME type from file extension
    pi = pathinfo (filename);
    fileExt = strtolower (pi["extension"]);            
    mimeType = mimeTypeList[fileExt];
    if (empty (mimeType))
      mimeType = "text/html";

    if (fileExt=="v1") 
    {
      // Execute V1 Script as CGI
      output = "", retCode = 0;
      envStr = "QUERY_STRING=".queryString;
      envStr.="\0";
      envStr.='CONTENT_TYPE='.headerList["CONTENT-TYPE"];
      envStr.="\0";
      envStr.='SCRIPT_FILENAME='.filename;
      envStr.="\0";
      envStr.='SCRIPT_NAME='.path;
      envStr.="\0";
      envStr.='DOCUMENT_ROOT='.docPath;
      envStr.="\0";
      envStr.='REQUEST_URI='.uri;
      envStr.="\0\0";

      cmd = './v1 -w "'.filename.'"';
      if (exec (cmd, output, retCode, (contentLength>0 ? (ssl ? ssl : client) : null), contentLength, envStr)) {                                
        clientWrite (client, "HTTP/1.1 200 OK\r\nServer: V1 HTTP-Server\r\n", ssl);
        clientWrite (client, output, ssl);
      }
      else {
        clientWrite (client, "HTTP/1.1 500 Internal Server Error\r\nServer: V1 HTTP-Server\r\n\r\nCannot execute: ".cmd, ssl);                          
      }
    }
    else {
      // Send file HTTP status 200 OK      
      lastModified = gmdate (HTTP_DATE_FORMAT, filemtime(filename));

      fSendFile = true;
      ifModSince = headerList["IF-MODIFIED-SINCE"]; 
      if (isset (ifModSince))
      { 
          // print ("Check modification ",ifModSince, " vs. ", lastModified );
          if (!strcmp (ifModSince, lastModified)) {                   
              clientWrite (client, "HTTP/1.1 304 Not Modified\r\nServer: V1 HTTP-Server\r\nDate:".gmdate (HTTP_DATE_FORMAT)."\r\nLast-Modified:".lastModified."\r\nContent-Length:".fileSize."\r\nContent-Type: ".mimeType."\r\nConnection:close\r\n\r\n", ssl);
              fSendFile = false;
          }
      }
      if (fSendFile) {
        clientWrite (client, "HTTP/1.1 200 OK\r\nServer: V1 HTTP-Server\r\nDate:".gmdate (HTTP_DATE_FORMAT)."\r\nLast-Modified:".lastModified."\r\nContent-Length:".fileSize."\r\nContent-Type: ".mimeType."\r\nConnection:close\r\n\r\n", ssl);

         // Send the file
        fh = null;
        if (fh = fopen (filename, "r")) {
          str = "";
          while (!feof (fh)) {
            len = fread (fh, str);
            clientWriteLen (client, str, len, ssl);
          }
          fclose (fh);
        }
      }
    }
  }
  else {
    // Send HTTP status 404 Not found
    clientWrite (client, "HTTP/1.1 404 Not Found\r\nServer: V1 HTTP-Server\r\nConnection:close\r\nContent-Type: text/html\r\n\r\n".path." not found on this server.", ssl);
  }

  // Close connection and finish thread
  if (ssl)
    SSL_free (ssl);
  fclose (client);
}


function port80Thread (fh) {
  global fExitAll;

  currThreadIdx = 0;
  threadList = array ();

  fh2 = null;

  while (!fExitAll && (fh2 = fsockaccept (fh, RECV_TIMEOUT))) {
    // print ("Connection from ", fsockip (fh2), " / ", gethostbyaddr(fsockip (fh2))); 

    // Get current thread
    do {
      if (currThreadIdx>MAX_THREADS)
        currThreadIdx = 0;  
      if (isset (threadList[currThreadIdx])) {                
        if (!thread_is_active (threadList[currThreadIdx])) {
          thread_close (threadList[currThreadIdx]);                    
          break;              
        }            
      }
      else {
        break;
      }

      currThreadIdx++;        
      if (currThreadIdx>MAX_THREADS) {
        // print ("Server seems busy.");
        sleep (10); // Wait until thread is available
      }
    } while (true);

    // Create new thread
    t = thread_create ("workerThread", fh2); 
    threadList[currThreadIdx]=t;  
    thread_start (t, false); // false = dont wait until thread is running

  }
  fclose (fh);
}


function port443Thread (fh) {
  global fExitAll;

  currThreadIdx = 0;
  threadList = array ();

  // Create SSL Context
  ctx = SSL_CTX_new ("TLSv1_2_server_method");
  if (!SSL_CTX_load_cert (ctx, CERT_DIR."/".SERVER_HOST.".crt", CERT_DIR."/".SERVER_HOST.".key")) {
    print ("Cannot load SSL certificate ", CERT_DIR."/".SERVER_HOST.".crt");
    exit (1);
  }
  SSL_CTX_set_tlsext_servername_callback (ctx, "servernameCallback");

  fh2 = null;
  while (!fExitAll && (fh2 = fsockaccept (fh, RECV_TIMEOUT))) {
    // print ("Connection from ", fsockip (fh2), " / ", gethostbyaddr(fsockip (fh2))); 

    // Create SSL
    ssl = SSL_new (ctx); 

    // Get current thread
    do {
      if (currThreadIdx>MAX_THREADS)
        currThreadIdx = 0;  
      if (isset (threadList[currThreadIdx])) {                
        if (!thread_is_active (threadList[currThreadIdx])) {
          thread_close (threadList[currThreadIdx]);                    
          break;              
        }            
      }
      else {
        break;
      }

      currThreadIdx++;        
      if (currThreadIdx>MAX_THREADS) {
        // print ("Server seems busy.");
        sleep (10); // Wait until thread is available
      }
    } while (true);

    // Create new thread    
    t = thread_create ("workerThread", fh2, ssl); 
    threadList[currThreadIdx]=t;  
    thread_start (t, false); // false = dont wait until thread is running
  }
  SSL_CTX_free (ctx);  
  fclose (fh);
}

// Create Port 80
fh = fsockserver (SERVER_HOST, SERVER_PORT);
if (!fh) {
  print ("Cannot create HTTP-Server on ",  SERVER_HOST, ":", SERVER_PORT);
  exit (1);
}
else {
  print ("HTTP-Server created on ",  fsockip (fh), ":", fsockport(fh));
  thread_start (thread_create ("port80Thread", fh));
}

if (SERVER_SSL_PORT>0) {
  // Create Port 443
  fh2 = fsockserver (SERVER_HOST, SERVER_SSL_PORT);
  if (!fh2) {
    print ("Cannot create HTTP-Server on ",  SERVER_HOST, ":", SERVER_SSL_PORT);
    exit (1);
  }
  else {
    print ("HTTP-Server created on ",  fsockip (fh2), ":", fsockport(fh2));
    thread_start (thread_create ("port443Thread", fh2));
  }
}

// Maintenance thread
thread_start (thread_create ("maintenanceThread"));
while (!fExitAll)
  sleep (1000);

?>

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.