Files
openresty-gateway/conf/conf.d/ai.sggai.site.conf
2026-05-18 22:32:08 +08:00

156 lines
6.0 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
server {
# 文件名前缀是 000-,会比 ai.sggai.site.conf 更早加载。
# 这样可以在不修改原配置文件的情况下,让这里的 mock 逻辑优先生效。
listen 80;
listen 443 ssl;
server_name ai.sggai.site;
ssl_certificate /etc/letsencrypt/live/ai.sggai.site/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ai.sggai.site/privkey.pem;
location ^~ /.well-known/acme-challenge/ {
root /var/www;
default_type text/plain;
try_files $uri =404;
}
client_max_body_size 200m;
gzip off;
gunzip off;
# 公共反代配置放在 server 级别,主后端 proxy_pass location 会继承这些配置。
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Authorization $http_authorization;
proxy_set_header Content-Type $http_content_type;
proxy_set_header Accept $http_accept;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header Originator $http_originator;
proxy_set_header Session_id $http_session_id;
proxy_set_header X-Codex-Beta-Features $http_x_codex_beta_features;
proxy_set_header X-Codex-Turn-Metadata $http_x_codex_turn_metadata;
proxy_set_header X-Stainless-Arch $http_x_stainless_arch;
proxy_set_header X-Stainless-Lang $http_x_stainless_lang;
proxy_set_header X-Stainless-Os $http_x_stainless_os;
proxy_set_header X-Stainless-Package-Version $http_x_stainless_package_version;
proxy_set_header X-Stainless-Retry-Count $http_x_stainless_retry_count;
proxy_set_header X-Stainless-Runtime $http_x_stainless_runtime;
proxy_set_header X-Stainless-Runtime-Version $http_x_stainless_runtime_version;
proxy_set_header X-Stainless-Timeout $http_x_stainless_timeout;
proxy_set_header X-App $http_x_app;
proxy_set_header Anthropic-Beta $http_anthropic_beta;
proxy_set_header Anthropic-Dangerous-Direct-Browser-Access $http_anthropic_dangerous_direct_browser_access;
proxy_set_header Anthropic-Version $http_anthropic_version;
proxy_set_header Accept-Encoding "";
proxy_set_header X-Real-IP "";
proxy_set_header X-Forwarded-For "";
proxy_set_header X-Forwarded-Proto "";
proxy_set_header X-Forwarded-Host "";
proxy_set_header X-Forwarded-Port "";
proxy_set_header Connection "";
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
proxy_cache_bypass 1;
proxy_connect_timeout 600s;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
# 只拦截很小的非流式 Chat Completions 请求。
# 不满足条件的请求会内部跳转到正常后端,不直接返回 mock。
location = /v1/chat/completions {
client_body_buffer_size 16k;
content_by_lua_block {
-- 不满足 mock 条件时,统一内部跳转到这个 named location。
local backend = "@ai_sggai_site_backend"
-- 请求体总大小上限:只处理非常小的探测类请求。
local max_body_bytes = 1024
-- 只处理 POST其它方法按普通请求转发到真实后端。
if ngx.req.get_method() ~= "POST" then
return ngx.exec(backend)
end
-- 先看 Content-Length超过阈值就不读取 body直接放行。
local content_length = tonumber(ngx.var.http_content_length)
if not content_length then
-- 没有 Content-Length 的请求可能是 chunked不为了判断 mock 去读取未知大小的 body。
return ngx.exec(backend)
end
-- 请求体太大,说明不是目标小请求,直接走真实后端。
if content_length > max_body_bytes then
return ngx.exec(backend)
end
-- 到这里说明 body 足够小,可以安全读取并解析 JSON。
ngx.req.read_body()
local body = ngx.req.get_body_data()
if not body or #body > max_body_bytes then
-- body 不在内存里或实际大小仍然超限时,不 mock转发到真实后端。
return ngx.exec(backend)
end
-- JSON 解析失败,或 stream 不是 false都不 mock。
local cjson = require "cjson.safe"
local payload = cjson.decode(body)
if type(payload) ~= "table" or payload.stream ~= false then
return ngx.exec(backend)
end
-- 必须有 messages 数组;没有就不 mock。
if type(payload.messages) ~= "table" then
return ngx.exec(backend)
end
local has_hi_content = false
-- 遍历所有 message只匹配精确的 "content": "hi"。
for _, message in ipairs(payload.messages) do
if type(message) == "table" and message.content == "hi" then
has_hi_content = true
break
end
end
-- 没有精确命中 "content": "hi",交给真实后端处理。
if not has_hi_content then
return ngx.exec(backend)
end
-- 命中小请求 + stream=false + "content": "hi" 条件,直接返回 mock 响应。
ngx.status = ngx.HTTP_OK
ngx.header["Content-Type"] = "application/json; charset=utf-8"
ngx.say([[{
"id": "chatcmpl-mock",
"object": "chat.completion",
"created": 1716030000,
"model": "xxx",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "ok" },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2 }
}]])
return ngx.exit(ngx.HTTP_OK)
}
}
# 仅供上面的 Lua 逻辑通过 ngx.exec 内部跳转使用,外部 URL 不能直接访问这个 location。
location @ai_sggai_site_backend {
proxy_pass http://10.1.0.1:3001;
}
# 普通流量保持原来的上游转发行为。
location / {
proxy_pass http://10.1.0.1:3001;
}
}