一个简单的文件服务器
家里的路由器基本上折腾差不多了,用的是小厂家的x86工控机,硬盘也是捡的垃圾,想到自己时不时的升级/重启/异常断电,还是备份一下的配置文件比较稳妥。我用的路由系统的基于debian的VYOS rolling版本,折腾了半个月已经非常熟悉了,用起来比较爽也很稳定,基本满足自己的各种需求。系统自带了同步配置的功能,每次commit前会先同步配置,支持以下几种方式:
http://<user>:<passwd>@<host>/<path>
https://<user>:<passwd>@<host>/<path>
ftp://<user>:<passwd>@<host>/<path>
sftp://<user>:<passwd>@<host>/<path>
scp://<user>:<passwd>@<host>/<path>
tftp://<host>/<path>
git+https://<user>:<passwd>@<host>/<path>
以上方式中,最简单的就是http方式了,经过抓取日志,vyos 会使用 "POST /config.boot-vyos.20240205_171540" 方式上传配置,其中uri是配置文件名,包体是明文的配置内容,正好我的服务器使用的是openresty,就用lua写了一个简单的文件服务器,接收vyos POST过来的配置。lua脚本实现了以下功能:
- 接收POST包体并保存到文件,文件名为POST请求的uri
- 使用http basic authentication的用户名密码认证
接收POST请求,直接接收POST包体然后使用io.open写入文件:
local file
local upload_path = '/var/www/html/vyos/'
ngx.req.read_body()
local file_name = ngx.var.uri
local data = ngx.req.get_body_data()
if file_name then
file = io.open(upload_path .. file_name, 'a+')
if not file then
ngx.log(ngx.ERR, "failed to open file ", file_name)
return
end
end
if data then
file:write(data)
file:close()
else
ngx.log(ngx.ERR, "data is empty")
return
end
用户名密码认证,使用nginx也能实现,但是认证文件要单独写进文件里。用lua就一个配置文件就能可以了。http basic auth主要是是解析http头Authorization,并使用base64解密用户名密码:
local function split(str, delimiter)
local result = {}
if not str or not delimiter then
return result
end
for match in (str..delimiter):gmatch("(.-)"..delimiter) do
if match then
table.insert(result, match)
end
end
return result
end
local function parseHeader(authorization)
local base64 = string.sub(authorization, 7)
local data = split(ngx.decode_base64(base64), ":")
return data[1], data[2]
end
local header = ngx.req.get_headers()["Authorization"]
if header then
local username, password = parseHeader(header)
if username ~= "vyos.user" or password ~= "vyos.pass" then
ngx.header["WWW-Authenticate"] = [[Basic realm="restricted"]]
ngx.exit(401)
end
end
以上就是核心逻辑,经过测试ngx.req.get_body_data() 读请求包体,会偶尔出现读取不到直接返回 nil 的情况。还需要额外设置client_max_body_size 和client_body_buffer_size, 强制在内存中保存请求体。
完整的nginx配置文件如下:
server {
listen 80;
server_name
backup.opswill.com
;
access_log /var/log/backup_access.log main;
error_log /var/log/backup_error.log;
client_max_body_size 100m;
client_body_buffer_size 100m;
location / {
content_by_lua_block {
local function split(str, delimiter)
local result = {}
if not str or not delimiter then
return result
end
for match in (str..delimiter):gmatch("(.-)"..delimiter) do
if match then
table.insert(result, match)
end
end
return result
end
local function parseHeader(authorization)
local base64 = string.sub(authorization, 7)
local data = split(ngx.decode_base64(base64), ":")
return data[1], data[2]
end
local header = ngx.req.get_headers()["Authorization"]
if header then
local username, password = parseHeader(header)
if username ~= "vyos.user" or password ~= "vyos.pass" then
ngx.header["WWW-Authenticate"] = [[Basic realm="restricted"]]
ngx.exit(401)
end
end
local file
local upload_path = '/var/www/html/vyos/'
ngx.req.read_body()
local file_name = ngx.var.uri
local data = ngx.req.get_body_data()
if file_name then
file = io.open(upload_path .. file_name, 'a+')
if not file then
ngx.log(ngx.ERR, "failed to open file ", file_name)
return
end
end
if data then
file:write(data)
file:close()
else
ngx.log(ngx.ERR, "data is empty")
return
end
}
}
}
用curl测试文件上传:
curl -X POST -d"body-abd-opswill.com" "https://vyos.user:vyos.pass@backup.ipip.dev/test.txt" -ivvvv
会在/var/www/html/vyos/下生成 text.txt文件,内容是body-abd-opswill.com。
至此,一个使用lua实现的简单的文件服务器就配置完成了,该文件服务器可以接收post上传文件到指定目录并实现了基本的用户名密码认证。