C++ and the CGI-BIN
Posted in
Afternoon watch, 8 bells (4:06 pm)

"Why," you may ask, "would I still be using the cgi-bin when there are so many other scripting options?"

Well, let me tell you why I do. I write applications. Some of the applications I write need to communicate with a server. Around 1998 I decided to learn socket programming, and used it to write several client/server programs (and my ongoing MUD server, but I digress…). The problem was that no matter how good you think you wrote it, something weird always happens and the server goes offline. Or the client is blocked by a firewall or proxy server. I also have started using some powerful C++ libraries that do encryption, etc that don't have PHP/Ruby/meh versions available, or sometimes even the possibility to port.

So I write some servers to run as CGI-BIN programs on Apache servers. This solves several problems. One, network firewalls and proxies never block the HTTP port. Two, the server binary is run every time a client connects, but only long enough to respond to a request. So if it crashes for some weird reason, it doesn't go offline. Three, as it is a command-line binary, it's very easy to test and debug.

So how do I leverage the simple power of the CGI-BIN in my next C++ server application? Simple!

First, the output always has to start with a MIME-type declaration.

std::cout << "Content-type: text/plain\n\n";

Next, if you care to, secure your application from prying eyes by checking the client's IP via the getenv function from cstdlib:

if(getenv("REMOTE_ADDR")) {
   myIP = getenv("REMOTE_ADDR");
} else {
   myIP = "127.0.0.1";
}

Always check to see if getenv returns something before trying to manipulate its return value. It's better programming practice, plus it'll be easier to debug when you run it on the command line during testing. Trust me.

There are several ways your program may get information from a client. While debugging, I've found command line is best. Some web programmers use POST, some like GET. How about supporting all three seamlessly?

To make this easy, you need a function to turn a std::string into a std::vector<std::string> given a delimiter. I'll assume you can write one yourself.

std::vector<std::string> args;

if(getenv("REQUEST_METHOD")) {
   std::string requestMethod = getenv("REQUEST_METHOD");
   std::string rawData;
   if(requestMethod == "POST") {
      std::cin >> rawData;
   } else {
      // assume we're using GET here and not cookies or something weird
      rawData = getenv("QUERY_STRING");
   }
   args = stringToVector(rawData, "&");
} else {
   for(int i=1; i<argc; ++i) {
      args.push_back(argv[i]);
}

As you can see, POST requests come in via std::cin, and GET requests are in the environment variable QUERY_STRING. Command line arguments always start with the name of the program, which is why I start at 1 instead of 0.

"But wait!" you say, "The arguments tend to come as name-value pairs! How do I get those out?" Well, allow me to continue. I like to use a std::map for storing name-value data. This data shows up as name1=value1 for each argument. You can send values only, or mix values and name-values, but I find it simpler to always use name-values. Once again, it's the stringToVector function to the rescue:

std::map<std::string, std::string> myArgMap;

for(unsigned int i=0; i<args.size(); ++i) {
   std::vector<std::string> temp = stringToVector(args[i], "=");
   if(temp.size() != 2) {
      // I skip this since it's not a name-value pair
      continue;
   }
   myArgMap.insert(std::make_pair(temp[0], temp[1])));
}

Now just seach the myArgMap for whatever variable you happen to want and use the value. Piece of cake, and easy to debug via command line, hand-typed URI in the browser address bar, or a full-fledged form submitting via POST. My binaries submit data via post because there is a limit to the length of GET requests on some systems.

Hope that helps you out!

1 Comment »

One Response to “C++ and the CGI-BIN”

  1. Scurvy Jake says:

    Just in case, here is how I convert a string to a vector:

    std::vector<std::string> stringToVector(const std::string &data, const std::string &delim) {
    	std::vector<std::string> myVector;
    			
    	std::string::size_type pos = 0, idx = 0;
    			
    	while((idx = data.find(delim, pos)) != std::string::npos) {
    		std::string token = data.substr(pos, idx-pos);
    		if(!token.empty()) {
    			myVector.push_back(token);
    		}
    		// skip over the delimiter
    		pos = idx + delim.length();
    	}
    	std::string token = data.substr(pos);
    	if(!token.empty()) {
    		myVector.push_back(token);
    	}
    	
    	return myVector;
    }
    

Leave a Reply