libcurl URL API 完全指南:从 curl_url 句柄创建到 URL 部件级解析、生成与重定向
发布时间:2026/9/10 23:03:23来源:尧图网络
libcurl URL API 完全指南从 curl_url 句柄创建到 URL 部件级解析、生成与重定向【免费下载链接】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导读本文围绕 curl 开源仓库中 libcurl URL APIURL interface的入口函数 curl_url 展开系统讲解以CURLU句柄为核心的 URL 解析与生成体系句柄的创建、复制与销毁完整 URL 的解析与相对 URL 重定向九个 URL 部件的按需读写十六个CURLU*标志位的语义以及如何通过CURLOPT_CURLU把句柄接入 easy 接口执行真实传输。读完本文你可以使用这套 API 在 C 程序中完成解析任意 URL、逐部件修改、重组输出、追加查询参数、错误码诊断的完整闭环并结合 lib/urlapi.c 的源码理解其底层行为。一、URL API 概览CURLU 句柄模型libcurl 的 URL 接口见总览文档 libcurl-url提供了一组用于解析parsing和生成generatingURL的函数。其核心抽象是CURLU句柄一个在内部持有单个 URL 各组成部件的对象。它从 curl 7.62.0 起加入且对所有协议Protocol: All可用。整个 API 由以下六个函数构成全部声明在 include/curl/urlapi.h 中函数职责引入版本curl_url创建一个新的 URL 句柄7.62.0curl_url_cleanup释放句柄及其全部资源7.62.0curl_url_dup复制一份句柄7.62.0curl_url_set解析完整 URL 或设置单个部件7.62.0curl_url_get提取完整 URL 或单个部件7.62.0curl_url_strerror将CURLUcode错误码转为可读字符串7.80.0使用 URL API 时你仍然只需要包含一个头文件#include curl/curl.hCURLU、CURLUPart、CURLUcode及全部标志位都会随之可见。二、句柄生命周期创建、复制与销毁2.1 创建句柄curl_url()curl_url()是 URL API 的入口函数签名如下原文档 SYNOPSIS#include curl/curl.h CURLU *curl_url();调用后它会分配一个 URL 对象并返回对应的CURLU *句柄供所有其他 URL API 函数使用。新创建的句柄中不存储任何部件no components stored所有内容都需要随后通过curl_url_set()写入。关于返回值文档明确给出两点保证成功时返回CURLU *内存不足out of memory时返回 NULL这是该函数唯一的失败途径调用方应做空指针检查。2.2 销毁句柄curl_url_cleanup()句柄不再需要时必须调用curl_url_cleanup()销毁void curl_url_cleanup(CURLU *handle);销毁语义见 curl_url_cleanup释放该句柄关联的全部资源传入 NULL 指针会立即返回、不做任何操作可安全地用于清理路径调用并返回之后任何对该句柄的继续使用都是非法行为use-after-free。2.3 复制句柄curl_url_dup()当你需要一份句柄的独立副本时使用curl_url_dup()CURLU *curl_url_dup(const CURLU *inhandle);它复制输入句柄标识的 URL 对象返回指向新副本的CURLU *指针。新句柄同样需要用curl_url_cleanup()释放内存不足时返回 NULL。典型的克隆场景见 curl_url_dup 的示例先curl_url_dup(url)得到url2用完分别curl_url_cleanup(url2)与curl_url_cleanup(url)。一个最小生命周期示例组合原文档示例int main(void) { CURLUcode rc; CURLU *url curl_url(); /* 创建句柄初始为空 */ CURLU *url2; rc curl_url_set(url, CURLUPART_URL, https://example.com, 0); if(!rc) { url2 curl_url_dup(url); /* 克隆一份 */ curl_url_cleanup(url2); /* 副本用完即释放 */ } curl_url_cleanup(url); /* 原句柄必须释放 */ }三、解析完整 URLcurl_url_set 与 CURLUPART_URL3.1 基础解析向句柄写入完整 URL 使用curl_url_set()签名见 curl_url_setCURLUcode curl_url_set(CURLU *url, CURLUPart part, const char *content, unsigned int flags);把part指定为CURLUPART_URL并传入 URL 字符串即可触发解析rc curl_url_set(h, CURLUPART_URL, https://example.com:449/foo/bar?namemoo, 0);解析成功后该 URL 会被拆解并按独立部件存储在句柄内如果 URL 语法不正确则返回错误码而非存储内容。第四个参数flags是一个位掩码bitmask用于按位开启特定行为默认传 0。需要留意的解析边界源码与文档共同确认该解析器只理解和解析层级式hierarchicalURL即包含://分隔符的 URL仅用单个冒号分隔的 scheme如mailto:fooexample.com不在支持范围内默认只接受 curl 内建支持的协议 scheme其他 scheme 会返回CURLUE_UNSUPPORTED_SCHEME要泛化解析任意 scheme 需设置CURLU_NON_SUPPORT_SCHEME除非设置CURLU_NO_AUTHORITY否则 URL 中不允许空白主机名blank hostname完整 URL 解析时主机名部件会以URL 解码后的形式存储输入字符串有8 MBCURL_MAX_INPUT_LENGTH上限超过返回CURLUE_MALFORMED_INPUT该限制在 lib/urlapi.c 中由CURL_MAX_INPUT_LENGTH控制。3.2 相对 URL 与重定向语义这是 URL API 最具特色的能力之一当句柄已包含一个 URL 时再设置一个相对 URL句柄会重定向到解析后的新位置见 libcurl-url 的 REDIRECT 一节/* 假设句柄 h 当前持有 https://example.com:449/foo/bar?namemoo */ rc curl_url_set(h, CURLUPART_URL, ../test?another, 0);设置成功后句柄内容被新 URL 的部件整体替换。重定向解析实现在 lib/urlapi.c 的redirect_url()函数中。两条配套规则同样值得注意设置空字符串作为重定向是允许的等同于清空但作为全新 URL 设置时会触发错误——即句柄已有 URL 时设空串可以句柄为空时设空串报错设置成功后之前存储的同名部件会被新内容整体替换。四、提取 URL 与部件curl_url_get 全解析4.1 函数签名与提取规则CURLUcode curl_url_get(const CURLU *url, CURLUPart part, char **content, unsigned int flags);part指定要提取的部件content指向一个char *函数会把新分配的字符串指针写回该变量。提取规则见 curl_url_get返回的字符串必须用curl_free()释放返回的字符串虽未在类型上约束但不应被修改提取出的部件默认不做 URL 解码除非在 flags 中设置CURLU_URLDECODE若函数返回错误则不会返回任何 URL 部件content不会被赋值。4.2 提取完整 URLchar *url; rc curl_url_get(h, CURLUPART_URL, url, 0); curl_free(url);当请求完整 URL 时函数使用句柄中所有可用部件拼出一个经过轻微整理slightly cleaned up的完整 URL。两个细节零长度的 query 和 fragment 默认会被排除在 URL 之外除非设置CURLU_GET_EMPTY文档建议使用CURLU_PUNYCODE标志获取尽可能规范化的 URL——因为 IDN 域名有多种写法可能映射到同一个 punycode 版本。4.3 提取单个部件一个句柄解析完成后可以随时逐个提取以下部件覆盖 libcurl-url 的 GET PARTS 一节rc curl_url_get(h, CURLUPART_FRAGMENT, fragment, 0); rc curl_url_get(h, CURLUPART_HOST, host, 0); rc curl_url_get(h, CURLUPART_PASSWORD, password, 0); rc curl_url_get(h, CURLUPART_PATH, path, 0); rc curl_url_get(h, CURLUPART_PORT, port, 0); rc curl_url_get(h, CURLUPART_QUERY, query, 0); rc curl_url_get(h, CURLUPART_SCHEME, scheme, 0); rc curl_url_get(h, CURLUPART_USER, user, 0); rc curl_url_get(h, CURLUPART_ZONEID, zoneid, 0);各部件在 get 方向上的语义要点部件语义要点CURLUPART_SCHEMEscheme 在 get 时不能进行 URL 解码CURLUPART_HOST若是 IPv6 数字地址返回时带方括号[]zone id 不包含在 host 中而是单独通过CURLUPART_ZONEID提供IPv6 名在设置时会被规范化尽可能短且保持语法正确CURLUPART_ZONEID仅当主机是数字 IPv6 地址时可能被设置CURLUPART_PORT端口在 get 时不能解码与其他部件一样以字符串返回且保证是合法的十进制 ASCII 端口号CURLUPART_PATH即使原 URL 没有路径也至少返回一个/URL 路径总是以斜杠开头CURLUPART_QUERY开头的问号只是分隔符不属于 query 内容query 不存在时返回 NULL零长度 query 默认返回 NULL除非设置CURLU_GET_EMPTY设置CURLU_URLDECODE时加号会被转成空格CURLUPART_FRAGMENT开头的井号只是分隔符不存在时返回 NULL零长度默认返回 NULL除非CURLU_GET_EMPTYCURLUPART_OPTIONS用户信息userinfo部分中可选的 options 字段仅在解析 pop3、smtp、imap 三种 scheme 的完整 URL 时识别不解析完整 URL 时仍可独立读写五、设置单个部件curl_url_set 的部件级写入在解析过完整 URL 之后或在完全未解析的情况下都可以通过curl_url_set()逐个设置部件见 libcurl-url 的 SET PARTS 一节rc curl_url_set(urlp, CURLUPART_FRAGMENT, anchor, 0); rc curl_url_set(urlp, CURLUPART_HOST, www.example.com, 0); rc curl_url_set(urlp, CURLUPART_PASSWORD, doe, 0); rc curl_url_set(urlp, CURLUPART_PATH, /index.html, 0); rc curl_url_set(urlp, CURLUPART_PORT, 443, 0); rc curl_url_set(urlp, CURLUPART_QUERY, namejohn, 0); rc curl_url_set(urlp, CURLUPART_SCHEME, https, 0); rc curl_url_set(urlp, CURLUPART_USER, john, 0); rc curl_url_set(urlp, CURLUPART_ZONEID, eth0, 0);set 方向的基础规则传入内容应为 URL 中使用的形式和编码即URL 编码形式默认不做 URL 编码除非设置CURLU_URLENCODE对已存在的部件设置新值会替换旧值设置成功即拷贝内容调用方无需在调用后保留原字符串将某部件设为 NULL 指针会清除该部件的内容输入字符串有 8 MB 上限超过返回CURLUE_MALFORMED_INPUT警告单独设置部件时可能接受无法通过完整 URL 解析得到的值如果混用之后拼出的完整 URL 可能非法需自行谨慎设置 scheme 时只接受最多 40 字节长度设置端口时十进制数值必须在0~65535之间否则报错只设 user 不设 password 时URL 会用空密码表示反之亦然路径不带前导斜杠时会自动补/若设置 IPv6 地址的同时又设置CURLU_URLENCODE会导致错误IPv6 地址会被弄坏。六、标志位全表get 端与 set 端语义所有标志位都以位掩码形式定义在 include/curl/urlapi.hCURLU_*宏get 与 set 各支持一部分。6.1 get 端标志curl_url_get 的 flags标志语义CURLU_DEFAULT_PORT句柄未存端口时返回该 scheme 的默认端口CURLU_DEFAULT_SCHEME句柄未存 scheme 时返回默认 scheme 而非报错CURLU_NO_DEFAULT_PORT端口与 scheme 默认端口相同时不返回端口号CURLU_URLDECODE返回前对内容做 URL 解码不解码 scheme、端口和完整 URLquery 额外享受加号转空格解码后若出现小于 32 的字节值则返回错误CURLUE_URLDECODECURLU_URLENCODE提取完整 URL 时对主机名做 URL 编码默认不编码以保持 IDN 域名原样显示但即使不要求编码%字节 37也总会编码以保证主机名合法CURLU_PUNYCODE在未设置CURLU_URLENCODE的前提下提取CURLUPART_HOST/CURLUPART_URL时将含非 ASCII 字节的 IDN 主机名以 punycode 返回无 IDN 能力且主机名含非 ASCII 时返回CURLUE_LACKS_IDN7.88.0 加入CURLU_PUNY2IDN提取CURLUPART_HOST/CURLUPART_URL时将 punycode 主机名转回 IDN UTF-8 形式转换失败返回CURLUE_BAD_HOSTNAME8.3.0 加入CURLU_GET_EMPTY允许返回空的 query/fragment 部件默认视为不存在空 query指问号后无内容空 fragment指井号后无内容8.8.0 加入CURLU_NO_GUESS_SCHEME若 scheme 是之前CURLU_GUESS_SCHEME猜测出来的get 时视为不存在取CURLUPART_SCHEME返回CURLUE_NO_SCHEME取CURLUPART_URL返回不带 scheme 的完整 URL8.9.0 加入6.2 set 端标志curl_url_set 的 flags标志语义CURLU_APPENDQUERY仅用于CURLUPART_QUERY把新内容追加到现有 query 末尾若原 query 不以结尾则自动补一个与CURLU_URLENCODE同用时第一个不做 URL 编码CURLU_NON_SUPPORT_SCHEME允许设置非内建支持的 scheme自然也无法校验其合法性CURLU_URLENCODE设置时对部件做 URL 编码scheme、port、URL 除外路径编码时以下字符保持原样! $ ( ) { } [ ] * , ; : query 在编码前先做空格转加号编码为字节级、不识字符集CURLU_DEFAULT_SCHEME允许 URL 不带 scheme随后自动补默认 schemeHTTPS与CURLU_GUESS_SCHEME同时设置时优先于猜测CURLU_GUESS_SCHEME允许 URL 不带 scheme根据主机名猜测最外层子域匹配 DICT、FTP、IMAP、LDAP、POP3、SMTP 则取相应 scheme否则默认 HTTP兼容老 curl 命令行行为猜测出的 scheme 可用 get 端CURLU_NO_GUESS_SCHEME识别出来CURLU_NO_AUTHORITY跳过 authority 校验允许空 authority类似 file scheme 的处理也使得https://、ftp://这类无主机名的 URL 可以被解析CURLU_PATH_AS_IS针对CURLUPART_URL跳过路径规范化即不做点段/点点段的折叠等价于传输选项CURLOPT_PATH_AS_ISCURLU_ALLOW_SPACE允许 URL 中出现空格ASCII 32scheme 内仍不允许允许的空格默认原样存储若同时设置CURLU_URLENCODE则编码为%207.78.0 加入CURLU_DISALLOW_USER解析CURLUPART_URL时拒绝内嵌凭据user/password否则返回CURLUE_USER_NOT_ALLOWED七、实战CURLU_APPENDQUERY 追加查询参数原文档特别演示了CURLU_APPENDQUERY的完整行为见 libcurl-url 的 CURLU_APPENDQUERY 一节。假设句柄当前持有https://example.com/?shoes2追加一个参数rc curl_url_set(urlp, CURLUPART_QUERY, hat1, CURLU_APPENDQUERY);函数检测到原 query 末尾缺少分隔符并自动注入句柄完整 URL 变为https://example.com/?shoes2hat1追加的内容同样可以要求 URL 编码此时编码过程会跳过字符从而正确处理数据中自带的。例如把candyNN追加到现有 query 并编码rc curl_url_set(urlp, CURLUPART_QUERY, candyNN, CURLU_APPENDQUERY | CURLU_URLENCODE);最终 URL 变为https://example.com/?shoes2hat1candyN%26NNN中的被编码为%26而第一个保持原样——这是构造动态 query 参数时最常用、也最易出错的场景。八、错误处理CURLUcode 与 curl_url_strerrorcurl_url_set()与curl_url_get()均返回CURLUcode枚举include/curl/urlapi.h 中定义完整错误列表见 libcurl-errorsCURLUE_OK值为 0表示成功。将错误码转为人类可读字符串使用curl_url_strerror()const char *curl_url_strerror(CURLUcode errornum);CURLUcode rc; CURLU *url curl_url(); rc curl_url_set(url, CURLUPART_URL, https://example.com, 0); if(rc) printf(URL error: %s\n, curl_url_strerror(rc)); curl_url_cleanup(url);常用错误码及其含义对应 urlapi.h 中的枚举错误码含义CURLUE_OK成功0CURLUE_BAD_HANDLE句柄非法CURLUE_BAD_PARTPOINTER部件指针非法CURLUE_MALFORMED_INPUT输入格式错误或超过 8 MB 上限CURLUE_BAD_PORT_NUMBER端口号非法CURLUE_UNSUPPORTED_SCHEMEscheme 不受支持未设置CURLU_NON_SUPPORT_SCHEMECURLUE_URLDECODEURL 解码失败如解码后出现 32 的字节CURLUE_OUT_OF_MEMORY内存不足CURLUE_USER_NOT_ALLOWED设置了CURLU_DISALLOW_USER却出现内嵌凭据CURLUE_UNKNOWN_PART未知的部件枚举值CURLUE_NO_SCHEME/CURLUE_NO_USER/CURLUE_NO_PASSWORD/CURLUE_NO_OPTIONS/CURLUE_NO_HOST/CURLUE_NO_PORT/CURLUE_NO_QUERY/CURLUE_NO_FRAGMENT/CURLUE_NO_ZONEID对应部件不存在CURLUE_BAD_HOSTNAME/CURLUE_BAD_IPV6/CURLUE_BAD_LOGIN/CURLUE_BAD_PASSWORD/CURLUE_BAD_PATH/CURLUE_BAD_QUERY/CURLUE_BAD_SCHEME/CURLUE_BAD_SLASHES/CURLUE_BAD_USER/CURLUE_BAD_FRAGMENT对应部件语法错误CURLUE_LACKS_IDN构建时无 IDN 能力却请求 IDN/punycode 转换CURLUE_TOO_LARGE内容过大CURLUE_BACKSLASHURL 中出现反斜杠九、接入传输CURLOPT_CURLU 与 easy 接口集成URL 句柄的最终价值在于驱动真实传输。通过CURLOPT_CURLU7.63.0 加入见 CURLOPT_CURLU可以把句柄直接交给 easy 接口CURLcode curl_easy_setopt(CURL *handle, CURLOPT_CURLU, CURLU *pointer);关键行为显式设置CURLOPT_CURLU会覆盖CURLOPT_URL发起传输前CURLOPT_URL或CURLOPT_CURLU必须设置其一libcurl 对句柄是只读使用不会改动其内容一次传输结束后应用可以更新句柄内容同一句柄用于后续请求时会使用更新后的内容——这为复用 URL 句柄 动态改写提供了干净的编程模型。int main(void) { CURL *curl curl_easy_init(); CURLU *urlp curl_url(); if(curl) { CURLcode result; CURLUcode ret; ret curl_url_set(urlp, CURLUPART_URL, https://example.com, 0); curl_easy_setopt(curl, CURLOPT_CURLU, urlp); result curl_easy_perform(curl); curl_url_cleanup(urlp); curl_easy_cleanup(curl); } }十、源码实现窥探lib/urlapi.c 的关键路径整套 API 的实现集中在 lib/urlapi.c数据结构struct Curl_URL即不透明类型CURLU。从源码结构可以梳理出以下关键实现点完整 URL 解析parseurl()是核心入口配合parse_scheme()scheme 解析、parse_authority()authority 解析、handle_path()/handle_query()/handle_fragment()等部件处理器parseurl_and_replace()负责把解析结果替换进句柄相对 URL 重定向redirect_url()实现相对路径解析对应前文../test?another的例子说明重定向能力是在 URL API 内部原生实现的而非简单字符串拼接主机名处理host_decode()/host_encode()处理主机的解码与编码hostname_check()负责主机名合法性校验——这对应文档中主机名以 URL 解码形式存储以及 IDN/punycode 标志位的行为get 端的组装urlget_url()负责拼接完整 URLurlget_format()负责单个部件的格式化输出file_url()专门处理 file schemeset 端的写入set_url()、set_url_scheme()含 40 字节 scheme 限制、set_url_port()端口范围校验 0~65535以及urlset_clear()置 NULL 清除部件输入上限各部件缓冲与解析缓冲均以CURL_MAX_INPUT_LENGTH初始化这正是文档所述 8 MB 输入限制的实现来源。十一、完整实战示例解析、改写、重组将前文知识串成一个完整可编译的程序解析一个 URL提取全部部件改写主机与路径追加查询参数最后重组输出完整 URL。#include stdio.h #include curl/curl.h int main(void) { CURLUcode rc; CURLU *url curl_url(); char *out; /* 1. 解析完整 URL */ rc curl_url_set(url, CURLUPART_URL, https://user:passexample.com:8443/a/b?old1#top, 0); if(rc) { printf(parse failed: %s\n, curl_url_strerror(rc)); goto out; } /* 2. 提取部件 */ { char *scheme NULL, *host NULL, *port NULL, *path NULL; curl_url_get(url, CURLUPART_SCHEME, scheme, 0); curl_url_get(url, CURLUPART_HOST, host, 0); curl_url_get(url, CURLUPART_PORT, port, 0); curl_url_get(url, CURLUPART_PATH, path, 0); printf(scheme%s host%s port%s path%s\n, scheme, host, port, path); curl_free(scheme); curl_free(host); curl_free(port); curl_free(path); } /* 3. 改写部件 */ curl_url_set(url, CURLUPART_HOST, www.example.org, 0); curl_url_set(url, CURLUPART_PATH, /index.html, 0); /* 4. 追加 query 参数自动补 并做 URL 编码 */ curl_url_set(url, CURLUPART_QUERY, newNN, CURLU_APPENDQUERY | CURLU_URLENCODE); /* 5. 重组完整 URL */ rc curl_url_get(url, CURLUPART_URL, out, 0); if(!rc) { printf(final URL: %s\n, out); curl_free(out); } out: curl_url_cleanup(url); return 0; }十二、注意事项与边界IPv6 无需特判带字面量 IPv6 地址的 URL 即使 libcurl 未启用 IPv6 支持也能被解析原文档 NOTES 一节明确说明空部件语义query 与 fragment 的空与不存在是两回事默认二者均视为不存在只有CURLU_GET_EMPTY能区分解码有副作用CURLU_URLDECODE是字符集无关的字节级转换且解码出 32 的控制字节会导致 get 返回错误get 端解码不影响 scheme、端口和完整 URL编码有豁免CURLU_URLENCODE在 set 端跳过 scheme/port/URL在 get 端仅影响完整 URL 的主机名部分且%始终会被编码以保持主机名合法8 MB 上限所有输入字符串不得超过 8 MBCURL_MAX_INPUT_LENGTH否则返回CURLUE_MALFORMED_INPUT不要混用来源单独设置部件时接受的数据可能无法通过完整 URL 解析还原混合使用可能导致最终拼出的 URL 非法。延伸阅读URL API 总览libcurl-url各函数手册curl_url_set、curl_url_get、curl_url_cleanup、curl_url_dup、curl_url_strerror与 easy 接口的集成CURLOPT_CURLU、CURLOPT_PATH_AS_IS错误码清单libcurl-errors头文件与实现include/curl/urlapi.h、lib/urlapi.c【免费下载链接】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),仅供参考
网站建设高端定制企业官网