Somebody asked a question today on the Universal Thread about creating a C# CGI executable for .NET. This seems kinda silly of course, since CGI is pretty much an outdated and slow technology superceded by more advanced application services and even newer low level APIs like ISAPI, NSAPI, Apache extensions etc. But nevertheless sometimes CGI is a quick and easy way to do something if you're working with a foreign environment and chances are that what you build as a CGI extension will run on any Web server.
The gentleman asking the question mentioned that he couldn’t get it to work so I quickly created a small program to see if there were any gotchas. It turns out it works fine.
Creating a CGI EXE is pretty easy of course. All you do is create a CONSOLE application. In this application the output you write to StdOut (ie. Console.Write() ) which becomes the output that gets written to the Web Server. You access Environment variables (ie. Environment.GetVariable()) to read any ServerVariables like QUERY_STRING, CONTENT_LENGTH etc. and you read from StdIn to retrieve any data posted to the application.
The following small program demonstrates all of this:
using System;
using System.IO;
using System.Collections;
using System.Text;
namespace CGITestApplication
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class CGITest
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
// *** Use this for debugging –
// Hit the link then attach debugger to this process
// and then pause to continue
// System.Threading.Thread.Sleep(30000);
// *** Loop through all the environement vars and write to string
IDictionary Dict = Environment.GetEnvironmentVariables();
StringBuilder sb = new System.Text.StringBuilder();
foreach(DictionaryEntry Item in Dict)
{
sb.Append((string) Item.Key + " - " + (string) Item.Value + "\r\n");
}
// *** Read individual values
string QueryString = Environment.GetEnvironmentVariable("QUERY_STRING");
// *** Read all the incoming form data both text and binary
string FormData = "";
byte[] Data = null;
if (Environment.GetEnvironmentVariable("REQUEST_METHOD") == "POST")
{
Stream s = Console.OpenStandardInput();
BinaryReader br = new BinaryReader(s);
string Length =
Environment.GetEnvironmentVariable("CONTENT_LENGTH");
int Size = Int32.Parse( Length);
Data = new byte[Size];
br.Read(Data,0,Size);
// *** don’t close the reader!
FormData = System.Text.Encoding.Default.GetString(Data,0,Size);
}
Console.Write(
@"HTTP/1.1 200 OK
Content-type: text/html
<html>
Hello World
<pre>
<b>Environment and Server Variables:</b>
" + sb.ToString() + @"
<b>Form Vars (if any):</b>
" + FormData + @"
</pre>
</html>
");
}
}
}
Couple of things of interest here in regards to retrieving the POST content. Note that I explicitly open the input stream here in order to retrieve the content as binary data. You’ll want to do this incase the data retrieved is something like a file upload or other binary content. If you just need default text you can use the Console.In TextReader to grab the content in the default encoding format. Make sure you don't close the Reader() or the request will fail.
To set this up with IIS you’ll need to do a few things:
- Create a virtual directory (or use an existing one)
- Make sure you enable Scripts and Executables if you’re
going to access the EXE directly with a URL
- In IIS 6 make sure you allow the EXE to be launched in
the Web Extensions (otherwise you get a 404 error)
- Stick the CGI executable into this directory
Debugging this executable called from IIS is a bit tricky too - you have to attach to the process when it's actually running. To debug I added a Thread.Sleep() to the handler code to allow enough time to attach a debugger to the process. Attach, then pause to break out of the Sleep. At that point you can step through the code. Note that you have to do this on every hit, since IIS launches the EXE and it then disappears, so you need to reattach on the next hit.
Now obviously CGI is pretty much legacy technology, and on IIS especially this is probably a waste of time since ASP.NET is so much easier and more optimized. However, for other Web Servers it might a be a quick and dirty way to get something up and running.
Realize also that CGI especially on IIS all fairly slow (you can feel the lag time between hits), so I hardly recommend this. But if you’re using a non MS server and you must support CGI there it is.
If you are using a CGI executable I would highly recommend you hide that fact behind a script map extension rather than accessing the EXE directly. Move the EXE into some directory that's not Web accessible and then create a script map to it. So for example, on IIS you can assign the .CGITEST extension to the above EXE file and then access the EXE through any accesses to this script extension.
Alright, I MUST have better things to do with my time, eh? <g>
Other Posts you might also like