/* test_http.c - HTTP 层纯逻辑测试(不联网) */ #include "http.h" #include "util.h" #include #include #include static int failures = 0; static int checks = 0; #define CHECK(cond, ...) \ do { \ checks++; \ if (!(cond)) { \ failures++; \ printf(" ✗ %s:%d: ", __FILE__, __LINE__); \ printf(__VA_ARGS__); \ printf("\n"); \ } \ } while (0) int main(void) { puts("重试判定:只重试服务端明确报错的情况"); /* 应当重试 */ long retryable[] = { 408, 409, 429, 500, 502, 503, 504 }; for (size_t i = 0; i < sizeof retryable / sizeof retryable[0]; i++) CHECK(http_status_retryable(retryable[i]) == 1, "HTTP %ld 应当可重试", retryable[i]); /* 绝不应当重试:这些是确定性错误,重试只是浪费时间和钱 */ long permanent[] = { 400, 401, 403, 404, 405, 413, 415, 422, 501, 200, 201, 204, 301, 0, -1 }; for (size_t i = 0; i < sizeof permanent / sizeof permanent[0]; i++) CHECK(http_status_retryable(permanent[i]) == 0, "HTTP %ld 不应重试", permanent[i]); /* 默认策略与上限的合理性 */ HttpRetry def = HTTP_RETRY_DEFAULT; HttpRetry none = HTTP_RETRY_NONE; CHECK(def.max_retries > 0, "默认应当会重试"); CHECK(none.max_retries == 0, "HTTP_RETRY_NONE 应当不重试"); CHECK(HTTP_MAX_BODY >= 1024 * 1024, "响应体上限不该小到影响正常使用"); CHECK(HTTP_MAX_ERRBODY < HTTP_MAX_BODY, "错误体上限应远小于正文上限"); /* ---- 调试输出的密钥脱敏 ---- */ puts("--verbose 输出脱敏"); { const char *key = "sk-0f114e3dac8249f0a36fd8fd5c517360"; char *buf = NULL; size_t sz = 0; FILE *f = open_memstream(&buf, &sz); /* 这条正是曾经漏出去的:libcurl 的 HTTP/2 调试行走的是 CURLINFO_TEXT, * 不是 HEADER_OUT,所以只处理 HEADER_OUT 的做法根本挡不住 */ const char *h2 = "== Info: [HTTP/2] [1] [authorization: Bearer sk-0f114e3dac8249f0a36fd8fd5c517360]\n"; http_write_redacted(f, h2, strlen(h2), key); /* 整块请求头(HEADER_OUT 是整块传来的,不是逐行) */ const char *hdrs = "POST /v1/chat/completions HTTP/2\r\n" "Authorization: Bearer sk-0f114e3dac8249f0a36fd8fd5c517360\r\n" "Content-Type: application/json\r\n"; http_write_redacted(f, hdrs, strlen(hdrs), key); /* 其它内容不能被改动 */ const char *plain = "== Info: TLSv1.3 (IN), TLS handshake, Finished (20):\n"; http_write_redacted(f, plain, strlen(plain), key); /* 没有密钥时原样输出 */ http_write_redacted(f, "no key here\n", 12, ""); fclose(f); CHECK(strstr(buf, key) == NULL, "输出里不应残留完整密钥"); CHECK(strstr(buf, "<已隐藏>") != NULL, "应替换为占位符"); CHECK(strstr(buf, "[HTTP/2]") != NULL, "HTTP/2 调试行本身应保留"); CHECK(strstr(buf, "Authorization: Bearer <已隐藏>") != NULL, "请求头里的密钥应脱敏: %.200s", buf); CHECK(strstr(buf, "TLS handshake") != NULL, "其它调试信息不能被误改"); CHECK(strstr(buf, "no key here") != NULL, "无密钥时原样输出"); /* 一次替换必须整段吃掉,不能留下半个密钥 */ CHECK(strstr(buf, "0f114e3dac") == NULL, "不应留下密钥片段"); free(buf); } puts("\n空请求体:POST 分支也要扛得住 body == NULL"); { /* body 允许为 NULL(http_probe 就是这么调的),但 POST 分支以前写的是 * 无条件的 strlen(body) —— 传 NULL 会在 curl_easy_setopt 期间就段错误。 * * 这里故意用一个格式非法的 URL:libcurl 会在真正联网之前就拒绝它, * 所以本测试仍然不碰网络。而 strlen(NULL) 发生在设置 POSTFIELDS 期间、 * curl_easy_perform 之前,也就是说旧代码在这里必崩 —— 这个断言对旧 * 代码是有效的回归守卫,不是走过场。 */ static const HttpRetry no_retry = HTTP_RETRY_NONE; char errbuf[256] = ""; int attempts = 0; long status = 0; http_global_init(); int rc = http_post(":::", "sk-test", NULL, 5, 0, &no_retry, NULL, &status, &attempts, errbuf, sizeof errbuf); http_global_cleanup(); CHECK(rc == -1, "非法 URL 应当返回 -1(实际返回 %d)", rc); CHECK(errbuf[0] != '\0', "失败时必须给出错误说明"); } printf("\n%d 项检查,%d 项失败\n", checks, failures); return failures ? 1 : 0; }