Building a minimal REST web server with netcat nc
I was looking for a minimal REST web server using nc to be a one-liner Docker container. For example:
* http://localhost/echo/marcello: prints marcello
* http://localhost/date: prints the server date
* http://localhost/...: others
I was looking at the question "A minimal web server using netcat " but it proxies shell script calls ... I just need some liners like the ones
while true ; do nc -l -p 1500 -c 'echo -e "HTTP/1.1 200 OK\n\n $(date)"'; done
The other solution posted is long and loaded from a file ...
source to share
I hacked @syme's example at fooobar.com/questions/97595 / ... and created a single user REST server. Some headers are missing, but it handles HTTP GET and 404 unimplemented resources correctly.
rm -f out ; mkfifo out ; trap "rm -f out" EXIT ; while true ; do cat out | nc -l 1500 > >(export REQUEST= ; while read line ; do line=$(echo "$line" | tr -d '[\r\n]') ; if echo "$line" | grep -qE '^GET /' ; then REQUEST=$(echo "$line" | cut -d ' ' -f2) ; elif [ "x$line" = x ] ; then HTTP_200="HTTP/1.1 200 OK" ; HTTP_LOCATION="Location:" ; HTTP_404="HTTP/1.1 404 Not Found" ; if echo $REQUEST | grep -qE '^/echo/' ; then printf "%s\n%s %s\n\n%s\n" "$HTTP_200" "$HTTP_LOCATION" $REQUEST ${REQUEST#"/echo/"} > out ; elif echo $REQUEST | grep -qE '^/date' ; then date > out ; elif echo $REQUEST | grep -qE '^/stats' ; then vmstat -S M > out ; elif echo $REQUEST | grep -qE '^/net' ; then ifconfig > out ; else printf "%s\n%s %s\n\n%s\n" "$HTTP_404" "$HTTP_LOCATION" $REQUEST "Resource $REQUEST NOT FOUND!" > out ; fi ; fi ; done) ; done
The formatted version is at https://gist.github.com/marcellodesales/9e4288f35ac2cc3e1b83#file-formatted
The API above implements the following:
- / echo / {name}
Returns the given name {name}
$ curl -i http://localhost:1500/echo/marcello
HTTP/1.1 200 OK
Location: /echo/marcello
marcello
- /date
Returns the server date
$ curl -i http://localhost:1500/date
Sun Oct 19 14:12:27 PDT 2014
- /statistics
Returns server statistics
$ curl -i http://localhost:1500/stats
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 11 374 383 2198 0 0 6 22 33 8 2 2 97 0 0
- /net
Server network printing
$ curl -i http://localhost:1500/net
docker0 Link encap:Ethernet HWaddr 56:84:7a:fe:97:99
inet addr:172.17.42.1 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::5484:7aff:fefe:9799/64 Scope:Link
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:120694 errors:0 dropped:0 overruns:0 frame:0
TX packets:141757 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:272911625 (272.9 MB) TX bytes:289945068 (289.9 MB)
eth0 Link encap:Ethernet HWaddr 00:0c:29:1f:d3:b5
inet addr:192.168.248.206 Bcast:192.168.248.255 Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:fe1f:d3b5/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:2322493 errors:0 dropped:0 overruns:0 frame:0
TX packets:1098965 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2367412677 (2.3 GB) TX bytes:700548644 (700.5 MB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:151566 errors:0 dropped:0 overruns:0 frame:0
TX packets:151566 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:305833574 (305.8 MB) TX bytes:305833574 (305.8 MB)
- / NOTHING / NOT / DONE
For anything the server doesn't implement, it prints a 404 message.
$ curl -i http://localhost:1500/wrong
HTTP/1.1 404 Not Found
Location: /wrong
Resource /wrong NOT FOUND!
Here's a formatted solution from the GIST above. You can save it as "web.sh" and run :)
rm -f out
mkfifo out
trap "rm -f out" EXIT
while true
do
cat out | nc -l 1500 > >( # parse the netcat output, to build the answer redirected to the pipe "out".
export REQUEST=
while read line
do
line=$(echo "$line" | tr -d '[\r\n]')
if echo "$line" | grep -qE '^GET /' # if line starts with "GET /"
then
REQUEST=$(echo "$line" | cut -d ' ' -f2) # extract the request
elif [ "x$line" = x ] # empty line / end of request
then
HTTP_200="HTTP/1.1 200 OK"
HTTP_LOCATION="Location:"
HTTP_404="HTTP/1.1 404 Not Found"
# call a script here
# Note: REQUEST is exported, so the script can parse it (to answer 200/403/404 status code + content)
if echo $REQUEST | grep -qE '^/echo/'
then
printf "%s\n%s %s\n\n%s\n" "$HTTP_200" "$HTTP_LOCATION" $REQUEST ${REQUEST#"/echo/"} > out
elif echo $REQUEST | grep -qE '^/date'
then
date > out
elif echo $REQUEST | grep -qE '^/stats'
then
vmstat -S M > out
elif echo $REQUEST | grep -qE '^/net'
then
ifconfig > out
else
printf "%s\n%s %s\n\n%s\n" "$HTTP_404" "$HTTP_LOCATION" $REQUEST "Resource $REQUEST NOT FOUND!" > out
fi
fi
done
)
done
source to share
Another complete and simple solution in Bash with NetCat is the following. It supports content types, file sizes, and more. Latest version can be found at https://github.com/jdoleczek/Bash-NetCat-HTTPD/blob/master/httpd.sh
#!/bin/bash
# LICENSE MIT
PORT=${1:-8080}
FILES=${2:-"./"}
NS=$(netstat -taupen 2>/dev/null | grep ":$PORT ")
test -n "$NS" && echo "Port $PORT is already taken" && exit 1
echo -e "\n\tHTTPD started for files in $FILES:"
for IP in $(ifconfig | grep "inet addr" | cut -d ':' -f 2 | cut -d ' ' -f 1) ; do
echo -e "\tlistening at $IP:$PORT"
done
echo -e "\n"
FIFO="/tmp/httpd$PORT"
rm -f $FIFO
mkfifo $FIFO
trap ctrl_c INT
function ctrl_c() {
rm -f $FIFO && echo -e "\n\tServer shut down.\n" && exit
}
while true; do (
read req < $FIFO;
req=$(echo $req | cut -d" " -f2 | cut -d"#" -f1 | cut -d"?" -f1 | cut -c2-);
>&2 echo -e -n "\tRequest: \"$req\"\t";
test -z "$req" && req="index.html"
if [ -f "$FILES$req" ] ; then
ext="${req##*.}"
ext=$(echo "$ext" | tr '[:upper:]' '[:lower:]')
case "$ext" in
"html" | "htm") CONTENTTYPE="text/html; charset=UTF-8" ;;
"json") CONTENTTYPE="application/json; charset=UTF-8" ;;
"css" | "less" | "sass") CONTENTTYPE="text/css" ;;
"txt") CONTENTTYPE="text/plain" ;;
"xml") CONTENTTYPE="text/xml" ;;
"js") CONTENTTYPE="application/javascript" ;;
"jpg" | "jpeg") CONTENTTYPE="image/jpeg" ;;
"png") CONTENTTYPE="image/png" ;;
"gif") CONTENTTYPE="image/gif" ;;
"ico") CONTENTTYPE="image/x-icon" ;;
"wav") CONTENTTYPE="audio/wav" ;;
"mp3") CONTENTTYPE="audio/mpeg3" ;;
"avi") CONTENTTYPE="video/avi" ;;
"mp4" | "mpg" | "mpeg" | "mpe") CONTENTTYPE="video/mpeg" ;;
*) CONTENTTYPE="application/octet-stream"
esac
echo "HTTP/1.x 200 OK"
echo "Date: $(LC_TIME=en_US date -u)"
echo "Server: promyk.doleczek.pl"
echo "Connection: close"
echo "Pragma: public"
echo "Content-Type: $CONTENTTYPE"
FILESIZE=$(wc -c < "$FILES$req")
echo -e "Content-Length: $FILESIZE\n"
cat "$FILES$req"
>&2 echo "[ ok ]"
else
echo -e "HTTP/1.x 404 Not found\n\n<h1>File not found.</h1>"
>&2 echo "[ no file ]"
fi
) | nc -l -k -w 1 -p $PORT > $FIFO; done;
source to share