使用shell+nginx编写简易web服务器

使用shell+nginx编写简易web服务器

如题:本文采用 linux 的shell 脚本作为后端,nginx做反向代理,实现一个简易的web服务器

优势:一个 几kb 的文件就是一个web 服务,没有依赖,不需要安装 Python 等环境。非常的轻量

劣势:一般只拿来做简易的功能,由于非常轻量,所以复杂功能不好实现,如果需要复杂功能,还不如拿 Python、java 等程序做

对于一些非常简单的功能,如果拿python或者java写后端,会显得比较重,而且部署、开机自启或者加入 systemctl 等都需要手动操作。太麻烦了,我只是需要一个小小的接口而已
所以还不如拿 shell 来实现,一个简单的接口,大约几十行的脚本就能实现了

基础

HTTP是基于文本的纯应用协议

上边这句话听过许多遍了,由于它是基于文本的,所以可以方便的读取它的参数、请求头、body等

关键是知道文本协议的构造即可,也就是协议是如何分割响应头、参数、响应体的

以下示例是发起一个http请求的纯文本:

GET /getcount HTTP/2
Host: pd.zwc365.com
User-Agent: curl/7.64.0
Accept: */*


可以看到:

  • 第一行发送的是请求协议:这里是 GET ,然后以空格分割, 第二项是 /getcount 表示请求路径,第三项是 HTTP/2 表示http的版本
  • 第二行开始是请求头,key-value 是以冒号 : 分割的
  • 最后分割请求体和请求头的是一个 空白行 , 由于get请求没有请求体,所以此处是空的

以下示例是响应一个 http 请求的纯文本

HTTP/2 200 
server: nginx
date: Tue, 23 Nov 2021 03:18:55 GMT
content-type: text/json
access-control-allow-origin: *
access-control-allow-methods: POST, GET, OPTIONS
access-control-allow-methods: *
access-control-max-age: 3600
access-control-allow-credentials: true

{"count": "4261715", "yesterday": "6892", "today": "2343"}

http的响应与请求结构大体一致

  • 第一行表示响应状态,以空格分割,第一项是http协议:HTTP/2 ,第二项表示http的状态码 200
  • 第二行开始是响应头,也是以冒号 : 分割的,随后都是响应头
  • 最后的响应体是用一个空白行分割的,从示例中看到 json 字段前是有一个空白行的

上面就是基本的 http 请求和响应的纯文本结构解析

基于此,可以采用shell 读取数据,并构造这样的结构,返回给用户即可

编写shell web服务器示例

#!/usr/bin/env bash

echo "Content-Type:text/json"
echo "Access-Control-Allow-Origin: *"
echo "Access-Control-Allow-Methods: POST, GET, OPTIONS"
echo "Status:200"
echo ""

PARAM_KEY=$EXTERNAL_PARAM
COUNT_SAVE_KEY="file_proxy_count_save_${PARAM_KEY}"
COUNT_YESTERDAY_SAVE_KEY="file_proxy_count_yesterday_save_${PARAM_KEY}"
COUNT_YESTERDAY_KEY="file_proxy_count_yesterday_${PARAM_KEY}"
# COUNT_TODAY_KEY="file_proxy_count_today_${PARAM_KEY}"

function redis-cli(){
    /usr/local/bin/redis-cli -h 127.0.0.1 -p 8888 $@
}

yesterday_full_count=`redis-cli get "$COUNT_YESTERDAY_SAVE_KEY"`
count=`redis-cli get "$COUNT_SAVE_KEY"`
yesterday_count=`redis-cli get "$COUNT_YESTERDAY_KEY"`
# today_count=`redis-cli get "$COUNT_TODAY_KEY"`
today_count=$(($count-$yesterday_full_count))

echo -E "{\"count\": \"${count}\"\
, \"yesterday\": \"${yesterday_count}\"\
, \"today\": \"${today_count}\"}"


以上便是一个数十行的shell 脚本。执行这段脚本会输出一个响应。内容如下:

HTTP/2 200 
server: nginx
date: Tue, 30 Nov 2021 02:16:33 GMT
content-type: text/json
access-control-allow-origin: *
access-control-allow-methods: POST, GET, OPTIONS

{"count": "4317661", "yesterday": "11358", "today": "2649"}

使用 Nginx 监听端口并转发 网址请求

上面编写了一段脚本。但是脚本中并未写如何监听 80 或者是 443 端口。那么如何处理 http 请求。这需要借助 Nginx

server{
    listen 80; listen 443 ssl http2;
    server_name script.zwc365.com;
    include conf.d/cfg/zwchttps.cfg;

    root /media/shellscript;
    access_log /var/log/nginx/script.log main buffer=64k flush=10s;

    fastcgi_param SCRIPT_FILENAME $request_filename;
    fastcgi_param REMOTE_USER $remote_user;
    fastcgi_param EXTERNAL_URI    $request_uri;
    fastcgi_param EXTERNAL_PARAM  $arg_ext;
    fastcgi_param EXTERNAL_PARAM2 $arg_ext2;
    fastcgi_param EXTERNAL_PARAM3 $arg_ext3;
    fastcgi_param EXTERNAL_PARAM4 $arg_ext4;
    include fastcgi_params;
    location / {
        fastcgi_pass unix:/var/run/fcgiwrap.socket;
    }
}

以上 nginx 配置,监听了 80 和 443 端口。并将监听到的程序,转发到 fastcgi 处理模块。通过 fastcgi 执行脚本并返回响应

那么参数的传递呢?使用的是:fastcgi_param 指令。

例如:配置了脚本执行时的 环境变量 : EXTERNAL_URI , 值为:request_uri

环境变量:EXTERNAL_PARAM 值为:arg_ext

说明:在nginx 中。如果要取 url 中的参数,使用: arg_ 开头,后面跟参数名。例如一个链接:http://www.baidu.com?token=123 ,那么取这个token就是:$arg_token

配置了 fastcgi_param 指令后,在 shell 脚本中,是一个环境变量,使用: $xxxx 取值即可

注意

建议只充当简易服务器,且尽量不要 sleep 或者 while 、 for 循环。因为默认的 fastcgi 是一个单进程。如果某个 shell 脚本阻塞或者执行时间过长。会导致后续的脚本被阻塞无法执行。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://zwc365.com/2021/11/23/使用shellnginx编写简易web服务器

Buy me a cup of coffee ☕.