HTTP CGI

I made this with AXCEL (http://sourceforge.net/projects/libaxcel/). Right now it only works on Linux because of a bash command it uses to determine file mime-types. Using the Pstream class, this implements CGI; where the webserver passes client data to an external program and relays the output back to the client.

The webserver. Usage: ./wserve webroot port [-c cgi_list_file]

#include "axcel.h"
using namespace axcel;

String root, cgi;

void* session(void* param) {
    Pstream p;
    TCP sock;
    String buf, s, t;
    File f;
    std::deque<String> envstrs;
    std::deque<char*> environ;
    std::deque<String>::iterator it;
    
    // Assign socket file descriptor to socket object's fd
    sock.fd = (uint)param;
    
    // Read http header (terminated by blank line)
    until (buf.ends("\r\n\r\n")) buf += sock.gets();
    
    // Display client's ip and http request to console
    con << sock.ip() << " -> " << buf.tok(0, '\n').rburn(' ') << '\n';
    
    // Unescape requested filename and prepend webroot path
    s = buf.tok(1, ' ').burn('?').uesc("%%%02x").prep(root);
    
    // Is this a CGI file request?
    if (cgi.str(s) > -1) {
        buf.rewind();
        
        // Build environment strings
        envstrs.clear();
        envstrs.push_back("SERVER_SOFTWARE=jakash3webserver/1.0");
        envstrs.push_back("SERVER_NAME=myipaddress");
        envstrs.push_back("SERVER_PROTOCOL=HTTP/1.1");
        envstrs.push_back("SERVER_PORT=80");
        envstrs.push_back(String("REQUEST_METHOD=") + buf.tok(0, ' '));
        envstrs.push_back(String("SCRIPT_NAME=") + s);
        envstrs.push_back(String("QUERY_STRING=") + buf.tok(1, ' ').slurp('?'));
        envstrs.push_back(String("REMOTE_ADDR=") + sock.ip());
        
        // Add additional environment strings from http request fields
        t = buf.gets();
        for (t = buf.gets().chomp(); !t.empty(); t = buf.gets().chomp())
            envstrs.push_back(String("HTTP_").cat(t.burn(':').rep('-', '_').uc()).cat('=').cat(t.slurp(": ")));
            
        // Build environment block
        environ.clear();
        for (it = envstrs.begin(); it != envstrs.end(); it++)
            environ.push_back(it->buf);
        environ.push_back(0);
        
        // Open CGI program
        p.open(s, 0, &(environ[0]));
        // Forward client's body data to CGI program
        p.write(sock.dump());
        // Forward CGI program's output to client
        sock.write(p.dump());
        // Close CGI stream and connection
        p.close();
        sock.close();
        return 0;
    }
    if (buf.starts("HEAD ") || buf.starts("GET ")) {
    
        // Append index.* file if this is a directory request
        if (s.ends('/'))
            if (ls().str("\nindex") > -1)
                s += ls().slurp("\nindex").burn('\n').prep("index");
                
        sock << "HTTP/1.1 ";
        if (fexist(s)) {
            f.open(s, "rb");
            unless (f.ok()) {
                sock <<
                    "500 Internal Server Error\r\n"
                    "Server: Jakash3's webserver\r\n"
                    "Connection: Close\r\n"
                    "Content-Length: 0\r\n"
                    "Date: " << Clock::utime() << "\r\n\r\n";
                sock.close();
                return 0;
            }
            sock <<
                "200 OK\r\n"
                "Server: Jakash3's webserver\r\n"
                "Connection: Close\r\n"
                "Content-Type: " << s.qq().prep("file --mime-type ").qx().slurp(' ').chomp() << "\r\n"
                "Content-Length: " << fsize(s) << "\r\n"
                "Date: " << Clock::utime() << "\r\n\r\n";
            unless (buf.starts("HEAD ")) {
                sock.write(f.dump());
                f.close();
            }
        } else {
            sock <<
                "404 Not Found\r\n"
                "Server: Jakash3's webserver\r\n"
                "Connection: Close\r\n"
                "Content-Length: 0\r\n"
                "Date: " << Clock::utime() << "\r\n\r\n";
        }
    }
    sock.close();
    return 0;
}

int main(int argc, char** argv) {
    unless (argc >= 3)
    
    /*
     *    Specify webroot path, port number, and
     *    optionally a file containing the absolute
     *    filename of a CGI program on each line
     */
        die("Usage: %s webroot port [-c CGIListFile]", argv[0]);
    
    TCP server, client;
    Thread t = session;
    root = argv[1];
    root = root.chomp('/');
    
    if (argc > 3)
        for (int i = 3; i < argc; i++)
            if (eq(argv[i], "-c")) cgi = fread(argv[++i]);
    
    server.listen(argv[2], 2);
    while (server.ok()) {
        server.accept(client);
        t.start((void*)client.fd);
    }
}
 

Example web form:

<html>
<body>

<form action="PATH_OF_CGI_PROGRAM" method="get">
First name: <input type="text" name="firstname" /><br />
Last name: <input type="text" name="lastname" />
<input type="submit" value="Submit" />
</form>

</body>
</html>

Example CGI program:

#include "axcel.h"
using namespace axcel;

int main() {
    String header, s, t;
    header <<
        "HTTP/1.1 200 OK\r\n"
        "Server: Jakash3's webserver\r\n"
        "Date: " << Clock::utime() << "\r\n"
        "Content-Type: text/html\r\n"
        "Connection: Close\r\n";
    s.clear();
    t = envget("QUERY_STRING");
    
    s << "<html><body>Hello " <<
        t.tok(0, '&').slurp('=').rep('+', ' ').uesc("%%%02x") <<
        ", your IP address is " << envget("REMOTE_ADDR") <<
        "</body></html>";
        
    header << "Content-Length: " << s.len() << "\r\n\r\n";
    con.puts(header + s);
}
 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: