Start, test, then stop a localhost web server in a Bash script
I wanted to write a bash script that would start a Datasette server running, run a request against it using curl
, then stop the server again.
It should then return an exit code indicating if the curl
request was succesful or not.
Research notes in this issue - here’s the final script I came up with:
#!/bin/bash
# Generate certificatespython -m trustme# This creates server.pem, server.key, client.pem
# Start the server in the backgrounddatasette --memory \ --ssl-keyfile=server.key \ --ssl-certfile=server.pem \ -p 8152 &
# Store the background process ID in a variableserver_pid=$!
# Wait for the server to startsleep 2
# Make a test request using curlcurl -f --cacert client.pem 'https://localhost:8152/_memory.json'
# Save curl's exit code (-f option causes it to return one on HTTP errors)curl_exit_code=$?
# Shut down the serverkill $server_pidsleep 1
# Clean up the certificatesrm server.pem server.key client.pem
echo $curl_exit_codeexit $curl_exit_code
There are a few additional tricks in this - it’s running python -m trustme
to create self-signed certificates in the current directory which are used for the test - but the key parts are these:
datasette ... &
- starts Datasette running as a background processserver_pid=$!
- records the PID of the server we just started so we can shut it down latercurl -f ...
- makes acurl
request, but returns an exit code that indicates if the request succeeded or was a 404 or 500 error or similarcurl_exit_code=$?
- records that exit code for laterkill $server_id
- causes the server to exit - I then did asleep 1
to provide time for it to output its shutdown text to the terminalexit $curl_exit_code
- the exit code for the Bash script is now the same as the exit code returned bycurl
I’m running this script in CI in GitHub Actions.
An improved version by Jan Lehnardt
Jan Lehnardt submitted a pull request with this improved version:
#!/bin/bash
# Generate certificatespython -m trustme# This creates server.pem, server.key, client.pem
cleanup () { rm server.pem server.key client.pem}
# Start the server in the backgrounddatasette --memory \ --ssl-keyfile=server.key \ --ssl-certfile=server.pem \ -p 8152 &
# Store the background process ID in a variableserver_pid=$!
test_url='https://localhost:8152/_memory.json'
# Wait for the server to start
# h/t https://github.com/pouchdb/pouchdb/blob/25db22fb0ff025b8d2c698da30c6c409066baa0c/bin/run-test.sh#L102-L113waiting=0until $(curl --output /dev/null --silent --insecure --head --fail --max-time 2 $test_url); do if [ $waiting -eq 4 ]; then echo "$test_url can not be reached, server failed to start" cleanup exit 1 fi let waiting=waiting+1 sleep 1done
# Make a test request using curlcurl -f --cacert client.pem $test_url
# Save curl's exit code (-f option causes it to return one on HTTP errors)curl_exit_code=$?
# Shut down the serverkill $server_pidwaiting=0# show all pids# | find just the $server_pid# | | don’t match on the previous grep# | | | we don’t need the output# | | | |until ( ! ps ax | grep $server_pid | grep -v grep > /dev/null ); do if [ $waiting -eq 4 ]; then echo "$server_pid does still exist, server failed to stop" cleanup exit 1 fi let waiting=waiting+1 sleep 1done
# Clean up the certificatescleanup
echo $curl_exit_codeexit $curl_exit_code
There’s not much extra commentary I can add to this - Jan’s inline comments are fantastic!
I really like the waiting=0
pattern here for retrying up to 4 times.
Worth breaking down the curl
command here a bit:
curl --output /dev/null --silent --insecure --head --fail --max-time 2 $test_url
It’s avoiding any output at all with a combination of writing output to /dev/null
and using --silent
to turn off logging.
It uses --insecure
because our server is running with a self-signed certificate, which will produce errors without this option - and here we just want to poll until the server is up and running.
--max-time
ensures each poll waits a maximum of two seconds.
And as before, --fail
causes curl
to return an exit code that indicates if the request was successful or not.
Jan used this as the impetus to start a new library of shell utility functions.