A Scripting Language for Web, Linux and Windows

A Scripting Language for Web, Linux and Windows

Example: HTTP Webserver

Demonstrates various functions and techniques of system functions, file functions, socket functions, thread functions and ssl module.

<?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 (shellexec only work on Windows)
      
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 (ctxCERT_DIR."/".servername.".crt"CERT_DIR."/".servername.".key")) {
      
serverCertList[servername]=ctx;
    }
    else {
      
SSL_CTX_free (ctx);
      print (
"Cannot load SSL certificate "CERT_DIR."/".servername.".crt");
    }
  }
  return 
serverCertList[servername];
}


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

function 
clientWriteLen (&client, &strlenssl=null) {
  if (
ssl)
    return 
SSL_write (sslstrlen);
  return 
fwrite (clientstrlen);
}

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

function 
workerThread (clientssl=null) {
  global 
docPathrealDocPathmimeTypeList;

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

  
lineIdx 0contentLength=0headerList=array ();

  
// Read the HTTP headers
  
line "";
  while (
clientReadln (clientlinessl)) {
    if (
lineIdx==0) {
      
assign (methoduriprotocol) = 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;
  }

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

  
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>&& strpos (realFilenamerealDocPath)!==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 (cmdoutputretCode, (contentLength>? (ssl ssl client) : null), contentLengthenvStr)) {                                
        
clientWrite (client"HTTP/1.1 200 OK\r\nServer: V1 HTTP-Server\r\n"ssl);
        
clientWrite (clientoutputssl);
      }
      else {
        
clientWrite (client"HTTP/1.1 500 Internal Server Error\r\nServer: V1 HTTP-Server\r\n\r\nCannot execute: ".cmdssl);                          
      }
    }
    else {
      
// Send file HTTP status 200 OK      
      
lastModified gmdate (HTTP_DATE_FORMATfilemtime(filename));

      
fSendFile true;
      
ifModSince headerList["IF-MODIFIED-SINCE"]; 
      if (isset (
ifModSince))
      { 
          
// print ("Check modification ",ifModSince, " vs. ", lastModified );
          
if (!strcmp (ifModSincelastModified)) {                   
              
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 (fhstr);
            
clientWriteLen (clientstrlenssl);
          }
          
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 (fhRECV_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
    
thread_create ("workerThread"fh2); 
    
threadList[currThreadIdx]=t;  
    
thread_start (tfalse); // false = dont wait until thread is running 

  
}
  
fclose (fh);
}


function 
port443Thread (fh) {
  global 
fExitAllserverCertList;

  
currThreadIdx 0;
  
threadList = array ();

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

  
fh2 null;
  while (!
fExitAll && (fh2 fsockaccept (fhRECV_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    
    
thread_create ("workerThread"fh2ssl); 
    
threadList[currThreadIdx]=t;  
    
thread_start (tfalse); // false = dont wait until thread is running  
  
}
  
SSL_CTX_free (ctx);  
  
fclose (fh);
}

// Create Port 80
fh fsockserver (SERVER_HOSTSERVER_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_HOSTSERVER_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);

?>

back to Home