{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/go/reminiscing-cgi-scripts/",
  "description": "Build a CGI script from scratch using Go's stdlib and Bash. Understand why Common Gateway Interface fell out of favor for modern web apps.",
  "path": "/go/reminiscing-cgi-scripts/",
  "publishedAt": "2023-12-25T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "textContent": "I've always had a thing for old-school web tech. By the time I joined the digital fray, CGI\nscripts were pretty much relics, but the term kept popping up in tech forums and discussions\nlike ghosts from the past. So, I got curious, started reading about them, and wanted to see\nif I could reason about them from the first principles. Writing one from the ground up with\nnothing but Go's standard library seemed like a good idea.\n\nTurns out, the basis of the technology is deceptively simple, but CGI scripts mostly went\nout of fashion because of their limitations around performance.\n\nWhat are those\n\nCGI scripts, or Common Gateway Interface scripts, emerged in the early 1990s as a solution\nfor creating dynamic web content. They acted as intermediaries between the web server and\nexternal applications, allowing servers to process user input and return personalized\ncontent. This made them essential for adding interactivity to websites, such as form\nsubmissions and dynamic page updates.\n\nThe key function of CGI scripts was to handle data from web forms, process it, and then\ngenerate an appropriate response. The server then takes this response and displays it on a\nnew web page. Here's how the process might look:\n\n\n\n{{< mermaid >}}\nsequenceDiagram\n    participant U as Client\n    participant S as Server\n    participant C as CGI Script\n\n    U->>S: Post request with a dynamic field value\n    S->>C: Execute the CGI script in a new process\n    Note right of C: CGI script receives the value\n    C-->>S: Process and return result\n    S-->>U: Respond with result\n{{< /mermaid >}}\n\n\n\nHow to write one\n\nCGI scripts are usually written in dynamic scripting languages like Perl, Ruby, Python, or\neven Bash. However, they can also be written in a static language where the server will need\nexecute the compiled binary. For this demo, we're going to write the server in Go using the\ncgi stdlib, but the CGI script itself will be written in Bash.\n\nHere's the plan:\n\n- Set up a basic HTTP server in Go.\n- The server will await an HTTP POST request containing a form field called name.\n- Upon receiving the request, the server will extract the value of name.\n- Next, it'll set the $name environment variable for the current process.\n- A Bash CGI script is invoked, which uses the $name environment variable to echo an\n  HTML-formatted dynamic message.\n- Finally, the server will then return this HTML response to the client.\n\nThe server lives in a single main.go script. I'm leaving out Go's verbose error handling\nfor clarity.\n\nUpon every new request, the server above will execute a CGI script written in Bash. Name the\nshell script as cgi-script.sh and place it in the same directory as the server's main.go\nfile. Here's how it looks:\n\nThe script just reads name from the environment variable, sets the Content-Type header,\ninjects the value of name into the message, and echos the out the final HTML response. The\nserver then just relays it back to the client. To test this:\n\n- Run the server with go run main.go.\n- Set the permission of the CGI script:\n    \n- Make a cURL request:\n    \n\nThis returns the following response:\n\nWhy they didn't catch on\n\nCGI scripts have fallen out of favor primarily due to concerns related to performance and\nsecurity. When a CGI script is executed, it initiates a new process for each request. While\nthis approach is straightforward, it becomes increasingly inefficient as web traffic volume\ngrows. However, it's worth noting that modern Linux kernels have made improvements in\nprocess spawning, and solutions like FastCGI utilize persistent process pools to reduce the\noverhead of creating new processes. Nevertheless, you still incur the VM startup cost for\neach request when using interpreted languages like Python or Ruby.\n\nModern application servers like Uvicorn, Gunicorn, Puma, Unicorn or even Go's standard\nserver have addressed these inefficiencies by maintaining persistent server processes. This,\nalong with the advantage of not having to bear the VM startup cost, has led people to opt\nfor these alternatives.\n\nAnother concern worth considering is the evident security issues associated with CGI\nscripts. Even in our simple example, the Bash script accepts any value for the name\nparameter and passes it directly to the response. This exposes a significant vulnerability\nto injection attacks. While it's possible to manually sanitize the input before passing it\nto the next step, many of these security steps are automatically handled for you by almost\nany modern web framework.\n\nFin!\n\nFurther reading\n\n- [Apache tutorial: Dynamic content with CGI]\n\n\n\n\n[apache tutorial: dynamic content with cgi]:\n    https://httpd.apache.org/docs/2.4/howto/cgi.html",
  "title": "Reminiscing CGI scripts"
}