Skip to content

Commit

Permalink
Add CGI documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesaoverton committed Nov 27, 2024
1 parent 6d06e54 commit 7367e36
Showing 1 changed file with 125 additions and 0 deletions.
125 changes: 125 additions & 0 deletions doc/cgi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# CGI: Common Gateway Interface

Nanobot can be run as a
[CGI script](https://en.wikipedia.org/wiki/Common_Gateway_Interface).
This is an old-fashioned but simple and flexible
way to deploy a web application.
It's particularly easy to call use Nanobot CGI from a "wrapper" server,
written in another language, such as Python.

To run Nanobot as a CGI script
you just execute the `nanobot` binary,
with some specific environment variables
and optional `STDIN` input,
and it will return an HTTP response on `STDOUT`.

The CGI environment variables are:

- `GATEWAY_INTERFACE` set to `CGI/1.1` -- required
- `REQUEST_METHOD` for the HTTP method, defaults to `GET`
- `PATH_INFO` with the path part of the URL, defaults to `/table`
- `QUERY_STRING` with the query string part of the URL, which is optional

Nanobot will return an
[HTTP response](https://en.wikipedia.org/wiki/HTTP#HTTP/1.1_response_messages),
with a status line,
zero or more lines of HTTP headers,
and blank line,
and an optional message body
which will usually contain the HTML or JSON response content.

You can test this from the command-line like so:

```console tesh-session="cgi"
$ nanobot init
Initialized a Nanobot project
$ GATEWAY_INTERFACE=CGI/1.1 PATH_INFO=/table.txt nanobot
status: 200 OK
content-type: text/plain
content-length: 291
date: ...

table path type description
table src/schema/table.tsv table All of the tables in this project.
column src/schema/column.tsv column Columns for all of the tables.
datatype src/schema/datatype.tsv datatype Datatypes for all of the columns
```

Nanobot's CGI mode works by
starting the same HTTP server used for `nanobot serve` on a random port,
executing the request,
and printing the response to `STDOUT`.
This is much less efficient than a long-running server,
but it's very simple
and works well enough for low volumes of traffic.


## Python

You can run Nanobot CGI from any language that can "shell out" to another process.
In Python, you can use the `subprocess` module, like so:

```python
import subprocess

result = subprocess.run(
['bin/nanobot')],
env={
'GATEWAY_INTERFACE': 'CGI/1.1',
'REQUEST_METHOD': 'GET',
'PATH_INFO': path,
'QUERY_STRING': request.query_string,
},
input=request.body.getvalue().decode('utf-8'),
text=True,
capture_output=True
)
print(result.stdout)
```

If you're already running a Python server,
such as Flask or Bottle,
that provides a `request` and `response`,
then you can "wrap" Nanobot inside the Python server
with code similar to this:

```python
import subprocess
from bottle import request, response

def run_cgi(path):
# Run nanobot as a CGI script.
result = subprocess.run(
['bin/nanobot')],
env={
'GATEWAY_INTERFACE': 'CGI/1.1',
'REQUEST_METHOD': 'GET',
'PATH_INFO': path,
'QUERY_STRING': request.query_string,
},
input=request.body.getvalue().decode('utf-8'),
text=True,
capture_output=True
)
# Parse the HTTP response: status, headers, blank line, body.
reading_headers = True
body = []
for line in result.stdout.splitlines():
# Watch for the blank line that separates HTTP headers from the body.
if reading_headers and line.strip() == '':
reading_headers = False
continue
# Add each HTTP header to the `response`.
if reading_headers:
name, value = line.split(': ', 1)
if name == 'status':
response.status = value
else:
response.set_header(name, value)
# Add all remanining lines to the `response` body.
else:
body.append(line)
# The `response` is set, so just return the body.
return '\n'.join(body)
```

0 comments on commit 7367e36

Please sign in to comment.