113 lines
5.2 KiB
C
113 lines
5.2 KiB
C
/* test_http.c - HTTP 层纯逻辑测试 (不联网) */
|
|
#include "http.h"
|
|
#include "util.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
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;
|
|
}
|