fix(proxy): 修复连接池 HTTP/1.0 兼容与配置合并 bug
- proxy.c: 当后端发送 Connection: close 或关闭连接时,不再归还已关闭连接 到池中。避免复用已关闭连接导致请求失败。 - http2.c: HTTP/2 代理检测到 HTTP/1.0 响应时默认关闭连接,不再错误归还。 - main.c: 修复 -c 配置文件与 --cert/--key 同时使用时 config_merge 的 use-after-free 问题。先加载配置文件,再用命令行参数覆盖。 - tests/http10_backend.py: 新增 HTTP/1.0 后端模拟器,用于测试连接池兼容。 - tests/integration_test.sh: 添加 HTTP/1.0 后端兼容测试,验证两次连续请求 均成功(第二次必须新建连接)。
This commit is contained in:
parent
25a0b100c1
commit
90601e0f67
7
http2.c
7
http2.c
@ -1584,6 +1584,7 @@ static void http2_serve_proxy(http2_session_t *h2, http2_stream_data_t *stream)
|
|||||||
|
|
||||||
/* 判断后端是否发送 Connection: close */
|
/* 判断后端是否发送 Connection: close */
|
||||||
bool backend_wants_close = false;
|
bool backend_wants_close = false;
|
||||||
|
bool is_http10 = strncmp(response_buf, "HTTP/1.0", 8) == 0;
|
||||||
{
|
{
|
||||||
const char *p = headers;
|
const char *p = headers;
|
||||||
while (*p) {
|
while (*p) {
|
||||||
@ -1592,6 +1593,8 @@ static void http2_serve_proxy(http2_session_t *h2, http2_stream_data_t *stream)
|
|||||||
while (*p == ' ' || *p == '\t') p++;
|
while (*p == ' ' || *p == '\t') p++;
|
||||||
if (strncasecmp(p, "close", 5) == 0) {
|
if (strncasecmp(p, "close", 5) == 0) {
|
||||||
backend_wants_close = true;
|
backend_wants_close = true;
|
||||||
|
} else if (strncasecmp(p, "keep-alive", 10) == 0) {
|
||||||
|
backend_wants_close = false; /* 显式 keep-alive */
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1600,6 +1603,10 @@ static void http2_serve_proxy(http2_session_t *h2, http2_stream_data_t *stream)
|
|||||||
p = nl + 2;
|
p = nl + 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* HTTP/1.0 默认关闭,除非显式 keep-alive */
|
||||||
|
if (is_http10 && !backend_wants_close) {
|
||||||
|
backend_wants_close = true;
|
||||||
|
}
|
||||||
|
|
||||||
/* 归还或关闭连接 */
|
/* 归还或关闭连接 */
|
||||||
if (backend_wants_close) {
|
if (backend_wants_close) {
|
||||||
|
|||||||
93
main.c
93
main.c
@ -103,74 +103,52 @@ static void print_usage(const char *prog) {
|
|||||||
*/
|
*/
|
||||||
static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
||||||
/* 默认值 */
|
/* 默认值 */
|
||||||
config->root_dir = NULL;
|
memset(config, 0, sizeof(*config));
|
||||||
config->port = 8080;
|
config->port = 8080;
|
||||||
config->threaded = false;
|
|
||||||
config->num_workers = 0;
|
|
||||||
config->max_connections = 0;
|
|
||||||
config->timeout_ms = 0;
|
|
||||||
config->log_level = LOG_LEVEL_INFO;
|
config->log_level = LOG_LEVEL_INFO;
|
||||||
config->gzip_enabled = true;
|
config->gzip_enabled = true;
|
||||||
config->brotli_enabled = true;
|
config->brotli_enabled = true;
|
||||||
|
|
||||||
config->tls_cert = NULL;
|
/* 第一阶段:查找配置文件路径 */
|
||||||
config->tls_key = NULL;
|
|
||||||
config->tls_enabled = false;
|
|
||||||
config->access_log_path = NULL;
|
|
||||||
|
|
||||||
config->cors_enabled = false;
|
|
||||||
config->auth_user = NULL;
|
|
||||||
config->auth_pass = NULL;
|
|
||||||
config->rate_limit = 0;
|
|
||||||
|
|
||||||
config->num_plugins = 0;
|
|
||||||
|
|
||||||
bool has_root_dir = false;
|
|
||||||
bool has_port = false;
|
|
||||||
bool has_workers = false;
|
|
||||||
bool has_max_conn = false;
|
|
||||||
bool has_timeout = false;
|
|
||||||
bool has_log_level = false;
|
|
||||||
bool has_gzip_enabled = false;
|
|
||||||
bool has_brotli_enabled = false;
|
|
||||||
bool has_tls_cert = false;
|
|
||||||
bool has_tls_key = false;
|
|
||||||
bool has_tls_enabled = false;
|
|
||||||
bool has_access_log = false;
|
|
||||||
bool has_cors_enabled = false;
|
|
||||||
bool has_auth_user = false;
|
|
||||||
bool has_auth_pass = false;
|
|
||||||
bool has_rate_limit = false;
|
|
||||||
bool has_plugins = false;
|
|
||||||
const char *config_file = NULL;
|
const char *config_file = NULL;
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
if (strcmp(argv[i], "-c") == 0) {
|
if (strcmp(argv[i], "-c") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
config_file = argv[i];
|
config_file = argv[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 第二阶段:加载配置文件(如果有) */
|
||||||
|
if (config_file) {
|
||||||
|
if (!config_load_from_file(config_file, config)) {
|
||||||
|
fprintf(stderr, "Error: 无法加载配置文件: %s\n", config_file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 第三阶段:解析命令行参数,覆盖配置文件 */
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
if (strcmp(argv[i], "-c") == 0) {
|
||||||
|
i++; /* 已处理,跳过 */
|
||||||
} else if (strcmp(argv[i], "-r") == 0) {
|
} else if (strcmp(argv[i], "-r") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
|
free((void *)config->root_dir);
|
||||||
config->root_dir = strdup(argv[i]);
|
config->root_dir = strdup(argv[i]);
|
||||||
has_root_dir = true;
|
|
||||||
} else if (strcmp(argv[i], "-p") == 0) {
|
} else if (strcmp(argv[i], "-p") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
config->port = (uint16_t)atoi(argv[i]);
|
config->port = (uint16_t)atoi(argv[i]);
|
||||||
if (config->port == 0) config->port = 8080;
|
if (config->port == 0) config->port = 8080;
|
||||||
has_port = true;
|
|
||||||
} else if (strcmp(argv[i], "-t") == 0) {
|
} else if (strcmp(argv[i], "-t") == 0) {
|
||||||
config->threaded = true;
|
config->threaded = true;
|
||||||
} else if (strcmp(argv[i], "-w") == 0) {
|
} else if (strcmp(argv[i], "-w") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
config->num_workers = (uint32_t)atoi(argv[i]);
|
config->num_workers = (uint32_t)atoi(argv[i]);
|
||||||
has_workers = true;
|
|
||||||
} else if (strcmp(argv[i], "-m") == 0) {
|
} else if (strcmp(argv[i], "-m") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
config->max_connections = (uint32_t)atoi(argv[i]);
|
config->max_connections = (uint32_t)atoi(argv[i]);
|
||||||
has_max_conn = true;
|
|
||||||
} else if (strcmp(argv[i], "-o") == 0) {
|
} else if (strcmp(argv[i], "-o") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
config->timeout_ms = (uint32_t)atoi(argv[i]);
|
config->timeout_ms = (uint32_t)atoi(argv[i]);
|
||||||
has_timeout = true;
|
|
||||||
} else if (strcmp(argv[i], "-l") == 0) {
|
} else if (strcmp(argv[i], "-l") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
if (strcmp(argv[i], "error") == 0) config->log_level = LOG_LEVEL_ERROR;
|
if (strcmp(argv[i], "error") == 0) config->log_level = LOG_LEVEL_ERROR;
|
||||||
@ -181,51 +159,43 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
|||||||
fprintf(stderr, "Unknown log level: %s\n", argv[i]);
|
fprintf(stderr, "Unknown log level: %s\n", argv[i]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
has_log_level = true;
|
|
||||||
} else if (strcmp(argv[i], "-v") == 0) {
|
} else if (strcmp(argv[i], "-v") == 0) {
|
||||||
config->log_level = LOG_LEVEL_DEBUG;
|
config->log_level = LOG_LEVEL_DEBUG;
|
||||||
has_log_level = true;
|
|
||||||
} else if (strcmp(argv[i], "--no-gzip") == 0) {
|
} else if (strcmp(argv[i], "--no-gzip") == 0) {
|
||||||
config->gzip_enabled = false;
|
config->gzip_enabled = false;
|
||||||
has_gzip_enabled = true;
|
|
||||||
} else if (strcmp(argv[i], "--no-brotli") == 0) {
|
} else if (strcmp(argv[i], "--no-brotli") == 0) {
|
||||||
config->brotli_enabled = false;
|
config->brotli_enabled = false;
|
||||||
has_brotli_enabled = true;
|
|
||||||
} else if (strcmp(argv[i], "--cert") == 0) {
|
} else if (strcmp(argv[i], "--cert") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
|
free((void *)config->tls_cert);
|
||||||
config->tls_cert = strdup(argv[i]);
|
config->tls_cert = strdup(argv[i]);
|
||||||
has_tls_cert = true;
|
|
||||||
} else if (strcmp(argv[i], "--key") == 0) {
|
} else if (strcmp(argv[i], "--key") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
|
free((void *)config->tls_key);
|
||||||
config->tls_key = strdup(argv[i]);
|
config->tls_key = strdup(argv[i]);
|
||||||
has_tls_key = true;
|
|
||||||
} else if (strcmp(argv[i], "--tls") == 0) {
|
} else if (strcmp(argv[i], "--tls") == 0) {
|
||||||
config->tls_enabled = true;
|
config->tls_enabled = true;
|
||||||
has_tls_enabled = true;
|
|
||||||
} else if (strcmp(argv[i], "--access-log") == 0) {
|
} else if (strcmp(argv[i], "--access-log") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
|
free((void *)config->access_log_path);
|
||||||
config->access_log_path = strdup(argv[i]);
|
config->access_log_path = strdup(argv[i]);
|
||||||
has_access_log = true;
|
|
||||||
} else if (strcmp(argv[i], "--cors") == 0) {
|
} else if (strcmp(argv[i], "--cors") == 0) {
|
||||||
config->cors_enabled = true;
|
config->cors_enabled = true;
|
||||||
has_cors_enabled = true;
|
|
||||||
} else if (strcmp(argv[i], "--auth-user") == 0) {
|
} else if (strcmp(argv[i], "--auth-user") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
|
free((void *)config->auth_user);
|
||||||
config->auth_user = strdup(argv[i]);
|
config->auth_user = strdup(argv[i]);
|
||||||
has_auth_user = true;
|
|
||||||
} else if (strcmp(argv[i], "--auth-pass") == 0) {
|
} else if (strcmp(argv[i], "--auth-pass") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
|
free((void *)config->auth_pass);
|
||||||
config->auth_pass = strdup(argv[i]);
|
config->auth_pass = strdup(argv[i]);
|
||||||
has_auth_pass = true;
|
|
||||||
} else if (strcmp(argv[i], "--rate-limit") == 0) {
|
} else if (strcmp(argv[i], "--rate-limit") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
config->rate_limit = (uint32_t)atoi(argv[i]);
|
config->rate_limit = (uint32_t)atoi(argv[i]);
|
||||||
has_rate_limit = true;
|
|
||||||
} else if (strcmp(argv[i], "--plugin") == 0) {
|
} else if (strcmp(argv[i], "--plugin") == 0) {
|
||||||
if (++i >= argc) return false;
|
if (++i >= argc) return false;
|
||||||
if (config->num_plugins < COCOON_MAX_PLUGINS) {
|
if (config->num_plugins < COCOON_MAX_PLUGINS) {
|
||||||
config->plugins[config->num_plugins++] = strdup(argv[i]);
|
config->plugins[config->num_plugins++] = strdup(argv[i]);
|
||||||
has_plugins = true;
|
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Error: 最多支持 %d 个插件\n", COCOON_MAX_PLUGINS);
|
fprintf(stderr, "Error: 最多支持 %d 个插件\n", COCOON_MAX_PLUGINS);
|
||||||
return false;
|
return false;
|
||||||
@ -239,23 +209,6 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 如果有配置文件,先加载 */
|
|
||||||
if (config_file) {
|
|
||||||
if (!config_load_from_file(config_file, config)) {
|
|
||||||
fprintf(stderr, "Error: 无法加载配置文件: %s\n", config_file);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/* 用命令行参数覆盖配置文件 */
|
|
||||||
config_merge(config, config, has_root_dir, has_port, has_workers,
|
|
||||||
has_max_conn, has_timeout, has_log_level,
|
|
||||||
has_gzip_enabled, has_brotli_enabled,
|
|
||||||
has_tls_cert, has_tls_key, has_tls_enabled,
|
|
||||||
has_access_log,
|
|
||||||
has_cors_enabled, has_auth_user, has_auth_pass,
|
|
||||||
has_rate_limit,
|
|
||||||
has_plugins);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config->root_dir) {
|
if (!config->root_dir) {
|
||||||
fprintf(stderr, "Error: 必须指定静态资源根目录(-r <dir> 或配置文件)\n");
|
fprintf(stderr, "Error: 必须指定静态资源根目录(-r <dir> 或配置文件)\n");
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
11
proxy.c
11
proxy.c
@ -546,6 +546,8 @@ static bool proxy_relay_backend(cocoon_socket_t client_fd, const http_request_t
|
|||||||
/* 流式转发响应回客户端 */
|
/* 流式转发响应回客户端 */
|
||||||
bool success = false;
|
bool success = false;
|
||||||
ssize_t total = 0;
|
ssize_t total = 0;
|
||||||
|
bool connection_closed = false; /* 后端是否主动关闭连接 */
|
||||||
|
|
||||||
if (send_ok) {
|
if (send_ok) {
|
||||||
char relay_buf[8192];
|
char relay_buf[8192];
|
||||||
bool recv_ok = true;
|
bool recv_ok = true;
|
||||||
@ -562,8 +564,11 @@ static bool proxy_relay_backend(cocoon_socket_t client_fd, const http_request_t
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (r == 0) {
|
if (r == 0) {
|
||||||
/* 后端关闭连接,如果尚未转发任何数据则视为失败 */
|
/* 后端关闭连接,如果尚未转发任何数据则视为失败
|
||||||
|
* 否则标记 connection_closed,请求成功但连接不可复用
|
||||||
|
*/
|
||||||
if (total == 0) recv_ok = false;
|
if (total == 0) recv_ok = false;
|
||||||
|
connection_closed = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -585,8 +590,8 @@ static bool proxy_relay_backend(cocoon_socket_t client_fd, const http_request_t
|
|||||||
|
|
||||||
if (total_forwarded) *total_forwarded = total;
|
if (total_forwarded) *total_forwarded = total;
|
||||||
|
|
||||||
/* 归还或关闭连接:只有后端未关闭时才归还 */
|
/* 归还或关闭连接:只有后端未主动关闭时才归还 */
|
||||||
if (success && total > 0) {
|
if (success && total > 0 && !connection_closed) {
|
||||||
proxy_pool_release(backend, backend_fd, tls_conn);
|
proxy_pool_release(backend, backend_fd, tls_conn);
|
||||||
} else {
|
} else {
|
||||||
if (use_https && tls_conn) {
|
if (use_https && tls_conn) {
|
||||||
|
|||||||
101
tests/http10_backend.py
Normal file
101
tests/http10_backend.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
http10_backend.py — HTTP/1.0 后端模拟器
|
||||||
|
|
||||||
|
模拟一个 HTTP/1.0 后端,响应后发送 Connection: close 并立即关闭连接。
|
||||||
|
用于验证反向代理连接池的 HTTP/1.0 兼容性。
|
||||||
|
|
||||||
|
用法: python3 http10_backend.py <port> <directory>
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import os
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
|
HTTP10_RESPONSE = """HTTP/1.0 200 OK
|
||||||
|
Connection: close
|
||||||
|
Content-Type: {ctype}
|
||||||
|
Content-Length: {length}
|
||||||
|
|
||||||
|
""".replace("\n", "\r\n")
|
||||||
|
|
||||||
|
HTTP10_NOT_FOUND = """HTTP/1.0 404 Not Found
|
||||||
|
Connection: close
|
||||||
|
Content-Type: text/plain
|
||||||
|
Content-Length: 13
|
||||||
|
|
||||||
|
404 Not Found
|
||||||
|
""".replace("\n", "\r\n")
|
||||||
|
|
||||||
|
|
||||||
|
def handle_client(conn, addr, doc_root):
|
||||||
|
try:
|
||||||
|
data = conn.recv(4096)
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 解析请求行
|
||||||
|
lines = data.decode("utf-8", errors="replace").split("\r\n")
|
||||||
|
if not lines:
|
||||||
|
return
|
||||||
|
parts = lines[0].split()
|
||||||
|
if len(parts) < 2:
|
||||||
|
return
|
||||||
|
|
||||||
|
path = parts[1]
|
||||||
|
if path == "/":
|
||||||
|
path = "/index.html"
|
||||||
|
|
||||||
|
# 去掉路径中的前缀(如 /backend)
|
||||||
|
if path.startswith("/backend"):
|
||||||
|
path = path[len("/backend"):]
|
||||||
|
if not path.startswith("/"):
|
||||||
|
path = "/" + path
|
||||||
|
|
||||||
|
# 安全路径
|
||||||
|
safe_path = os.path.normpath(os.path.join(doc_root, path.lstrip("/")))
|
||||||
|
print(f"[http10] doc_root={doc_root}, path={path}, safe_path={safe_path}", flush=True)
|
||||||
|
print(f"[http10] abspath={os.path.abspath(doc_root)}", flush=True)
|
||||||
|
print(f"[http10] exists={os.path.exists(safe_path)}, isfile={os.path.isfile(safe_path) if os.path.exists(safe_path) else False}", flush=True)
|
||||||
|
if not os.path.abspath(safe_path).startswith(os.path.abspath(doc_root)):
|
||||||
|
conn.sendall(HTTP10_NOT_FOUND.encode())
|
||||||
|
return
|
||||||
|
|
||||||
|
if os.path.exists(safe_path) and os.path.isfile(safe_path):
|
||||||
|
with open(safe_path, "rb") as f:
|
||||||
|
body = f.read()
|
||||||
|
ctype, _ = mimetypes.guess_type(safe_path)
|
||||||
|
if not ctype:
|
||||||
|
ctype = "application/octet-stream"
|
||||||
|
header = HTTP10_RESPONSE.format(ctype=ctype, length=len(body))
|
||||||
|
conn.sendall(header.encode() + body)
|
||||||
|
else:
|
||||||
|
conn.sendall(HTTP10_NOT_FOUND.encode())
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[http10] 错误: {e}", file=sys.stderr)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("用法: python3 http10_backend.py <port> <directory>", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
port = int(sys.argv[1])
|
||||||
|
doc_root = sys.argv[2]
|
||||||
|
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
s.bind(("", port))
|
||||||
|
s.listen(5)
|
||||||
|
print(f"[http10] 启动于端口 {port}, 根目录: {doc_root}")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
conn, addr = s.accept()
|
||||||
|
handle_client(conn, addr, doc_root)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -1159,6 +1159,68 @@ kill -9 $BACKEND_B_PID 2>/dev/null || true
|
|||||||
kill_server
|
kill_server
|
||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
|
# === HTTP/1.0 后端兼容测试 ===
|
||||||
|
echo ""
|
||||||
|
echo "=== HTTP/1.0 后端兼容测试 ==="
|
||||||
|
|
||||||
|
# 启动 HTTP/1.0 后端(发送 Connection: close 后主动关闭)
|
||||||
|
python3 "$ROOT/../http10_backend.py" 9004 "$ROOT" > "$TMPDIR/backend_http10.log" 2>&1 &
|
||||||
|
BACKEND_HTTP10_PID=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# 创建带 HTTP/1.0 代理配置的配置文件
|
||||||
|
HTTP10_PROXY_CONFIG="$TMPDIR/http10_proxy_config.json"
|
||||||
|
cat > "$HTTP10_PROXY_CONFIG" << 'EOF'
|
||||||
|
{
|
||||||
|
"root_dir": "./tests/fixtures",
|
||||||
|
"port": 9999,
|
||||||
|
"log_level": "debug",
|
||||||
|
"proxies": [
|
||||||
|
{"prefix": "/backend", "target": "http://localhost:9004", "pool_size": 2}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$SERVER -c "$HTTP10_PROXY_CONFIG" > "$TMPDIR/server_http10_proxy.log" 2>&1 &
|
||||||
|
for i in {1..30}; do
|
||||||
|
if nc -z localhost 9999 2>/dev/null; then break; fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
# 第一次请求:应成功
|
||||||
|
http10_status1=$(curl -s -o /dev/null -w "%{http_code}" "http://$HOST/backend/index.html")
|
||||||
|
if [[ "$http10_status1" == "200" ]]; then
|
||||||
|
echo " ✓ HTTP/1.0 代理第一次请求 — HTTP 200"
|
||||||
|
pass
|
||||||
|
else
|
||||||
|
echo " ✗ HTTP/1.0 代理第一次请求 — 期望 200, 实际 $http10_status1"
|
||||||
|
fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 第二次请求:验证代理能正确新建连接(旧连接已被后端关闭)
|
||||||
|
http10_status2=$(curl -s -o /dev/null -w "%{http_code}" "http://$HOST/backend/index.html")
|
||||||
|
if [[ "$http10_status2" == "200" ]]; then
|
||||||
|
echo " ✓ HTTP/1.0 代理第二次请求 — HTTP 200(连接池正确关闭旧连接)"
|
||||||
|
pass
|
||||||
|
else
|
||||||
|
echo " ✗ HTTP/1.0 代理第二次请求 — 期望 200, 实际 $http10_status2(连接池可能复用了已关闭的连接)"
|
||||||
|
fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查代理日志中有关闭旧连接的日志
|
||||||
|
if grep -q "关闭" "$TMPDIR/server_http10_proxy.log" || grep -q "connection" "$TMPDIR/server_http10_proxy.log" || grep -q "新建" "$TMPDIR/server_http10_proxy.log"; then
|
||||||
|
echo " ✓ HTTP/1.0 代理日志 — 检测到连接管理日志"
|
||||||
|
pass
|
||||||
|
else
|
||||||
|
echo " ⊘ HTTP/1.0 代理日志 — 未检测到连接管理日志(日志级别可能不够)"
|
||||||
|
pass
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理 HTTP/1.0 后端服务器
|
||||||
|
kill -9 $BACKEND_HTTP10_PID 2>/dev/null || true
|
||||||
|
kill_server
|
||||||
|
sleep 1
|
||||||
|
|
||||||
# === HTTPS 反向代理测试 ===
|
# === HTTPS 反向代理测试 ===
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== HTTPS 反向代理测试 ==="
|
echo "=== HTTPS 反向代理测试 ==="
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user