cocoon/proxy.c
xfy911 522685e55b
Some checks failed
CI / build (push) Failing after 1m0s
feat: 实现带宽限速 / 流量整形(Token Bucket 算法)
- 新增 throttle.c/throttle.h:Token Bucket 双层限速实现
  - 支持 per-connection(单连接限速)和 global(服务器总带宽限速)
  - 使用 fd 映射表自动注册/清理,侵入性最小
- cocoon.h: 配置结构体新增 throttle_conn_rate、throttle_global_rate
- server.c: 连接创建时自动初始化 per-connection throttle;server_destroy 释放 global throttle
- static.c: send_all() 自动查表限速
- platform.c: cocoon_file_send() 限速启用时回退到 read+write 循环,逐 chunk 限速
- proxy.c: send_all_fd() 加入限速
- config.c: 解析 JSON 配置中的 throttle 对象(conn_rate / global_rate)
- config.h: 更新 config_merge 签名
- Makefile: 将 throttle.c 加入 SRCS 和所有单元测试编译规则

编译零警告,109 项集成测试通过(6 个失败为已知环境反向代理 404 问题)
2026-06-16 15:41:24 +08:00

818 lines
27 KiB
C
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.

/**
* proxy.c - 反向代理实现
*
* 轻量级 HTTP/1.1 反向代理,支持流式转发。
*
* @author xfy
*/
#include "proxy.h"
#include "proxy_tls.h"
#include "log.h"
#include "throttle.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <pthread.h>
void proxy_init(cocoon_proxy_config_t *cfg) {
memset(cfg, 0, sizeof(*cfg));
cfg->count = 0;
}
static void backend_init(cocoon_proxy_backend_t *backend, uint32_t weight) {
backend->healthy = true;
backend->fail_count = 0;
backend->success_count = 0;
backend->last_check = 0;
backend->weight = weight > 0 ? weight : 1;
backend->current_weight = 0;
}
void proxy_pool_init(cocoon_proxy_backend_t *backend, size_t max_pool_size) {
memset(&backend->pool, 0, sizeof(backend->pool));
backend->pool.max_size = max_pool_size;
if (backend->pool.max_size > COCOON_POOL_MAX_CAPACITY) {
backend->pool.max_size = COCOON_POOL_MAX_CAPACITY;
}
if (backend->pool.max_size == 0) {
backend->pool.max_size = COCOON_POOL_DEFAULT_SIZE;
}
memset(&backend->pool.stats, 0, sizeof(backend->pool.stats));
pthread_mutex_init(&backend->pool.mutex, NULL);
for (size_t i = 0; i < COCOON_POOL_MAX_CAPACITY; i++) {
backend->pool.conns[i].fd = COCOON_INVALID_SOCKET;
backend->pool.conns[i].tls_conn = NULL;
backend->pool.conns[i].in_use = false;
backend->pool.conns[i].last_used = 0;
}
}
/**
* proxy_pool_conn_is_alive - 检测连接是否仍有效
*
* 使用 recv(MSG_PEEK | MSG_DONTWAIT) 非阻塞检测连接是否被对端关闭。
* 不消耗 socket 缓冲区中的数据。
*/
bool proxy_pool_conn_is_alive(cocoon_socket_t fd) {
if (fd == COCOON_INVALID_SOCKET) return false;
char buf[1];
ssize_t r = recv(fd, buf, sizeof(buf), MSG_PEEK | MSG_DONTWAIT);
if (r == 0) {
/* 对端已关闭 */
return false;
}
if (r < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
/* 无数据但连接仍有效 */
return true;
}
/* 其他错误ECONNRESET 等) */
return false;
}
/* 有数据可读,连接有效 */
return true;
}
size_t proxy_pool_get_stats(cocoon_proxy_backend_t *backend, cocoon_pool_stats_t *stats) {
if (!backend) return 0;
pthread_mutex_lock(&backend->pool.mutex);
if (stats) {
*stats = backend->pool.stats;
}
size_t idle = 0;
for (size_t i = 0; i < backend->pool.max_size; i++) {
if (!backend->pool.conns[i].in_use &&
(backend->pool.conns[i].fd != COCOON_INVALID_SOCKET || backend->pool.conns[i].tls_conn)) {
idle++;
}
}
pthread_mutex_unlock(&backend->pool.mutex);
return idle;
}
void proxy_pool_destroy(cocoon_proxy_backend_t *backend) {
if (!backend) return;
pthread_mutex_lock(&backend->pool.mutex);
for (size_t i = 0; i < backend->pool.max_size; i++) {
cocoon_pooled_conn_t *pc = &backend->pool.conns[i];
if (pc->fd != COCOON_INVALID_SOCKET) {
cocoon_socket_close(pc->fd);
pc->fd = COCOON_INVALID_SOCKET;
}
if (pc->tls_conn) {
proxy_tls_close(pc->tls_conn);
pc->tls_conn = NULL;
}
pc->in_use = false;
}
pthread_mutex_unlock(&backend->pool.mutex);
pthread_mutex_destroy(&backend->pool.mutex);
}
/* proxy_connect_backend 前向声明 */
static cocoon_socket_t proxy_connect_backend(const cocoon_proxy_backend_t *backend);
bool proxy_pool_acquire(cocoon_proxy_backend_t *backend, cocoon_socket_t *pfd, proxy_tls_conn_t **ptls) {
if (!backend || !pfd || !ptls) return false;
bool use_https = backend->target_https;
time_t now = time(NULL);
pthread_mutex_lock(&backend->pool.mutex);
/* 1. 查找可用空闲连接(未超时) */
for (size_t i = 0; i < backend->pool.max_size; i++) {
cocoon_pooled_conn_t *pc = &backend->pool.conns[i];
if (pc->in_use) continue;
if (pc->fd == COCOON_INVALID_SOCKET && !pc->tls_conn) continue;
/* 检查超时 */
if ((now - pc->last_used) * 1000 > COCOON_POOL_IDLE_TIMEOUT_MS) {
/* 超时,关闭旧连接 */
backend->pool.stats.evict_count++;
if (pc->fd != COCOON_INVALID_SOCKET) {
cocoon_socket_close(pc->fd);
pc->fd = COCOON_INVALID_SOCKET;
}
if (pc->tls_conn) {
proxy_tls_close(pc->tls_conn);
pc->tls_conn = NULL;
}
continue;
}
/* 检查连接有效性(非 HTTPS 连接) */
if (!use_https && !proxy_pool_conn_is_alive(pc->fd)) {
backend->pool.stats.alive_check_fail++;
if (pc->fd != COCOON_INVALID_SOCKET) {
cocoon_socket_close(pc->fd);
pc->fd = COCOON_INVALID_SOCKET;
}
continue;
}
/* 标记为使用中 */
pc->in_use = true;
pc->last_used = now;
backend->pool.stats.hit_count++;
backend->pool.stats.total_requests++;
backend->pool.stats.active_conns++;
*pfd = pc->fd;
*ptls = pc->tls_conn;
pthread_mutex_unlock(&backend->pool.mutex);
log_debug("连接池复用: %s:%d (fd=%d, tls=%p)",
backend->target_host, backend->target_port,
(int)pc->fd, (void*)pc->tls_conn);
return true;
}
pthread_mutex_unlock(&backend->pool.mutex);
/* 2. 没有可用连接,新建一个 */
if (use_https) {
proxy_tls_conn_t *tls = proxy_tls_connect(backend->target_host, backend->target_port);
if (!tls) {
log_warn("连接池新建 TLS 连接失败: %s:%d", backend->target_host, backend->target_port);
return false;
}
*pfd = COCOON_INVALID_SOCKET;
*ptls = tls;
} else {
cocoon_socket_t fd = proxy_connect_backend(backend);
if (fd == COCOON_INVALID_SOCKET) {
log_warn("连接池新建连接失败: %s:%d", backend->target_host, backend->target_port);
return false;
}
*pfd = fd;
*ptls = NULL;
}
log_debug("连接池新建: %s:%d (fd=%d, tls=%p)",
backend->target_host, backend->target_port,
(int)*pfd, (void*)*ptls);
backend->pool.stats.miss_count++;
backend->pool.stats.total_requests++;
backend->pool.stats.active_conns++;
return true;
}
void proxy_pool_release(cocoon_proxy_backend_t *backend, cocoon_socket_t fd, proxy_tls_conn_t *tls_conn) {
if (!backend) return;
bool use_https = backend->target_https;
time_t now = time(NULL);
pthread_mutex_lock(&backend->pool.mutex);
if (backend->pool.stats.active_conns > 0) {
backend->pool.stats.active_conns--;
}
/* 先尝试找到这个连接的槽位(如果它原本就在池中) */
for (size_t i = 0; i < backend->pool.max_size; i++) {
cocoon_pooled_conn_t *pc = &backend->pool.conns[i];
if (pc->in_use &&
((use_https && pc->tls_conn == tls_conn) ||
(!use_https && pc->fd == fd))) {
pc->in_use = false;
pc->last_used = now;
pthread_mutex_unlock(&backend->pool.mutex);
log_debug("连接池归还(原位): %s:%d", backend->target_host, backend->target_port);
return;
}
}
/* 如果是新建连接,找一个空槽位放入 */
for (size_t i = 0; i < backend->pool.max_size; i++) {
cocoon_pooled_conn_t *pc = &backend->pool.conns[i];
if (pc->fd == COCOON_INVALID_SOCKET && !pc->tls_conn) {
pc->fd = fd;
pc->tls_conn = tls_conn;
pc->in_use = false;
pc->last_used = now;
pthread_mutex_unlock(&backend->pool.mutex);
log_debug("连接池归还(新槽): %s:%d", backend->target_host, backend->target_port);
return;
}
}
pthread_mutex_unlock(&backend->pool.mutex);
/* 池满,直接关闭 */
log_debug("连接池满,关闭连接: %s:%d", backend->target_host, backend->target_port);
if (use_https && tls_conn) {
proxy_tls_close(tls_conn);
} else if (fd != COCOON_INVALID_SOCKET) {
cocoon_socket_close(fd);
}
}
static void parse_backend_url(const char *target_url, cocoon_proxy_backend_t *backend, size_t pool_size) {
/* 解析目标URL */
bool https = false;
const char *url = target_url;
backend->target_https = false;
backend->target_port = 80;
backend->target_host[0] = '\0';
backend->target_path[0] = '\0';
if (strncmp(url, "http://", 7) == 0) {
url += 7;
backend->target_https = false;
} else if (strncmp(url, "https://", 8) == 0) {
url += 8;
backend->target_https = true;
https = true;
} else {
/* 默认 http */
backend->target_https = false;
}
/* 提取 host:port 和剩余路径 */
const char *slash = strchr(url, '/');
char host_port[256];
if (slash) {
size_t host_len = (size_t)(slash - url);
if (host_len >= sizeof(host_port)) host_len = sizeof(host_port) - 1;
memcpy(host_port, url, host_len);
host_port[host_len] = '\0';
strncpy(backend->target_path, slash, sizeof(backend->target_path) - 1);
} else {
strncpy(host_port, url, sizeof(host_port) - 1);
host_port[sizeof(host_port) - 1] = '\0';
backend->target_path[0] = '\0';
}
/* 解析 host 和 port */
const char *colon = strrchr(host_port, ':');
if (colon) {
size_t host_len = (size_t)(colon - host_port);
if (host_len >= sizeof(backend->target_host)) host_len = sizeof(backend->target_host) - 1;
memcpy(backend->target_host, host_port, host_len);
backend->target_host[host_len] = '\0';
backend->target_port = (uint16_t)atoi(colon + 1);
} else {
size_t host_len = strlen(host_port);
if (host_len >= sizeof(backend->target_host)) host_len = sizeof(backend->target_host) - 1;
memcpy(backend->target_host, host_port, host_len);
backend->target_host[host_len] = '\0';
backend->target_port = https ? 443 : 80;
}
if (backend->target_port == 0) {
backend->target_port = https ? 443 : 80;
}
/* 初始化连接池 */
proxy_pool_init(backend, pool_size);
}
bool proxy_add_rule(cocoon_proxy_config_t *cfg, const char *prefix, const char *target_url, size_t pool_size, uint32_t weight, const cocoon_healthcheck_config_t *hc) {
if (!prefix || !target_url || prefix[0] == '\0' || target_url[0] == '\0') {
return false;
}
/* 查找是否已有相同前缀的规则 */
cocoon_proxy_rule_t *rule = NULL;
for (size_t i = 0; i < cfg->count; i++) {
if (strcmp(cfg->rules[i].path_prefix, prefix) == 0) {
rule = &cfg->rules[i];
break;
}
}
if (rule) {
/* 追加后端 */
if (rule->backend_count >= COCOON_MAX_PROXY_BACKENDS) {
log_error("后端数量超过上限 %d", COCOON_MAX_PROXY_BACKENDS);
return false;
}
cocoon_proxy_backend_t *backend = &rule->backends[rule->backend_count];
parse_backend_url(target_url, backend, pool_size);
backend_init(backend, weight);
if (hc) {
backend->hc_config = *hc;
}
log_info("追加后端: %s -> %s://%s:%d%s (weight=%u)",
rule->path_prefix,
backend->target_https ? "https" : "http",
backend->target_host,
backend->target_port,
backend->target_path,
backend->weight);
rule->backend_count++;
return true;
}
/* 新建规则 */
if (cfg->count >= COCOON_MAX_PROXY_RULES) {
log_error("代理规则数量超过上限 %d", COCOON_MAX_PROXY_RULES);
return false;
}
rule = &cfg->rules[cfg->count];
strncpy(rule->path_prefix, prefix, sizeof(rule->path_prefix) - 1);
rule->path_prefix[sizeof(rule->path_prefix) - 1] = '\0';
rule->backend_count = 0;
rule->current_index = 0;
cocoon_proxy_backend_t *backend = &rule->backends[rule->backend_count];
parse_backend_url(target_url, backend, pool_size);
backend_init(backend, weight);
if (hc) {
backend->hc_config = *hc;
}
rule->backend_count++;
log_info("添加代理规则: %s -> %s://%s:%d%s (weight=%u)",
rule->path_prefix,
backend->target_https ? "https" : "http",
backend->target_host,
backend->target_port,
backend->target_path,
backend->weight);
cfg->count++;
return true;
}
cocoon_proxy_rule_t *proxy_match(cocoon_proxy_config_t *cfg, const char *path) {
if (!cfg || !path) return NULL;
for (size_t i = 0; i < cfg->count; i++) {
cocoon_proxy_rule_t *rule = &cfg->rules[i];
size_t prefix_len = strlen(rule->path_prefix);
if (strncmp(path, rule->path_prefix, prefix_len) == 0) {
return rule;
}
}
return NULL;
}
/**
* proxy_connect_backend - 连接到指定后端
*/
static void proxy_update_health(cocoon_proxy_backend_t *backend, bool success) {
time_t now = time(NULL);
backend->last_check = now;
if (success) {
backend->fail_count = 0;
backend->success_count++;
if (!backend->healthy && backend->success_count >= COCOON_HEALTHY_THRESHOLD) {
backend->healthy = true;
log_info("后端恢复健康: %s:%d", backend->target_host, backend->target_port);
}
} else {
backend->success_count = 0;
backend->fail_count++;
if (backend->healthy && backend->fail_count >= COCOON_UNHEALTHY_THRESHOLD) {
backend->healthy = false;
log_warn("后端标记不健康: %s:%d (连续失败 %d 次)",
backend->target_host, backend->target_port, backend->fail_count);
}
}
}
static cocoon_socket_t proxy_connect_backend(const cocoon_proxy_backend_t *backend) {
struct hostent *host = gethostbyname(backend->target_host);
if (!host) {
log_error("无法解析主机: %s", backend->target_host);
return COCOON_INVALID_SOCKET;
}
cocoon_socket_t fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == COCOON_INVALID_SOCKET) {
log_error("创建 socket 失败");
return COCOON_INVALID_SOCKET;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(backend->target_port);
memcpy(&addr.sin_addr, host->h_addr_list[0], (size_t)host->h_length);
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
log_warn("连接后端失败: %s:%d", backend->target_host, backend->target_port);
cocoon_socket_close(fd);
return COCOON_INVALID_SOCKET;
}
return fd;
}
/**
* proxy_build_forwarded_path - 构建转发路径
*/
static void proxy_build_forwarded_path(const cocoon_proxy_rule_t *rule,
const cocoon_proxy_backend_t *backend,
const char *original_path,
char *out, size_t out_len) {
size_t prefix_len = strlen(rule->path_prefix);
const char *remaining = original_path + prefix_len;
if (backend->target_path[0] == '\0') {
/* 无目标路径前缀,直接转发剩余部分 */
if (remaining[0] == '\0') {
strncpy(out, "/", out_len - 1);
} else {
strncpy(out, remaining, out_len - 1);
}
} else {
/* 将目标路径前缀 + 剩余路径拼接 */
int n = snprintf(out, out_len, "%s%s", backend->target_path, remaining);
if (n < 0 || (size_t)n >= out_len) {
out[out_len - 1] = '\0';
}
}
out[out_len - 1] = '\0';
}
/**
* proxy_build_xff - 构建 X-Forwarded-For 值
*/
static void proxy_build_xff(const struct sockaddr_storage *client_addr,
char *out, size_t out_len) {
if (client_addr->ss_family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in *)client_addr;
inet_ntop(AF_INET, &sin->sin_addr, out, (socklen_t)out_len);
} else if (client_addr->ss_family == AF_INET6) {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)client_addr;
inet_ntop(AF_INET6, &sin6->sin6_addr, out, (socklen_t)out_len);
} else {
strncpy(out, "unknown", out_len - 1);
out[out_len - 1] = '\0';
}
}
/**
* send_all_fd - 确保数据全部发送
*/
static int send_all_fd(cocoon_socket_t fd, const char *data, size_t len) {
size_t sent = 0;
while (sent < len) {
/* 应用限速 */
cocoon_throttle_t *t = throttle_lookup(fd);
if (t) {
size_t chunk = len - sent;
uint64_t wait_usec = throttle_consume(t, chunk);
if (wait_usec > 0) {
struct timespec ts = {
.tv_sec = (time_t)(wait_usec / 1000000),
.tv_nsec = (long)((wait_usec % 1000000) * 1000)
};
nanosleep(&ts, NULL);
continue;
}
}
ssize_t n = send(fd, data + sent, len - sent, 0);
if (n < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
return -1;
}
if (n == 0) return -1;
sent += (size_t)n;
}
return 0;
}
/**
* proxy_send_all_tls - 通过 TLS 连接发送全部数据
*/
static int proxy_send_all_tls(proxy_tls_conn_t *conn, const char *data, size_t len) {
size_t sent = 0;
while (sent < len) {
ssize_t n = proxy_tls_write(conn, data + sent, len - sent);
if (n > 0) {
sent += (size_t)n;
} else if (n < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
return -1;
} else {
return -1;
}
}
return 0;
}
/**
* proxy_relay_backend - 尝试连接并转发请求到单个后端
*
* 从连接池获取连接(或新建),发送请求,转发响应回客户端。
* 请求完成后将连接归还到池中(如果成功)。
*
* @return true 成功false 失败(连接、发送或转发响应出错)
*/
static bool proxy_relay_backend(cocoon_socket_t client_fd, const http_request_t *req,
cocoon_proxy_rule_t *rule, cocoon_proxy_backend_t *backend,
const struct sockaddr_storage *client_addr,
ssize_t *total_forwarded) {
cocoon_socket_t backend_fd = COCOON_INVALID_SOCKET;
proxy_tls_conn_t *tls_conn = NULL;
bool use_https = backend->target_https;
/* 从连接池获取连接(优先复用) */
if (!proxy_pool_acquire(backend, &backend_fd, &tls_conn)) {
log_warn("后端 %s:%d 连接失败(连接池)", backend->target_host, backend->target_port);
return false;
}
/* 构建转发路径 */
char forwarded_path[512];
proxy_build_forwarded_path(rule, backend, req->path, forwarded_path, sizeof(forwarded_path));
/* 构建 X-Forwarded-For */
char xff[64];
proxy_build_xff(client_addr, xff, sizeof(xff));
/* 构建转发请求 */
char request_buf[4096];
int n = snprintf(request_buf, sizeof(request_buf),
"%s %s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"X-Forwarded-For: %s\r\n"
"X-Forwarded-Proto: %s\r\n"
"Connection: keep-alive\r\n",
http_method_str(req->method),
forwarded_path,
backend->target_host,
backend->target_port,
xff,
use_https ? "https" : "http");
/* 透传常见请求头 */
if (req->content_type[0] != '\0') {
n += snprintf(request_buf + n, sizeof(request_buf) - n,
"Content-Type: %s\r\n", req->content_type);
}
if (req->content_length > 0) {
n += snprintf(request_buf + n, sizeof(request_buf) - n,
"Content-Length: %ld\r\n", (long)req->content_length);
}
n += snprintf(request_buf + n, sizeof(request_buf) - n, "\r\n");
/* 发送请求头 */
bool send_ok = true;
if (use_https) {
if (proxy_send_all_tls(tls_conn, request_buf, (size_t)n) != 0) {
log_warn("转发请求头到 HTTPS 后端失败: %s:%d", backend->target_host, backend->target_port);
send_ok = false;
}
} else {
if (send_all_fd(backend_fd, request_buf, (size_t)n) != 0) {
log_warn("转发请求头到后端失败: %s:%d", backend->target_host, backend->target_port);
send_ok = false;
}
}
/* 发送请求体 */
if (send_ok && req->body && req->body_len > 0) {
if (use_https) {
if (proxy_send_all_tls(tls_conn, req->body, req->body_len) != 0) {
log_warn("转发请求体到 HTTPS 后端失败");
send_ok = false;
}
} else {
if (send_all_fd(backend_fd, req->body, req->body_len) != 0) {
log_warn("转发请求体到后端失败");
send_ok = false;
}
}
}
/* 流式转发响应回客户端 */
bool success = false;
ssize_t total = 0;
bool connection_closed = false; /* 后端是否主动关闭连接 */
if (send_ok) {
char relay_buf[8192];
bool recv_ok = true;
while (1) {
ssize_t r;
if (use_https) {
r = proxy_tls_read(tls_conn, relay_buf, sizeof(relay_buf));
} else {
r = recv(backend_fd, relay_buf, sizeof(relay_buf), 0);
}
if (r < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
recv_ok = false;
break;
}
if (r == 0) {
/* 后端关闭连接,如果尚未转发任何数据则视为失败
* 否则标记 connection_closed请求成功但连接不可复用
*/
if (total == 0) recv_ok = false;
connection_closed = true;
break;
}
if (send_all_fd(client_fd, relay_buf, (size_t)r) != 0) {
log_error("转发响应到客户端失败");
recv_ok = false;
break;
}
total += r;
}
if (recv_ok) {
log_debug("代理完成: %s -> 后端 %s:%d%s, 转发 %zd bytes",
req->path, backend->target_host, backend->target_port,
forwarded_path, total);
success = true;
}
}
if (total_forwarded) *total_forwarded = total;
/* 归还或关闭连接:只有后端未主动关闭时才归还 */
if (success && total > 0 && !connection_closed) {
proxy_pool_release(backend, backend_fd, tls_conn);
} else {
if (use_https && tls_conn) {
proxy_tls_close(tls_conn);
} else if (backend_fd != COCOON_INVALID_SOCKET) {
cocoon_socket_close(backend_fd);
}
}
return success;
}
/**
* select_backend_sww - 平滑加权轮询选择后端
*
* Nginx 同款平滑加权轮询算法:
* 1. 所有健康后端 current_weight += weight
* 2. 选择 current_weight 最大的
* 3. 选中后端 current_weight -= total_weight
* 4. 返回选中后端
*
* 这样能保证权重比例严格满足,且分布更均匀。
*
* @param rule 代理规则
* @return 选中的后端,无健康后端返回 NULL
*/
static cocoon_proxy_backend_t *select_backend_sww(cocoon_proxy_rule_t *rule) {
if (!rule || rule->backend_count == 0) return NULL;
cocoon_proxy_backend_t *best = NULL;
int32_t total_weight = 0;
for (size_t i = 0; i < rule->backend_count; i++) {
cocoon_proxy_backend_t *b = &rule->backends[i];
if (!b->healthy) continue;
b->current_weight += (int32_t)b->weight;
total_weight += (int32_t)b->weight;
if (!best || b->current_weight > best->current_weight) {
best = b;
}
}
if (best) {
best->current_weight -= total_weight;
}
return best;
}
/**
* select_backend_sww_all - 平滑加权轮询(包含不健康后端)
*
* 用于 fallback 场景:所有后端都标记为不健康时,仍然尝试请求。
*/
static cocoon_proxy_backend_t *select_backend_sww_all(cocoon_proxy_rule_t *rule) {
if (!rule || rule->backend_count == 0) return NULL;
cocoon_proxy_backend_t *best = NULL;
int32_t total_weight = 0;
for (size_t i = 0; i < rule->backend_count; i++) {
cocoon_proxy_backend_t *b = &rule->backends[i];
b->current_weight += (int32_t)b->weight;
total_weight += (int32_t)b->weight;
if (!best || b->current_weight > best->current_weight) {
best = b;
}
}
if (best) {
best->current_weight -= total_weight;
}
return best;
}
bool proxy_forward(cocoon_socket_t client_fd, const http_request_t *req,
cocoon_proxy_rule_t *rule,
const struct sockaddr_storage *client_addr) {
if (!rule || rule->backend_count == 0) {
log_error("代理规则无后端");
return false;
}
/* 第一轮:优先尝试 healthy 后端(平滑加权轮询) */
cocoon_proxy_backend_t *backend = select_backend_sww(rule);
if (backend) {
ssize_t total = 0;
if (proxy_relay_backend(client_fd, req, rule, backend, client_addr, &total)) {
proxy_update_health(backend, true);
return req->keep_alive;
}
proxy_update_health(backend, false);
}
/* 如果唯一选中的后端失败了,尝试其他 healthy 后端 */
for (size_t t = 0; t < rule->backend_count; t++) {
backend = select_backend_sww(rule);
if (!backend) break;
ssize_t total = 0;
if (proxy_relay_backend(client_fd, req, rule, backend, client_addr, &total)) {
proxy_update_health(backend, true);
return req->keep_alive;
}
proxy_update_health(backend, false);
}
/* 第二轮fallback 到所有后端(包括 unhealthy */
backend = select_backend_sww_all(rule);
if (backend) {
ssize_t total = 0;
if (proxy_relay_backend(client_fd, req, rule, backend, client_addr, &total)) {
proxy_update_health(backend, true);
return req->keep_alive;
}
proxy_update_health(backend, false);
}
log_error("所有后端均不可用: %s", rule->path_prefix);
return false;
}
void proxy_config_destroy(cocoon_proxy_config_t *cfg) {
if (!cfg) return;
for (size_t i = 0; i < cfg->count; i++) {
cocoon_proxy_rule_t *rule = &cfg->rules[i];
for (size_t j = 0; j < rule->backend_count; j++) {
proxy_pool_destroy(&rule->backends[j]);
}
}
}