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 (servername4strlen (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" servername315360000"sha256"2048))
                print (
"Cannot create self signed certificate for ".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;
    }

    
// Get path and query string
    
queryString "";
    
strpos (uri'?');
    if (
p!==false) {
        
queryString substr (urip+1strlen (uri)-p-1);
        
path substr (uri0p);
    }
    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, -11)!="/")  {
            
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>&& strpos (realFilenamerealDocPath)!==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 => v) {
                
envStr.='HTTP_'.str_replace ("-""_"strtoupper (k)).'='.v;
                
envStr.="\0";
            }
            
envStr.="\0";

            
cmd './v1 -w "'.filename.'"';
            if (
exec (cmdoutputretCode, (contentLength>? (ssl ssl client) : null), contentLengthenvStr)) {
                
responseStatus "200 OK";
                
//  Analyze response header
                
strpos (output"\n\n");
                if (
p!==false || ((strpos (output"\r\n\r\n"))!=false)) {
                    
responseHeader substr (output0p);
                    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 (responseHeaderp2p3-p2), " \r");
                    }
                }
                
clientWrite (client"HTTP/1.1 ".responseStatus."\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: ".cmd."\r\n\r\n"ssl);
            }
        }
        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
    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 (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 ("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_HOST315360000"sha256"2048)) {
            print (
"Cannot create self signed certificate for ".SERVER_HOST);
            exit (
1);
        }
    }

    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 null;
httpThread null;
if (
SERVER_PORT>0) {
    
fh fsockserver (SERVER_HOSTSERVER_PORT0);
    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_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 (httpsThread thread_create ("port443Thread"fh2));
    }
}

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

?>

back to Home