Menu

HTTP-Server

Kalle
:::php
<?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);
}
?>