curl--time-cond条件请求指南:基于时间戳的增量下载与缓存验证
【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl
导读:--time-cond(短选项-z)让 curl 在 HTTP 与 FTP 场景下按文件的最后修改时间发起条件请求,实现"只下载比指定时间更新(或更旧)的文件"这一经典增量同步能力。本文以 time-cond.md 为骨架,结合 tool_getparam.c 的参数解析实现、transfer.c 的条件判定逻辑以及 libcurl 的配套选项文档,完整讲解时间表达式语法、+/-/=前缀语义、文件 mtime 回退机制,并延伸介绍 libcurl API 层面的CURLOPT_TIMECONDITION/CURLOPT_TIMEVALUE与--etag-compare、--remote-time的配合使用。读完你将能写出可靠的缓存刷新、增量备份与"未修改不下载"脚本。
一、选项概览与基本用法
在 curl 命令行中,--time-cond的完整定义位于 docs/cmdline-opts/time-cond.md:
| 属性 | 值 |
|---|---|
| 长选项 | --time-cond |
| 短选项 | -z |
| 参数 | <time>(时间字符串或文件名) |
| 适用协议 | HTTP、FTP |
| 类别 | http、ftp |
| 加入版本 | 5.8 |
| Multi | single(单 URL 场景) |
该选项的核心语义:请求一个比给定时间更晚修改的文件,或者比给定时间更早修改的文件。默认情况下请求"比指定时间更新"的文件;在时间表达式前加一个-前缀,则反转为请求"比指定时间更旧"的文件。
文档自带的三个示例(见 time-cond.md):
# 请求在 2021-09-01 12:18:00 之后被修改过的文件(默认 If-Modified-Since 语义) curl -z "Wed 01 Sep 2021 12:18:00" $URL # 请求在 2021-09-01 12:18:00 之前被修改过的文件(加 - 前缀) curl -z "-Wed 01 Sep 2021 12:18:00" $URL # 以 file 作为时间来源:读取该文件的 mtime 作为比较基准 curl -z file $URL二、时间表达式:字符串解析与文件 mtime 回退
--time-cond的参数<time>支持两种形态:
- 日期字符串:可以是各种常见的日期格式,最终由
curl_getdate(3)解析为 Unix 时间戳(自 1970-01-01 00:00:00 UTC 起的秒数)。完整语法参见 docs/libcurl/curl_getdate.md。 - 文件名:如果参数不匹配任何内置日期格式,则被当作文件名处理,curl 会尝试读取该文件的修改时间(mtime)作为比较基准——这是脚本中最常用的"以上次下载的文件时间为准"模式。
从命令行解析的实现来看,src/tool_getparam.c中的parse_time_cond()(tool_getparam.c)不仅处理了默认语义,还支持三种显式前缀:
static ParameterError parse_time_cond(struct OperationConfig *config, const char *nextarg) { switch(*nextarg) { case '+': nextarg++; FALLTHROUGH(); default: /* If-Modified-Since: (section 14.28 in RFC2068) */ config->timecond = CURL_TIMECOND_IFMODSINCE; break; case '-': /* If-Unmodified-Since: (section 14.24 in RFC2068) */ config->timecond = CURL_TIMECOND_IFUNMODSINCE; nextarg++; break; case '=': /* Last-Modified: (section 14.29 in RFC2068) */ config->timecond = CURL_TIMECOND_LASTMOD; nextarg++; break; } config->condtime = (curl_off_t)curl_getdate(nextarg, NULL); ... }由此可以得到完整的语义映射:
| 前缀 | 含义 | 底层枚举 | 对应 HTTP 语义 |
|---|---|---|---|
| (无,默认) | 请求比指定时间更新的文件 | CURL_TIMECOND_IFMODSINCE | If-Modified-Since(RFC 2068 §14.28) |
+ | 同上,显式写出的"更新于" | CURL_TIMECOND_IFMODSINCE | If-Modified-Since |
- | 请求比指定时间更旧的文件 | CURL_TIMECOND_IFUNMODSINCE | If-Unmodified-Since(RFC 2068 §14.24) |
= | 请求文件的修改时间恰好等于给定时间 | CURL_TIMECOND_LASTMOD | Last-Modified(RFC 2068 §14.29,用于校验响应头) |
2.1 curl_getdate 支持的日期格式
curl_getdate的解析规则(docs/libcurl/curl_getdate.md)相当宽松,日期字符串由空格分隔的若干"条目"组成,条目顺序无关紧要,支持:
- 日历日期:月份名只接受三个字母的英文缩写;数字可带前导零;年份可用 2 位或 4 位。例如
06 Nov 1994、06-Nov-94、Nov-94 6。两位年份的推断规则:大于 70 视为1900 + 年,其余视为2000 + 年。 - 当日时间:必须用 6 位数字加两个冒号,即
HH:MM:SS;省略时默认00:00:00。例如18:19:21。 - 时区:支持少量缩写(如
MST),更通用的是相对 UTC 的偏移量,如-1200、+0100。 - 星期几:可写全称(
Sunday、Monday...)或前三个字母缩写,通常不影响解析结果。 - 纯数字:形如
YYYYMMDD的十进制数会被解读为年/月/日,例如20040912表示 2004-09-12。
curl_getdate遵循的标准包括 RFC 822(及其更新 RFC 1123)、RFC 850(已被 RFC 1036 取代)以及 ANSI C 的asctime()格式——这正是 RFC 7231 允许 HTTP 应用使用的日期格式集合。
2.2 解析失败与边界
- 解析失败时
curl_getdate返回-1。具体边界(docs/libcurl/curl_getdate.md):- 有符号 32 位
time_t:年份大于 2037 或小于 1903 返回-1; - 无符号 32 位
time_t:年份大于 2106 或小于 1970 返回-1; - 64 位
time_t:年份小于 1583 返回-1(格里高利历 1582 年才引入,此前无"真实"日期)。
- 有符号 32 位
- 该函数"对合法日期正常解析,但并不总能检测并拒绝错误日期,例如 2 月 30 日"(文档原文声明)。
- 时间来源为不存在文件时:curl 会输出一条警告,然后不附带任何时间条件继续传输(即退化为普通下载)。这在 time-cond.md 中有明确说明,也是脚本化时需要注意的静默降级行为。
三、底层原理:条件如何在请求与响应中生效
3.1 条件判定函数
libcurl 内部通过 transfer.c 中的Curl_meets_timecondition()判断远程文件时间是否满足条件:
bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc) { if((timeofdoc == 0) || (data->set.timevalue == 0)) return TRUE; switch(data->set.timecondition) { case CURL_TIMECOND_IFMODSINCE: default: if(timeofdoc <=>CURLcode curl_easy_setopt(CURL *handle, CURLOPT_TIMECONDITION, long cond);枚举值定义在 include/curl/curl.h:
#define CURL_TIMECOND_IFMODSINCE 1L #define CURL_TIMECOND_IFUNMODSINCE 2L #define CURL_TIMECOND_LASTMOD 3L默认值为CURL_TIMECOND_NONE (0),即不启用时间条件。注意:CURL_TIMECOND_*枚举在 8.13.0 起成为long类型,此前版本传入curl_easy_setopt时需要显式long转换(见 CURLOPT_TIMECONDITION.md 的 HISTORY 一节)。
4.2 CURLOPT_TIMEVALUE:提供基准时间
接口原型(docs/libcurl/opts/CURLOPT_TIMEVALUE.md):
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_TIMEVALUE, long val);val是自 1970-01-01 起的秒数(Unix epoch)。文档特别提醒:在long为 32 位的系统(如 Windows)上,该选项无法表示 2038 年之后的日期,此时应改用CURLOPT_TIMEVALUE_LARGE(见 docs/libcurl/opts/CURLOPT_TIMEVALUE_LARGE.md)。
官方示例(两个文档共用):
int main(void) { CURL *curl = curl_easy_init(); if(curl) { CURLcode result; curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); /* January 1, 2020 is 1577833200 */ curl_easy_setopt(curl, CURLOPT_TIMEVALUE, 1577833200L); /* If-Modified-Since the above time stamp */ curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); /* Perform the request */ result = curl_easy_perform(curl); curl_easy_cleanup(curl); } }这个 C 代码片段与命令行curl -z "Wed 01 Sep 2021 12:18:00" $URL完全等价:TIMEVALUE对应<time>解析后的时间戳,TIMECONDITION对应默认的 IFMODSINCE 语义。命令行工具正是在 tool_getparam.c 中通过curl_getdate()把日期字符串换算成config->condtime,再在后续 setopt 阶段写入这两个 option。
五、实战场景与配套选项
5.1 增量下载:以上次文件时间为基准
利用"参数是文件名时取其 mtime"的机制,可以轻松实现只下载新内容:
# 首次下载(file 不存在时会警告并降级为无条件下载) curl -z last.txt -o data.txt https://example.com/data.txt # 手动把本次下载的时间戳记录下来,供下次比较 curl -z last.txt -o data.txt https://example.com/data.txt \ && touch -r data.txt last.txt # 若文件未修改,服务器返回 304,curl 输出 0 字节成功结果结合--remote-time(remote-time.md)可以更进一步:该选项让 curl 解析远程文件的修改时间并把它赋给本地输出文件,从而让本地文件时间始终反映远端状态:
# 下载后把本地文件时间设为远程文件的修改时间 curl -z last.txt --remote-time -o data.txt https://example.com/data.txt5.2 ETag 方案:与 --etag-compare 的取舍
基于时间戳的条件请求存在天然局限——时间粒度只能到秒,且远程文件 mtime 不一定可靠。HTTP 生态更精确的做法是 ETag(实体标签)。curl 提供了 --etag-compare 与配套的--etag-save:前者从文件中读取 ETag,并通过自定义If-None-Match头发起条件请求;后者把响应的 ETag 保存到文件。
# 首次请求:保存 ETag curl --etag-save etag.txt -o data.txt https://example.com/data.txt # 后续请求:携带 If-None-Match,内容未变则返回 304 curl --etag-compare etag.txt -o data.txt https://example.com/data.txt两者的差异值得注意:
--time-cond基于Last-Modified/If-Modified-Since时间比较,适用 HTTP 与 FTP;--etag-compare基于ETag/If-None-Match强校验,仅适用 HTTP,且要求 etag 文件只有单行内容,--etag-compare只能配合单个 URL 使用;- 时间条件在远程 mtime 未知时静默失效,而 ETag 方案对内容变化的检测更可靠(能捕获"内容变了但时间没变"的情况)。
两者可以互为补充:时间方案简单、跨协议,ETag 方案精确、仅限 HTTP。
5.3 测试用例佐证
仓库的测试数据可以印证该功能的实际行为:tests/data/test1511以及tests/libtest/lib1511.c对应条件请求的 libtest 用例,tests/data/test1593~test1596与tests/libtest/lib1593.c覆盖时间条件相关场景。读者可以结合 tests/data 目录下的测试定义,观察 304 响应、0 字节传输与CURLINFO_CONDITION_UNMET的配合关系。
六、小结与注意事项
--time-cond是 curl 中最实用的缓存/增量同步工具之一,使用时注意以下几点:
- 默认语义:不带前缀请求"更新于"指定时间的文件(
If-Modified-Since);-前缀反转语义;=前缀对应Last-Modified。 - 参数两种形态:合法日期字符串走
curl_getdate解析;否则按文件名取其 mtime。 - 静默降级:参数文件不存在时仅输出警告并转为无条件传输;远程文件 mtime 未知时条件不生效。
- 零字节成功:条件不满足时得到的是 0 字节成功传输,脚本中可用
CURLINFO_CONDITION_UNMET(libcurl API)区分"未修改"与"空内容"。 - 时间戳范围:命令行内部使用
curl_off_t保存时间;API 层面CURLOPT_TIMEVALUE在 32 位long平台有 2038 年限制,需用CURLOPT_TIMEVALUE_LARGE规避。 - Y2038 与解析边界:
curl_getdate对 32 位time_t系统存在 2037/2106 年边界,超出范围返回 -1。
相关文档索引:time-cond.md、curl_getdate.md、CURLOPT_TIMECONDITION.md、CURLOPT_TIMEVALUE.md、CURLOPT_TIMEVALUE_LARGE.md、CURLINFO_CONDITION_UNMET.md、etag-compare.md、remote-time.md。
【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考