持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情
前言
兄弟萌, 我实现了一个实用的小工具, 特来分享.
事情刚开始是这样的, 我需要一个脚本来实现代码仓库web hook的任务, 首先想到的是直接调用php, 但是php-fpm是以www-data用户运行的, 很多调用是无法实现的. 此时, 我就需要一个常驻的偶尔调用一下的, 能够以root用户执行的http服务器, 但是网上查了查, 发现并么有符合我要求的现成轮子, 于是它来了.
是什么
简单介绍一下这个轮子是做什么用的. 简单说, 就是启动一个执行shell脚本的http服务.
它启动一个服务, 来监听指定端口并响应HTTP请求, 脚本在监听到新的请求后, 会根据请求路径以root身份调用相应的脚本执行任务并返回对应的内容.
如果需要HTTPS外边再套一层nginx就行了.
使用介绍
这个破玩意如何使用呢? 为了简化使用, 我将其打包成了docker镜像. 注意, 此服务每一次调用会启动一个新的进程, 故不适用于高并发场景. 当前介绍为最新版本使用指南, 历史版本内容在最下方(建议使用latest版本, 可直接忽略下方历史文档).
启动
假设脚本的本地运行目录为: /usr/share/script.
创建文件/usr/share/script/test.bash. 内容如下:
#!/usr/bin/env bash
echo "return"
给脚本赋予执行权限: chmod +x /usr/share/script/test.bash
docker命令行启动:
docker run -it -d -p 80:80 -v /usr/share/script:/opt/script hujingnb/http_cron
# 仅监听 127.0.0.1 的端口
docker run -it -p 127.0.0.1:80:80/tcp -v /usr/share/script:/opt/script hujingnb/http_cron
docker-composer启动:
version: '3.1'
services:
tcp_cron:
build: hujingnb/http_cron
container_name: http_cron
restart: always
port:
- 80:80
volumes:
- /usr/share/script:/opt/script
OK, 此时访问请求: http:127.0.0.1/test, 就会看到返回内容return了.
脚本运行机制
路由分配
根据请求的request_uri调用对应的脚本.
若请求为: /user/change_name.
那么会将脚本的工作路径/opt/script(工作路径通过环境变量WORKSPACE修改) 与请求拼在一起, 拼接后的路径为: /opt/script/user/change_name, 依次寻找以下后缀文件, 首次找到的为执行脚本:
/opt/script/user/change_name.pl/opt/script/user/change_name.sh/opt/script/user/change_name.bash/opt/script/user/change_name.php/opt/script/user/change_name.py/opt/script/user/change_name.rb
若没有找到脚本, 或访问根路径, 返回404.
注意, 所有脚本都需要赋予执行权限.
接收请求
脚本通过环境变量接收请求内容, bash脚本可直接通过$METHOD_TYPE读取. 有如下内容:
METHOD_TYPE: 请求的方法.GETPOST等HTTP_VERSION: 请求的HTTP版本.HTTP/1.1REQUEST_URI: 请求原始路径(去掉GET参数的).QUERY_STR:GET请求的原始参数字符串FORM_CONTENT: 若请求是POST, 则此变量保存请求体的字符串内容.- 没有对内容进行解析. 因为根据
content-type不同, 解析方式不同. 请自行解析 - 注意, 若内容过大, 可通过
INIT_FORM_CONTENT将其关闭. 并从REQUEST_BODY_FILE获取请求内容
- 没有对内容进行解析. 因为根据
REQUEST_BODY_FILE: 用于临时存放请求内容的文件- 当
INIT_FORM_CONTENT为1时(默认行为), 会将文件内容读出并放入环境变量FORM_CONTENT
- 当
QUERY_PARAM_xxx: 解析后的GET请求参数.xxx为参数名HEADER_xxx: 请求的header内容.xxx为header名称- 将所有字母大写, 并将
-替换为_
- 将所有字母大写, 并将
REMOTE_ADDR: 客户端 IP 地址REMOTE_PORT: 客户端端口
不同类型脚本读取系统env环境变量的方式不同, 请自行搜索.
响应请求
通过调用shell命令设置返回. 命令如下:
#!/usr/bin/env bash
# 设置响应码, 命令没有返回. 默认为200
# 多次调用取最后一次
set-status 200
# 添加返回的响应头信息
add-header <header_key> <header_value>
# 脚本所有的输出作为响应体返回
echo 1234
若是其他脚本请参考调用系统命令的方式,
配置
可通过如下配置进行自定义修改.
环境变量
WORKSPACE: 修改运行脚本的查找目录. 默认为:/opt/script.LISTEN_PORT: 修改脚本监听的端口. 默认80INIT_FORM_CONTENT: 请求内容默认放在临时文件中, 脚本会将其读出并放入环境变量FORM_CONTENT中. 若请求太大, 需要自行读取, 可关闭.- 默认为1
- 关闭时, 保存内容的文件路径在环境变量
REQUEST_BODY_FILE中
MAX_CONCURRENT: fcgiwrap启动进程数量, 可支持的最大并发数- 默认为4. 请根据机器性能酌情修改
若此配置无法满足, 请自行覆盖nginx配置文件. 路径: /etc/nginx/conf.d/default.conf
docker命令行通过-e参数添加环境变量
docker-composer通过environment参数修改环境变量
环境准备
为了保证镜像的大小, 只安装了必要的软件, 包括python PHP运行环境都没有. 故, 若你有额外需求的话, 有如下两种方式来实现系统环境的定制化:
1.覆盖/opt/init.bash文件
镜像在每次启动时, 都会首先执行/opt/init.bash文件, 可以在这里安装额外的软件等初始化操作.
请注意, /opt/init.bash脚本每次镜像启动都会执行一次. 此脚本默认不执行任何操作.
2. 镜像引用
你也可以通过FROM的方式来制作自己的镜像.
问题
启动子进程后台运行, 无法立即返回
问题
在脚本中使用如下命令启动子进程:
sleep 60 &
查看到此命令立即返回, 但接口没有立即收到响应.
解决
需要将脚本的输入/输出/错误流都关闭. 解决方案如下:
// 方案一: 子进程启动时自己关闭
exec 0<&-
exec 1>&-
exec 2>&-
// 方案二: 启动子进程时关闭
sleep 60 1>&- 2>/dev/null &
或者你可以将结果重定向到其他文件. 对shell重定向不了解的可参考: hujingnb.com/archives/77…
解决方案参考: github.com/gnosek/fcgi…
此镜像当前版本为1.2.4, 会随着问题的发现而更新, 若你发现了问题也可以提merge或issues.
后续更新会更新到这里: hujingnb.com/archives/72…