CGI (Common Gateway Interface) is one of the original ways to develop dynamic web applications. Although the industry has generally moved on from this style of web development, playing with CGI is a fun historical activity.
The full source code for this project can be found on github
CGI has many known issues with things like performance and security. I would not recommend actually using CGI to build an application.
Traditionally cgi scripts would be put into a folder like /cgi-bin
under apache and accessed from there. Instead of setting up and configuring apache, I decided to use Go’s built in cgi library to provide the server.
The following snippet will listen on port 8080 and forward the request to the bash script cgichild.sh
:
package main import ( "net/http" "net/http/cgi" ) func cgiHandler(w http.ResponseWriter, r *http.Request) { handler := cgi.Handler{Path: "cgichild.sh"} handler.ServeHTTP(w, r) } func main() { http.HandleFunc("/", cgiHandler) http.ListenAndServe("localhost:8080", nil) }
The cgi application can be any script or program that can interact with environment variables, standard input, and standard output. Below is an example of a simple cgi bash script:
#!/bin/bash echo "Content-type: text/html" echo "" echo '<html><body>' echo 'Hello From Bash <br/>Environment:' echo '<pre>' /usr/bin/env echo '</pre>' echo '</body></html>' exit 0
You can do the same thing using C++:
#include <iostream> using namespace std; int main(int argc, char **argv, char **envp) { cout << "Content-type: text/html" << std::endl << std::endl; cout << "<html><body>" << std::endl; cout << "Hello From C++ <br/>Environment:" << std::endl; cout << "<pre>" << std::endl; for (char **env = envp; *env != 0; env++) { char *thisEnv = *env; cout << thisEnv << std::endl; } cout << "</pre>" << std::endl; cout << "</body></html>" << std::endl; return 0; }
You can write a similar application manually in golang. However, golang also has support for writing a cgi console application that looks like a regular http handler. It will translate any environment variables into an http.Request
and will connect http.ResponseWriter
back to standard output. A similar example would look like this (adopted from this gist):
package main import ( "fmt" "net/http" "net/http/cgi" ) func handler(w http.ResponseWriter, r *http.Request) { header := w.Header() header.Set("Content-Type", "text/html; charset=utf-8") fmt.Fprintln(w, "<html><body>") fmt.Fprintln(w, "Hello from Go Complex <br/> Environment:") fmt.Fprintln(w, "<pre>") fmt.Fprintln(w, "Method:", r.Method) fmt.Fprintln(w, "URL:", r.URL.String()) query := r.URL.Query() for k := range query { fmt.Fprintln(w, "Query", k+":", query.Get(k)) } r.ParseForm() form := r.Form for k := range form { fmt.Fprintln(w, "Form", k+":", form.Get(k)) } post := r.PostForm for k := range post { fmt.Fprintln(w, "PostForm", k+":", post.Get(k)) } fmt.Fprintln(w, "RemoteAddr:", r.RemoteAddr) if referer := r.Referer(); len(referer) > 0 { fmt.Fprintln(w, "Referer:", referer) } if ua := r.UserAgent(); len(ua) > 0 { fmt.Fprintln(w, "UserAgent:", ua) } for _, cookie := range r.Cookies() { fmt.Fprintln(w, "Cookie", cookie.Name+":", cookie.Value, cookie.Path, cookie.Domain, cookie.RawExpires) } fmt.Fprintln(w, "</pre>") fmt.Fprintln(w, "</body></html>") } func main() { err := cgi.Serve(http.HandlerFunc(handler)) if err != nil { fmt.Println(err) } }
CGI is fun to play with. If you never need to kick off a program for every web request, give it a shot. Also, the Go standard library proves itself yet again to be extremely comprehensive.
2019-01-23