加载中...
不想等待可以点我关掉

前几天听说Vercel支持部署Go程序了,看了下文档发现局限性蛮大:

  1. 必须放置go文件在/api/目录里
  2. 只能以单个文件处理单个路由这种进行调用
  3. 使用http.HandlerFunc暴露入口
    确实是支持,基本上没法部署任意成熟的Go程序,随便找一个都没在用net/http

在本地开发环境中可以发现,在访问对应路由时会编译一个二进制文件,再运行来获取返回数据。大胆猜想下Vercel提供了一个相对开放的运行环境,应该具有相当大的可玩性。

那我能不能整一个替身,在用户访问时替身去检查目标程序的运行情况,没运行就运行并等到初始化完成,运行了就直接反代,实际测试下。

物料清单

  1. Artalk amd64二进制程序
  2. Neon pgsql
  3. Upstash redis(可选)

文件结构

1
2
3
4
5
6
7
8
9
10
11
.
├── api
│ └── main.py
├── artalk
├── artalk.yml
├── homepage
│ └── index.html # Demo前端,实际不用
├── requirements.txt
└── vercel.json

3 directories, 6 files
api\main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import os
import subprocess
import asyncio
import httpx
import uvicorn
from fastapi import FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI(docs_url=None, redoc_url=None)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

# 全局配置
cwd = os.getcwd()
ARTALK_BIN = f"{cwd}/artalk"
ARTALK_ARGS = ["server"]
TARGET = "http://127.0.0.1:23366"
_check_lock = asyncio.Lock()
_artalk_process: subprocess.Popen = None

async def _ensure_target_up():
"""
确保 TARGET 可访问。若不可访问,启动 artalk 服务并等待它可达。
"""
global _artalk_process

if _check_lock.locked():
return

async with _check_lock:
if await _target_is_up():
return

# 启动 artalk 服务(若尚未启动)
if _artalk_process is None or _artalk_process.poll() is not None:
_artalk_process = subprocess.Popen(
[ARTALK_BIN] + ARTALK_ARGS,
cwd=cwd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

for _ in range(30):
if await _target_is_up():
return
await asyncio.sleep(1.5)

raise RuntimeError("artalk 服务启动超时,TARGET 仍不可达")

async def _target_is_up() -> bool:
"""
简单检测 TARGET 根路径是否返回 2xx/3xx 状态码。
"""
try:
async with httpx.AsyncClient(timeout=1.0) as client:
resp = await client.get(TARGET)
return 200 <= resp.status_code < 400
except Exception:
return False

@app.api_route("/{full_path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
async def proxy(full_path: str, request: Request):
await _ensure_target_up()

url = f"{TARGET}/{full_path}"
headers = dict(request.headers)
headers.pop("host", None)
body = await request.body()

async with httpx.AsyncClient(timeout=None) as client:
resp = await client.request(
request.method,
url,
headers=headers,
content=body,
params=request.query_params
)

response_headers = dict(resp.headers)

# 判断是否为 JS 或 CSS 并添加缓存标头
if full_path.endswith(".js") or full_path.endswith(".css") \
or resp.headers.get("content-type", "").startswith(("text/javascript", "text/css")):
# **Cache-Control: public, max-age=2592000** 即 30 天
response_headers["Cache-Control"] = "public, s-max-age=2592000"
response_headers["Vercel-CDN-Cache-Control"] = "public, max-age=2592000"
response_headers["CDN-Cache-Control"] = "public, max-age=2592000"


return Response(
content=resp.content,
status_code=resp.status_code,
headers=response_headers
)

if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)

requirements.txt
1
2
3
4
5
requests
fastapi
uvicorn
Werkzeug
httpx
vercel.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"version": 2,
"public": true,
"rewrites": [
{
"source": "/(.*)",
"destination": "/api/main.py"
}
],
"headers": [
{
"source": "/dist/(.*)",
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Access-Control-Allow-Origin",
"value": "*"
},
{
"key": "Access-Control-Allow-Methods",
"value": "*"
},
{
"key": "Access-Control-Allow-Headers",
"value": "*"
}
]
}
]
}

由于vercel运行环境只读,请先修改好配置文件artalk.yml
修改:

  1. 关闭log
  2. 修改数据库,redis配置
  3. 邮件等等

部署

我使用的vercel-cli直接部署,也可以推送到GitHub仓库,不过注意要是私有仓库以防止配置泄露

$

效果

缺点

  1. 不能持久化运行(最长五分钟),初次运行等待时间长
  2. 同时也会带来很多无必要的数据库交互
  3. 目前仅验证了思路,具体功能(比如邮件发送)正不正常未知。而且没优化好,经常报错