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
/*
  HTTP(S)-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.

  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 or 0 not using HTTP
const SERVER_SSL_PORT = 0; // 443 to use https:// or 0 not using HTTPS
const CERT_DIR = "cert"; // Directory of SSL certificates (PEM format)
const MAX_THREADS = 15; // 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/gif",
    
"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 () {
    global 
fExitAll;
    
fFirstStart = true;
    while (!
fExitAll) {
        
sleep (500);
        
// Do some maintenance
        
if (fFirstStart && isset (argv[3])) {
            
// Start Webbrowser URL
            
print ("Start ".argv[3]);
            
shellexec (argv[3]);
            
fFirstStart = false;
        }
    }
}

// Function load certificate by servername from cert/ directory
function servernameCallback (servername) {
    global 
serverCertList;

    if (!isset (
serverCertList[servername])) {
        
// Check with/without www.
        
fOnlyCheck = false;
        if (
strpos (servername, "www.")===0) {
            
servername2 = substr (servername, 4, strlen (servername)-4);
        }
        else {
            
fOnlyCheck = true;
            
servername2 = "www.".servername;
        }
        if (isset (
serverCertList[servername2])) {
            return 
serverCertList[servername2];
        }
        if (!
fOnlyCheck)
            
servername = servername2;

        if (!
is_file (CERT_DIR."/".servername.".crt")) {
            
// Create self signed certificate
            
if (!SSL_create_selfsigned_cert (CERT_DIR."/".servername.".crt", CERT_DIR."/".servername.".key" , servername, 315360000, "sha256", 2048))
                print (
"Cannot create self signed certificate for ".servername);
        }

        
ctx = SSL_CTX_new ();
        if (
SSL_CTX_load_cert (ctx, CERT_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, &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;
    }

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

    
fFound = fRedirect = false;

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

    
// Handle the filename
    
fileSize = 0;
    if (
fFound) {
        
fileSize = filesize (filename);
    }
    
pi = pathinfo (filename);
    
fileExt = strtolower (pi["extension"]);

    
// Check if filename is outside docPath or special file extensions for security reasons
    
realFilename = realpath (filename);
    if ((
fFound && fileSize>0 && strpos (realFilename, realDocPath)!==0) || fileExt=="key" || fileExt=="htaccess") {
        
// 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\nRequested ressource ".path." is forbidden.\r\n\r\n", ssl);
    }
    else
    if (
fFound) {
        
// Get MIME type from file 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='.realDocPath;
            
envStr.="\0";
            
envStr.='REQUEST_URI='.uri;
            
envStr.="\0";
            
envStr.='REMOTE_ADDR='.fsockip (client);
            
envStr.="\0";
            
// HTTP vars
            
foreach (headerList as k => v) {
                
envStr.='HTTP_'.str_replace ("-", "_", strtoupper (k)).'='.v;
                
envStr.="\0";
            }
            
envStr.="\0";

            
cmd = './v1 -w "'.filename.'"';
            if (
exec (cmd, output, retCode, (contentLength>0 ? (ssl ? ssl : client) : null), contentLength, envStr)) {
                
responseStatus = "200 OK";
                
//  Analyze response header
                
p = strpos (output, "\n\n");
                if (
p!==false || ((p = strpos (output, "\r\n\r\n"))!=false)) {
                    
responseHeader = substr (output, 0, p);
                    if (
strpos (responseHeader, "\nLocation:")!==false) {
                        
responseStatus = "301 Moved Temporarly";
                    }
                    if ((
p2=strpos (responseHeader, "\nStatus:"))!==false) {
                        
p2+=8;
                        
p3 = strpos (responseHeader, "\n", p2);
                        if (
p3===false)
                            
p3 = strlen (responseHeader);
                        
responseStatus = trim (substr (responseHeader, p2, p3-p2), " \r");
                    }
                }
                
clientWrite (client, "HTTP/1.1 ".responseStatus."\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."\r\n\r\n", 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
    if (
fRedirect) {
        
clientWrite (client, "HTTP/1.1 301 Moved permanently\r\nServer: V1 HTTP-Server\r\nConnection:close\r\nLocation: ".path."\r\n\r\n", ssl);
    }
    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.\r\n\r\n", 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, serverCertList;

    
currThreadIdx = 0;
    
threadList = array ();

    
// Create SSL Context
    
ctx = SSL_CTX_new ("TLS_server_method");

    if (!
is_file (CERT_DIR."/".SERVER_HOST.".crt")) {
        
// Create self signed
        
if (!SSL_create_selfsigned_cert (CERT_DIR."/".SERVER_HOST.".crt", CERT_DIR."/".SERVER_HOST.".key" , SERVER_HOST, 315360000, "sha256", 2048)) {
            print (
"Cannot create self signed certificate for ".SERVER_HOST);
            exit (
1);
        }
    }

    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);
    }
    
serverCertList[SERVER_HOST]=ctx;

    
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 = null;
httpThread = null;
if (
SERVER_PORT>0) {
    
fh = fsockserver (SERVER_HOST, SERVER_PORT, 0);
    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 (httpThread = thread_create ("port80Thread", fh));
    }
}

fh2 = null;
httpsThread = null;
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 (httpsThread = thread_create ("port443Thread", fh2));
    }
}

// Maintenance thread
thread_start (mainThread = thread_create ("maintenanceThread"));
while (
true) {
        
sleep (1000);
}

?>

back to Home