0x01: 编写 lua 脚本
以下为示例代码,使用了 MySQL 作为持久化的存储,使用了 OpenResty 自带的 lua_shared_dict 作为缓存,这里可以根据自身的实际需求去更换不同的存储。
local host = os.getenv("APP_FRONTEND_URL")
local db_host = os.getenv("DATABASE_HOST")
local db_port = os.getenv("DATABASE_PORT")
local db_database = os.getenv("DATABASE")
local db_user = os.getenv("DATABASE_USERNAME")
local db_password = os.getenv("DATABASE_PASSWORD")
local expire = os.getenv("CACHE_EXPIRED_MINUTES")
if host == nil then
host = "SHORTLINK_DEFAULT_REDIRECT"
end
if db_host == nil or db_database == nil or db_user == nil or db_password == nil then
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
if db_port == nil then
db_port = 3306
end
if expire == nil then
expire = 15
end
local uri = ngx.var.request_uri
if not uri or uri == "/"
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
local links = ngx.shared.links
local redirect, flags, err = links:get(uri)
if not redirect or redirect == nil then
local mysql = require "resty.mysql"
local db, err = mysql:new()
if not db then
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
db:set_timeout(1000)
local ok, err, errno, sqlstate = db:connect{
host = db_host,
port = db_port,
database = db_database,
user = db_user,
password = db_password,
max_packet_size = 1024 * 1024
}
if not ok then
db:close()
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
sql = "SELECT value FROM short_link WHERE key
= " .. "\'" .. uri .. "\'" .. " ORDER BY created_at DESC LIMIT 1"
local res, err, errno, sqlstate = db:query(sql)
if not res or next(res) == nil then
db:close()
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
local result = res[1]["value"]
if(type(result) ~= "string") then
db:close()
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
if string.match(result, "^https-://[%w+%.]+[/%w+]+.*") ~= nil then
redirect = result
elseif string.match(result, "^%w[%w+%.]+.*") ~= nil then
redirect = "http://" .. result
else
redirect = host .. "/" .. result
end
local ok, err = links:set(uri, redirect, 60 * expire)
if not ok then
db:close()
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
db:close()
end
ngx.redirect(redirect, ngx.HTTP_MOVED_PERMANENTLY)
0x02: 搭建本地测试环境
本地的测试环境使用 docker 进行搭建,编写好 docker-compose 和相关配置文件后就可以进行测试。
# docker-compose.yaml
version: '3'
services:
mysql:
image: mysql:5.7
ports:
- "3306:3306"
expose:
- "3306"
volumes:
- ./data/mysql:/var/lib/mysql
- ./logs/mysql:/var/log/mysql
- ./etc/mysql:/etc/mysql
environment:
MYSQL_USER: test
MYSQL_PASSWORD: test123456
MYSQL_DATABASE: test
MYSQL_ROOT_PASSWORD: test123456
networks:
- test
openresty:
image: openresty/openresty:centos
ports:
- 8080:8080
- 80:80
volumes:
- ./etc/nginx/conf.d:/etc/nginx/conf.d
- ./etc/nginx/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf
- ./logs/nginx:/var/log/nginx/
- ./lualib/shortlink.lua:/usr/local/openresty/lualib/shortlink.lua;
environment:
APP_FRONTEND_URL: xxxxxxx
DATABASE_HOST: xxxxxxx
DATABASE_PORT: xxxxxxx
DATABASE: xxxxxxx
DATABASE_USERNAME: xxxxxxx
DATABASE_PASSWORD: xxxxxxx
CACHE_EXPIRED_MINUTES: xxxxxxx
networks:
- test
entrypoint:
- /usr/bin/openresty
- -g
- daemon off;
networks:
test:
# nginx.conf
user nobody;
worker_processes 1;
pid logs/nginx.pid;
error_log logs/error.log notice;
env APP_FRONTEND_URL;
env DATABASE;
env DATABASE_HOST;
env DATABASE_PORT;
env DATABASE_USERNAME;
env DATABASE_PASSWORD;
env CACHE_EXPIRED_MINUTES;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main;
gzip on;
sendfile on;
tcp_nopush on;
keepalive_timeout 60;
lua_shared_dict links 128m; # 用于分配缓存大小
include /etc/nginx/conf.d/*.conf;
}
# shortlink.conf
server {
listen 80;
server_name SHORTLINK_VHOST_NAME;
access_log /var/log/nginx/shortlink.access.log;
error_log /var/log/nginx/shortlink.error.log;
resolver local=on ipv6=off;
resolver_timeout 5s;
location / {
default_type text/plain;
content_by_lua_file /usr/local/openresty/lualib/shortlink.lua;
}
location = /robots.txt {
return 200 "User-Agent: *\nDisallow: ";
}
location = /probe {
return 200 "";
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/local/openresty/nginx/html;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
location ~ /\.ht {
deny all;
}
}
可以注意到,上面的 nginx 配置文件中加入了多条 env directives
是为了让 nginx worker 可以继承传入的环境变量。resolver local=on ipv6=off;
这行配置是为了 nginx 可以读取到 /etc/resolve.conf
中的默认 dns 地址,也可以通过给地址自行配置,不配置会出现 no resolver defined to resolve xxx.xxx
的错误,导致 lua 无法正常访问数据库。
0x03: 编写部署文件
完成上述工作后,就可以着手开始进行部署工作了,部署文件示例如下:
# config-map
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-shortlink-proxy
annotations:
configVersion: CONFIG_VERSION
namespace: APP_ENV
data:
nginx.conf: |
user nobody;
worker_processes SHORTLINK_WORKER_NUM;
pid logs/nginx.pid;
error_log logs/error.log notice;
env APP_FRONTEND_URL;
env DATABASE;
env DATABASE_HOST;
env DATABASE_PORT;
env DATABASE_USERNAME;
env DATABASE_PASSWORD;
env CACHE_EXPIRED_MINUTES;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local]
"$request" ' '$status $body_bytes_sent
"$http_referer" ' '"$http_user_agent"
"$http_x_forwarded_for"';
access_log logs/access.log main;
gzip on;
sendfile on;
tcp_nopush on;
keepalive_timeout 60;
lua_shared_dict links 128m;
include /etc/nginx/conf.d/*.conf;
}
shortlink.conf: |
server {
listen 80;
server_name SHORTLINK_VHOST_NAME;
access_log /var/log/nginx/shortlink.access.log;
error_log /var/log/nginx/shortlink.error.log;
resolver local=on ipv6=off;
resolver_timeout 5s;
location / {
default_type text/plain;
content_by_lua_file /usr/local/openresty/lualib/shortlink.lua;
}
location = /robots.txt {
return 200 "User-Agent: *\nDisallow: ";
}
location = /probe {
return 200 "";
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/local/openresty/nginx/html;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
location ~ /\.ht {
deny all;
}
}
shortlink.lua: |
local host = os.getenv("APP_FRONTEND_URL")
local db_host = os.getenv("DATABASE_HOST")
local db_port = os.getenv("DATABASE_PORT")
local db_database = os.getenv("DATABASE")
local db_user = os.getenv("DATABASE_USERNAME")
local db_password = os.getenv("DATABASE_PASSWORD")
local expire = os.getenv("CACHE_EXPIRED_MINUTES")
if host == nil then
host = "SHORTLINK_DEFAULT_REDIRECT"
end
if db_host == nil or db_database == nil or db_user == nil or db_password ==nil then
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
if db_port == nil then
db_port = 3306
end
if expire == nil then
expire = 15
end
local uri = ngx.var.request_uri
if not uri or uri == "/"
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
local links = ngx.shared.links
local redirect, flags, err = links:get(uri)
if not redirect or redirect == nil then
local mysql = require "resty.mysql"
local db, err = mysql:new()
if not db then
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
db:set_timeout(1000)
local ok, err, errno, sqlstate = db:connect{
host = db_host,
port = db_port,
database = db_database,
user = db_user,
password = db_password,
max_packet_size = 1024 * 1024
}
if not ok then
db:close()
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
sql = "SELECT value FROM shortlink WHERE key
= " .. "\'" .. uri .. "\'" .. " ORDER BY time DESC LIMIT 1"
local res, err, errno, sqlstate = db:query(sql)
if not res or next(res) == nil then
db:close()
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
local result = res[1]["value"]
if(type(result) ~= "string") then
db:close()
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
if string.match(result, "^https-://[%w+%.]+[/%w+]+.*") ~= nil then
redirect = result
elseif string.match(result, "^%w[%w+%.]+.*") ~= nil then
redirect = "http://" .. result
else
redirect = host .. "/" .. result
end
local ok, err = links:set(uri, redirect, 60 * expire)
if not ok then
db:close()
ngx.redirect(host, ngx.HTTP_MOVED_PERMANENTLY)
end
db:close()
end
ngx.redirect(redirect, ngx.HTTP_MOVED_PERMANENTLY)
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: dp-shortlink-proxy
labels:
app: shortlink-proxy
namespace: APP_ENV
spec:
replicas: 1
selector:
matchLabels:
app: shortlink-proxy
template:
metadata:
annotations:
configVersion: CONFIG_VERSION
service.shortlink.version: SHORTLINK_VERSION
labels:
app: shortlink-proxy
spec:
imagePullSecrets:
- name: registry-key
containers:
- name: shortlink-proxy
image: SHORTLINK_IMAGE_REPOSITORY:SHORTLINK_VERSION
imagePullPolicy: IfNotPresent
env:
- name: APP_FRONTEND_URL
value: "https://APP_FRONTEND_SERVER_NAME"
- name: DATABASE_HOST
valueFrom:
secretKeyRef:
name: APP_ENV-database-secret
key: host
- name: DATABASE_PORT
value: "DB_PORT"
- name: DATABASE
value: "DB_DATABASE"
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:
name: APP_ENV-database-secret
key: username
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: APP_ENV-database-secret
key: password
- name: CACHE_EXPIRED_MINUTES
value: "SHORTLINK_CACHE_TIMEOUT"
ports:
- name: shortlink-svc
containerPort: 80
volumeMounts:
- name: shortlink-proxy-logs
mountPath: /usr/local/nginx/logs
- name: shortlink-proxy-conf
mountPath: /usr/local/openresty/nginx/conf/nginx.conf
subPath: nginx.conf
- name: shortlink-proxy-conf
mountPath: /etc/nginx/conf.d/shortlink.conf
subPath: shortlink.conf
- name: shortlink-proxy-conf
mountPath: /usr/local/openresty/lualib/shortlink.lua
subPath: shortlink.lua
resources:
requests:
cpu: 100m
memory: 300Mi
limits:
cpu: 2000m
memory: 2000Mi
command: ["/usr/bin/openresty"]
args: ["-g", "daemon off;"]
readinessProbe:
httpGet:
path: /probe
port: 80
initialDelaySeconds: 10
periodSeconds: 10
- name: filebeat
image: SHORTLINK_IMAGE_REPOSITORY:SHORTLINK_VERSION
imagePullPolicy: IfNotPresent
workingDir: /opt/filebeat
volumeMounts:
- name: shortlink-proxy-logs
mountPath: /var/logs/nginx
- name: filebeat-conf
mountPath: /opt/filebeat/filebeat.yml
subPath: filebeat.yml
command: ["./filebeat"]
args: ["-e", "-c", "./filebeat.yml"]
volumes:
- name: shortlink-proxy-logs
emptyDir: {}
- name: shortlink-proxy-conf
configMap:
name: cm-shortlink-proxy
- name: filebeat-conf
configMap:
name: cm-filebeat
# Service
apiVersion: v1
kind: Service
metadata:
name: svc-shortlink-proxy
annotations:
service.shortlink.protocol: http
labels:
app: shortlink-proxy
namespace: APP_ENV
spec:
type: LoadBalancer
ports:
- name: shortlink-svc
port: 80
protocol: TCP
targetPort: shortlink-svc
selector:
app: shortlink-proxy
# Secret
apiVersion: v1
kind: Secret
metadata:
name: APP_ENV-database-secret
type: Opaque
stringData:
host: DB_HOST
username: DB_USERNAME
password: DB_PASSWORD
上面的配置含有很多环境变量,我采取的方案是结合 env
文件和脚本进行统一替换,编写好配置文件后,依次将上述配置文件应用到部署集群后就可以正式对外提供服务了。完结撒花,如果有什么写的不对的地方,欢迎各位观众老爷指出。
0x04 短地址怎么来,这里有个小 demo
<?php
function generateShortLinks(string $url, int $short_len = 6) : array
{
$base_chars = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2',
'3', '4', '5', '6', '7', '8', '9'
];
$short_arr = [];
$md5_arr = str_split(md5($long_url), 8);
foreach ($md5_arr as $md5_str) {
$hex = hexdec($md5_str) & 0x3fffffff;
$short_str = null;
for ($i = 0; $i < $short_len; $i++) {
$index = 0x0000003d & $hex;
$short_str .= $base_chars[$index];
$hex = $hex >> ($short_len - 1);
}
array_push($short_arr, $short_str);
}
return $short_arr;
}
$long_url = 'https://www.apple.com/cn/';
var_dump(generateShortLinks($long_url));
0 条评论