如题:本文采用 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 脚本阻塞或者执行时间过长。会导致后续的脚本被阻塞无法执行。