关于 CURL 的一切(全)
原文:Everything curl - the book
译者:飞龙
协议:CC BY-NC-SA 4.0
引言
Everything curl 是 curl 所有事物的全面指南。项目、命令行工具、库、一切是如何开始的以及它是如何成为今天的有用工具的。它解释了我们如何进一步开发它,使用它的要求,如何通过代码或错误报告进行贡献,以及为什么数百万现有用户使用它。
这本书旨在对普通读者和有一定经验的开发者都既有兴趣又有用。它为每个人提供了选择的机会。
不要从头到尾阅读这本书。阅读你感兴趣的章节或内容,并根据需要来回翻阅。
这本书本身就是一个开源项目:开放、完全免费下载和阅读。任何人都可以评论,任何人都可以贡献并帮助。将你的错误报告、想法、拉取请求或评论发送给我们,我或其他人将相应地改进这本书。
这本书永远不会完成。我打算继续完善它。虽然我可能在某个时刻认为它相当完整,涵盖了项目的各个方面(即使这看起来像是一个难以逾越的目标),但 curl 项目将继续发展,因此书中总会有需要更新的事物。
这个书项目始于 2015 年 9 月底。
网站
everything.curl.dev是这本书的家园。它以网络版的形式提供在线书籍。
这本书也提供了PDF和ePUB版本。
该书的网站由 Fastly 托管。自 2024 年 3 月 18 日起,书籍内容由mdBook渲染。
内容
所有书籍内容都托管在 GitHub 的github.com/curl/everything-curl仓库中。
作者
希望能成为这本书的合著者之一,我是丹尼尔·斯特伯格。我创立了 curl 项目,本质上我是一个开发者——为了乐趣和利润。我住在瑞典斯德哥尔摩,并在那里工作。
关于丹尼尔的所有信息都可以在daniel.haxx.se上找到。
贡献
如果你在这份文档中发现了错误、遗漏、错误或明显的谎言,请发送受影响段落的更新版本给我们,我们将进行修改和更新。我们感谢并认可所有帮助的人。
最好,你可以在书的 GitHub 页面提交错误或拉取请求。
贡献者
许多人报告了错误、改进了章节或以其他方式帮助这本书取得成功。这些朋友包括以下人员:
GitHub 上的 AaronChen0,alawvt,Amin Khoshnood,amnkh,Anders Roxell,Angad Gill,Aris (Karim) Merchant,auktis,Ben Bodenmiller Ben Peachey,bookofportals,Bruno Baguette,Carlton Gibson,Chris DeLuca,Citizen Esosa,Dan Fandrich,Daniel Brown,Daniel Sabsay,David Piano,DrDoom74 在 GitHub 上,Emil Hessman,enachos71 在 GitHub 上,ethomag 在 GitHub 上,Fabian Keil,faterer 在 GitHub 上,Frank Dana,Frank Hassanabad,Gautham B A,Geir Hauge,Harry Wright,Helena Udd,Hubert Lin,i-ky 在 GitHub 上,infinnovation-dev 在 GitHub 上,Jay Ottinger,Jay Satiro,Jeroen Ooms,Johan Wigert,John Simpson,JohnCoconut 在 GitHub 上,Jonas Forsberg,Josh Vanderhook,JoyIfBam5,KJM 在 GitHub 上,knorr3 在 GitHub 上,lowttl 在 GitHub 上,Luca Niccoli,Manuel 在 GitHub 上,Marius Žilėnas,Mark Koester,Martin van den Nieuwelaar,mehandes 在 GitHub 上,Michael Kaufmann,Ms2ger,Mohammadreza Hendiani,Nick Travers,Nicolas Brassard,Oscar 在 GitHub 上,Oskar Köök,Patrik Lundin,RekGRpth 在 GitHub 上,Ryan McQuen,Saravanan Musuwathi Kesavan,Senthil Kumaran,Shusen Liu,Sonia Hamilton,Spiros Georgaras,Stephen,Steve Holme,Stian Hvatum,strupo 在 GitHub 上,Viktor Szakats,Vitaliy T,Wayne Lai,Wieland Hoffmann。
许可证
本文档遵循Creative Commons Attribution 4.0 许可协议。
如何阅读
这里概述了本书的主要章节及其涵盖的内容。
1. cURL 项目
项目是如何开始的,我们如何工作,以及发布频率如何等更多信息。
2. 网络和协议
网络和协议究竟是什么?
3. 安装 curl 和 libcurl
如何以及在哪里获取和安装 curl。
4. 源代码
对 curl 源树及其代码布局如何以及如何工作的描述。
5. 构建 curl
如何从源代码构建 curl 和 libcurl。
6. 命令行概念
从一开始。如何从命令行使用 curl?
7. 命令行传输
深入研究如何使用 curl 命令行工具进行和控制互联网传输。
8. 命令行 HTTP
深入探讨 curl 命令行工具的 HTTP 特定操作。
9. 命令行 FTP
在本章中学习如何使用 curl 执行 FTP 特定操作。
10. libcurl
如何使用 libcurl 以及如何在使用它编写自己的应用程序时使用它。基础。
11. libcurl 传输
如何设置和控制 libcurl 以使用 API 进行互联网传输。
12. libcurl HTTP
深入了解如何使用 libcurl 进行和控制 HTTP 特定传输。
13. libcurl 辅助工具
libcurl 提供了一套额外的 API、辅助工具,这些工具在传输之外提供了一些功能。这些是 API 和子系统,可以使你的 libcurl 使用应用程序更加出色。管理 URL、提取 HTTP 头信息等。
14. libcurl 示例
独立使用 libcurl 的示例,展示了如何轻松编写一个简单的第一个应用程序。
15. libcurl 绑定
对流行的 libcurl 绑定及其与 libcurl C API 相似性的概述。
16. libcurl 内部
在底层,它的工作方式是这样的…
17. 索引
索引。
The cURL project

curl 标志
关于开源项目的一个有趣细节是,它们被称为项目,好像它们在时间上有所限制或者永远无法完成。cURL 项目是由一些松散耦合的志愿者组成的团队,他们共同的目标是使用互联网协议进行可靠的数据传输,作为开源项目。
-
项目开始
-
名称
-
curl 做什么?
-
项目沟通
-
邮件列表礼仪
-
邮件列表
-
报告错误
-
商业支持
-
发布版本
-
安全
-
信任
-
行为准则
-
开发
-
开发团队
-
curl 用户
-
未来
它是如何开始的
回到 1996 年,Daniel Stenberg在业余时间编写了一个 IRC 机器人,这是一个自动化的程序,将为专门用于 Amiga 计算机的聊天室(EFnet 网络上的#amiga)的参与者提供服务。他开始想,获取一些更新的货币汇率并让他的机器人为聊天室用户提供在线服务以获取当前汇率,比如询问机器人“请将 200 美元兑换成 SEK”或类似的内容,会很有趣。
为了使提供的汇率尽可能准确,机器人需要每天从托管这些汇率的一个网站上下载汇率。为此任务需要一个下载 HTTP 数据的工具。当时快速搜索了一下,Daniel 发现了一个名为 httpget 的小工具(由巴西开发者 Rafael Sagula 编写)。它几乎完成了任务,只是需要在这里和那里做一些小小的调整。
Rafael 于 1996 年 11 月 11 日发布了 HttpGet 0.1 版本,在次年的 12 月,也就是 0.2 版本发布时,Daniel 首次对其进行了修改。不久之后,Daniel 接管了那几百行代码的维护工作。
HttpGet 1.0 于 1997 年 4 月 8 日发布,新增了全新的 HTTP 代理支持。
我们很快找到了并修复了通过 GOPHER 获取货币的支持。一旦添加了 FTP 下载支持,项目名称也发生了变化,urlget 2.0 于 1997 年 8 月发布。HTTP-only 的时代已经过去了。
项目逐渐变得更大。当添加了上传功能,并且名称再次具有误导性时,项目名称再次发生了变化,于 1998 年 3 月 20 日发布了 curl 4 版本。(保留了之前名称的版本编号。)
我们认为1998 年 3 月 20 日是 curl 的生日。
这个名字
给事物命名是困难的。
这个工具是用来上传和下载由 URL 指定的数据的。它是一个客户端程序(‘c’),一个 URL 客户端,并且会显示数据(默认情况下)。‘c’代表 Client 和 URL:cURL。它也可以读作 see URL,这也有助于。
没有其他需要,所以选择了这个名字,我们再也没有回头看过。
之后,有人建议 curl 实际上可能是一个巧妙的递归缩写词(其中缩写词的第一个字母指回同一个词):“Curl URL Request Library”。
虽然这很棒,但这其实并不是最初的想法。我们希望我们那么聪明…
在我们的 curl 出现之前,已经存在并使用 curl 这个名字的其他项目,但我们并不知情。
发音
我们大多数人发音 curl 时,初始音是 k 的音,就像英语单词 curl 一样。它与 girl 和 earl 等词押韵。Merriam Webster 有一个短 WAV 文件来帮助发音。
混淆和错误
在我们的 curl 被创造出来不久之后,另一个 curl 出现了,它创造了一种编程语言。那个 curl 仍然存在。
几种用于各种编程语言的 libcurl 绑定使用 curl 或 CURL 这个术语,部分或全部用来描述它们的绑定。有时你会发现用户在谈论 curl,但既没有提到这个项目的命令行工具,也没有提到这个项目制作的库。
作为动词
“To curl something”有时被用作使用非浏览器工具从 URL 下载文件或资源的参考。
curl 做什么?
cURL 是一个项目,其首要目的和重点是制作两个产品:
-
curl:命令行工具
-
libcurl:具有 C API 的传输库
工具和库都使用互联网协议为指定为 URL 的资源进行互联网传输。
与互联网协议传输相关的一切都可以被认为是 curl 的业务。与此不相关的事情应避免,并留给其他项目和产品。
考虑到 curl 和 libcurl 试图避免处理实际传输的数据,这可能很重要。例如,它对 HTML 或其他在 HTTP 上流行的内容一无所知,但它知道如何通过 HTTP 传输此类数据。
这两个产品不仅频繁用于驱动成千上万或数百万的脚本和应用程序,以连接到互联网的世界,而且它们也广泛用于服务器测试、协议调整和尝试新事物。
该库被用于各种可想象的嵌入式设备中,需要互联网传输的地方:车载信息娱乐系统、电视、蓝光播放器、机顶盒、打印机、路由器、游戏系统等。
命令行工具
从命令行运行 curl 是自然而然的,丹尼尔从未考虑过除了默认将数据输出到 stdout、终端之外的其他任何事情。标准 Unix 哲学的“一切皆管道”信条是丹尼尔所坚信的。curl 就像‘cat’或其他 Unix 工具一样;它将数据发送到 stdout,以便与其他工具链式连接,完成您想要的工作。这也是为什么几乎所有的 curl 选项,允许从文件读取或写入文件,也都有选择将其输出到 stdout 或从 stdin 的能力。
遵循 Unix 风格的命令行工具工作方式,curl 是否应该支持命令行上的多个 URL 也从未有过疑问。
命令行工具旨在通过脚本或其他自动方式完美运行。它不包含任何其他图形用户界面或用户界面,除了输入和输出纯文本。
库
虽然命令行工具先出现,但在 2000 年,网络引擎被剥离并转换为库,我们今天仍然拥有的概念是在 2000 年 8 月的 libcurl 7.1 中引入的。从那时起,命令行工具就成为了围绕库的逻辑薄层,以制作一个执行所有繁重工作的工具。
libcurl 旨在为任何希望将客户端文件传输功能添加到其软件中的人提供,无论平台、架构和目的。libcurl 的许可也非常宽松,以避免成为障碍。
libcurl 是用传统和保守的 C 语言编写的。在其他语言更受欢迎的情况下,人们为它们创建了 libcurl 绑定。
项目沟通
cURL 是一个开源项目,由来自世界各地、居住和工作在世界上许多时区的自愿成员组成。为了使这种设置真正有效,沟通和开放性是关键。我们保持所有沟通公开,并使用开放的沟通渠道。大多数讨论都在邮件列表上进行,我们使用问题跟踪器,在那里所有问题都会被讨论和处理,让所有关心的人都能全面了解。
重要的是要认识到我们都在共同照顾这个项目,我们解决问题并添加功能。有时,一个常规的贡献者会感到厌倦并逐渐消失,有时一个新热情的贡献者会从阴影中走出来,开始更多地帮助。为了尽可能保持这个团队的前进,保持开放的讨论非常重要,这也是我们为什么反对用户私下讨论或尝试通过电子邮件向个别团队成员询问开发问题、问题、调试或其他事情的原因。
在这个时代,邮件列表可能被认为是过时的通信方式——没有花哨的网页论坛或类似的东西。因此,使用邮件列表正逐渐成为一种并非到处都练习的艺术,可能对你来说有点奇怪和不寻常。但不必担心。这仅仅是向一个地址发送电子邮件,然后该地址会将这封电子邮件发送给所有订阅者。我们的邮件列表最多有几千名订阅者。如果你是第一次发邮件,可能先阅读几封旧邮件,以了解文化和被认为的良好实践会是个好主意。
邮件列表和问题跟踪器已经更换过几次托管服务提供商,并且有理由怀疑未来可能会再次发生这种情况。这正是长期存在的项目可能会遇到的事情。
一些用户也常在libera.chat的#curl 频道上挂出。
邮件列表礼仪
就像许多社区和亚文化一样,我们已经制定了我们认为正确的行为准则和如何在邮件列表上沟通的规则。curl 邮件列表礼仪遵循传统开源项目的风格。
不要单独给一个人发邮件
许多人直接向一个人发送一个问题。一个人收到很多邮件,而只有一个人能给你回复。这个问题可能是其他人也想问的问题。这些其他人没有其他方式阅读回复,只能向那个人提问。因此,那个人会收到大量的邮件。
如果你真的想联系个人并可能为其服务付费,请尽一切可能去做,但如果只是另一个 curl 问题,请将其带到合适的列表中。
回复或新邮件
请不要将现有消息作为发布到列表的快捷方式来回复。
许多邮件程序和网页存档器使用邮件中的信息将它们作为线程、作为讨论特定主题的帖子集合来保持在一起。如果你不打算在同一或类似的主题上回复,不要只是点击回复现有邮件并更改主题;创建一个新的邮件。
回复到列表
当回复列表中的消息时,请确保你进行群组回复或回复所有人,而不仅仅是回复你回复的单个邮件的作者。
我们正在积极反对私下回复给单个个人。请将后续讨论保持在列表上。
使用有意义的主题
请使用与邮件内容相关的有意义的主题。这样在之后查找邮件会容易得多,并且有助于跟踪邮件线程和主题。
不要顶楼回复
如果你回复一条消息,请不要使用顶楼回复。顶楼回复是指你在邮件顶部写下新文本,并在下面插入之前引用的邮件对话。这迫使用户以相反的顺序阅读邮件才能正确理解。
这就是为什么顶楼回复是如此糟糕的原因:
A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?
A: Top-posting.
Q: What is the most annoying thing in email?
除了混乱的阅读顺序(尤其是在使用规定的底楼回复风格混合在一起时),它还使得无法仅引用原始邮件的部分。
当你回复邮件时,让邮件客户端插入之前的邮件引用。然后你将光标放在邮件的第一行,并向下移动通过邮件,删除所有不为你评论提供上下文的引用部分。当你想添加评论时,你就在引用之后立即进行,直接在相关的引用之后。然后你再次向下继续。
当大多数引用已被移除,你已经添加了自己的话时,你就完成了。
HTML 不适用于邮件
请关闭那些 HTML 编码的消息。你可以把所有那些有趣的邮件发给你的朋友。我们使用纯文本邮件。
引用
尽可能少引用。只需提供足够的上下文,您不能省略。更详细描述可以在这里找到。
摘要
我们允许订阅者订阅邮件列表的摘要版本。摘要是一组汇总在一起的单封邮件。
如果您决定回复作为摘要发送的邮件,如果您真的无法正常订阅,那么您必须考虑以下两点:
切断所有与您想要回复的邮件无关的邮件和闲聊。
将主题名称改为一个有意义的、与主题相关的名称,最好是您想要回复的单封邮件的实际主题。
请告诉我们您是如何解决问题的
许多人向列表发送问题,人们花费了一些时间和精力来提供好的答案。
如果您是提问者,请考虑再次回复,以防其中一条提示解决了您的问题。那些回答问题的人很高兴知道他们提供了好的答案,并且您解决了问题。太多次了,提问的人再也没有消息,我们永远不知道他/她是否因为问题解决了而离开,或者是因为问题无法解决。
将解决方案发布出来也有助于遇到相同问题(们)的其他用户。他们可以看到(可能在网络存档中)建议的修复方案实际上至少帮助了一个人。
邮件列表
一些最重要的邮件列表包括...
curl-users
这是 curl 命令行工具用户和开发者的主要邮件列表,用于关于 curl 概念、命令行选项、curl 能使用的协议,甚至相关工具的问题和帮助。我们倾向于将开发问题或更高级的 bug 修复讨论转移到 curl-library,因为 libcurl 是驱动 curl 大部分功能的引擎。
查看 curl-users
curl-library
这是主要的发展邮件列表,也是 libcurl 用户的列表。我们讨论如何在应用程序中使用 libcurl 以及 libcurl 本身的发展。关于 libcurl 行为、调试和文档问题等。
查看 curl-library
curl-announce
这个邮件列表只发布关于新版本和安全问题的公告——没有其他内容。这个列表是为那些想要从项目中获取更多信息的人准备的。
查看 curl-announce
报告错误
开发团队做了大量的测试。我们有一个完整的测试套件,每天在众多平台上频繁运行,以测试所有代码并确保一切按预期工作。
尽管如此,有时事情并没有按预期的方式工作,我们依赖人们向我们报告这些问题。
一个错误是一个问题
任何问题都可以被视为一个错误。手册中措辞奇怪,导致你无法理解某些内容,这也是一个错误。组合多个选项产生的意外副作用可能是一个错误——或者也许应该有更好的文档说明?也许这个选项根本就没有做你期望它做的事情?这是一个问题,我们应该修复它。
必须知道问题才能修复
这听起来可能简单且不复杂,但这是我们和其他项目中的一个基本真理。仅仅因为这是一个旧项目并且有数千名用户,并不意味着开发团队知道你刚刚遇到的这个问题。也许用户没有像你那样关注细节,或者也许它从未触发过其他人。
我们依赖遇到问题的用户来报告它们。我们需要知道它们的存在才能修复它们。
修复问题
软件工程在很大程度上是关于解决问题的。为了解决问题,开发者需要了解如何重复它,而要做到这一点,他们需要被告知触发问题的环境条件。
一个好的错误报告
一个好的报告解释了发生了什么以及你原本认为会发生什么。告诉我们你使用了不同组件的确切版本,并一步一步地告诉我们你是如何到达问题的。
在你提交错误报告后,你可以期待会有后续的问题或者可能需要你尝试各种事情,以便开发者可以缩小怀疑范围并确保你的问题得到正确定位。
如果提交者提交了错误报告然后放弃,如果开发者未能理解它,未能重现它或在工作时遇到其他问题,那么这个报告可能会被关闭。不要放弃你的报告。
在 GitHub 上的curl 错误跟踪器报告 curl 的错误。
测试
仔细且正确地测试软件是一项大量工作。测试在数十个操作系统和 CPU 架构上运行的软件,以及具有自己错误集合和规范解释的服务器实现,更是工作量巨大。
curl 项目有一个测试套件,它遍历所有现有的测试用例,运行每个测试,并验证结果是否正确,以及没有发生其他问题,例如内存泄漏或协议层中的可疑问题。
测试套件是在你自行构建 curl 之后运行的。有许多志愿者也通过每天自动运行测试套件几次来帮忙,以确保最新的提交得到测试。这样我们可以在引入后不久发现最严重的缺陷。
我们并没有测试所有内容,即使我们尝试测试某些内容,也总会有一些微妙的错误得以通过,有些甚至直到多年后才被发现。
由于不同系统的特性和互联网上的一些奇怪用例,最终一些最好的测试是由用户在运行代码以执行他们自己的用例时完成的。
测试套件中另一个限制因素是,测试设置本身比 curl 和 libcurl 的可移植性差,因此实际上存在一些平台,curl 可以正常运行,但测试套件却无法执行。
商业支持
curl 和 libcurl 的商业支持由 curl 的创始人 Daniel Stenberg 通过公司 wolfSSL 提供。
wolfSSL 为 curl 提供全球范围内的商业支持,由 curl 的专家团队完成。wolfSSL 负责 curl 的定制、移植到新的操作系统、功能开发、补丁维护、错误修复、上游提交、培训、libcurl API 使用代码审查、扫描您的 curl 使用安全性以及更多——提供从基础到全天候 24/7 支持的多种不同支持选项。并保证响应时间。
有关联系方式,请参阅 支持页面。
发布
在 curl 项目中,一个发布意味着将代码库 master 分支中的所有源代码打包,对包进行签名,标记代码库中的该点,然后将它上传到网站供全世界下载。
这是一个适用于 curl 可以在其上运行的平台的单一源代码存档。这是 curl 和 libcurl 的唯一包。
我们从未从项目中发送任何 curl 或 libcurl 的二进制文件,只有一个例外:我们托管为 Windows 用户构建的官方 curl 二进制文件。所有其他与操作系统一起提供的或在其他下载网站上提供的打包二进制文件都是由项目外的志愿者完成的。
几年前,我们努力以八周为一个周期进行发布,除非出现一些真正严重和紧急的问题,否则我们会坚持这个计划。我们每周三发布,然后八周后的又一个周三再次发布,以此类推。不间断。
对于每个版本,我们都会在代码库中将源代码标记为 curl 版本号,并更新变更日志。
到 2024 年 1 月,我们已经发布了 253 个版本。整个发布历史和变更日志可在我们的curl 发布日志中找到。
发布周期

curl 发布周期可视化
每日快照
每次对源代码的更改都会提交并推送到源代码库。这个库托管在 github.com 上,目前使用 git(但并非一直如此)。从库中构建 curl 时,需要生成和设置一些东西,有时这会给人们带来一些问题或摩擦。为了帮助解决这个问题,我们提供了每日快照。
每日快照每天生成(命名很聪明,对吧?)就像在那个点发布了一个版本一样。它生成一个包含所有源代码和通常作为发布一部分的所有文件的包,并将其放入包中,上传到这个特殊位置,以便有兴趣的人获取最新的代码进行测试、实验或其他用途。
快照保留大约 20 天后将被删除。
安全
在 curl 项目中,安全是我们首要关注的问题。我们对此非常重视,并努力提供所有协议和相关代码的安全和安全的实现。一旦我们得知有关安全相关问题的知识或仅仅是怀疑有问题,我们就会处理它,并尝试在下一个待发布版本中提供修复和安全通知,绝不迟于那时。
我们采用负责任的披露政策,这意味着我们更愿意在公众视野之外讨论和解决安全修复问题,并在我们向全世界宣布问题和修复方案之前几天通知 openwall.org 列表上的供应商。这样做是为了缩短坏人可以利用问题的时间,直到部署了修复版本。
过去的网络安全问题
在这些年中,我们遇到了不少与安全相关的问题。我们努力详细记录每个问题,列出所有细节并清晰陈述,以帮助用户。curl 的使用者应该能够弄清楚他们特定的 curl 版本和使用案例容易受到哪些问题的威胁。
为了帮助解决这个问题,我们提供了这个瀑布图,展示了所有漏洞如何影响 curl 的各个版本,并且我们有这个所有已知安全问题的完整列表,自本项目诞生以来。
信任
要征服世界,软件需要被信任。建立信任需要信任,但如果基础被证明有裂缝,所有的一切都可能迅速崩溃。
在 curl 项目中,我们以几种不同的方式为我们的用户建立信任:
-
我们在所有事情上都是完全透明的。每一个决定,每一次讨论,以及每一行代码和每一个考虑的代码更改都是公开的,并且是在公开环境中完成的。
-
我们努力编写可靠的代码。我们编写测试用例,审查代码,记录最佳实践,并有一个风格指南帮助我们保持代码的一致性。
-
我们尽可能地遵守承诺和保证。我们不破坏 API,也不放弃对旧系统的支持。
-
安全性至关重要,我们认真对待每一起报告的事件,并认识到我们必须修复所有已知的问题,并且需要负责任地去做。我们尽最大努力不危及我们的用户。
-
我们表现得像成年人。我们可以傻傻的,也可以开玩笑,但我们负责任地做,并遵循我们的行为准则。每个人都应该能够信任我们的行为。
行为准则
作为这个项目的贡献者和维护者,我们承诺尊重所有通过报告问题、发布功能请求、更新文档、提交拉取请求或补丁以及其他活动做出贡献的人。
我们致力于让每个人都能在项目中参与,享受一个没有骚扰的经历,无论其经验水平、性别、性别认同和表达、性取向、残疾、个人外貌、体型、种族、民族、年龄或宗教。
参与者不可接受的行为示例包括使用性语言或图像、侮辱性评论或人身攻击、捣乱、公开或私下骚扰、侮辱或其他不专业行为。
项目维护者有权并有责任移除、编辑或拒绝与该行为准则不一致的评论、提交、代码、维基编辑、议题和其他贡献。不遵守行为准则的项目维护者可能会被从项目团队中移除。
此行为准则适用于项目空间内以及个人代表项目或其社区时在公共空间的行为。
对于滥用、骚扰或其他不可接受的行为,可以通过开启一个议题或联系项目维护者之一或多个来报告。
开发
我们鼓励每个人都参与 curl 和 libcurl 的开发。我们感激所有我们能得到的帮助,虽然这个项目的主要部分是源代码,但还需要和有用的不仅仅是编码和调试帮助。
我们在公开场合开发和讨论所有内容,最好是邮件列表上。
GitHub 上的源代码
curl 和 libcurl 的源代码也已提供并公开发布,并且随着每个版本的发布,它继续被上传到主网站。
自 2010 年 3 月以来,curl 的源代码库一直托管在github.com。通过跟踪那里的变化,您可以密切跟进我们的日常开发。
开发团队
Daniel Stenberg 是该项目的创始人,也是自称的领导者。其他所有参与或贡献项目的人都是在之后加入的。一些贡献者工作了一段时间后又离开了。大多数贡献者只是短暂地参与,以修复他们的错误或合并功能或类似的事情。计算我们所知的所有贡献者的名字,我们已经从超过 3,000 个个人那里得到了帮助。
没有正式的成员资格或需要做的事情来加入项目。如果你参与沟通或开发,你就是项目的一部分。每个贡献者自己决定参与多少以及以何种方式参与。
在该项目中,任何一年内提交十个或更多提交的人的完整名单是:
Alessandro Ghedini, Ben Greear, Benoit Neil, Bill Hoffman, Bill Nagel, Björn Stenberg, Brad Hards, Dan Fandrich, Daniel Gustafsson, Daniel Stenberg, Dominick Meglio, Emanuele Torre, Emil Engler, Fabian Frank, Fabian Keil, Gergely Nagy, Gisle Vanem, Guenter Knauf, Harry Sintonen, Isaac Boukris, Jacob Hoffman-Andrews, Jakub Zakrzewski, James Housley, Jay Satiro, Jiri Hruska, Joe Mason, Johannes Schindelin, Josh Soref, Julien Chaffraix, Kamil Dudka, Marc Hoersken, Marcel Raad, Mark Salisbury, Marty Kuhrt, Max Dymond, Michael Kaufmann, Michael Osipov, Michal Marek, Michał Antoniak, Nicholas Nethercote, Nick Zitzmann, Nikos Mavrogiannopoulos, Patrick Monnerat, Peter Wu, Philip Heiduck, Rikard Falkeborn, Ruslan Baratov, Ryan Schmidt, Simon Warta, Stefan Eissing, Steinar H. Gunderson, Sterling Hughes, Steve Holme, Svyatoslav Mishyn, Tatsuhiro Tsujikawa, Tor Arntsen, Viktor Szakats, Yang Tse
curl 用户

两百亿安装
我们估计全球有超过两百亿次的 curl 安装。说起来很好听,但现实中我们当然没有确切的数字。我们只是基于观察和趋势进行估算和猜测。这也取决于我们究竟将“安装”定义为什么。让我们详细说明一下。
开源
由于该项目是开源且许可宽松,这意味着几乎任何人都可以以源代码格式或二进制形式重新分发 curl。
计算下载量
curl 命令行工具和 libcurl 库可以通过 curl 网站下载,适用于大多数操作系统,它们通过第三方安装程序提供给许多人,并且默认安装在更多操作系统中。这使得从 curl 网站计算下载量作为衡量手段完全不合适。
寻找用户
因此,我们无法计算下载量,任何人都可以重新分发它,而且没有人被迫告诉我们他们使用 curl。我们如何确定这些数字?我们如何确定用户数量?答案是,我们真的无法以任何合理的准确度来确定。
相反,我们依赖于目击者的报告、间接证据、互联网上的发现,偶尔的“关于”框或许可证协议中提到 curl,或者作者请求帮助并告诉我们他们的使用情况。
curl 许可证规定用户需要在某处重复它,比如在文档中,但在许多情况下我们很难找到它,而且如果他们决定不遵守这个小许可证要求,我们也很难采取任何行动。
命令行工具用户
命令行工具 curl 被全球的程序员在 shell 和批处理脚本中广泛使用,用于调试服务器和测试事物。毫无疑问,每天都有数百万人在使用它。
嵌入式库
libcurl 使得我们的项目能够触达大量用户。许多用户希望快速轻松地将客户端文件传输功能集成到他们的应用程序中,而 libcurl 的卓越可移植性也帮助了这一点:你可以在各种平台上几乎编写相同的应用程序,同时仍然可以使用 libcurl 进行传输。
libcurl 是用 C 语言编写的,没有或只有少数必需的依赖项,这也帮助它在嵌入式系统中得到使用。
libcurl 在智能手机操作系统、车载娱乐系统、电视、机顶盒、音频和视频设备(如蓝光播放器和高端接收器)中广泛使用。它通常用于家庭路由器和打印机。
许多畅销游戏也在 Windows 和游戏机上使用 libcurl。

不同设备、工具、应用程序和服务都运行 curl
在网站后端
PHP 的 libcurl 绑定可能是第一个,如果不是第一个,真正流行并广泛使用的 libcurl 绑定之一。它很快就被采用作为 PHP 用户传输数据的默认方式,并且现在它已经在这个位置上超过十年了,PHP 也已经成为互联网上相当流行的技术(最近的数字表明,大约四分之一的互联网网站使用 PHP)。
一些需求量特别高的网站正在使用 PHP,并在后端使用 libcurl。Facebook 和 Yahoo 就是这样的两个网站。
知名用户
没有任何强制措施要求用户告诉我们他们在他们的服务或产品中使用 curl 或 libcurl。我们通常只是偶然发现他们这样做,通过阅读关于对话、文档和许可协议的内容。当然,一些公司也直接告诉我们。
我们过去在我们的网站上收集使用项目产品“在商业环境中”的公司和产品名称。我们这样做主要是为了向其他大品牌炫耀,如果这些其他公司可以构建依赖我们的产品,也许你也可以?
公司名单包含数百个名称,但从中提取一些较大的或更知名的品牌,这里有一个相当不错的列表,当然,这只是一个小的选择:
Adobe、Altera、AOL、Apple、AT&T、BBC、Blackberry、BMW、Bosch、Broadcom、Chevrolet、Cisco、Comcast、Facebook、Google、Hitachi、Honeywell、HP、华为、HTC、IBM、Intel、LG、Mazda、Mercedes-Benz、Microsoft、Motorola、NASA、Netflix、Nintendo、Oracle、Panasonic、Philips、Pioneer、RBS、Samsung、SanDisk、SAP、SAS Institute、SEB、Sharp、Siemens、Sony、Spotify、Sun、Swisscom、Tomtom、Toshiba、VMware、Xilinx、Yahoo、Yamaha
使用 curl 的知名高流量应用
Google 的 YouTube 应用、Google 照片应用、Spotify、Instagram、Skype(在 Android 上)、捆绑 iOS 的,还有《侠盗猎车手 V》、《堡垒之夜》。
未来

curl 未来
在 curl 的未来发展中,看不到任何放缓的迹象,无论是已报告的 bug、开发速度,还是互联网协议的开发或更新。
我们期待着更多协议的支持,以及现有协议内更多功能的支持,还有更多更好的 API,以便 libcurl 允许用户进行更高效、更快的传输。
项目随意维护一个待办事项文件,其中包含了一些我们未来可以工作的想法。它还保持一个已知 bug文档,列出了我们希望修复的已知问题。
存在一份路线图文档,描述了一些短期计划,一些活跃的开发者认为他们会在接下来着手实施。当然,我们无法保证我们始终会遵循它。
我们高度依赖开发者加入并从事他们想要完成的工作,无论是 bug 修复还是新功能开发。
网络和协议
在深入探讨如何使用 curl 完成任务之前,让我们先看看所有这些网络是什么以及它是如何工作的,通过简化和一些小捷径来提供一个易于理解的概述。
基础知识可以在网络简化章节中找到,该章节试图从 curl 的角度简单描绘网络是什么,以及协议部分,它解释了“协议”的确切含义以及它是如何工作的。
-
网络简化
-
协议
-
curl 协议
-
HTTP 基础
网络简化
网络意味着在互联网上的两个端点之间进行通信。互联网只是一堆相互连接的机器(实际上是计算机),每个机器使用自己的独立地址(称为IP 地址)。每台机器的地址可以是不同类型的,机器甚至可以有临时地址。这些计算机也被称为主机。
客户端和服务器
你坐在前面的电脑、平板或手机通常被称为客户端,而你想要与之交换数据的远端机器被称为服务器。客户端和服务器之间的主要区别在于它们扮演的角色。在后续操作中,这些角色被反转是没有什么的。
转换请求总是由客户端发起的,因为服务器无法联系客户端,但客户端可以联系服务器。
哪台机器
当我们作为客户端想要从那里的一台机器(服务器)发起传输或向其传输时,我们通常不知道它的 IP 地址,而是知道它的名称。我们想要与之通信的机器的名称通常嵌入在我们使用 curl 或浏览器等工具工作时处理的 URL 中。
我们可能会使用一个像 http://example.com/index.html 这样的 URL,这意味着客户端连接到并与名为 example.com 的主机进行通信。
主机名解析
一旦客户端知道了主机名,它需要找出具有该名称的主机有哪些 IP 地址,以便能够联系它。
将名称转换为 IP 地址的过程称为“名称解析”。该名称被解析为一个或一组地址。这通常由一个DNS 服务器来完成,DNS 就像一个大型的查找表,可以将名称转换为地址——实际上,是互联网上的所有名称。计算机通常已经知道运行 DNS 服务器的计算机的地址,因为这是网络设置的一部分。
因此,网络客户端会向 DNS 服务器请求,“你好,请给我所有example.com的地址”。DNS 服务器会回应一个地址列表。或者,如果出现拼写错误,它可以回应说该名称不存在。
建立连接
对于客户端想要联系的主机,它有一个或多个 IP 地址,它会发送一个连接请求。它想要建立的连接被称为 TCP (传输控制协议) 或 QUIC 连接,这就像在两台计算机之间连接一根无形的线。一旦建立,这根线就可以用来在两个方向上发送数据流。
如果客户端为该主机收到了多个地址,它在连接时会遍历该地址列表,如果其中一个地址失败,它会尝试连接到下一个地址,重复此过程,直到找到一个有效的地址或所有地址都失败。
连接到端口号
当使用 TCP 或 QUIC 连接到远程服务器时,客户端会选择在哪个端口号上进行连接。端口号只是为特定服务预留的专用位置,允许同一服务器在相同时间内监听其他端口号上的其他服务。
大多数常见的协议都有客户端和服务器使用的默认端口号。例如,当使用http://example.com/index.html URL 时,该 URL 指定了一个名为HTTP的方案,告诉客户端它应该默认尝试服务器上的 TCP 端口号 80。如果 URL 使用HTTPS,则默认端口号为 443。
URL 可以包含自定义端口号。如果没有指定端口号,客户端将使用 URL 中使用的方案的默认端口号。
安全性
在 TCP 连接建立之后,许多传输需要在继续之前双方协商一个更好的安全级别(例如,如果使用HTTPS),这是通过 TLS(传输层安全性)完成的。如果是这样,客户端和服务器首先进行 TLS 握手,只有在成功之后才会继续。
如果连接使用 QUIC 完成,TLS 握手将在连接阶段自动进行。
传输数据
当连接的隐喻性字符串连接到远程计算机时,两台机器之间就建立了连接。然后可以使用这个连接来交换数据。这种交换是通过协议完成的,如下一章所述。
传统上,下载是指数据从服务器传输到客户端;相反,上传是指数据从客户端发送到服务器。客户端在这里;服务器在那里。
断开连接
当单个传输完成时,连接可能已经完成了它的用途。然后它可以被重新用于进一步的传输,或者可以断开并关闭。
协议
用于请求数据发送(无论是哪个方向)的语言被称为协议。协议精确地描述了如何请求服务器提供数据,或者告诉服务器有数据即将到来。
协议通常由 IETF(互联网工程任务组)定义,它托管 RFC 文档,这些文档精确地描述了每个协议的工作方式:客户端和服务器应该如何行动,应该发送什么等等。
curl 支持哪些协议?
curl 支持允许数据在任一方向或两个方向上传输的协议。我们通常也限制自己使用那些在 RFC 中有 URI 格式描述或至少是某种广泛使用的协议,因为 curl 主要使用 URL(URI)作为输入键来指定传输。
本文中提到的最新版本的 curl(截至撰写本文时)支持以下协议:
DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS, WSS
更进一步地复杂化,这些协议通常存在不同的版本或风味。
还有哪些其他协议?
世界充满了协议,既有旧的也有新的。旧协议被废弃和淘汰,新协议被引入。永远没有稳定的状态,但情况每天都在变化,每年都在变化。你可以放心,未来将在上述列表中添加新的协议,并且现有协议也将有新的版本。
当然,已经存在其他一些 curl 尚未支持的协议。我们愿意支持更多适合 curl 通用范例的协议,我们只需要开发者为他们编写必要的代码调整即可。
协议是如何开发的?
新版本现有协议和全新的协议通常是由那些认为现有协议不够好的人或团队开发的。它们的一些特性使它们不适合特定的用例,或者可能有一些新想法可以应用于改进事物。
当然,没有人阻止任何人完全按照自己的意愿在自己的后院开发一个协议,但主要协议通常在早期就带到 IETF,在那里它们被讨论、完善、辩论和润色,然后最终,理想情况下,被转化为发布的 RFC 文档。
软件开发者然后阅读 RFC 规范,并根据他们对这些文件中文字的解释在世界范围内部署他们的代码。有时会出现一些规范有非常不同的解释,或者工程师只是懒惰,忽略了规范中的合理建议,部署了一些不符合规范的东西。因此,编写与规范的其他实现互操作性的软件可能是一项艰巨的工作。
协议变化有多大?
就像软件一样,协议规范经常更新,并且会创建新的协议版本。
大多数协议都允许一定程度的可扩展性,这使得新的扩展随着时间的推移出现,这些扩展是有意义并值得支持的。
即使规范保持不变,协议的解释有时也会发生变化。
本章中提到的协议都是应用协议,这意味着它们通过更底层的协议(如 TCP、UDP 和 TLS)进行传输。它们本身也是随时间变化、获得新功能和受到攻击的协议,这迫使 curl 适应和改变,以处理新的安全方式等。
关于遵守标准和谁是对的
通常,有一些协议规范告诉我们如何为特定协议发送和接收数据。我们遵循的协议规范是由 IETF 汇编和发布的 RFC。
有些协议在最终的 RFC 中没有得到适当的文档记录,例如,我们的实现基于一个甚至不是最新版本的 Internet-draft 的 SFTP。
然而,协议是由双方讨论的,就像在任何对话中一样,理解某事或解释规范中的给定指令都有两个理解方面。此外,许多网络软件的编写者没有仔细关注规范,所以他们最终采取了捷径,或者他们可能只是对文本有不同的解释。有时甚至错误和漏洞会导致软件以规范未规定甚至规范中明确禁止的方式运行。
在 curl 项目中,我们使用已发布的规范作为行动规则,直到我们了解其他情况。如果流行的替代实现的行为与我们认为的规范不同,并且这种替代行为在互联网上广泛使用,那么我们改变脚步并决定像其他人一样行动的可能性很大。如果我们认为我们遵循了规范但服务器拒绝与我们交谈,而当我们将规则稍微弯曲时却可以正常工作,那么我们可能最终会以那种方式弯曲规则——如果我们仍然可以与其他实现成功工作的话。
最终,这是一个个人决定,在我们认为规范和现实世界不一致的每个情况下都值得讨论。
在最坏的情况下,我们引入选项,让应用程序开发者和 curl 用户对 curl 应该做什么有最终决定权。我说最坏,因为通常很难要求用户做出这些决定,因为这通常涉及到棘手的细节和奇怪的情况,这对用户来说要求很高。我们应该始终尽力避免将此类协议决策推给用户。
curl 协议
curl 支持大约 28 种协议。我们说“大约”是因为这取决于你如何计数以及你如何看待明显不同的协议。
DICT
DICT 是一种字典网络协议,它允许客户端向字典服务器询问单词的含义或解释。参见 RFC 2229。Dict 服务器和客户端使用 TCP 端口 2628。
FILE
FILE 实际上不是一个 网络 协议。它是一个 URL 方案,允许你告诉 curl 从本地文件系统获取文件,而不是从远程服务器通过网络获取。参见 RFC 1738。
FTP
FTP 代表文件传输协议,是一种古老的(起源于 20 世纪 70 年代初)在客户端和服务器之间来回传输文件的方式。参见 RFC 959。多年来它得到了极大的扩展。FTP 服务器和客户端使用 TCP 端口 21 加上一个额外的端口,尽管第二个端口通常在通信过程中动态建立。
请参阅外部页面 FTP 与 HTTP 的比较,了解它与 HTTP 的不同之处。
FTPS
FTPS 代表安全文件传输协议。它遵循在协议名称后附加一个 'S' 的传统,以表示该协议像正常 FTP 一样执行,但添加了 SSL/TLS 安全层。参见 RFC 4217。
此协议在使用防火墙和其他网络设备时存在问题。
GOPHER
设计用于“在互联网上分发、搜索和检索文档”,Gopher 在某种程度上是 HTTP 的祖父,因为 HTTP 已经完全取代了相同的用例。参见 RFC 1436。Gopher 服务器和客户端使用 TCP 端口 70。
GOPHERS
TLS 上的 Gopher。对旧协议的最近扩展。
HTTP
超文本传输协议(HTTP)是互联网和网络上传输数据最广泛使用的协议。参见 RFC 9110 了解 HTTP 语义,RFC 9112 了解 HTTP/1.1,RFC 9113 了解 HTTP/2,这时它使用 QUIC(RFC 8999)并通过 UDP 完成。
IMAP
互联网消息访问协议(IMAP)是一种用于访问、控制和“阅读”电子邮件的协议。参见 RFC 3501。IMAP 服务器和客户端使用 TCP 端口 143。虽然连接到服务器最初是明文,但客户端可以通过显式请求使用 STARTTLS 命令升级连接以支持 SSL/TLS 通信。参见 RFC 2595。
IMAPS
安全 IMAP 是通过 SSL/TLS 连接进行的 IMAP。此类连接隐式地开始使用 SSL/TLS,因此服务器和客户端使用 TCP 端口 993 互相通信。参见 RFC 8314。
LDAP
轻量级目录访问协议(LDAP)是一种用于访问和维护分布式目录信息的协议。基本上是一个数据库查找。参见 RFC 4511。LDAP 服务器和客户端使用 TCP 端口 389。
LDAPS
安全 LDAP 是通过 SSL/TLS 连接进行的 LDAP。
MQTT
消息队列遥测传输,MQTT,是一种在物联网系统中常用,主要用于涉及较小设备的协议。它是一种所谓的“发布-订阅”协议。
POP3
第 3 版邮局协议(POP3)是一种从服务器检索电子邮件的协议。参见 RFC 1939。POP3 服务器和客户端使用 TCP 端口 110。虽然连接到服务器最初是明文,但客户端可以通过显式请求使用STLS命令升级连接来支持 SSL/TLS 通信。参见 RFC 2595。
POP3S
安全 POP3 是通过 SSL/TLS 连接进行的 POP3。此类连接隐式地开始使用 SSL/TLS,因此服务器和客户端使用 TCP 端口 995 进行通信。参见 RFC 8314。
RTMP
实时消息传输协议(RTMP)是一种用于流式传输音频、视频和数据的协议。RTMP 服务器和客户端使用 TCP 端口 1935。
RTSP
实时流式传输协议(RTSP)是一种用于控制流媒体服务器的网络控制协议。参见 RFC 2326。RTSP 服务器和客户端使用 TCP 和 UDP 端口 554。
SCP
安全复制(SCP)协议旨在将文件复制到远程 SSH 服务器或从其复制。SCP 服务器和客户端使用 TCP 端口 22。
SFTP
SSH 文件传输协议(SFTP)提供通过可靠数据流进行文件访问、文件传输和文件管理的功能。SFTP 服务器和客户端使用 TCP 端口 22。
SMB
服务器消息块(SMB)协议也称为 CIFS。它是一种应用层网络协议,主要用于提供对文件、打印机和串行端口以及网络节点之间各种通信的共享访问。SMB 服务器和客户端使用 TCP 端口 445。
SMBS
通过 TLS 进行的 SMB。
SMTP
简单邮件传输协议(SMTP)是一种用于电子邮件传输的协议。参见 RFC 5321。SMTP 服务器和客户端使用 TCP 端口 25。虽然连接到服务器最初是明文,但客户端可以通过显式请求使用STARTTLS命令来支持 SSL/TLS 通信。参见 RFC 3207。
SMTPS
安全 SMTP 是通过 SSL/TLS 连接进行的 SMTP。此类连接隐式地开始使用 SSL/TLS,因此服务器和客户端使用 TCP 端口 465 进行通信。参见 RFC 8314。
TELNET
TELNET 是一种在网络中使用的应用层协议,它通过虚拟终端连接提供双向交互式文本通信设施。参见 RFC 854。TELNET 服务器和客户端使用 TCP 端口 23。
TFTP
简单文件传输协议(TFTP)是一种通过 UDP 进行简单文件传输的协议,用于从远程主机获取文件或将文件放置在远程主机上。TFTP 服务器和客户端使用 UDP 端口 69。
WS
WebSocket 是一种双向的类似 TCP 协议,通过 HTTP(S) 请求进行设置。WS 是在普通 HTTP 上完成的明文版本方案。curl 7.86.0 中添加了对这种方案的实验性支持。
WSS
WebSocket 是一种双向的类似 TCP 协议,通过 HTTP(S) 请求进行设置。WSS 是在 HTTPS 上完成的加密版本方案。curl 7.86.0 中添加了对这种方案的实验性支持。
HTTP 基础
HTTP 是一个易于学习基础的协议。客户端连接到服务器——并且总是客户端采取主动——发送请求并接收响应。请求和响应都由头部和主体组成。在两个方向上可能会有很少或很多信息传输。
客户端发送的 HTTP 请求以请求行开始,随后是头部信息,然后可选地跟一个主体。最常见的一种 HTTP 请求可能是 GET 请求,它要求服务器返回一个特定的资源,并且这种请求不包含主体。
当客户端连接到 ‘example.com’ 并请求 ‘/’ 资源时,它发送一个没有请求主体的 GET 请求:
GET / HTTP/1.1
User-agent: curl/2000
Host: example.com
…服务器可能会响应如下内容,包括响应头部和响应主体(‘hello’)。响应的第一行还包含了响应代码和服务器支持的具体版本:
HTTP/1.1 200 OK
Server: example-server/1.1
Content-Length: 5
Content-Type: plain/texthello
如果客户端发送一个包含小请求主体(‘hello’)的请求,它可能看起来像这样:
POST / HTTP/1.1
Host: example.com
User-agent: curl/2000
Content-Length: 5hello
服务器总是会响应 HTTP 请求,除非出现错误。
将 URL 转换为请求
因此,当 HTTP 客户端被赋予一个要操作的 URL 时,该 URL 被使用,分解,这些部分被用于向服务器发送的出站请求的各个地方。让我们以一个示例 URL 为例:
https://www.example.com/path/to/file
-
https 表示 curl 使用 TLS 连接到远程端口 443(这是在 URL 中未指定端口时的默认端口号)。
-
www.example.com 是 curl 解析到的主机名,它会解析为一个或多个 IP 地址以进行连接。这个主机名也用于 HTTP 请求中的
Host:头部。 -
/path/to/file 在 HTTP 请求中用于告诉服务器 curl 想要获取的确切文档/资源
安装 curl 和 libcurl
curl 是完全免费、开源且可用的。对于大多数操作系统和架构,有多种方式可以获取并安装它。本节为您提供了开始的一些答案,但不是完整的参考。
一些操作系统默认安装了 curl,而另一些则没有。
此外,您始终可以从curl.se下载源代码,或从那里找到可下载的二进制包。
通常,libcurl 会与 curl 一起安装。
-
Linux
-
Windows
-
macOS
-
容器
Linux
Linux 发行版自带了包管理器,允许您安装它们提供的软件。大多数 Linux 发行版如果默认没有安装,会提供 curl 和 libcurl 的安装选项。
Ubuntu 和 Debian
apt是用于在 Debian Linux 和 Ubuntu Linux 及其衍生版上安装预构建包的工具。
为了安装 curl 命令行工具,通常您只需
apt install curl
确保依赖项已安装,通常 libcurl 也会作为一个单独的包一起安装。
如果您想针对 libcurl 构建应用程序,您需要安装一个开发包以获取包含头文件和一些额外文档等。然后您可以选择您喜欢的具有 TLS 后端的 libcurl:
apt install libcurl4-openssl-dev
或者
apt install libcurl4-gnutls-dev
Redhat 和 CentOS
在 Redhat Linux 和 CentOS Linux 衍生版中,您使用yum来安装包。使用以下命令安装命令行工具:
yum install curl
您可以使用以下方式安装 libcurl 开发包(包括包含文件和一些文档等):
yum install libcurl-devel
Fedora
Fedora Workstation 和其他基于 Fedora 的发行版使用dnf来安装包。
使用以下命令安装命令行工具:
dnf install curl
为了安装 libcurl 开发包,您需要运行:
dnf install libcurl-devel
不可变的 Fedora 发行版
如 Silverblue、Kinoite、Sericea、Onyx 等发行版使用rpm-ostree来安装包。请记住在安装后重启系统。
rpm-ostree install curl
为了安装 libcurl 开发包,您需要运行:
rpm-ostree install libcurl-devel
nix
Nix是 NixOS 发行版的默认包管理器,但它也可以在任何 Linux 发行版中使用。
为了安装命令行工具:
nix-env -i curl
Arch Linux
curl 位于 Arch Linux 的核心仓库中。这意味着如果您遵循正常的安装程序,它应该会自动安装。
如果 curl 未安装,Arch Linux 使用pacman来安装包:
pacman -S curl
SUSE 和 openSUSE
在 SUSE Linux 和 openSUSE Linux 中,您使用zypper来安装包。为了安装 curl 命令行工具:
zypper install curl
为了安装 libcurl 开发包,您需要运行:
zypper install libcurl-devel
SUSE SLE Micro 和 openSUSE MicroOS
这些版本的 SUSE/openSUSE Linux 是不可变的操作系统,具有只读的根文件系统,为了安装包,您将使用transactional-update而不是zypper。为了安装 curl 命令行工具:
transactional-update pkg install curl
并且为了安装 libcurl 开发包:
transactional-update pkg install libcurl-devel
Gentoo
此包安装了工具、libcurl、头文件和 pkg-config 文件等
emerge net-misc/curl
Void Linux
在 Void Linux 中,您使用xbps-install来安装包。为了安装 curl 命令行工具:
xbps-install curl
为了安装 libcurl 开发包:
xbps-install libcurl-devel
Windows
自 1804 版本起,Windows 10 随操作系统捆绑了 curl 工具。如果您使用的是较旧的 Windows 版本或者只想升级到 curl 项目发布的最新版本,请从 curl.se/windows 下载最新的官方 curl Windows 版本,并安装它。
在您的 Windows 系统上安装 curl 和 libcurl 有几种不同的方法:
-
MSYS2
-
vcpkg
MSYS2
MSYS2是基于mingw-w64的流行 Windows 构建系统,包括 gcc 和 clang 编译器。MSYS2 使用名为pacman(从 arch-linux 移植而来)的包管理器,并拥有大约 2000 个预编译的mingw-packages。MSYS2 旨在构建独立软件:使用 mingw-w64 编译器构建的二进制文件不依赖于 MSYS2 本身[¹]。
在 MSYS2 上获取 curl 和 libcurl
关于mingw-w64-curl包的当前信息可以在 msys2 网站上找到:https://packages.msys2.org/base/mingw-w64-curl。我们还可以在此找到各种可用版本的安装说明。例如,要安装 curl 的默认 x64 二进制文件,我们运行:
pacman -Sy mingw-w64-x86_64-curl
此包包含curl命令行工具以及 libcurl 头文件和共享库。默认的curl包使用 OpenSSL 后端构建,因此依赖于mingw-w64-x86_64-openssl。还有mingw-w64-x86_64-curl-gnutls和mingw-w64-x86_64-curl-gnutls包,有关更多详细信息,请参阅msys2 网站。
就像在 Linux 上一样,我们可以使用pkg-config来查询构建针对 libcurl 所需的标志。使用 mingw64 shell(它会自动设置路径以包含/mingw64)启动 msys2 并运行:
pkg-config --cflags libcurl
# -IC:/msys64/mingw64/includepkg-config --libs libcurl
# -LC:/msys64/mingw64/lib -lcurl
pacman 包管理器安装预编译的二进制文件。接下来,我们将解释如何使用 pacman 本地构建 curl,例如自定义配置。
在 MSYS2 上构建 libcurl
使用 pacman 构建包几乎和安装一样简单。整个流程都包含在mingw-w64-curl包的PKGBUILD文件中。我们可以轻松修改该文件以自行重新构建包。
如果我们从干净的 msys2 安装开始,我们首先想要安装一些构建工具,如autoconf、patch和git。启动 msys2 shell 并运行:
# Sync the repositories
pacman -Syu# Install git, autoconf, patch, etc
pacman -S git base-devel# Install GCC for x86_64
pacman -S mingw-w64-x86_64-toolchain
现在克隆mingw-packages仓库并转到mingw-w64-curl包:
git clone https://github.com/msys2/MINGW-packages
cd MINGW-packages/mingw-w64-curl
此目录包含用于构建 curl 的 PKGBUILD 文件和补丁。查看 PKGBUILD 文件以了解发生了什么。现在要编译它,我们可以这样做:
makepkg-mingw --syncdeps --skippgpcheck
就这样。--syncdeps参数会自动检查并提示安装mingw-w64-curl的依赖项(如果尚未安装)。一旦过程完成,当前目录中就会有 3 个新文件,例如:
-
pacman -U mingw-w64-x86_64-curl-7.80.0-1-any.pkg.tar.zst -
pacman -U mingw-w64-x86_64-curl-gnutls-7.80.0-1-any.pkg.tar.zst -
pacman -U mingw-w64-x86_64-curl-winssl-7.80.0-1-any.pkg.tar.zst
使用pacman -u命令安装此类本地包文件:
pacman -U mingw-w64-x86_64-curl-winssl-7.80.0-1-any.pkg.tar.zst
查看 msys2 文档 或加入 gitter 以了解更多关于使用 pacman 和 msys2 进行构建的信息。
[¹]: 请注意不要将 mingw-package 的 mingw-w64-curl 与 msys-packages 的 curl 和 curl-devel 混淆。后者是 msys2 环境本身的一部分(例如,用于支持 pacman 下载),但不适合分发。要构建不依赖于 MSYS2 本身的可分发软件,你始终需要 mingw-w64-… 软件包和工具链。
vcpkg
Vcpkg 帮助您在 Windows、Linux 和 MacOS 上管理 C 和 C++ 库。
在 vcpkg 上没有 curl 包,只有 libcurl。
安装 libcurl
vcpkg.exe install curl:x64-windows
macOS
macOS 已经将 curl 工具捆绑在操作系统中多年。如果你想升级到 curl 项目提供的最新版本,我们建议安装 homebrew(一个 macOS 软件包管理器),然后从那里安装 curl 包:
brew install curl
注意,在安装 curl 时,brew 不会在默认的 homebrew 文件夹中创建一个 curl 符号链接,以避免与 macOS 版本的 curl 发生冲突。
运行以下命令以使 brew curl 成为你的 shell 中的默认版本:
echo 'export PATH="$(brew --prefix)/opt/curl/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
获取 macOS 的 libcurl
当你按照上述描述使用 homebrew 安装 curl 工具时,它也会一起安装 libcurl 及其相关的头文件。
libcurl 也随着 macOS 本身安装,并且始终可用。如果你从 Apple 安装了开发环境 XCode,你可以直接使用 libcurl,无需安装任何额外的东西,因为 curl 的包含文件已经包含在其中。
容器
docker和podman都是容器化工具。docker 镜像托管在hub.docker.com/r/curlimages/curl
您可以使用以下命令运行 curl 的最新版本:
docker的命令:
docker run -it --rm docker.io/curlimages/curl www.example.com
podman的命令:
podman run -it --rm docker.io/curlimages/curl www.example.com
在容器中无缝运行 curl
可以创建一个别名,以便在容器中无缝运行 curl,就像它在宿主操作系统上安装的原生应用程序一样。
在 Bash、ZSH、Fish shell 中将 curl 定义为容器化工具别名的命令:
Bash 或 zsh
使用docker调用 curl:
alias curl='docker run -it --rm docker.io/curlimages/curl'
使用podman调用 curl:
alias curl='podman run -it --rm docker.io/curlimages/curl'
鱼类
使用docker调用 curl:
alias -s curl='docker run -it --rm docker.io/curlimages/curl'
使用podman调用 curl:
alias -s curl='podman run -it --rm docker.io/curlimages/curl'
简单调用curl www.example.com来发送请求
在 kubernetes 中运行 curl
有时使用 curl 来排查 k8s 网络问题可能很有用,就像:
kubectl run -i --tty curl --image=curlimages/curl --restart=Never \-- "-m 5" www.example.com
源代码
源代码当然是这个项目的实际引擎部分。毕竟,它是一个软件项目。
curl 和 libcurl 是用 C 语言编写的。
主机和下载
您可以在官方 curl 网站上找到最新 curl 和 libcurl 版本的源代码 curl.se。同时,还提供了校验和数字签名,以帮助您验证下载到本地系统中的文件与 curl 团队最初上传的原始文件中的字节顺序是否相同。
如果您更愿意直接从源代码仓库工作与 curl 源代码,您可以在 curl GitHub 仓库 中找到所有详细信息。
克隆代码
git clone https://github.com/curl/curl.git
这将在您的本地系统上的一个目录中下载并解压最新的 curl 代码。
-
开源
-
代码布局
-
处理构建选项
-
代码风格
-
贡献
-
报告漏洞
-
网站
开源
什么是开源
通常,开源软件是可以被任何人自由访问、使用、更改和共享(以修改或未修改的形式)的软件。开源软件通常由许多人制作,并按照定义的许可证进行分发。
自由软件是一个较老的、相关的术语,从我们所有的目的和用途来看,它基本上意味着相同的事情,但为了简便起见,我们在本文档中坚持使用“开源”这个术语。
-
许可证
-
版权
许可证
curl 和 libcurl 在一个名为 MIT 许可证的开放源代码许可下分发。它简短、简单且易于理解。以下是完整内容:
COPYRIGHT AND PERMISSION NOTICECopyright © 1996 - 2024, Daniel Stenberg, <daniel@haxx.se>, and many
contributors, see the THANKS file.All rights reserved.Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
THIRD PARTY RIGHTS. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization of
the copyright holder.
这是一段法律术语,表示你被允许更改代码、重新分发代码、重新分发由代码构建的二进制文件,以及使用它构建专有代码,但任何人都不要求你将任何更改反馈给项目——但你不能声称是你编写的。
在项目早期,我们在确定这个许可之前尝试过几种不同的其他许可。我们最初是 GPL,然后尝试了 MPL,最终选择了这个 MIT 衍生许可证。我们无意再次更改许可证。
著作权
著作权是由一个国家的法律授予原创作品创作者对其使用和分发享有专有权利的法律权利。
版权所有者可以通过许可其作品来同意允许他人使用。这就是我们在 curl 项目中做的事情。版权是许可工作的基础。
Daniel Stenberg 是 curl 项目中大多数版权的所有者。
独立
许多开源项目都在伞形组织中运行。这些组织包括 GNU 项目、Apache 软件基金会、资助项目的更大公司或类似组织。curl 项目不属于任何这样的更大组织,而是完全独立和自由的。
没有公司控制 curl 的命运,curl 项目也不需要遵循任何伞形组织的指南。
curl 不是一个正式的公司、组织或任何类型的法律实体。curl 只是一个非正式的人类集合,分布在全球各地,他们共同参与一个软件项目。
合法
curl 项目遵守其工作国家的国家法律。然而,它是一个高度可见的国际项目,几乎可以在地球上的每个国家下载和使用,因此在使用 curl 时可能会违反某些当地法律。这只是它的本质,如果不确定,你应该检查你自己的当地情况。
已有涉及 curl 提供技术的诉讼。作者所知的一个案例是在美国的一个专利案件,他们坚持认为他们有权恢复文件传输。
作为一种通用的软件组件,它可以在任何地方供每个人使用,有时 libcurl——特别是——被用于邪恶或直接恶意的方式。例如,被用于病毒和恶意软件中。这是不幸的,但我们无法阻止。
代码布局
curl 源代码树既不大也不复杂。要记住的关键一点是 libcurl 是库,而这个库是 curl 命令行工具的最大组件。
root
我们试图将源树根目录中的文件数量保持在最低。如果您检查发布存档与 git 仓库中存储的内容相比,可能会看到文件有细微的差异,因为一些文件是由发布脚本生成的。
其中一些较为显著的包括:
-
buildconf:(已弃用)用于从 git 仓库中构建 curl 源代码时构建 configure 和其他内容的脚本。 -
buildconf.bat:buildconf 的 Windows 版本。在从 git 检出完整源代码后运行此脚本。 -
CHANGES:在发布时生成并放入发布存档。它包含源代码库中的 1000 个最新更改。 -
configure:一个生成的脚本,用于在 Unix-like 系统上构建 curl 时生成设置。 -
COPYING:详细说明使用代码规则的许可证。 -
GIT-INFO:仅在 git 中存在,包含有关从 git 检出代码后如何构建 curl 的信息。 -
maketgz:用于生成发布存档和每日快照的脚本 -
README:curl 和 libcurl 的简要总结。 -
RELEASE-NOTES:包含最新发布版本所做的更改;当在 git 中找到时,包含自上次发布以来旨在进入即将发布版本中的更改。
lib
此目录包含 libcurl 的完整源代码。它是所有平台的相同源代码——超过一百个 C 源文件和一些额外的私有头文件。用于构建针对 libcurl 的应用程序时使用的头文件不存储在此目录中;请参阅 include/curl。
根据您自己的构建中启用的功能和您的平台提供的功能,某些源文件或源文件的部分可能包含在您的特定构建中未使用的代码。
lib/vtls
libcurl 中的 VTLS 子部分是所有 libcurl 可以构建以支持的所有 TLS 后端的家园。这个“虚拟”TLS 内部 API 是一个与后端无关的 API,用于内部访问 TLS 和加密函数,而主代码不知道使用了哪个特定的 TLS 库。这允许构建 libcurl 的人从广泛的 TLS 库中选择来构建。
我们还在网站上维护了一个SSL 比较表,以帮助用户。
-
AmiSSL:为 AmigaOS 制作的 OpenSSL 分支(使用
openssl.c) -
BearSSL
-
BoringSSL:由 Google 维护的 OpenSSL 分支。(使用
openssl.c) -
GnuTLS
-
LibreSSL:由 OpenBSD 团队维护的 OpenSSL 分支。(使用
openssl.c) -
mbedTLS
-
OpenSSL
-
rustls:用 Rust 编写的 TLS 库
-
Schannel:Windows 上的原生 TLS 库。
-
安全传输:macOS 上的原生 TLS 库
-
wolfSSL
src
此目录包含 curl 命令行工具的源代码。这是所有运行此工具的平台相同的源代码。
命令行工具所做的多数工作是将给定的命令行选项转换为相应的 libcurl 选项或选项集,然后确保正确地发出它们,以根据用户的意愿驱动网络传输。
此代码像任何其他应用程序一样使用 libcurl。
include/curl
这里提供了供 libcurl 使用应用程序使用的公共头文件。其中一些在配置或发布时生成,因此在 git 仓库中看起来与发布存档中的不完全相同。
在现代 libcurl 中,应用程序预期在其 C 源代码中包含的只是#include <curl/curl.h>。
docs
主要的文档位置。此目录中的文本文件通常是纯文本文件。我们已经开始逐渐转向 Markdown 格式,因此一些(但数量正在增加)文件使用.md扩展名来表示。
大多数这些文档也会自动显示在 curl 网站上,从文本转换为友好的网页格式/外观。
-
BINDINGS: 列出所有已知的 libcurl 语言绑定及其位置 -
BUGS: 如何报告错误及其位置 -
CODE_OF_CONDUCT.md: 我们期望人们在项目中如何表现 -
CONTRIBUTE: 在为项目做出贡献时需要考虑的事项 -
curl.1: curl 命令行工具的 man 页面,采用 nroff 格式 -
curl-config.1: curl-config 的 man 页面,采用 nroff 格式 -
FAQ: 关于各种 curl 相关主题的常见问题 -
FEATURES: curl 功能的未完成列表 -
HISTORY: 描述项目是如何开始的以及多年来是如何发展的 -
HTTP2.md: 如何使用 curl 和 libcurl 进行 HTTP/2 -
HTTP-COOKIES: curl 如何支持和使用 HTTP cookies -
index.html: 作为文档索引页面的基本 HTML 页面 -
INSTALL: 如何从源代码构建和安装 curl 和 libcurl -
INSTALL.cmake: 如何使用 CMake 构建 curl 和 libcurl -
INSTALL.devcpp: 如何使用 devcpp 构建 curl 和 libcurl -
INTERNALS: curl 和 libcurl 内部结构的详细信息 -
KNOWN_BUGS: 已知错误和问题的列表 -
LICENSE-MIXING: 描述如何组合不同的第三方模块及其各自的许可证 -
MAIL-ETIQUETTE: 在我们的邮件列表上如何进行沟通 -
MANUAL: 关于如何使用 curl 的教程式指南 -
mk-ca-bundle.1: mk-ca-bundle 工具的 man 页面,采用 nroff 格式 -
README.cmake: CMake 详细信息 -
README.netware: Netware 详细信息 -
README.win32: win32 详细信息 -
RELEASE-PROCEDURE: 如何进行 curl 和 libcurl 的发布 -
RESOURCES: 进一步阅读 curl 如何做事情的更多资源 -
ROADMAP.md: 我们未来想要工作的内容 -
SECURITY: 我们如何处理安全漏洞 -
SSLCERTS: TLS 证书处理的文档 -
SSL-PROBLEMS: 常见 SSL 问题和其原因 -
THANKS: 感谢这个广泛的友好人士名单,curl 今天得以存在。 -
TheArtOfHttpScripting:使用 curl 进行 HTTP 脚本编写的教程 -
TODO:我们可以或你可以着手实现的事情 -
VERSIONS:libcurl 版本编号的工作方式
docs/libcurl
所有 libcurl 函数都有自己的 man 页面,以 .3 扩展名存储在单独的文件中,使用 nroff 格式,位于此目录中。还有一些其他文件,如下所述。
-
ABI -
index.html -
libcurl.3 -
libcurl-easy.3 -
libcurl-errors.3 -
libcurl.m4 -
libcurl-multi.3 -
libcurl-share.3 -
libcurl-thread.3 -
libcurl-tutorial.3 -
symbols-in-versions
docs/libcurl/opts
此目录包含三个不同 libcurl 函数的单独选项的 man 页面。
curl_easy_setopt() 选项以 CURLOPT_ 开头,curl_multi_setopt() 选项以 CURLMOPT_ 开头,curl_easy_getinfo() 选项以 CURLINFO_ 开头。
docs/examples
包含大约 100 个独立的示例,旨在帮助读者理解如何使用 libcurl。
参见本书的 libcurl 示例部分。
scripts
方便的脚本。
-
contributors.sh:从给定的哈希/标签以来的 git 仓库中提取所有贡献者。目的是为 RELEASE-NOTES 文件生成一个列表,并允许在更新时手动添加的名称仍然保留在那里。该脚本使用THANKS-filter文件来重写一些名称。 -
contrithanks.sh:从给定的哈希/标签以来的 git 仓库中提取贡献者,过滤掉所有已在THANKS中提到的名称,然后将THANKS输出到 stdout,并在末尾附加新贡献者的列表;目的是允许更容易地更新THANKS文档。该脚本使用THANKS-filter文件来重写一些名称。 -
log2changes.pl:为发布版本生成CHANGES文件,如发布脚本中使用的那样。它只是简单地转换 git log 输出。 -
zsh.pl:为 zsh 壳的用户提供 curl 命令行补全的辅助脚本。
处理构建选项
curl 和 libcurl 的源代码被精心编写,以便在几乎所有的计算机平台上构建和运行。这只能通过辛勤工作和遵循一些指南(当然,还有相当数量的测试)来实现。
一个黄金法则是在代码中始终添加 #ifdef 来检查特定功能,然后让设置脚本(configure 或 CMake 或硬编码)在程序编译到用户的计算机配置之前检查这些功能是否存在。此外,作为额外的好处,由于这种编写代码的方式,即使某些功能在系统中存在并且可以使用,也可以明确地关闭它们。例如,当用户想要构建一个具有更小体积或禁用某些协议支持的库版本时,情况就是这样。
当例如提供一个特定操作系统或可能不是始终存在的特定功能的单个文件时,项目有时会在整个源文件周围使用 #ifdef 保护。这样做是为了使所有平台始终能够构建所有文件——这大大简化了构建脚本和 makefile。无论如何,完全 #ifdef 出来的文件几乎不会增加构建时间。
而不是在代码中到处撒 #ifdef,只要可能,我们提供函数和宏,使代码看起来和工作方式相同,不受现有功能的影响。其中一些是空宏,用于缺少这些功能的构建。
TLS 处理和名称解析都使用一个内部 API 来处理,该 API 隐藏了特定的实现和第三方软件库的选择。这样,大多数内部工作在 libcurl 被告知使用哪个 TLS 库或名称解析系统时都是相同的。
代码风格
具有共同样式的源代码比在不同地方使用不同样式的代码更容易阅读。这有助于使代码感觉像是一个连续的代码库。易于阅读是代码的一个重要属性,有助于在添加新内容时更容易进行代码审查,并在开发者试图弄清楚事情出错的原因时有助于调试代码。统一的样式比个人贡献者满足个人口味更重要。
我们的 C 代码有一些风格规则。其中大部分由checksrc.pl脚本验证和执行。可以通过make checksrc调用,或者在./configure --enable-debug被使用后,构建系统默认执行。
通常,遵循指南对任何人来说都不是问题,因为你只需要复制源代码中已经使用的样式,并且在我们的规则集中没有特别不寻常的规则。
我们还努力编写在所有主要平台以及尽可能多的平台上无警告的代码。显然会导致警告的代码不接受原样使用。
命名
尝试为你的新函数和变量名使用一个不令人困惑的命名方案。这并不意味着你必须在代码的其他地方使用相同的命名,只是名字应该是逻辑的、可理解的,并且根据它们的使用目的来命名。文件局部函数应该被定义为静态的。我们喜欢小写命名。
所有供公共使用的符号都必须以curl开头。全局内部符号以Curl开头。
缩进
我们只使用空格进行缩进,从不使用制表符。每个新的开括号使用两个空格。
if(something_is_true) {while(second_statement == fine) {moo();}
}
注释
由于我们编写的是 C89 代码,不允许使用//注释。它们直到 C99 标准中才被引入。我们只使用/*注释*/。
/* this is a comment */
长行
curl 中的源代码宽度不得超过 79 列。即使在现代大屏幕和高分辨率屏幕时代,保持这一规定的两个原因如下:
-
较窄的列比宽列更容易阅读。这也是为什么报纸几十年来或几个世纪来都使用栏目的原因。
-
较窄的列宽允许开发者更容易地在不同的窗口中并排查看多段代码。我经常在同一屏幕上并排打开两到三个源代码窗口,以及多个终端和调试窗口。
花括号
在 if/while/do/for 表达式中,我们将开括号写在关键字所在的同一行上,然后我们将闭合括号设置在初始关键字相同的缩进级别上。如下所示:
if(age < 40) {/* clearly a youngster */
}
如果括号中只包含一行语句,你可以省略括号:
if(!x)continue;
对于函数,开括号应该放在单独的一行上:
int main(int argc, char **argv)
{return 1;
}
在下一行使用 else
当使用花括号给条件表达式添加else子句时,我们在闭合花括号之后的新行上添加它。如下所示:
if(age < 40) {/* clearly a youngster */
}
else {/* probably intelligent */
}
括号前不加空格
当使用 if/while/do/for 编写表达式时,关键字与开括号之间不应有空格。例如:
while(1) {/* loop forever */
}
使用布尔条件
而不是在 if/while 条件中测试条件值,如布尔值与 TRUE 或 FALSE,指针与 NULL 或不为 NULL,整数与零或不为零,我们更喜欢:
result = do_something();
if(!result) {/* something went wrong */return result;
}
条件中不允许有赋值操作
为了提高可读性和减少条件语句的复杂性,我们避免在 if/while 条件中赋值变量。我们反对这种风格:
if((ptr = malloc(100)) == NULL)return NULL;
相反,我们鼓励使用更清晰的上述版本:
ptr = malloc(100);
if(!ptr)return NULL;
在新的一行上创建新的区块
我们从不将多个语句写在同一源行上,即使对于短的 if()条件也是如此。
if(a)return TRUE;
else if(b)return FALSE;
永远不要:
if(a) return TRUE;
else if(b) return FALSE;
操作符周围的空格
请在 C 表达式中操作符两侧使用空格。后缀(), [], ->, ., ++, –和一元+, -, !, ~, &操作符除外,它们不应有空格。
示例:
bla = func();
who = name[0];
age += 1;
true = !false;
size += -2 + 3 * (a + b);
ptr->member = a++;
struct.field = b--;
ptr = &address;
contents = *pointer;
complement = ~bits;
empty = (!*string) ? TRUE : FALSE;
返回值不需要括号
我们使用带括号的‘return’语句来返回值:
int works(void)
{return TRUE;
}
sizeof 参数的括号
当在代码中使用 sizeof 运算符时,我们更喜欢将其参数用括号括起来:
int size = sizeof(int);
列对齐
有些语句不能在单行上完成,因为行会太长,语句难以阅读,或者由于其他风格指南。在这种情况下,语句跨越多行。
如果续行符是表达式或子表达式的部分,那么你应该在适当的列上对齐,以便容易判断它是语句的哪一部分。操作符不应开始续行符。在其他情况下,遵循 2 个空格缩进指南。以下是一些来自 libcurl 的示例:
if(Curl_pipeline_wanted(handle->multi, CURLPIPE_HTTP1) &&(handle->set.httpversion != CURL_HTTP_VERSION_1_0) &&(handle->set.httpreq == HTTPREQ_GET ||handle->set.httpreq == HTTPREQ_HEAD))/* did not ask for HTTP/1.0 and a GET or HEAD */return TRUE;
如果没有括号,使用默认缩进:
data->set.http_disable_hostname_check_before_authentication =(0 != va_arg(param, long)) ? TRUE : FALSE;
使用开括号进行函数调用:
if(option) {result = parse_login_details(option, strlen(option),(userp ? &user : NULL),(passwdp ? &passwd : NULL),NULL);
}
与“当前打开”的括号对齐:
DEBUGF(infof(data, "Curl_pp_readresp_ %d bytes of trailing ""server response left\n",(int)clipamount));
平台相关代码
使用#ifdef HAVE_FEATURE来进行条件代码。我们避免在#ifdef 行中检查特定的操作系统或硬件。HAVE_FEATURE 应由 unix-like 系统的 configure 脚本来生成,而对于其他系统则硬编码在config-[system].h文件中。
我们还鼓励使用宏/函数,当 libcurl 构建时没有该功能,它们可能是空的或定义为常量,以使代码无缝。例如,magic()函数根据构建时的条件而有所不同:
#ifdef HAVE_MAGIC
void magic(int a)
{return a + 2;
}
#else
#define magic(x) 1
#endifint content = magic(3);
不使用 typedef 的结构体
尽可能使用结构体,但不要为它们使用 typedef。使用struct name的方式来标识它们:
struct something {void *valid;size_t way_to_write;
};
struct something instance;
不合适:
typedef struct {void *wrong;size_t way_to_write;
} something;
something instance;
贡献
贡献意味着帮助。
当你向项目贡献任何内容——代码、文档、错误修复、建议或仅仅是好的建议——我们假设你是经过授权的,并且你提供这些内容没有违反任何合同或法律。如果你没有授权,请不要向我们贡献。
向像 curl 这样的项目贡献可能有很多不同的方式。虽然源代码是构建产品的必需品,但我们还依赖于良好的文档、测试(包括测试代码和测试基础设施)、网络内容、用户支持和更多。
将你的更改或建议发送给团队,通过共同努力,我们可以修复问题、改进功能、澄清文档、添加功能或帮助你使任何其他你帮助的事情得到适当的处理。我们确保改进的代码和文档正确地合并到源树中,其他类型的贡献也适合接收。
请通过 邮件列表、提交问题或提交拉取请求来发送你的贡献。
建议
想法容易,实现困难。是的,我们确实欣赏好的想法和建议,关于做什么以及如何做,但如果你也愿意参与将想法变为现实,那么这些想法真正成为实际功能的可能性会大大增加。
我们已经在 TODO 文档中收集了想法,并且通常对流行的网络协议的当前趋势有所了解,所以通常不需要提醒我们那些。
应添加的内容
向 curl 或 libcurl 添加任何内容的最佳方法当然是首先将你的想法和建议提交给 curl 项目团队成员,然后与他们讨论这个想法是否适合添加,以及如何以最佳方式实现——假设你想将其合并到源代码仓库中。
项目通常批准那些改善当前协议支持的函数,特别是那些流行的客户端或浏览器有但 curl 还缺少的功能。
当然,你也可以向项目添加非代码内容,如文档、图形或网站内容,但一般规则同样适用于此。
如果你正在修复你遇到的问题或其他人报告的问题,我们非常高兴收到你的修复并将其尽快合并,
不应添加的内容
没有好的规则说明你可以添加或不能添加哪些功能,或者我们永远不会接受,但让我试着提几点你应该避免的事情,以减少摩擦并更快地成功:
-
不要先写一个大补丁然后发送到列表中进行讨论。始终先在列表上讨论,并尽早发送你的初步审查请求,以便获得对你设计和方法的反馈。这样可以避免浪费时间走上一条最终可能需要重写的路线。
-
当在代码中引入新内容时,您需要遵循已经存在的风格和架构。当您向普通传输代码路径添加代码时,例如,它必须以非阻塞方式异步工作。我们不接受引入阻塞行为的代码——我们已经有太多尚未移除的这类代码。
-
快速修补或脏解决方案,这些方案在您不运行的平台或您不熟悉的架构上可能无法正常工作。我们不关心您是否急于求成或它对您是否有效。我们不接受高风险代码或难以阅读或理解的代码。
-
破坏构建的代码。当然,我们承认有时我们必须添加代码到某些区域,使得新功能可能依赖于特定的第三方库或特定的操作系统等,但我们永远不能以牺牲所有其他系统为代价这样做。我们不破坏构建,并确保所有测试都能成功运行。
git
我们首选的源代码管理工具是git。
虽然有时 git 不是最容易学习和掌握的工具,但一个普通开发者和贡献者需要了解的所有基本步骤都是直接的,并且学习它们不需要花费太多时间或精力。
这本书不能帮助您学习 git。然而,在这个时代,所有软件开发者都应该学习 git。
您可以通过我们的 GitHub 页面上的网页浏览器浏览 curl git 树,网址为github.com/curl/curl。
要从 git 检出 curl 源代码,您可以像这样克隆它:
git clone https://github.com/curl/curl.git
拉取请求
在 GitHub 上通过所谓的拉取请求(pull request)来做出自己的更改并将它们贡献回项目是一种流行且方便的方式。
首先,在 GitHub 网站上创建自己的源代码版本,称为分支(fork)。这样,您就可以克隆自己的 curl git 树到本地副本。
您编辑自己的本地副本,提交更改,然后将它们推送到 GitHub 上的 git 仓库,然后在 GitHub 网站上,您可以根据对原始 curl 仓库本地副本所做的更改选择创建一个拉取请求。
我们建议您在专门的独立分支上完成打算用于拉取请求的工作,而不是在 master 分支上,这样在审查后或如果您意识到这是一个死胡同并决定将其丢弃时,您就可以更容易地更新拉取请求。
为邮件列表制作补丁
即使您选择不创建拉取请求而更喜欢传统的、可靠的将补丁发送到 curl-library 邮件列表的方法,仍然是一个好习惯,在本地 git 分支上工作并在此处提交您的更改。
分支使得在需要更改内容时编辑和变基变得容易,并且当上游更新时,它也使得保持与 master 分支的同步变得容易。
一旦你的提交足够好,可以发送到邮件列表,你只需使用git format-patch创建补丁并发送即可。甚至更高级的用户会直接使用git send-email,让 git 自己发送邮件。
git 提交风格
当你在 git 中提交补丁时,你给出一个提交信息来描述你正在提交的更改。我们在项目中有一个特定的风格,我们要求你使用:
[area]: [short line describing the main effect][separate the above single line from the rest with an empty line][full description, no wider than 72 columns that describes as much as
possible as to why this change is made, and possibly what things
it fixes and everything else that is related][Bug: link to source of the report or more related discussion]
[Reported-by: John Doe—credit the reporter]
[whatever-else-by: credit all helpers, finders, doers]
不要忘记,如果你提交的是他人的作品,请使用git commit --author="Jane Doe <jane@example.com>",并确保在通过以下命令提交之前,你的 GitHub 用户名和电子邮件在 git 中设置正确:
git config --global user.name "johndoe"
git config --global user.email "johndoe@example.com"
作者和*-by:行当然是为了确保我们在项目中给予适当的认可。我们不希望未经明确说明来源就使用他人的作品。给予正确的认可至关重要。
谁决定什么可以进入?
首先,可能对每个人来说并不明显,但当然只有有限的一组人能够将提交合并到实际的官方 git 仓库中。让我们称他们为核心团队。
其他所有人都可以从自己的 curl 仓库分叉,他们可以在其中提交和推送更改,在线托管,并从中构建自己的 curl 版本等等,但为了将更改推送到官方仓库,他们需要由信任的人进行推送。
核心团队是一小部分 curl 开发者,他们在项目中有几年的经验,并证明他们是技术娴熟的开发者,并且完全理解我们在项目中进行的价值观和开发风格。他们中的一些人列在开发团队部分。
你总是可以将讨论带到邮件列表,并争论为什么你认为你的更改应该被接受,或者甚至反对即将进入的其他更改等等。你甚至可以建议自己或其他人被赋予“推送权限”,成为该团队中精选的少数人之一。
丹尼尔仍然是项目负责人,虽然很少需要,但在似乎没有偏向任何一方或未能达成共识的辩论中,他有最终决定权。
报告漏洞
所有已知和公开的 curl 或 libcurl 相关漏洞均列在curl 网站的安全页面上。
除非设置了必要的配置以将问题仅限于报告者和项目安全团队访问,否则不应在项目的公共错误跟踪器中输入安全漏洞。
漏洞处理
处理新安全漏洞的典型流程如下。
在正式宣布之前,关于漏洞的信息不应公开。这意味着,例如,不应创建一个错误跟踪器条目来跟踪问题,因为这会使问题公开,并且不应在任何项目的公共邮件列表上讨论。此外,与任何提交相关的消息在公共宣布之前不应提及提交的安全性质。
-
发现问题的个人,报告者,在
hackerone.com/curl上报告漏洞。那里提交的问题会达到少数精选和信任的人。 -
与 curl 或 libcurl 中未公开的安全漏洞报告或管理无关的消息将被忽略,不需要采取进一步行动。
-
安全团队中的某个人会向原始报告者发送一封电子邮件以确认报告。
-
安全团队调查报告,要么拒绝它,要么接受它。
-
如果报告被拒绝,团队会写信给报告者解释原因。
-
如果报告被接受,团队会写信给报告者,让他/她知道报告已被接受,并且他们正在修复这个问题。
-
安全团队讨论问题,制定解决方案,考虑问题的 影响,并建议发布时间表。这次讨论应尽可能涉及报告者。
-
信息发布应尽可能快,通常与包含修复的即将发布的版本同步。如果报告者或其他人认为下一个计划发布的版本太远,那么应考虑为安全原因考虑一个单独的更早的发布。
-
编写关于问题的安全公告草案,解释问题是什么,其影响,受影响的版本,任何解决方案或绕过方法,以及修复何时发布,确保正确地认可所有贡献者。
-
使用 HackerOne 为此目的的表格请求 CVE 编号(常见漏洞和暴露)。
-
更新安全公告,包括 CVE 编号。
-
考虑通知distros@openwall以让他们为即将到来的公共安全漏洞公告做好准备 - 附加安全公告草案以供信息参考。请注意,“distros”不接受超过 14 天的禁令,并且他们对 Windows 特定的缺陷不感兴趣。
-
安全团队在一个私有分支中提交了修复。理想情况下,提交信息应包含 CVE 编号。这个修复通常也会分发到“distros”邮件列表,以便它们在公开宣布之前使用这个修复。
-
在下一个版本发布的那一天,私有分支被合并到主分支并推送。一旦推送,信息对公众可访问,实际的发布应该随后立即进行。
-
项目团队创建了一个包含修复的版本。
-
项目团队以我们通常宣布版本的方式向全世界宣布了版本和漏洞——信息被发送到 curl-announce、curl-library 和 curl-users 邮件列表。
-
网站上的安全页面应该提到新的漏洞。
curl-security@haxx.se
这个列表上都有谁?你必须满足一些条件,然后我们可能会邀请你加入这个列表,或者你也可以要求加入。这真的不是正式的。我们只要求你在 curl 项目中有一个长期的参与,并且你已经表现出对项目及其工作方式的理解。你必须已经存在了一段时间,而且你应该没有在近期消失的计划。
我们不公开参与者的名单主要是因为它随着时间的推移会有所变化,而某个地方的名单只会存在过时的风险。
网站
curl 网站的大部分内容也可在公共 git 仓库中找到,尽管它与源代码仓库分开,因为通常对同一群人来说并不有趣,我们可以维护一个拥有推送权限等的不同人员名单。
网站 git 仓库可在以下 URL 的 GitHub 上找到:github.com/curl/curl-www,您可以通过以下方式克隆网站代码:
git clone https://github.com/curl/curl-www.git
构建网站
该网站是一个定制的设置,主要从一组源文件构建静态 HTML 文件。源文件使用一个增强版的 C 预处理器fcpp和一组 perl 脚本进行预处理。手册页面通过roffit转换为 HTML。请确保 fcpp、perl、roffit、make 和 curl 都包含在您的$PATH 中。
第一次克隆 git 仓库后,在源根目录树中运行一次sh bootstrap.sh以获取符号链接和一些初始本地文件设置,然后您可以通过在源根目录树中调用make来本地构建网站。
注意,这并不使你拥有一个完整的网站镜像,因为一些脚本和文件仅可在实际网站上获取,但它应该足够让你本地查看大多数 HTML 页面。
运行本地克隆
网站以易于托管本地副本的方式进行构建,以便在将更改推送到生产中的官方网站之前进行浏览和测试。我们建议您将网站命名为curl.local,并将其添加到您本地/etc/hosts文件中的一个条目。然后,将您的 HTTP 服务器的文档根指向 curl-www 源代码根目录。
网站基础设施
-
公共 curl 网站托管在curl.se。
-
域名归Daniel Stenberg所有
-
主要源机器由Haxx赞助
-
curl.se 域名由由Kirei赞助的 anycast 分布式 DNS 服务器提供服务
-
该网站通过由Fastly运行的 CDN 向全球提供服务
-
网站每 N 分钟从 GitHub 更新一次。CDN 前端缓存内容 Y 分钟(不同类型的缓存内容时间不同)
构建 curl 和 libcurl
该项目的源代码编写方式允许它在尽可能少的限制和要求下编译和构建在任何操作系统和平台上。
如果你有一个 32 位(或更大)的 CPU 架构,如果你有一个符合 C89 规范的编译器,如果你有一个大致支持 POSIX 套接字 API 的系统,那么你很可能可以为你的目标系统构建 curl 和 libcurl。
对于最受欢迎的平台,curl 项目已经提供了已经完成和准备好的构建系统,以便你可以轻松自行构建。
还有友好的人和机构将 curl 和 libcurl 的二进制包组合在一起,并使它们可供下载。以下将探讨不同的选项。
最新版本?
查看 curl 网站curl 官网,你可以看到项目发布的最新 curl 和 libcurl 版本。这就是你可以获取的最新源代码发布包。
当你选择为你选择的操作系统或发行版预构建和预包装版本时,你可能并不总是能找到最新版本,但你可能不得不满足于为你的环境打包的最新版本,或者你需要从源代码自行构建。
curl 项目还在此 URL 上以某种更易于机器读取的格式提供有关最新版本的信息:https://curl.se/info。
发布源代码
curl 项目创建的源代码可以构建成 curl 和 libcurl 两个产品。从源代码到二进制的转换通常被称为“构建”。你从源代码构建 curl 和 libcurl。
curl 项目根本不提供任何预构建的二进制文件——它只提供源代码。可以在 curl 网站下载页面找到并从互联网上的其他地方安装的二进制文件都是由其他友好的人和机构构建并提供给全世界的。
源代码由大量包含 C 代码的文件组成。一般来说,相同的文件集用于构建 curl 支持的所有平台和计算机架构的二进制文件。curl 可以在大量平台上构建和运行。如果你使用的是罕见的操作系统,那么从源代码构建 curl 可能是最简单或可能是唯一的方法来获取 curl。
使构建 curl 变得容易是 curl 项目的优先事项,尽管我们并不总是能成功。
git 与发布 tar 包
当创建发布 tar 包时,会生成一些文件并包含在最终的发布包中。这些生成的文件不在 git 仓库中,因为它们是生成的,没有必要存储在 git 中。
当然,你也可以选择构建存在于git 仓库中的最新版本。然而,这可能会稍微脆弱一些,并且可能需要稍微更多的细节关注。
如果你从 git 检出构建 curl,在构建之前你需要自己生成一些文件。在 Linux 和类 Unix 系统上,通过运行 autoreconf -fi 来完成此操作,在 Windows 上,运行 buildconf.bat。
在 Linux 和类 Unix 系统上
在 Linux 和其他类 Unix 系统上构建 curl 有两种明显不同的方式;一种是使用 configure 脚本,另一种是 CMake 方法。
有两种不同的构建环境,以满足不同人的观点和口味。基于 configure 的构建可能是更成熟、更全面的构建系统,并且可能应该被认为是默认的。
在 Windows 上
在 Windows 上至少有四种不同的构建方式。上述提到的方式,CMake 方法 和使用 configure 与 msys 一起工作,但更受欢迎和常见的方法可能是使用 Microsoft 的 Visual Studio 编译器通过 nmake 或项目文件进行构建。请参阅 windows 部分的构建说明。
了解更多
-
Autotools - 使用 configure 进行构建
-
CMake
-
单独安装
-
在 Windows 上 - Windows 特定的构建方式
-
依赖项
-
TLS 库
Autotools
Autotools 是一组不同的工具集合,它们一起使用来生成 configure 脚本。configure 脚本由想要构建 curl 的用户运行,它执行一系列操作:
-
它检查你的系统中存在的特性和功能。
-
它提供了命令行选项,以便你作为构建者可以决定在构建过程中启用或禁用哪些功能。特性和协议等可以切换开启/关闭,甚至编译器警告级别等。
-
它提供了命令行选项,让构建者指定 curl 构建时可以使用的各种第三方依赖项的特定安装路径。
-
它指定了在最终构建完成并调用
make install时,生成的安装文件应该放置在哪个文件路径上。
在最基本的使用中,只需在源目录中运行 ./configure 就足够了。当脚本完成后,它会输出一个总结,说明它检测/启用了哪些选项,以及哪些功能仍然被禁用,其中一些可能是因为它未能检测到那些功能正常工作所需的必要第三方依赖项的存在。如果总结不是你所期望的,请再次调用 configure,使用新的选项或调整之前使用的选项。
在 configure 完成后,你调用 make 来构建整个项目,然后最终调用 make install 来安装 curl、libcurl 以及相关内容。make install 需要你拥有在系统中的正确权限,以便在安装目录中创建和写入文件,否则会显示错误。
交叉编译
交叉编译意味着你在一种架构上构建源代码,但输出是为了在另一种架构上运行而创建的。例如,你可以在 Linux 机器上构建源代码,但输出可以在 Windows 机器上运行。
为了使交叉编译工作,你需要为特定目标系统设置一个专门的编译器和构建系统。如何获取和安装该系统,本书不涉及。
一旦你有了交叉编译器,你可以指示 configure 在构建 curl 时使用该编译器而不是本地编译器,这样最终结果就可以移动到另一台机器上并使用。
静态链接
默认情况下,configure 设置构建文件,以便以下 make 命令创建 libcurl 的共享和静态版本。你可以使用 --disable-static 或 --disable-shared 选项来更改这一点。
如果你想要使用第三方库的静态版本而不是共享库来构建,你需要准备好进行一场艰难的战斗。curl 的 configure 脚本专注于设置和构建共享库。
与静态库链接相比,与共享库链接的一个不同之处在于共享库如何处理自己的依赖项,而静态库则不这样做。为了将库 xyz 作为共享库链接,基本上只需在链接器命令行中添加 -lxyz,无论 xyz 本身是用哪些其他库构建的。但是,如果 xyz 是一个静态库,我们还需要在链接器命令行中指定 xyz 的每个依赖项。curl 的配置脚本无法跟上或知道所有可能的依赖项,因此想要使用静态库的用户通常需要提供要链接的库列表。
选择 TLS 后端
基于配置的构建为用户提供在构建时从多种不同的 TLS 库中进行选择。您可以通过使用正确的命令行选项来选择它们。在 curl 7.77.0 之前,配置脚本会自动检查 OpenSSL,但现代版本不再这样做。
-
AmiSSL:
--with-amissl -
AWS-LC:
--with-openssl -
BearSSL:
--with-bearssl -
BoringSSL:
--with-openssl -
GnuTLS:
--with-gnutls -
LibreSSL:
--with-openssl -
mbedTLS:
--with-mbedtls -
OpenSSL:
--with-openssl -
Rustls:
--with-rustls(指向 rustls-ffi 安装路径) -
Schannel:
--with-schannel -
安全传输:
--with-secure-transport -
wolfSSL:
--with-wolfssl
如果您没有指定要使用哪个 TLS 库,配置脚本将失败。如果您想构建不支持 TLS 的版本,您必须明确使用 --without-ssl 来请求。
这些 --with-* 选项还允许您提供安装前缀,以便配置脚本在您指定的位置搜索特定库。例如:
./configure --with-gnutls=/home/user/custom-gnutls
您可以选择构建支持多个TLS 库的版本,通过在配置命令行上指定多个 --with-* 选项。使用 --with-default-ssl-backend=[NAME] 指定哪个作为默认 TLS 后端。例如,构建支持 GnuTLS 和 OpenSSL,并默认使用 OpenSSL:
./configure --with-openssl --with-gnutls \--with-default-ssl-backend=openssl
选择 SSH 后端
基于配置的构建为用户提供在构建时从多种不同的 SSH 库中进行选择。您可以通过使用正确的命令行选项来选择它们。
-
libssh2:
--with-libssh2 -
libssh:
--with-libssh -
wolfSSH:
--with-wolfssh
这些 --with-* 选项还允许您提供安装前缀,以便配置脚本在您指定的位置搜索特定库。例如:
./configure --with-libssh2=/home/user/custom-libssh2
选择 HTTP/3 后端
基于配置的构建为用户提供在构建时选择不同的 HTTP/3 库。您可以通过使用正确的命令行选项来选择它们。
-
quiche:
--with-quiche -
ngtcp2:
--with-ngtcp2 --with-nghttp3 -
msh3:
--with-msh3
CMake
CMake 是一种适用于大多数现代平台(包括 Windows)的替代构建方法。使用这种方法,你首先需要在构建机器上安装 cmake,调用 cmake 生成构建文件,然后进行构建。使用 cmake 的-G标志,你可以选择为哪个构建系统生成文件。查看cmake --help以获取你的 cmake 安装支持的“生成器”列表。
在 cmake 命令行上,第一个参数指定了查找 cmake 源文件的位置,如果是在同一目录下,则为.(一个点)。
在 Linux 上使用纯 make 和同一目录下的 CMakeLists.txt 文件进行构建,你可以这样做:
cmake -G "Unix Makefiles" .
make
或者依赖这样一个事实:在 Unix 系统中,makefiles 是默认的:
cmake .
make
要创建一个用于构建的子目录并在其中运行 make,可以这样做:
mkdir build
cd build
cmake ..
make
单独安装
有时候,当你从源代码构建 curl 和 libcurl 时,你这样做是为了实验、测试或者可能是调试。在这些情况下,你可能还没有准备好替换你的系统级 libcurl 安装。
许多现代系统已经将 libcurl 安装在了系统中,所以当你构建和安装你的测试版本时,你需要确保你的新构建是为了你的目的而使用的。
我们收到了很多报告,说人们构建并安装了他们自己的 curl 和 libcurl 版本,但当他们随后调用他们新的 curl 构建时,新的工具找到了系统中的较旧的 libcurl,并使用它。这往往会让用户感到困惑。
静态链接
你可以通过与 libcurl 进行静态链接来避免 curl 找到较旧的动态 libcurl 库的问题。然而,这却引发了一系列其他挑战,因为将具有多个第三方依赖项的现代库静态链接是很困难的。当你静态链接时,你需要确保你向链接器提供了所有依赖项。这不是我们推荐的方法。
动态链接
当你在现代系统上调用curl时,有一个运行时链接器(通常称为ld.so),它会加载可执行文件构建时使用的共享库。共享库会在一组路径中搜索和加载。
问题通常在于系统 libcurl 库存在于那个路径中,而你的新构建的 libcurl 不存在。或者它们两个都存在于路径中,但系统的一个被首先找到。
在 Linux 系统上,运行时链接器的路径顺序通常定义在/etc/ld.so.conf中。你可以更改顺序,并且可以向搜索目录列表中添加新目录。记得在更新后运行ldconfig。
临时安装
如果你构建了一个 libcurl 并安装了它,你只想为单个应用程序使用它,或者只是想测试一下,编辑和更改动态库路径可能有点过于侵入性。
一个普通的 Unix 提供了一些其他我们推荐的替代方案。
LD_LIBRARY_PATH
你可以在你的 shell 中设置这个环境变量,让运行时链接器在特定的目录中查找。这会影响在这个变量设置的所有可执行文件。
这对于快速检查来说很方便,或者如果你想要在不同的调用中使用不同的 libcurl,让你的单个curl可执行文件使用不同的 libcurl。
当你将你的新 curl 构建安装在$HOME/install时,它可能看起来像这样:
export LD_LIBRARY_PATH=$HOME/install/lib
$HOME/install/bin/curl https://example.com/
rpath
通常,强制加载你自己的单独的 libcurl 而不是系统的一个更好的方法,是设置你构建的特定curl可执行文件的rpath。这给运行时链接器提供了一个特定的路径来检查这个特定的可执行文件。
这是在链接时完成的,如果你使用应用程序构建自己的 libcurl,你可以让它加载你的自定义 libcurl 构建,如下所示:
gcc -g example.c -L$HOME/install/lib -lcurl -Wl,-rpath=$HOME/install/lib
当设置了rpath后,链接到$HOME/install/lib/libcurl.so的可执行文件将使运行时链接器使用该特定路径和库,而系统中的其他二进制文件将继续使用系统库 curl。
当你想让你的自定义构建的curl使用它自己的 libcurl,并将它们安装到$HOME/install时,这个配置命令行可能看起来像这样:
LDFLAGS="-Wl,-rpath,$HOME/install/lib" ./configure ...
如果你的系统支持 rpath 的运行路径形式,通常最好使用它,因为它可以被LD_LIBRARY_PATH环境变量覆盖。它还可能在测试 curl 的树内构建时防止 libtool 错误,因为那时 libtool 可以使用LD_LIBRARY_PATH。较新的链接器在指定 rpath 时默认使用 rpath 的运行路径形式,但其他链接器可能需要一个额外的链接器标志-Wl,--enable-new-dtags,如下所示:
LDFLAGS="-Wl,-rpath,$HOME/install/lib -Wl,--enable-new-dtags" \./configure ...
Windows
您可以在 Windows 上以多种不同的方式构建 curl。我们推荐使用来自 Microsoft 的 MSVC 编译器或免费开源的 mingw 编译器。然而,构建过程并不仅限于这些。
如果您使用 mingw,您可能想使用 autotools 构建系统。
winbuild
这就是使用命令行构建 curl 和 libcurl 的方法。
使用nmake工具以 MSVC 构建,如下所示:
cd winbuild
决定在您的构建中启用/禁用哪些选项。该目录中的README.md文件详细说明了所有选项,但一个示例命令行可能看起来像这样(为了可读性分成几行):
nmake WITH_SSL=dll WITH_NGHTTP2=dll ENABLE_IPV6=yes \
WITH_ZLIB=dll MACHINE=x64
Visual C++项目文件
使用 CMake,您可以生成一组 Visual Studio 项目文件:
cmake -B build -G 'Visual Studio 17 2022'
一旦生成,您就可以导入它们,并像通常一样使用 Visual Studio 进行构建。
Mingw
您可以使用 mingw 编译器套件构建 curl。使用 CMake 为您生成一组 Makefiles:
cmake -B build -G "MinGW Makefiles"
依赖项
制作优秀软件的关键是建立在其他优秀软件之上。通过使用许多其他人使用的库,我们减少了对相同事物的重复发明,并且由于有更多的人使用相同的代码,我们得到了更可靠的软件。
curl 提供的许多功能都需要它被构建为使用一个或多个外部库。它们然后成为 curl 的依赖项。它们都不是必需的,但大多数用户希望至少使用其中的一些。
HTTP 压缩
如果使用适当的第三方库构建 curl,则 curl 可以自动解压缩通过 HTTP 传输的数据。您可以将 curl 构建为使用这些库中的一个或多个:
-
使用 zlib 进行 gzip 压缩
-
使用 brotli 进行 brotli 压缩
-
使用 libzstd 进行 zstd 压缩
通过网络获取压缩数据使用更少的带宽,这也可能导致更短的传输时间。
c-ares
c-ares.org/
可以使用 c-ares 来构建 curl 以实现异步名称解析。另一个启用异步名称解析的选项是使用线程化名称解析后端构建 curl,这样就会为每个名称解析创建一个单独的辅助线程。c-ares 在同一个线程中完成所有操作。
nghttp2
nghttp2.org/
这是一个处理 HTTP/2 封装的库,是 curl 支持 HTTP 版本 2 的先决条件。
openldap
www.openldap.org/
这个库是允许 curl 支持 LDAP 和 LDAPS URL 方案的选项之一。在 Windows 上,您还可以选择构建使用 winldap 库的 curl。
librtmp
rtmpdump.mplayerhq.hu/
要启用 curl 对 RTMP URL 方案的支持,必须使用来自 RTMPDump 项目的 librtmp 库构建 curl。
libpsl
rockdaboot.github.io/libpsl/
当您使用 libpsl 支持构建 curl 时,cookie 解析器会了解公共后缀列表,并相应地处理此类 cookie。
libidn2
www.gnu.org/software/libidn/libidn2/manual/libidn2.html
curl 通过 libidn2 库的帮助处理国际域名 (IDN)。
SSH 库
如果您希望 curl 具有 SCP 和 SFTP 支持,请使用以下 SSH 库之一进行构建:
-
libssh2
-
libssh
-
wolfSSH
TLS 库
有许多不同的 TLS 库可供选择,因此它们在 单独的部分 中进行了介绍。
QUIC 和 HTTP/3
要构建支持 HTTP/3 的 curl,您需要以下这些集合之一:
-
ngtcp2 + nghttp3
-
quiche (实验性)
-
msquic + msh3 (实验性)
TLS 库
要使 curl 支持基于 TLS 的协议,例如 HTTPS、FTPS、SMTPS、POP3S、IMAPS 等,你需要使用第三方 TLS 库进行构建,因为 curl 本身不实现 TLS 协议。
curl 被编写为与大量 TLS 库一起工作:
-
AmiSSL
-
AWS-LC
-
BearSSL
-
BoringSSL
-
GnuTLS
-
libressl
-
mbedTLS
-
OpenSSL
-
rustls
-
Schannel(原生 Windows)
-
Secure Transport(原生 macOS)
-
WolfSSL
当你构建 curl 和 libcurl 以使用这些库之一时,确保你在构建机器上安装了该库及其包含的头文件非常重要。
configure
下面,你将学习如何告诉 configure 使用不同的库。configure 脚本默认不选择任何 TLS 库。你必须选择一个,或者指示 configure 你想要构建不带 TLS 支持,使用--without-ssl。
OpenSSL, BoringSSL, libressl
./configure --with-openssl
configure 默认在其默认路径中检测到 OpenSSL。你可以选择性地将 configure 指向一个自定义安装路径前缀,以便在其中找到 OpenSSL:
./configure --with-openssl=/home/user/installed/openssl
可选方案 BoringSSL 和 libressl 看起来足够相似,以至于 configure 以与 OpenSSL 相同的方式检测它们。然后它使用额外的措施来确定它使用的是哪种特定风味。
GnuTLS
./configure --with-gnutls
configure 默认在其默认路径中检测到 GnuTLS。你可以选择性地将 configure 指向一个自定义安装路径前缀,以便在其中找到 gnutls:
./configure --with-gnutls=/home/user/installed/gnutls
WolfSSL
./configure --with-wolfssl
configure 默认在其默认路径中检测到 WolfSSL。你可以选择性地将 configure 指向一个自定义安装路径前缀,以便在其中找到 WolfSSL:
./configure --with-wolfssl=/home/user/installed/wolfssl
mbedTLS
./configure --with-mbedtls
configure 默认在其默认路径中检测到 mbedTLS。你可以选择性地将 configure 指向一个自定义安装路径前缀,以便在其中找到 mbedTLS:
./configure --with-mbedtls=/home/user/installed/mbedtls
Secure Transport
./configure --with-secure-transport
configure 默认在其默认路径中检测到 Secure Transport。你可以选择性地将 configure 指向一个自定义安装路径前缀,以便在其中找到 Secure Transport:
./configure --with-secure-transport=/home/user/installed/darwinssl
Schannel
./configure --with-schannel
configure 默认在其默认路径中检测到 Schannel。
(WinSSL 曾是 Schannel 的替代名称,而早期 curl 版本则需要--with-winssl)
BearSSL
./configure --with-bearssl
configure 默认在其默认路径中检测到 BearSSL。你可以选择性地将 configure 指向一个自定义安装路径前缀,以便在其中找到 BearSSL:
./configure --with-bearssl=/home/user/installed/bearssl
Rustls
./configure --with-rustls
当被指示使用 rustls 时,curl 实际上正在尝试查找和使用 rustls-ffi 库——rustls 库的 C API。configure 默认在其默认路径中检测到 rustls-ffi。你可以选择性地将 configure 指向一个自定义安装路径前缀,以便在其中找到 rustls-ffi:
./configure --with-rustls=/home/user/installed/rustls-ffi
BoringSSL
构建 boringssl
$HOME/src 是我在本例中放置代码的位置。您可以选择任何您喜欢的位置。
$ cd $HOME/src
$ git clone https://boringssl.googlesource.com/boringssl
$ cd boringssl
$ mkdir build
$ cd build
$ cmake -DCMAKE_POSITION_INDEPENDENT_CODE=on ..
$ make
设置构建树以便 curl 的配置程序能够检测到
在 boringssl 源代码树根目录下,确保存在一个 lib 目录和一个 include 目录。lib 目录应包含两个库(我将它们作为符号链接放入构建目录)。include 目录默认已经存在。按照以下方式构建和填充 lib(在源代码树根目录下执行命令,而不是在 build/ 子目录下)。
$ mkdir lib
$ cd lib
$ ln -s ../build/ssl/libssl.a
$ ln -s ../build/crypto/libcrypto.a
配置 curl
LIBS=-lpthread ./configure --with-ssl=$HOME/src/boringssl(在此我指出 boringssl 树的根目录)
在配置结束时,确认它显示检测到使用了 BoringSSL。
构建 curl
在 curl 源代码树中运行 make。
现在,您可以使用 make install 等命令正常安装 curl。
命令行概念
curl 最初是一个命令行工具,多年来无数用户从 shell 提示符和脚本中调用它。
输入垃圾输出垃圾
curl 自身意愿很小。它在很大程度上试图取悦你和你所期望的。这也意味着它会尝试与你提供的内容互动。如果你拼写了一个选项,它可能会执行一些意外的操作。如果你传递了一个稍微不合法的 URL,curl 仍然可能会处理它并继续进行。这意味着你可以在某些选项中输入疯狂的数据,并且 curl 可以在其传输操作中传递这些疯狂的数据。
这是一个设计选择,因为它允许你真正调整 curl 如何进行协议通信,并且你可以以最富有创意的方式让 curl 处理你的服务器实现。
-
差异
-
命令行选项
-
选项取决于版本
-
URLs
-
URL 通配符
-
列表选项
-
配置文件
-
变量
-
密码
-
进度条
-
版本
-
持久连接
-
退出代码
-
作为 curl 复制
差异
二进制文件和不同平台
命令行工具 curl 是一个 二进制可执行文件。curl 项目本身并不分发或提供二进制文件。二进制文件高度依赖于系统,并且通常也绑定到特定的系统版本。
不同的人在不同的平台上使用不同的第三方库和不同的构建时选项构建的不同版本的 curl,使得工具在不同的地方提供不同的功能。此外,curl 是持续开发的,因此新版本的工具可能比旧版本具有更多和更好的功能。
命令行、引号和别名
curl 可以在许多不同的命令行环境中使用,包括各种 shell 和提示符。它们各自都有自己的一套限制、规则和指南需要遵循。curl 工具被设计成可以与它们中的任何一个协同工作而不会引起麻烦,但有时你的特定命令行系统可能不符合其他人使用的或文档中描述的。
命令行系统之间的一种区别,例如,是如何在参数周围放置引号,比如嵌入空格或特殊符号。在大多数类 Unix shell 中,你使用双引号(")和单引号('),这取决于你是否希望在引号字符串内允许变量扩展,但在 Windows 上没有对单引号版本的支持。
在某些环境中,例如 Windows 上的 PowerShell,命令行系统的作者决定他们认为更了解情况,并“帮助”用户在输入 curl 时使用另一个工具而不是 curl,通过提供一个在执行命令行时具有优先级的别名来实现。为了正确使用 PowerShell 中的 curl,你需要输入其完整的名称,包括扩展名:curl.exe 或删除别名。
不同的命令行环境有不同的最大命令行长度,并强制用户限制放入单行中的数据量。curl 通过提供一种通过文件或 stdin 使用 -K 选项 来提供命令行选项的方式来自适应这一点。
命令行选项
当告诉 curl 执行某项操作时,你通过 curl 调用,并附带零个、一个或多个命令行选项来伴随你想要传输的 URL 或 URL 集合。curl 支持超过两百个不同的选项。
短选项
命令行选项将关于你希望 curl 如何表现的信息传递给 curl。例如,你可以使用-v 选项让 curl 开启详细模式:
curl -v http://example.com
-v 在这里用作“短选项”。你用减号和紧随其后的单个字母来写这些选项。许多选项只是开关,用于打开或改变两个已知状态之间的某个东西。它们可以用那个选项名来使用。然后你也可以在减号后组合几个单字母选项。要请求详细模式和 curl 跟随 HTTP 重定向:
curl -vL http://example.com
curl 的命令行解析器始终解析整行,你可以将选项放在你喜欢的任何位置;它们也可以出现在 URL 之后:
curl http://example.com -Lv
当然,两个单独的短选项也可以分别指定,例如:
curl -v -L http://example.com
长选项
单字母选项很方便,因为它们书写和使用的速度都很快,但由于字母表中的字母数量有限,而且有许多东西需要控制,并非所有选项都像这样可用。因此,为这些提供了长选项名。此外,为了方便和允许脚本更具可读性,大多数短选项都有更长的别名。
长选项总是用两个减号(或称为“短划线”,根据你更喜欢叫什么)然后是名称,并且每个双减号只能写一个选项名。使用长选项格式请求详细模式看起来如下:
curl --verbose http://example.com
并且使用长格式请求 HTTP 重定向看起来如下:
curl --verbose --location http://example.com
选项的参数
并非所有选项都是简单的布尔标志,用于启用或禁用功能。对于其中一些,你需要传递数据,比如可能是一个用户名或文件路径。你可以通过首先写选项,然后写参数,并用空格分隔来实现。例如,如果你想通过 HTTP POST 发送一个任意字符串数据到服务器:
curl -d arbitrary http://example.com
即使你使用选项的长格式,它也以相同的方式工作:
curl --data arbitrary http://example.com
当使用带参数的短选项时,实际上你也可以在不使用空格分隔符的情况下写入数据:
curl -darbitrary http://example.com
带空格的参数
有时候你想要向一个选项传递一个参数,而这个参数包含一个或多个空格。例如,你想要设置 curl 使用的用户代理字段为“我是你的父亲”,包括这三个空格。那么,当你通过命令行将字符串传递给 curl 时,你需要将字符串放在引号内。使用的引号取决于你的 shell/命令提示符,但通常在大多数地方使用双引号即可:
curl -A "I am your father" http://example.com
如果没有使用引号,比如你这样写命令行:
curl -A I am your father http://example.com
… 使得 curl 只使用 ‘I’ 作为用户代理字符串,而下面的字符串,am、your 和 father 由于它们不以 - 开头,因此被视为独立的 URL,而 curl 只处理选项和 URL。
要使字符串本身包含双引号,这在例如你想向服务器发送 JSON 字符串时很常见,你可能需要使用单引号(除了在 Windows 上,那里的单引号工作方式不同)。发送 JSON 字符串 { "name": "Darth" }:
curl -d '{ "name": "Darth" }' http://example.com
或者,如果你想避免单引号的问题,你可能更喜欢通过文件将数据发送给 curl,这样就不需要额外的引号。假设我们称这个文件为‘json’,它包含上述提到的数据:
curl -d @json http://example.com
负面选项
对于那些可以开启的选项,也有一种方法可以将其关闭。这时,你需要在选项名称前加上一个初始的 no- 前缀来使用选项的长格式。例如,要关闭详细模式:
curl --no-verbose http://example.com
选项取决于版本
curl首次在 1998 年的辉煌年份中在命令行上键入。那时它已经可以在指定的 URL 上工作,并且接受给它提供的零个、一个或多个命令行选项。
从那时起,我们添加了更多的选项。我们边走边添加选项,几乎每个 curl 的新版本都有一到几个新选项,允许用户修改其操作的某些方面。
由于 curl 项目有着相当快速的发布链,每八周就发布一个新版本,因此你至少不是总是使用 curl 的最新发布版本。有时你可能甚至使用的是几年前的 curl 版本。
本书中所描述的所有命令行选项当然都是在某个时候添加到 curl 中的,而在 1998 年 curl 首次发布的那天,其中只有一小部分选项可用。你可能需要检查你的 curl 版本,并与 curl 手册页进行交叉验证,以确定某些选项是在何时添加的。如果你希望使用现代 curl 版本的命令行回退到可能运行较旧安装的旧系统,这一点尤为重要。
curl 的开发者正在努力不改变现有的行为。1998 年、2003 年或 2010 年编写的用于 curl 的命令行今天都应该能够未经修改地运行。
URLs
curl 被称为 curl 是因为其名称中包含子串 URL(统一资源定位符)。它操作于 URL。URL 是我们随意使用的网络地址字符串的名称,就像我们通常看到的以HTTP://开头或以 www 开头的那些。
URL 在严格意义上是这些的旧称。URI(统一资源标识符)是它们更现代和正确的名称。其语法在RFC 3986中定义。
当 curl 接受“URL”作为输入时,它实际上是一个“URI”。curl 理解的多数协议也有相应的 URI 语法文档,该文档描述了该特定 URI 格式的工作方式。
-
方案
-
用户名和密码
-
主机
-
端口号
-
路径
-
查询
-
FTP 类型
-
片段
-
浏览器
-
许多选项和 URL
-
连接重用
-
并行传输
-
trurl
方案
URL 以“方案”开头,这是http://部分的官方名称。这表明 URL 使用哪种协议。方案必须是 curl 支持的可识别方案,否则它会显示错误消息并停止。此外,方案不能以空格开头或包含任何空格。
方案分隔符
方案标识符通过://序列与 URL 的其余部分分开。这是一个冒号和两个正斜杠。存在只有单个斜杠的 URL 格式,但 curl 不支持任何这些格式。有两个额外的注意事项需要注意,关于斜杠的数量:
curl 允许一些非法语法,并尝试在内部进行纠正;因此,它也理解并接受一个或三个斜杠的 URL,尽管实际上它们并不是正确形成的 URL。curl 这样做是因为浏览器开始采用这种做法,因此偶尔会在野外使用这样的 URL。
file:// URL 的格式为file://<hostname>/<path>,但可以使用的主机名只有localhost、127.0.0.1或空白(什么都没有):
file://localhost/path/to/file
file://127.0.0.1/path/to/file
file:///path/to/file
在其中插入任何其他主机名会使 curl 的最新版本返回错误。
特别注意上面的第三个例子(file:///path/to/file)。路径前有三个斜杠。这又是一个常见的错误区域,浏览器允许用户使用错误的语法,因此作为一个特殊例外,Windows 上的 curl 也允许这种不正确的格式:
file://X:/path/to/file
…其中 X 是 Windows 风格的驱动器字母。
没有方案
作为便利,curl 还允许用户从 URL 中省略方案部分。然后它根据主机名的前一部分猜测要使用的协议。这种猜测是基本的,因为它只是检查主机名的前一部分是否与一组协议之一匹配,并假设您打算使用该协议。这种启发式方法基于服务器传统上被这样命名的这一事实。通过这种方式检测到的协议是 FTP、DICT、LDAP、IMAP、SMTP 和 POP3。在无方案的 URL 中,任何其他主机名都会使 curl 默认为 HTTP。
例如,这可以从 FTP 站点获取一个文件:
curl ftp.funet.fi/README
当从 HTTP 服务器获取数据时:
curl example.com
您可以使用--proto-default选项将默认协议修改为 HTTP 以外的其他协议。
支持的方案
curl 支持或可以构建以支持以下传输方案和协议:
DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, POP3, POP3S, RTMP, RTMPS, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS 和 WSS
用户名和密码
在 URL 方案之后,可能嵌入用户名和密码字段。如今,这种语法的使用通常是不受欢迎的,因为您很容易在脚本或其他方式中泄露这些信息。例如,使用给定的用户名和密码列出 FTP 服务器的目录:
curl ftp://user:password@example.com/
URL 中包含用户名和密码是完全可选的。curl 还允许通过正常的命令行选项提供这些信息,而无需在 URL 中。
如果用户名和/或密码中包含非 ASCII 字母或可能包含:或@,请记住对该字母进行 URL 编码:将其写作%HH,其中HH是十六进制字节值。:是%3a,而@是%40。
主机
URL 中的主机名部分当然只是一个可以解析为数值 IP 地址的名称,或者就是该数值地址本身。
curl http://example.com
当指定数值地址时,对于 IPv4 地址,使用点分版本:
curl http://127.0.0.1/
…而对于 IPv6 地址,数值版本需要放在方括号内:
curl http://[2a04:4e42::561]/
当使用主机名时,将名称转换为 IP 地址通常是通过使用系统的解析器功能来完成的。这通常允许系统管理员在/etc/hosts文件(或等效文件)中提供本地名称查找。
国际域名(IDN)
curl 知道如何处理 IDN 名称,你只需像传递普通名称一样传递它们:
curl https://räksmörgås.se
端口号
每个协议都有一个 curl 使用的默认端口号,除非提供了指定的端口号。可选的端口号可以在 URL 的主机名部分之后提供,用冒号和十进制表示的端口号。例如,请求端口 8080 上的 HTTP 文档:
curl http://example.com:8080/
当使用 IPv4 地址命名时:
curl http://127.0.0.1:8080/
当使用 IPv6 地址命名时:
curl http://[fdea::1]:8080/
端口号是一个无符号的 16 位数字,因此它必须在 0 到 65535 的范围内。
TCP 与 UDP
给定的端口号用于设置与 URL 中指定的服务器建立连接时使用的连接。端口号可以是 TCP 端口号或 UDP 端口号,具体取决于实际使用的底层传输协议。TCP 是最常见的一种,但 TFTP 和 HTTP/3 使用 UDP。
使用 file:// 方案的 URL 不能有端口号。
路径
每个 URL 都包含一个路径。如果没有给出,则隐含/。例如,当您只使用主机名时,如:
curl https://example.com
路径被发送到指定的服务器,以确定请求或提供的确切资源。
路径的确切使用取决于协议。例如,从 FTP 服务器默认匿名用户获取文件README:
curl ftp://ftp.example.com/README
对于具有目录概念的协议,URL 以斜杠结尾表示它是一个目录而不是文件。因此,从 FTP 服务器请求目录列表隐含了这样的斜杠:
curl ftp://ftp.example.com/tmp/
如果您想在路径字段中包含非 ASCII 字母或甚至空格(),请记住要“URL 编码”该字母:将其写成%HH,其中HH是十六进制字节值。例如,空格是%20。
查询
URL 的查询部分是位于问号(?)右侧但位于片段左侧的数据,该片段以哈希(#)开头。
查询可以是任何经过 URL 编码的字符序列。通常,使用由与号(&)分隔的键/值对序列。例如,在 https://example.com/?name=daniel&tool=curl。
为了帮助用户创建这样的查询集,curl 提供了适当的命令行选项--url-query [content]。此选项将内容(通常是一个名称+值对)添加到提供的 URL 查询部分的末尾。
在添加查询部分时,curl 会添加与号分隔符。
语法与--data-urlencode使用的语法相同,但有一个扩展:使用+前缀。见下文。
-
content: 对内容进行 URL 编码,并将其添加到查询中。只需小心确保内容不包含任何=或@符号,因为这会使语法与以下其他情况之一匹配。 -
=content: 对内容进行 URL 编码,并将其添加到查询中。初始的=符号不包括在数据中。 -
name=content: 对内容部分进行 URL 编码,并将其添加到查询中。注意,预期名称部分已经进行了 URL 编码。 -
@filename: 从指定的文件(包括任何换行符)加载数据,对数据进行 URL 编码,并将其添加到查询中。 -
name@filename: 从指定的文件(包括任何换行符)加载数据,对数据进行 URL 编码,并将其添加到查询中。名称部分附加一个等号,结果为name=urlencoded-file-content。注意,预期名称已经进行了 URL 编码。 -
+content: 不进行任何编码,将内容添加到查询中。
FTP 类型
这不是一个广泛使用的功能。
用于标识 FTP 服务器上文件的 URL 具有一个特殊功能,允许您同时告知客户端(在这种情况下为 curl)资源的文件类型。这是因为 FTP 有一些特别之处,可以改变传输模式,从而以不同于其他模式的方式处理文件。
您可以通过在 URL 后附加;type=A来告诉 curl FTP 资源是 ASCII 类型。使用 ASCII 从example.com的根目录获取foo文件可以这样实现:
curl "ftp://example.com/foo;type=A"
尽管 curl 默认为 FTP 使用二进制传输,但 URL 格式允许您通过 type=I 指定二进制类型:
curl "ftp://example.com/foo;type=I"
最后,如果您传递的类型是 D,您可以告诉 curl 所标识的资源是一个目录。
curl "ftp://example.com/foo;type=D"
…这样就可以作为一个替代格式,而不是像上面提到的那样以路径结束符结束。
片段
URL 提供了片段部分。这通常在浏览器中表现为一个哈希符号(#)和网页中特定名称的名称。这样的 URL 可能看起来像这样:
https://www.example.com/info.html#the-plot
当将 URL 传递给 curl 时,curl 支持片段,但片段部分实际上从未真正通过网络发送,因此它对 curl 的操作是否有或没有片段没有影响。
如果你想将 # 字符作为路径的一部分,而不是作为片段的分隔符,请确保将其 URL 编码,即 %23:
curl https://www.example.com/info.html%23the-plot
片段技巧
由于片段部分实际上并未在网络中使用,因此在构建命令行时可以利用这一点。
例如,如果你想从服务器请求相同的 URL 10 次,你可以创建一个循环,并将循环指令放在片段部分。就像这样:
curl https://example.com/#[1-10]
浏览器
浏览器通常支持和使用与 curl 不同的 URL 标准。curl 使用 RFC 3986 作为指导,而浏览器使用WHATWG URL 规范。
这很重要,因为两个 URL 标准并不相同。它们并不完全兼容,尽管在大多数日常使用中这些差异很少显现。有时,根据其中一个规范解释的 URL,当由另一个规范解释时,处理方式可能会有所不同。因此,curl 和浏览器并不总是以相同的方式处理 URL。
WHATWG 规范也在不断变化。
由于 curl 被开发出来能够执行浏览器可以执行的操作,因此 curl URL 解析器已经略微调整,以适应一些差异。例如,当从传入的 HTTP 头中读取 URL 时,它接受空格;它接受一个、两个或三个斜杠作为方案和主机名之间的分隔符。这就是我们有时说 curl 的解析器是RFC 3986+兼容的原因。
curl 努力不破坏现有的行为,这使得它仍然支持 1998 年所支持的 URL 和 URL 格式。浏览器则不支持。
浏览器的地址栏
当你使用现代网络浏览器时,它们在主窗口顶部通常展示的地址栏并不是使用 URL 或甚至 URI。实际上,它们主要使用 IRI(国际资源标识符),这是 URI 的超集,允许国际化,如非拉丁符号等,但它们通常还超出了这个范围,例如,它们倾向于处理空格,并在百分编码上执行一些这些提到的规范没有说明客户端应该做的魔法操作。
地址栏非常简单,是供人类输入和查看类似 URI 的字符串的界面。
有时,浏览器地址栏中显示的内容和你可以传递给 curl 的内容之间的差异是显著的。
许多选项和 URL
如上所述,curl 支持数百个命令行选项,它还支持无限数量的 URL。如果你的 shell 或命令行系统支持,你可以传递给 curl 的命令行长度实际上是没有限制的。
curl 首先解析整个命令行,应用命令行选项的愿望,然后逐个(从左到右的顺序)遍历 URL 以执行操作。
对于某些选项(例如告诉 curl 存储传输位置的-o或-O),你可能希望在命令行上为每个 URL 指定一个选项。
curl 为它在最后一个使用的 URL 上的操作返回一个退出代码。如果你希望 curl 在集合中第一个失败的 URL 上出错退出,请使用--fail-early选项。
每个给定 URL 一个输出
如果你使用包含两个 URL 的命令行,你必须告诉 curl 如何处理这两个 URL。-o和-O选项指示 curl 如何保存 URL 的输出,所以你可能希望命令行上的选项数量与 URL 数量相同。
如果命令行上的 URL 数量多于输出选项,那么没有对应输出指令的 URL 内容将被发送到 stdout。
使用--remote-name-all标志会自动让 curl 表现得像是为所有没有输出选项的给定 URL 使用了-O。
每个 URL 单独的选项
在前面的章节中,我们描述了 curl 总是解析整个命令行中的所有选项,并将这些选项应用于它传输的所有 URL。
那只是一个简化:curl 还提供了一个选项(-:, --next),它会在一组选项和 URL 之间插入一个边界,对于这些 URL 它应用了这些选项。当命令行解析器找到一个--next选项时,它会将以下选项应用于下一组 URL。因此,--next选项在选项集和 URL 之间充当了一个分隔符。你可以使用任意多的--next选项。
例如,我们向一个 URL 执行 HTTP GET 请求并跟随重定向,然后向另一个不同的 URL 执行第二个 HTTP POST 请求,最后用对第三个 URL 的 HEAD 请求来结束。所有这些都在一个命令行中完成:
curl --location http://example.com/1 --next--data sendthis http://example.com/2 --next--head http://example.com/3
如果在命令行上没有使用--next选项尝试这样做,将会生成一个非法命令行,因为 curl 会尝试同时组合 POST 和 HEAD:
Warning: You can only select one HTTP request method! You asked for both
Warning: POST (-d, --data) and HEAD (-I, --head).
连接重用
建立 TCP 连接,尤其是 TLS 连接,可能是一个缓慢的过程,即使在高速网络中也是如此。
记住 curl 内部有一个连接池是有用的,它会保持之前使用的连接活跃,并在使用后一段时间内保持连接状态,以便后续对同一主机的请求可以重用已建立的连接。
当然,它们只能保持活跃,直到 curl 工具运行结束。这是尝试在同一个命令行中完成多个传输而不是运行多个独立的 curl 命令行调用的一个很好的理由。
并行传输
逐个按顺序获取指定 URL 的默认行为使得可以精确地了解每个 URL 的获取时间,但可能会较慢。
curl 提供了 -Z(或 --parallel)选项,该选项指示 curl 尝试以并行方式执行指定的传输。当启用此选项时,curl 会同时执行许多传输而不是顺序执行。默认情况下,它一次可以执行多达 50 个传输,并且一旦其中一个完成,下一个就会启动。
对于需要从不同来源下载许多文件,其中一些可能较慢,而另一些可能较快的场景,这可以极大地加快速度。
如果 50 个并行传输对您来说不合适,--parallel-max 选项允许您更改该数量。
并行传输进度条
自然地,显示单个传输文件传输进度的普通进度条显示对于并行传输并不那么有用,因此当 curl 执行并行传输时,它会显示一个不同的进度条,该进度条在单行中显示有关所有当前进行中的传输的信息。
多路复用之前的连接
当 curl 被要求执行并行传输时,它优先考虑额外的传输重用和多路复用发生在现有连接之上。这可能会潜在地降低所需的连接总数(以及资源),但可能会在启动时稍微慢一些。
使用 --parallel-immediate,curl 被指示反转优先级,并优先创建新的连接,而不是冒险等待一小段时间以查看传输是否可以在另一个连接上进行多路复用。
trurl
trurl 是一个独立的命令行工具,其唯一目的是解析、操作和输出 URL 及其部分。它是 curl 的配套工具,满足您的命令行和脚本需求。
trurl 使用 libcurl 的 URL 解析器。这确保了 curl 和 trurl 对 URL 的看法始终一致,并且这两个工具以相同和一致的方式解析它们。
用法
通常,您会将一个或多个 URL 传递给 trurl,并指定您希望输出的组件。可能还会在修改 URL(s)的同时进行。
trurl 了解 URL,每个 URL 由最多十个独立且独立的 组件 组成。这些组件可以使用 trurl 提取、删除和更新。
trurl 示例命令行
替换 URL 的主机名:
$ trurl --url https://curl.se --set host=example.com
https://example.com/
通过设置组件创建 URL:
$ trurl --set host=example.com --set scheme=ftp
ftp://example.com/
重定向 URL:
$ trurl --url https://curl.se/we/are.html --redirect here.html
https://curl.se/we/here.html
更改端口号:
$ trurl --url https://curl.se/we/../are.html --set port=8080
https://curl.se:8080/are.html
从 URL 中提取路径:
$ trurl --url https://curl.se/we/are.html --get '{path}'
/we/are.html
从 URL 中提取端口号:
$ trurl --url https://curl.se/we/are.html --get '{port}'
443
将路径段附加到 URL 上:
$ trurl --url https://curl.se/hello --append path=you
https://curl.se/hello/you
将查询段附加到 URL 上:
$ trurl --url "https://curl.se?name=hello" --append query=search=string
https://curl.se/?name=hello&search=string
从 stdin 读取 URL:
$ cat urllist.txt | trurl --url-file -
...
输出 JSON:
$ trurl "https://fake.host/hello#frag" --set user=::moo:: --json
[{"url": "https://%3a%3amoo%3a%3a@fake.host/hello#frag","parts": {"scheme": "https","user": "::moo::","host": "fake.host","path": "/hello","fragment": "frag"}}
]
从查询中删除跟踪元组:
$ trurl "https://curl.se?search=hey&utm_source=tracker" \--trim query="utm_*"
https://curl.se/?search=hey
显示特定的查询键值:
$ trurl "https://example.com?a=home&here=now&thisthen" -g '{query:a}'
home
对查询组件中的键/值对进行排序:
$ trurl "https://example.com?b=a&c=b&a=c" --sort-query
https://example.com?a=c&b=a&c=b
处理使用分号分隔的查询:
$ trurl "https://curl.se?search=fool;page=5" --trim query="search" \--query-separator ";"
https://curl.se?page=5
在 URL 路径中接受空格:
$ trurl "https://curl.se/this has space/index.html" --accept-space
https://curl.se/this%20has%20space/index.html
更多
您想了解的所有关于 trurl 的信息都可以在 curl.se/trurl 找到。它可能已经适用于您选择的 Linux 发行版。
URL globbing
有时你想要获取一系列大部分相同的 URL,其中只有一小部分在请求之间发生变化。这可能是一个数字范围,也可能是一组名称。curl 提供“globbing”作为指定这种类型多个 URL 的简便方法。
globbing 使用保留符号[]和{}来实现这一点,这些符号通常不能作为合法 URL 的一部分(除了数字 IPv6 地址,但 curl 仍然可以很好地处理它们)。如果 globbing 妨碍了你,可以使用-g, --globoff禁用它。
当从命令行提示符调用时使用[]或{}序列,你可能必须将完整的 URL 放在双引号内,以避免 shell 干扰它。这也适用于其他特殊处理的字符,例如‘&’,‘?’和‘*’。
虽然 curl 中的大多数传输相关功能都由 libcurl 库提供,但 URL globbing 功能不是。
数字范围
你可以使用[N-M]语法请求数字范围,其中 N 是起始索引,它向上到并包括 M。例如,你可以请求 100 个按数字命名的图片:
curl -O "http://example.com/[1-100].png"
它甚至可以处理没有前缀的范围,比如如果数字始终是三位数:
curl -O "http://example.com/[001-100].png"
或者你可能只想下载偶数编号的图片,所以你告诉 curl 一个步进计数器。这个例子中的范围从 0 到 100,步长为 2:
curl -O "http://example.com/[0-100:2].png"
字母范围
curl 还可以进行字母范围的遍历,比如当一个网站有从 a 到 z 命名的部分:
curl -O "http://example.com/section[a-z].html"
列表
有时候部分并不遵循这样的简单模式,然后你可以自己提供完整的列表,但然后在花括号内而不是用于范围的括号内:
curl -O "http://example.com/{one,two,three,alpha,beta}.html"
组合
你可以在同一个 URL 中使用多个 glob,这样 curl 也会遍历这些 URL。要下载 Ben、Alice 和 Frank 的图片,两种分辨率 100 x 100 和 1000 x 1000,命令行可能看起来像这样:
curl -O "http://example.com/{Ben,Alice,Frank}-{100x100,1000x1000}.jpg"
或者下载棋盘上所有图片,通过两个坐标范围 0 到 7 进行索引:
curl -O "http://example.com/chess-[0-7]x[0-7].jpg"
当然,你也可以混合范围和序列。获取 Web 服务器和邮件服务器一周的日志:
curl -O "http://example.com/{web,mail}-log[0-6].txt"
globbing 的输出变量
在本章前面所有的 globbing 示例中,我们选择使用-O / --remote-name选项,这使得 curl 使用使用的 URL 的文件名部分保存目标文件。
有时候这还不够。你正在下载多个文件,也许你想要将它们保存在不同的子目录中,或者以不同的方式创建保存的文件名。curl 当然也有针对这些情况的解决方案:输出文件名变量。
在 URL 中使用的每个“glob”都会得到一个单独的变量。它们被引用为#[num] - 这意味着单个字符#后跟 glob 编号,第一个 glob 从 1 开始,最后一个 glob 结束。
保存两个不同网站的主页:
curl "http://{one,two}.example.com" -o "file_#1.txt"
将带有两个 glob 的命令行输出的结果保存在子目录中:
curl "http://{site,host}.host[1-5].example.com" -o "subdir/#1_#2"
在 URL 中使用[]{}
当在 1990 年代 curl 中引入通配符概念时,我们所有人都使用相同的互联网标准来定义 URL 语法,在这个标准中,这四个符号被记录为保留。如果你想在 URL 中使用它们(使用%HH样式进行 URL 编码),你必须对它们进行 URL 编码。因此,这些符号没有在 URL 中使用,并且对于通配符用途来说非常吸引人。
后来,URL 语法逐渐放宽并发生变化,如今我们时不时地看到 URL 被使用,其中四个符号[]{}之一被直接使用,而没有进行 URL 编码。将这样的 URL 传递给 curl 会导致当通配符解析器疯狂时,curl 会输出语法错误。
为了解决这个问题,你有两个独立的选项。你可以自己编码这些符号,或者关闭通配符匹配。
按照以下方式对符号进行编码:
| 符号 | 编码 |
|---|---|
[ |
%5b |
] |
%5d |
{ |
%7b |
} |
%7d |
或者使用 -g 或 --globoff 来关闭通配符匹配。
列出选项
curl 有超过两百五十个命令行选项,而且这个数字随着时间的推移还在不断增加。未来几年,选项的数量可能会达到甚至超过三百。
要找出您需要执行特定操作的选项,可以让 curl 列出它们。首先,使用 curl --help 或简单地 curl -h 可以获得最重要和最常用的选项列表。然后,您可以为 -h 提供一个额外的“类别”来获取该特定区域的更多选项。使用 curl -h category 可以列出所有现有的类别,或者使用 curl -h all 来列出 所有 可用的选项。
使用 curl --manual 选项可以输出 curl 的完整手册页面。这是一份详尽且完整的文档,详细介绍了每个选项的工作原理,累积了数千行的文档内容。浏览这些内容是一项繁琐的工作,我们鼓励您使用搜索功能在这些文本海洋中查找。有些人可能也会欣赏其 网络版本 的手册页面。
配置文件
使用多个命令行选项的 Curl 命令可能会变得难以操作。字符数量甚至可能超过您的终端应用程序允许的最大长度。
为了帮助这种情况,curl 允许您在纯文本配置文件中编写命令行选项,并告诉 curl 在适用时从该文件中读取选项。
您还可以使用配置文件将数据分配给变量,并使用函数转换数据,这使得它们非常有用。这将在变量部分中讨论。
下面的一些示例包含多行以提高可读性。反斜杠(\)用于指示终端忽略换行符。
指定要使用的配置文件
使用-K或长形式--config选项告诉 curl 从配置文件中读取。
curl \--config configFile.txt \--url https://example.com
指定的文件路径相对于您的终端中的当前目录。
您可以为配置文件命名任何您喜欢的名称。在上面的示例中,使用configFile.txt是为了简单起见。
语法
每行输入一个命令。使用井号符号作为注释:
# curl config file# Follow redirects
--location# Do a HEAD request
--head
命令行选项
您可以使用短选项和长选项,就像您在命令行中写的那样。
您也可以不使用前导的两个连字符来编写长选项,使其更容易阅读。
# curl config file# Follow redirects
location# Do a HEAD request
head
参数
带参数的命令行选项必须在选项所在的同一行提供其参数。
# curl config fileuser-agent "Everything-is-an-agent"
您也可以在选项和其参数之间使用=或:。如您所见,这不是必需的,但有些人喜欢它提供的清晰度。再次设置用户代理选项:
# curl config fileuser-agent = "Everything-is-an-agent"
我们上面使用的用户代理字符串示例没有空格,所以从技术上讲,引号不是必需的:
# curl config fileuser-agent = Everything-is-an-agent
有关何时使用引号的更多信息,请参阅下文“何时使用引号”。
URL
当在命令行中输入 URL 时,所有不是选项的内容都被假定为 URL。然而,在配置文件中,您必须使用--url或url来指定 URL。
# curl config fileurl = https://example.com
何时使用引号
您需要在以下情况下使用双引号:
-
参数包含空格,或以字符
:或=开头。 -
您需要使用转义序列(可用选项:
\\,\",\t,\n,\r和\v。任何字母前的反斜杠都被忽略)。
如果包含空格的参数没有用双引号括起来,curl 会认为下一个空格或换行符是参数的结束。
默认配置文件
当调用 curl 时,它总是(除非使用-q),检查默认配置文件,如果找到则使用它。
Curl 按以下顺序在以下位置查找默认配置文件:
-
$CURL_HOME/.curlrc -
$XDG_CONFIG_HOME/.curlrc(自 7.73.0 版添加) -
$HOME/.curlrc -
Windows:
%USERPROFILE%\\.curlrc -
Windows:
%APPDATA%\\.curlrc -
Windows:
%USERPROFILE%\\Application Data\\.curlrc -
非 Windows:使用 getpwuid 查找主目录
-
在 Windows 上,如果在上述序列中找不到
.curlrc文件,它会在 curl 可执行文件所在的同一目录中查找一个。
在 Windows 系统中,每个位置会检查两个文件名:.curlrc 和 _curlrc,优先选择前者。旧版的 curl 在 Windows 上仅检查 _curlrc。
变量
这个命令行和配置文件变量的概念是在 curl 8.3.0 中添加的。
用户可以使用 --variable varName=content 将一个 变量 设置为纯字符串,或者从文件内容中设置,使用 --variable varName@file,其中文件可以是如果设置为单个短横线(-)的 stdin。
在此上下文中,变量被赋予一个特定的名称并包含内容。可以设置任意数量的变量。如果您再次设置相同的变量名称,它将被新的内容覆盖。变量名称区分大小写,最长可达 128 个字符,可以由字符 a-z、A-Z、0-9 和下划线组成。
以下一些示例包含多行以提高可读性。反斜杠(\)用于指示终端忽略换行。
设置变量
您可以使用 --variable 在命令行中设置变量,或者使用配置文件中的 variable(不带短横线)设置变量。
curl --variable varName=content
或者在一个配置文件中:
# Curl config filevariable varName=content
从文件分配内容
您也可以将纯文本文件的内容分配给变量:
curl --variable varName@filename
展开
当选项名称以 --expand- 为前缀时,可以使用 {{varName}} 在选项参数中展开变量。这使得变量 varName 的内容被插入。
如果您引用的名称作为变量不存在,则插入一个空字符串。
通过转义符将 {{ 原样插入字符串中:
\\{{
在下面的示例中,设置了变量 host 并进行了展开:
curl \ --variable host=example \--expand-url "https://{{host}}.com"
对于没有 --expand- 前缀指定的选项,变量不会被展开。
变量内容包含在展开时未编码的空字节会导致 curl 以错误退出。
环境变量
使用 --variable %VARNAME 导入环境变量。如果给定的环境变量未设置,此导入将使 curl 以错误退出。用户还可以选择如果环境变量不存在,则使用 =content 或 @file 设置默认值,如上所述。
例如,将 %USER 环境变量分配给 curl 变量并将其插入到 URL 中。因为没有指定默认值,所以如果环境变量不存在,此操作将失败:
curl \ --variable %USER \--expand-url "https://example.com/api/{{USER}}/method"
相反,如果 %USER 不存在,则使用 dummy 作为默认值:
curl \--variable %USER=dummy \--expand-url "https://example.com/api/{{USER}}/method"
展开 --variable
--variable 选项本身也可以展开,这允许您将变量分配给其他变量的内容。
curl \--expand-variable var1={{var2}} \--expand-variable fullname=’Mrs {{first}} {{last}}’ \--expand-variable source@{{filename}}
或者在一个配置文件中:
# Curl config filevariable host=exampleexpand-variable url=https://{{host}}.comexpand-variable source@{{filename}}
函数
当展开变量时,curl 提供一组 函数 来更改它们的展开方式。函数在变量后使用冒号加函数名应用,如下所示:{{varName:function}}。
可以将多个函数应用于变量。它们将按从左到右的顺序应用:{{varName:func1:func2:func3}}
这些功能可用:trim、json、url 和 b64
函数:trim
展开变量时,不包含前导和尾随空白。空白定义为:
-
水平制表符
-
空格
-
新行
-
垂直标签
-
表格分隔符和回车符
这在从文件读取数据时非常有用。
--expand-url “https://example.com/{{path:trim}}”
函数:json
将变量扩展为有效的 JSON 字符串。这使得将有效的 JSON 插入参数中变得更容易(结果 JSON 中不包括引号)。
--expand-json "\"full name\": \"{{first:json}} {{last:json}}\""
要先修剪变量,请按以下顺序应用这两个函数:
--expand-json "\"full name\": \"{{varName:trim:json}}\""
函数:url
扩展变量为 URL 编码。也称为 百分编码。此函数确保所有输出字符在 URL 中都是合法的,其余字符编码为 %HH,其中 HH 是表示 ascii 值的两个十六进制数字。
--expand-data “varName={{varName:url}}”
要先修剪变量,请按以下顺序应用这两个函数:
--expand-data “varName={{varName:trim:url}}”
函数:b64
将变量扩展为 base64 编码。Base64 是一种仅使用 64 个特定字符的二进制数据编码方式。
--expand-data “content={{value:b64}}”
要先修剪变量,请按以下顺序应用这两个函数:
--expand-data “content={{value:trim:b64}}”
示例:将名为 $HOME/.secret 的文件内容获取到名为 fix 的变量中。确保内容经过修剪并作为 POST 数据进行百分编码发送:
curl \--variable %HOME=/home/default \--expand-variable fix@{{HOME}}/.secret \--expand-data "{{fix:trim:url}}" \--url https://example.com/ \
密码
密码很棘手且敏感。泄露密码可能会让除了你之外的其他人访问原本受保护的资源和数据。
curl 提供了几种从用户接收密码并随后将其传递或用于其他目的的方法。
curl 最基本的认证选项是-u / --user。它接受一个参数,即用户名和密码,由冒号分隔。就像当alice想要请求需要 HTTP 认证的页面,而她的密码是12345时:
$ curl -u alice:12345 http://example.com/
命令行泄露
这里正在发生几件可能不好的事情。首先,我们在命令行中输入密码,而命令行可能对同一系统上的其他用户是可读的(假设你有一个多用户系统)。curl 通过尝试从进程列表中清除密码来帮助最小化这种风险。
避免在命令行上传递用户名和密码的一种方法是用一个.netrc 文件或一个配置文件。你也可以使用-u选项而不指定密码,然后 curl 在运行时提示用户输入。
网络泄露
其次,这个命令行将用户凭据发送到 HTTP 服务器,这是一个明文协议,对中间人或其他窥探者开放,他们可以监视连接并查看发送的内容。在这个命令行示例中,它使 curl 使用 HTTP 基本认证,这是完全不安全的。
有几种方法可以避免这种情况,关键是当然要避免在网络上发送凭据的明文协议或认证方案。最简单的方法可能是确保你使用协议的加密版本。使用 HTTPS 而不是 HTTP,使用 FTPS 而不是 FTP 等等。
如果你需要坚持使用明文和不安全的协议,那么看看你是否可以切换到使用一种避免在明文中发送凭据的认证方法。如果你想使用 HTTP,这些方法包括摘要(--digest)、协商(--negotiate.)和 NTLM(--ntlm)。
进度条
curl 有一个内置的进度条。当 curl 被调用以传输数据(无论是上传还是下载)时,它可以在终端屏幕上显示该进度条,以显示传输的进度,即当前的传输速度、已经进行的时间以及它认为可能剩余的时间直到完成。
如果 curl 认为输出将发送到终端,进度条将被抑制,因为进度条会干扰该输出并弄乱显示的内容。用户也可以使用-s / --silent选项强制关闭进度条,该选项告诉 curl 保持安静。
如果你调用 curl 而没有得到进度条,请确保你的输出不是指向终端。
curl 还提供了一个替代的更简单的进度条,你可以通过-# / --progress-bar启用它。正如长名称所暗示的,它显示传输为一个进度条。
在 curl 被要求传输数据时,它无法确定请求操作的总大小,这导致进度条包含的细节更少,例如,它无法预测传输时间等。
单位
进度条显示字节数和每秒字节数。
它还使用后缀表示更大的字节数,使用 1024 进制系统,因此 1024 是一个千字节(1K),2048 是 2K 等。curl 支持这些:
| Suffix | 数量 | 名称 |
|---|---|---|
| K | 2¹⁰ | 千字节 |
| M | 2²⁰ | 兆字节 |
| G | 2³⁰ | 吉字节 |
| T | 2⁴⁰ | 太字节 |
| P | 2⁵⁰ | 太字节 |
时间使用 H:MM:SS 格式显示,表示小时、分钟和秒。
进度条图例
进度条的存在是为了向用户显示实际上正在发生某事。输出中的不同字段具有以下含义:
% Total % Received % Xferd Average Speed Time Curr.Dload Upload Total Current Left Speed
0 151M 0 38608 0 0 9406 0 4:41:43 0:00:04 4:41:39 9287
从左到右:
| Title | 含义 |
|---|---|
% |
整个传输完成的百分比 |
Total |
整个预期传输的总大小(如果已知) |
% |
下载完成的百分比 |
Received |
当前已下载的字节数 |
% |
上传完成的百分比 |
Xferd |
当前已上传的字节数 |
Average Speed Dload |
到目前为止整个下载的平均传输速度,以每秒字节数表示 |
Average Speed Upload |
到目前为止整个上传的平均传输速度,以每秒字节数表示 |
Time Total |
完成操作预计时间,以HH:MM:SS格式表示的小时、分钟和秒 |
Time Current |
从传输开始经过的时间,以HH:MM:SS格式表示的小时、分钟和秒 |
Time Left |
完成剩余的预计时间,以HH:MM:SS格式表示的小时、分钟和秒 |
Curr. Speed |
过去 5 秒内的平均传输速度(传输的前 5 秒基于更少的时间,当然)以每秒字节数表示 |
版本
要了解您安装的 curl 版本,请运行
curl --version
或者使用简写版本:
curl -V
该命令行的输出通常是四行,其中一些可能相当长,可能会在您的终端窗口中换行。
2020 年 6 月 Debian Linux 的一个示例输出:
curl 7.68.0 (x86_64-pc-linux-gnu) libcurl/7.68.0 OpenSSL/1.1.1g
zlib/1.2.11 brotli/1.0.7 libidn2/2.3.0 libpsl/0.21.0 (+libidn2/2.3.0)
libssh2/1.8.0 nghttp2/1.41.0 librtmp/2.3
Release-Date: 2020-01-08
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3
pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS brotli GSS-API HTTP2 HTTPS-proxy IDN IPv6 Kerberos
Largefile libz NTLM NTLM_WB PSL SPNEGO SSL TLS-SRP UnixSockets
而在相同日期的 Windows 10 机器上运行的相同命令行看起来像:
curl 7.55.1 (Windows) libcurl/7.55.1 WinSSL
Release-Date: [unreleased]
Protocols: dict file ftp ftps http https imap imaps pop3 pop3s smtp smtps
telnet tftp
Features: AsynchDNS IPv6 Largefile SSPI Kerberos SPNEGO NTLM SSL
这四行的含义是什么?
第 1 行:curl
第一行以curl开头,首先显示工具的主版本号。然后是工具构建的平台(括号内),以及 libcurl 版本。这三个字段对所有 curl 构建都是通用的。
如果 curl 版本号后面附加了-DEV,则表示该版本是从开发源代码直接构建的,并且它不是一个官方发布和认可的版本。
这行剩余部分包含 curl 构建使用的第三方组件名称,通常旁边有斜杠分隔的各自版本号。例如OpenSSL/1.1.1g和nghttp2/1.41.0。这可以告诉你 curl 使用了哪些 TLS 后端。
第 1 行:TLS 版本
第 1 行可能包含一个或多个 TLS 库。curl 可以构建以支持多个 TLS 库,这使得 curl 在启动时选择为这次调用使用哪个特定的后端。
如果 curl 支持多个 TLS 库,则默认未选择的库列在括号内。因此,如果您没有指定要使用哪个后端(使用CURL_SSL_BACKEND环境变量),则使用未加括号的列表中的后端。
第 2 行:发布日期
这行显示了 curl 版本由 curl 项目发布日期,也可以显示一个次要的“补丁日期”,如果它在最初发布后以某种方式更新过。
这表示如果 curl 不是从发布 tarball 构建的,而是像上面所示的那样,Microsoft 为 Windows 10 做了,curl 项目不推荐这样做。
第 3 行:协议
这是 curl 构建支持的按字母顺序排列的所有传输协议(URL 方案)。所有名称均以小写字母显示。
此列表可以包含以下协议:
dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, mqtt, pop3, pop3s, rtmp, rtsp, scp, sftp, smb, smbs, smtp, smtps, telnet 和 tftp
第 4 行:特性
这个 curl 构建支持的特性列表。如果名称出现在列表中,则表示该特性已启用。如果名称不在列表中,则表示该特性未启用。
可能存在的特性:
-
alt-svc - 支持
alt-svc:头 -
AsynchDNS - 这个 curl 使用异步域名解析。异步域名解析可以使用 c-ares 或线程解析后端。
-
brotli - 支持通过 HTTP(S)自动 brotli 压缩
-
CharConv - curl 构建时支持字符集转换(如 EBCDIC)
-
调试 - 这个 curl 使用的是带有调试的 libcurl 构建的。这可以启用更多的错误跟踪和内存调试等。仅适用于 curl 开发者。
-
GSS-API - 启用了 GSS-API 认证。
-
HTTP2 - 内置了 HTTP/2 支持。
-
HTTP3 - 内置了 HTTP/3 支持。
-
HTTPS 代理 - 这个 curl 被构建来支持 HTTPS 代理。
-
IDN - 这个 curl 支持 IDN - 国际域名。
-
IPv6 - 你可以使用 IPv6。
-
krb4 - 支持 FTP 的 Krb4。
-
Largefile - 这个 curl 支持大文件传输,文件大小超过 2GB。
-
libz - 支持通过 HTTP 压缩文件的自动 gzip 解压缩。
-
Metalink - 这个 curl 支持 Metalink。在现代 curl 版本中,此选项永远不会可用。
-
MultiSSL - 这个 curl 支持多个 TLS 后端。第一行详细说明了确切的 TLS 库。
-
NTLM - 支持 NTLM 认证。
-
NTLM_WB - 支持 NTLM 认证。
-
PSL - 公共后缀列表(PSL)可用,这意味着这个 curl 已经内置了关于公共后缀的知识,用于 cookie。
-
SPNEGO - 支持 SPNEGO 认证。
-
SSL - 支持各种协议的 SSL 版本,例如 HTTPS、FTPS、POP3S 等。
-
SSPI - 支持 SSPI。
-
TLS-SRP - 支持 TLS 的 SRP(安全远程密码)认证。
-
UnixSockets - 提供 Unix 套接字支持。
持久连接
当设置到网站的连接时,curl 会保留旧连接一段时间,这样如果下一次传输使用的是之前传输相同的宿主,它就可以再次重用相同的连接,从而节省大量时间。我们称这种连接为持久连接。curl 总是尝试保持连接活跃,并在可能的情况下重用现有连接。
连接被保存在连接池中,有时也被称为连接缓存。
然而,curl 命令行工具只能保持连接活跃,直到它运行结束,因此一旦它退出回到你的命令行,它就必须关闭所有当前打开的连接(并且释放和清理它用于减少后续操作时间的所有其他缓存)。我们称活跃连接的池为连接缓存。
如果你想要对同一主机或同一基本 URL 执行 N 次传输或操作,通过尽可能少地使用 curl 命令行来执行它们,而不是每次只使用一个 URL 调用 curl,你可以获得很多速度上的提升。
退出代码
在该项目中投入了大量努力,以便在出现问题时 curl 能够返回一个可用的退出代码,并且在操作按计划进行时总是返回 0(零)。
如果你编写了一个调用 curl 的 shell 脚本或批处理文件,你可以始终检查返回代码以检测被调用命令中的问题。以下是在撰写本文时找到的返回代码列表。随着时间的推移,我们倾向于逐渐添加新的代码,所以如果你收到这里未列出的代码,请参阅更更新的 curl 文档以获取帮助。
一个基本的 Unix shell 脚本可能看起来像这样:
#!/bin/sh
curl http://example.com
res=$?
if test "$res" != "0"; thenecho "the curl command failed with: $res"
fi
可用的退出代码
-
不支持的协议。这个 curl 构建不支持此协议。通常这种情况发生是因为 URL 拼写错误,使用了前面有空格的方案部分,或者将
http拼写为htpt或类似的形式。另一个常见错误是,你使用了一个在构建时禁用了一个或多个协议的 libcurl 安装,你现在要求 libcurl 使用在构建时被禁用的那些协议之一。 -
初始化失败。这通常是一个内部错误或 libcurl 安装或系统 libcurl 运行时的问题。
-
URL 格式错误。语法不正确。这发生在你输入错误的 URL 导致其最终错误,或者在罕见的情况下,你使用的是 curl 不支持的 URL,因为没有一个所有人都遵守的通用 URL 标准。
-
需要的功能或选项未启用或是在构建时明确禁用。为了让 curl 能够执行此操作,你可能需要另一个 libcurl 构建。
-
无法解析代理。给定代理主机的地址无法解析。要么给定的代理名称是错误的,要么 DNS 服务器行为异常,不知道这个名称,或者甚至可能是你在上面运行 curl 的系统配置错误,导致它找不到/不使用正确的 DNS 服务器。
-
无法解析主机。给定的远程主机地址无法解析。给定服务器的地址无法解析。要么给定的主机名是错误的,要么 DNS 服务器行为异常,不知道这个名称,或者甚至可能是你在上面运行 curl 的系统配置错误,导致它找不到/不使用正确的 DNS 服务器。
-
无法连接到主机。curl 成功获取了机器的 IP 地址,并尝试设置到主机的 TCP 连接,但失败了。这可能是因为你指定了错误的端口号,输入了错误的域名,错误的协议,或者可能是因为中间有防火墙或其他网络设备阻止了流量通过。
-
未知 FTP 服务器响应。服务器发送了 curl 无法解析的数据。这可能是由于 curl 或服务器中的错误,或者服务器使用的是 curl 不支持的 FTP 协议扩展。解决这个问题的唯一真正方法是调整 curl 选项,尝试让它使用其他 FTP 命令,这些命令可能不会收到这个未知服务器的响应。
-
FTP 访问被拒绝。服务器拒绝登录或拒绝访问您想要到达的特定资源或目录。最常见的情况是您尝试切换到服务器上不存在的目录。当然,目录就是您在 URL 中指定的内容。
-
FTP 接受失败。在使用活动 FTP 会话等待服务器回连时,通过控制连接发送了错误代码或类似情况。
-
FTP 奇怪的 PASS 回复。Curl 无法解析发送到 PASS 请求的回复。PASS 是 curl 发送到服务器的命令,其中包含密码,即使是匿名连接到 FTP 服务器实际上也发送了一个密码 - 一个固定的匿名字符串。如果 curl 收到它不理解的命令回复,这强烈表明这根本不是 FTP 服务器,或者服务器严重损坏。
-
在活动 FTP 会话(使用 PORT)等待服务器连接期间,超时已过期。服务器回连花费了太长时间。这通常表明有某些因素阻止服务器成功连接到 curl,例如防火墙或其他网络配置。
-
对 FTP PASV 命令的未知响应。Curl 无法解析发送到 PASV 请求的回复。这是一个奇怪的服务器。PASV 用于在被动模式下设置第二个数据传输连接,有关更多信息,请参阅 FTP 使用两个连接部分。您可能可以通过使用 PORT 选项来绕过这个问题。
-
未知 FTP 227 格式。Curl 无法解析服务器发送的 227 行。这肯定是一个损坏的服务器。227 是 FTP 服务器在发送 curl 如何在被动模式下连接回它的信息时的响应。您可能可以通过使用 PORT 选项来绕过这个问题。
-
FTP 无法获取主机。无法使用我们在 227 行中获取的主机 IP 地址。这很可能是内部错误。
-
HTTP/2 错误。检测到 HTTP2 封装层存在问题。这相当通用,可能是几个问题中的一个,请参阅错误消息以获取详细信息。
-
FTP 无法设置二进制。无法将传输方法更改为二进制。此服务器已损坏。curl 需要在开始传输之前将其设置为正确的模式,否则传输无法工作。
-
文件部分传输。只有文件的一部分被传输。当传输被认为已完成时,curl 会验证它实际上接收到的数据量是否与之前告知的数据量相同。如果这两个数字不匹配,这就是错误代码。这可能意味着 curl 接收到的字节数少于广告的,或者接收到的更多。curl 本身无法知道哪个数字是错误的,或者是否正确,如果有的话。
-
FTP 无法下载/访问指定的文件。RETR(或类似)命令失败。当尝试下载文件时,curl 从服务器收到了错误。
-
未使用
-
引号错误。一个引号命令从服务器返回了错误。curl 允许通过几种不同的方式向 IMAP、POP3、SMTP 或 FTP 服务器发送自定义命令,并具有一个通用的检查,以确保命令可以工作。当任何单独发出的命令失败时,这就是返回的退出状态。通常建议查看 FTP 通信中的标题,以更好地理解确切失败的原因和方式。
-
HTTP 页面未检索到。请求的 URL 未找到或返回了 HTTP 错误代码为 400 或以上的其他错误。此返回代码仅在使用
-f, --fail时出现。 -
写入错误。Curl 无法将数据写入本地文件系统或类似的地方。curl 从网络以块的形式接收数据,并像在(或写入 stdout)一样逐块存储它。如果写入操作出现错误,这就是退出状态。
-
未使用
-
上传失败。服务器拒绝接受或存储 curl 尝试发送给它的文件。这通常是由于服务器上的访问权限错误,但也可能由于磁盘空间不足或其他资源限制而发生。这种错误可能适用于许多协议。
-
读取错误。各种读取问题。与退出状态 23 相反。当 curl 向服务器发送数据时,它从本地文件或 stdin 或类似的地方逐块读取数据,如果读取以某种方式失败,这就是 curl 返回的退出状态。
-
内存不足。内存分配请求失败。curl 需要分配比系统愿意提供的更多内存,因此 curl 必须退出。尝试使用较小的文件或确保 curl 有更多的内存来工作。
-
操作超时。根据条件,达到了指定的超时时间。curl 提供了几个超时,并且这个退出代码告诉其中一个超时限制已达到。延长超时或尝试更改其他允许 curl 更快完成操作的事情。通常,这发生在网络和远程服务器情况下,您无法在本地影响。
-
未使用
-
FTP PORT 失败。PORT 命令失败。并非所有 FTP 服务器都支持 PORT 命令;尝试使用 PASV 进行传输。PORT 命令用于请求服务器通过回连到 curl 来创建数据连接。另请参阅 FTP 使用两个连接部分。
-
FTP 无法使用 REST。REST 命令失败。此命令用于恢复 FTP 传输。curl 需要发出 REST 命令以执行范围或恢复传输。服务器损坏,尝试不使用范围/恢复作为粗略的解决方案执行相同的操作。
-
未使用
-
HTTP 范围错误。范围请求未成功。由于 HTTP 请求的恢复不一定被认可或支持,因此此退出代码表示对于此服务器上的此资源,无法进行范围或恢复传输。
-
HTTP POST 错误。内部 POST 请求生成错误。如果您遇到此错误,请向 curl 项目报告确切情况。
-
TLS/SSL 连接错误。SSL 握手失败。SSL 握手可能由于许多不同的原因而失败,因此错误消息可能提供一些额外的线索。可能是因为各方无法就 SSL/TLS 版本、可接受的加密套件或类似内容达成一致。
-
错误的下载恢复。无法继续之前中断的下载。当请求恢复传输而实际上无法完成时,可能会返回此错误。对于 FILE、FTP 或 SFTP。
-
使用 FILE:// 方案时无法读取给定的文件。无法打开文件。文件可能不存在,或者可能是权限问题?
-
LDAP 无法绑定。LDAP “绑定”操作失败,这是 LDAP 操作的必要步骤,因此这意味着 LDAP 查询无法执行。这可能是因为用户名或密码错误,或其他原因。
-
LDAP 搜索失败。给定的搜索条件导致 LDAP 搜索返回错误。
-
未使用
-
未使用
-
被回调终止。应用程序告诉 libcurl 终止操作。此错误代码通常不会对用户可见,也不会对 curl 工具的用户可见。
-
函数参数错误。一个函数被一个错误的参数调用 - 此返回代码存在是为了帮助应用程序作者理解为什么 libcurl 无法执行某些操作,并且 curl 工具永远不会返回此错误。如果这种情况发生在您身上,请向 curl 项目提交错误报告。
-
未使用
-
接口错误。指定的出站网络接口无法使用。curl 通常自行决定出站网络和 IP 地址,但当明确要求使用特定的一个而 curl 无法使用时,可能会发生此错误。
-
未使用
-
重定向太多。在跟随 HTTP 重定向时,libcurl 达到了应用程序设定的最大数量。libcurl 不会限制重定向的最大数量,但 curl 工具默认设置为 50。此限制是为了防止无限循环的重定向。使用
--max-redirs选项更改限制。 -
指定了未知的 libcurl 选项。这可能发生在你使用的 curl 版本与底层 libcurl 版本不匹配的情况下。也许你的新版本 curl 试图使用旧版 libcurl 中未引入的选项,而这个选项在你使用的 curl 工具代码中是已知的,因为它是更新的。为了降低这种风险并确保不会发生这种情况:请使用相同版本号的 curl 和 libcurl。
-
telnet 选项格式不正确。你提供给 curl 的 telnet 选项没有使用正确的语法。
-
未使用
-
服务器 SSL/TLS 证书或 SSH 指纹验证失败。在这种情况下,curl 无法确定服务器是否是它声称的那个。有关更多详细信息,请参阅使用 curl 的 TLS 和使用 curl 的 SCP 和 SFTP 部分。
-
服务器没有回复任何内容,这在当前上下文中被视为错误。当一个 HTTP(S)服务器响应 HTTP(S)请求时,只要服务器是活跃和健康的,它总是会返回某些内容。所有有效的 HTTP 响应都有一个状态行和响应头。完全没有收到任何内容表明服务器可能出现了故障,或者可能是某些因素阻止 curl 到达正确的服务器,或者你正在尝试连接到错误的端口号等。
-
未找到 SSL 加密引擎。
-
无法将 SSL 加密引擎设置为默认。
-
发送网络数据失败。在网络操作中,发送数据是 curl 操作的关键部分,当 curl 从最低层网络层收到发送失败的错误时,会返回此退出状态。为了确定为什么会发生这种情况,通常需要进行一些深入的挖掘。首先启用详细模式,进行跟踪,并在可能的情况下使用 Wireshark 或类似工具检查网络流量。
-
接收网络数据失败。在网络操作中,接收数据是 curl 操作的关键部分,当 curl 从最低层网络层收到接收数据失败的错误时,会返回此退出状态。为了确定为什么会发生这种情况,通常需要进行一些深入的挖掘。首先启用详细模式,进行跟踪,并在可能的情况下使用 Wireshark 或类似工具检查网络流量。
-
未使用
-
本地证书存在问题。客户端证书存在问题,因此无法使用。权限问题?错误的密码短语?
-
无法使用指定的 SSL 加密密钥。加密密钥的名称需要精确指定,而且不幸的是,它们也特定于 curl 构建时使用的特定 TLS 后端。有关支持的加密密钥列表及其编写方式,请参阅在线文档
curl.se/docs/ssl-ciphers.html。 -
无法使用已知的 CA 证书验证对等证书。这通常意味着证书是自签名的,或者是由不在 curl 使用的 CA 存储库中的 CA(证书颁发机构)签发的。
-
传输编码不可识别。从服务器接收到的内容无法被 curl 解析。
-
未使用
-
文件大小超出最大限制。当 curl 被告知限制下载,如果文件太大则不进行下载时,这是该条件的退出代码。
-
请求的 SSL(TLS)级别失败。在大多数情况下,这意味着 curl 在请求升级到 TLS 时失败了。
-
发送数据需要回绕,但失败了。在某些情况下,curl 需要回绕以重新发送数据,如果无法完成,则操作失败。
-
初始化 OpenSSL SSL 引擎失败。这只有在使用 OpenSSL 时才会发生,并且表示一个严重的内部问题。
-
用户名、密码或类似信息未被接受,curl 登录失败。请验证凭证是否正确提供,并且它们是否以正确的编码方式。
-
TFTP 服务器上找不到文件。
-
TFTP 服务器上的权限问题。
-
TFTP 服务器磁盘空间不足。
-
非法 TFTP 操作。
-
TFTP 传输 ID 未知。
-
文件已存在(TFTP)。
-
TFTP 上没有这样的用户。
-
未使用
-
未使用
-
读取 SSL CA 证书时出现问题。默认或指定的 CA 证书包无法读取/使用以验证服务器证书。
-
URL 中引用的资源(文件)不存在。
-
SSH 会话期间发生未指定的错误。这有时表明 curl 使用的 SSH 库与 curl 与服务器通信时使用的 SSH 版本之间存在不兼容性问题。
-
无法关闭 SSL 连接。
-
未使用
-
无法加载 CRL 文件,文件缺失或格式错误。
-
TLS 证书颁发者检查失败。这种情况最常见的原因是服务器在 TLS 握手过程中没有发送适当的中间证书。
-
FTP
PRET命令失败。这是一个非标准命令,并非所有服务器都支持它。 -
RTSP:CSeq 编号不匹配。
-
RTSP:会话标识符不匹配。
-
无法解析 FTP 文件列表。服务器使用的 FTP 目录列表格式无法被 curl 解析。此服务器上不能使用 FTP 通配符。
-
FTP 块回调报告错误。
-
没有可用的连接,会话正在排队。
-
SSL 公钥与已固定的公钥不匹配。要么你提供了错误的公钥,要么服务器已更改。
-
SSL 证书状态无效。服务器在 TLS 握手过程中没有提供有效的证书。
-
HTTP/2 帧层中的流错误。这通常是一个无法恢复的错误,但尝试强制 curl 使用 HTTP/1 可能绕过它。
-
在回调内部调用了 API 函数。如果 curl 工具返回此错误,则表示内部出现了问题。
-
认证错误。
-
HTTP/3 层错误。这是一个相当通用的错误,可能是几个问题之一,请参阅错误消息以获取详细信息。
-
QUIC 连接错误。这个错误可能是由 TLS 库错误引起的。QUIC 是用于 HTTP/3 的传输协议。
-
代理握手错误。通常这意味着 SOCKS 代理没有配合。
-
需要 TLS 客户端证书,但未提供。
-
内部对 poll()或 select()的调用返回了无法恢复的错误。
错误消息
当 curl 以非零代码退出时,它也会输出一个错误消息(除非使用了--silent)。这个错误消息可能会向退出状态数字本身添加一些额外的信息或情况,因此相同的错误编号可以得到不同的错误消息。
用户还可以使用–write-out 自定义错误消息。伪变量%{onerror}允许您设置仅在错误时显示的消息,并提供%{errormsg}和%{exitcode}等变量。
例如:
curl --write-out "%{onerror}curl says: (%{exitcode}) %{errormsg}" \https://curl.se/
“未使用”
上面的退出代码列表包含了一些标记为“未使用”的值。这些是 curl 现代版本中未使用的退出状态代码,但在过去曾被使用或打算使用。它们可能在 curl 的将来版本中使用。
此外,在此列表中使用的最高错误状态是 99,但未来的 curl 版本可能会在该数字之后添加更多的退出代码。
Copy as curl
使用 curl 重新执行用户刚刚使用浏览器完成的操作是一个常见的请求和人们寻求帮助的领域。
如何获取一个 curl 命令行来获取资源,就像浏览器获取它那样,既简单又方便?Chrome、Firefox、Edge 和 Safari 都有这个功能。
从 Firefox
您可以使用 Firefox 的网络工具查看显示的网站。当您看到 HTTP 流量时,在“Web Developer->Network”工具中右键单击您想要重复的特定请求,然后在出现的菜单中选择“Copy as cURL”。如下面的截图所示。此操作将在您的剪贴板中生成一个 curl 命令行,然后您可以将它粘贴到您喜欢的 shell 窗口中。此功能在所有 Firefox 安装中默认可用。

使用 Firefox 的 copy as curl
从 Chrome 和 Edge
当您在 Chrome 或 Edge 中弹出 More tools->Developer mode 并选择网络标签页时,您可以看到用于获取网站资源的 HTTP 流量。在您感兴趣的特定资源行上,您用鼠标右键单击并选择“Copy as cURL”,它将在您的剪贴板中为您生成一个命令行。将此粘贴到 shell 中以获取一个执行传输的 curl 命令行。此功能在所有 Chrome 和 Chromium 安装中默认可用。*(注意:Windows 中的 Chromium 浏览器可能会由于 Chromium 中的一个 *bug* 而产生一个不正确的命令行,该命令行由于引号错误而出现错误)。

使用 Chrome 的 copy as curl
从 Safari
在 Safari 中,直到您进入 preferences->Advanced 并启用它之前,“开发”菜单是不可见的。但一旦您这样做了,您可以在开发菜单中选择 Show web inspector,然后您可以看到一个弹出的新控制台,它与 Firefox 和 Chrome 的开发工具类似。
选择网络标签页,重新加载网页,然后您可以对您想要使用 curl 获取的特定资源右键单击,就像您在 Safari 中那样。

使用 Safari 的 copy as curl
在 Firefox 中,不使用开发者工具
如果您经常需要这样做,您可能会觉得使用开发者工具有点不方便,而且弹出以复制命令行有些繁琐。那么 cliget 就是您完美的插件,因为它在右键菜单中提供了一个新选项,这样您可以快速生成命令行,就像以下示例中我在 Firefox 中右键单击一个图片时那样:

使用 Firefox 的 cliget
不完美
这些方法都为你提供了一个命令行来重现它们的 HTTP 传输。它们通常不是解决你问题的完美方案。为什么?好吧,主要是因为这些工具是编写来重新运行你复制的完全相同的请求,而你通常想要重新运行相同的逻辑,但不需要发送完全相同的 cookie 和文件内容等。
这些工具为你提供了带有静态和固定 cookie 内容的命令行,用于发送请求,因为这就是浏览器请求中发送的 cookie 内容。你很可能会想要重写命令行,以便动态适应服务器在之前的响应中告诉你的 cookie 中的任何内容。等等。
复制为卷曲功能在处理 -F 参数时也常常臭名昭著,它们通常会提供手工编写的 --data-binary 解决方案,包括 MIME 分隔符字符串等。
命令行传输
前几章描述了 curl 的概念和一些基本命令行信息。您使用命令行选项,并传递 URL 来进行操作。
在本章中,我们将更深入地探讨使用 curl 进行实际传输。curl 工具能做什么,以及如何告诉 curl 使用这些功能来为您发送或检索数据。您应该将这些功能视为不同的工具,它们在这里是为了帮助您尽可能方便地完成文件传输任务。
-
详细模式
-
下载
-
上传
-
传输控制
-
连接
-
超时
-
.netrc
-
代理
-
TLS
-
SCP 和 SFTP
-
读取电子邮件
-
发送电子邮件
-
DICT
-
IPFS
-
MQTT
-
TELNET
-
TFTP
详细模式
如果您的 curl 命令没有执行或返回您期望的结果,您应该首先本能地运行带有-v / --verbose选项的命令以获取更多信息。
当启用详细模式时,curl 变得更加健谈,并解释和显示更多它的行为。它添加了信息性测试,并以‘*’作为前缀。例如,让我们看看 curl 在尝试一个简单的 HTTP 示例(将下载的数据保存到名为“saved”的文件中)时可能会说什么:
$ curl -v http://example.com -o saved
* Rebuilt URL to: http://example.com/
好吧,所以我们使用了一个 curl 认为不完整的 URL 来调用它,因此它帮助我们并在继续之前添加了一个尾随斜杠。
* Trying 93.184.216.34...
这告诉我们 curl 现在正在尝试连接到这个 IP 地址。这意味着域名‘example.com’已经被解析为一个或多个地址,这是 curl 尝试连接的第一个(也可能是唯一的)地址。
* Connected to example.com (93.184.216.34) port 80 (#0)
这成功了。curl 连接到了该网站,这里它解释了域名如何映射到 IP 地址,以及它在哪个端口上连接。‘(#0)’部分是 curl 为这个连接分配的内部编号。如果您在同一个命令行中尝试多个 URL,您可以看到它使用更多的连接或重用连接,因此连接计数器可能会增加或不会增加,这取决于 curl 决定需要做什么。
如果我们使用HTTPS:// URL 而不是 HTTP URL,还会有很多行解释 curl 如何使用 CA 证书来验证服务器的证书,以及服务器证书的一些细节等。包括选择了哪些加密套件以及更多的 TLS 细节。
除了从 curl 内部获取的附加信息外,-v详细模式还会显示 curl 发送和接收的所有头部信息。对于没有头部信息的协议(如 FTP、SMTP、POP3 等),我们可以将命令和响应视为头部信息,因此它们也会在-v模式下显示。
如果我们继续查看上述命令的输出(但忽略实际的 HTML 响应),curl 会显示:
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/7.45.0
> Accept: */*
>
这是发送到网站的完整 HTTP 请求。这个请求是在默认的 curl 7.45.0 安装中看起来是什么样子,当然,在不同的版本之间可能会有所不同,特别是如果您添加了命令行选项,它会有所变化。
HTTP 请求头部的最后一行看起来是空的,确实是空的。它标志着头部和主体的分隔,在这个请求中,没有“主体”要发送。
继续进行,并假设一切按计划进行,发送的请求会从服务器获得相应的响应,而这个 HTTP 响应在响应体之前开始,包含一组头部信息:
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Cache-Control: max-age=604800
< Content-Type: text/html
< Date: Sat, 19 Dec 2015 22:01:03 GMT
< Etag: "359670651"
< Expires: Sat, 26 Dec 2015 22:01:03 GMT
< Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT
< Server: ECS (ewr/15BD)
< Vary: Accept-Encoding
< X-Cache: HIT
< x-ec-custom-error: 1
< Content-Length: 1270
<
这可能看起来像是胡言乱语,但这是一组正常的 HTTP 头部信息——关于响应的元数据。第一行的“200”可能是其中最重要的信息,意味着“一切正常”。
收到的头部信息的最后一行,如您所见,是空的,这是 HTTP 协议用来表示头部信息结束的标记。
在头部信息之后是实际的响应体,数据负载。常规的 -v verbose 模式不会显示这些数据,但它只会显示
{ [1270 bytes data]
那么应该有 1270 字节在“已保存”的文件中。你还可以看到响应中有一个名为 Content-Length: 的头部,其中包含确切的文件长度(尽管它可能并不总是出现在响应中)。
HTTP/2 和 HTTP/3
当使用 HTTP 协议的第二版或第三版进行文件传输时,curl 会发送和接收压缩的头部信息。为了以可读和易懂的方式显示 HTTP/2 和 HTTP/3 的头部信息,curl 会以类似于 HTTP/1.1 的风格显示未压缩的版本。
寂静
与 verbose 相反,当然是要让 curl 更加安静。使用 -s(或 --silent)选项可以让 curl 关闭进度条,并且在发生错误时不会输出任何错误信息。它会变得沉默。但它仍然会输出你请求下载的数据。
在激活了寂静模式后,你可以通过添加 -S 或 --show-error 来请求在失败时仍然输出错误信息。
-
跟踪选项
-
输出内容
跟踪选项
有时候-v是不够的。特别是当你想要存储包括实际传输数据的完整流时。
对于 curl 使用 HTTPS、FTPS 或 SFTP 等协议进行加密文件传输的情况,其他网络监控工具(如 Wireshark 或 tcpdump)无法像 curl 那样轻松地完成这项工作。
对于这一点,curl 提供了另外两个选项,你可以用它们来代替-v。
--trace [filename]将完整的跟踪保存到指定的文件名中。你也可以使用-(单个减号)来代替文件名,以便将其传递到标准输出。你会这样使用它:
$ curl --trace dump http://example.com
完成后,会有一个dump文件,可能会相当大。在这种情况下,dump文件的前 15 行看起来像:
== Info: Rebuilt URL to: http://example.com/
== Info: Trying 93.184.216.34...
== Info: Connected to example.com (93.184.216.34) port 80 (#0)
=> Send header, 75 bytes (0x4b)
0000: 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a GET / HTTP/1.1..
0010: 48 6f 73 74 3a 20 65 78 61 6d 70 6c 65 2e 63 6f Host: example.co
0020: 6d 0d 0a 55 73 65 72 2d 41 67 65 6e 74 3a 20 63 m..User-Agent: c
0030: 75 72 6c 2f 37 2e 34 35 2e 30 0d 0a 41 63 63 65 url/7.45.0..Acce
0040: 70 74 3a 20 2a 2f 2a 0d 0a 0d 0a pt: */*....
<= Recv header, 17 bytes (0x11)
0000: 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d HTTP/1.1 200 OK.
0010: 0a .
<= Recv header, 22 bytes (0x16)
0000: 41 63 63 65 70 74 2d 52 61 6e 67 65 73 3a 20 62 Accept-Ranges: b
0010: 79 74 65 73 0d 0a ytes..
每一个发送和接收的字节都会单独以十六进制数字显示。接收到的头部信息按行输出。
如果你认为十六进制数没有帮助,你可以尝试使用--trace-ascii [filename],同时它也接受-作为标准输出,这使得前 15 行跟踪看起来像:
== Info: Rebuilt URL to: http://example.com/
== Info: Trying 93.184.216.34...
== Info: Connected to example.com (93.184.216.34) port 80 (#0)
=> Send header, 75 bytes (0x4b)
0000: GET / HTTP/1.1
0010: Host: example.com
0023: User-Agent: curl/7.45.0
003c: Accept: */*
0049:
<= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
<= Recv header, 22 bytes (0x16)
0000: Accept-Ranges: bytes
<= Recv header, 31 bytes (0x1f)
0000: Cache-Control: max-age=604800
时间戳
--trace-time选项在打印每一行时,在详细/跟踪输出前加上高分辨率计时器。它与常规的-v / --verbose选项以及--trace和--trace-ascii一起工作。
一个例子可能看起来像这样:
$ curl -v --trace-time http://example.com
23:38:56.837164 * Rebuilt URL to: http://example.com/
23:38:56.841456 * Trying 93.184.216.34...
23:38:56.935155 * Connected to example.com (93.184.216.34) port 80 (#0)
23:38:56.935296 > GET / HTTP/1.1
23:38:56.935296 > Host: example.com
23:38:56.935296 > User-Agent: curl/7.45.0
23:38:56.935296 > Accept: */*
23:38:56.935296 >
23:38:57.029570 < HTTP/1.1 200 OK
23:38:57.029699 < Accept-Ranges: bytes
23:38:57.029803 < Cache-Control: max-age=604800
23:38:57.029903 < Content-Type: text/html
---- snip ----
行都是本地时间,以小时:分钟:秒表示,然后是那一秒中的微秒数。
识别传输和连接
虽然使用这些选项在屏幕或文件上显示的跟踪信息流是连续的,即使你的命令行可能使 curl 使用大量的单独连接和不同的传输,但有时你想要看到以下各种信息对应的具体传输或连接。为了更好地理解跟踪输出。
你可以然后添加--trace-ids到行中,你将看到 curl 如何为所有跟踪添加两个数字:连接号和传输号。它们是两个独立的标识符,因为连接可以被重用,多个传输可以使用相同的连接。
更多数据
如果跟踪数据量不足。例如,当你怀疑并想要在更基础的底层协议级别调试问题时,curl 为你提供了--trace-config选项。
使用这个选项,你告诉 curl 也记录它默认不包含的组件的日志,例如 TLS、HTTP/2 或 HTTP/3 协议的详细信息。它还提供了方便的选项来添加连接和传输标识符以及时间戳。
--trace-config选项接受一个参数,其中你可以指定一个以逗号分隔的列表,指定你想要跟踪的区域。例如,包括标识符并显示 HTTP/2 的详细信息:
curl --trace-config ids,http/2 https://example.com
选项的确切集合各不相同,但这里有一些可以尝试的选项:
| area | description |
|---|---|
ids |
与--trace-ids提供的相同标识符 |
time |
与--trace-time提供的相同时间输出 |
all |
显示所有可能的内容 |
tls |
TLS 协议交换详情 |
http/2 |
HTTP/2 帧信息 |
http/3 |
HTTP/3 帧信息 |
* |
未来版本中可能添加的额外信息 |
使用 all 进行快速运行通常是一个很好的方法来查看显示的具体区域,因为这样你可以进行后续的运行,并设置更具体的区域。
写出
--write-out或简写为-w,在传输完成后输出文本和信息。它提供了一系列变量,您可以将这些变量包含在输出中,这些变量包含已设置的值和传输的信息。
通过将纯文本传递给此选项来指示 curl 输出一个字符串:
curl -w "formatted string" http://example.com/
…你也可以让 curl 从指定的文件中读取该字符串,如果在该字符串前加上‘@’:
curl -w @filename http://example.com/
…或者甚至让 curl 从 stdin 读取字符串,如果您使用-作为文件名:
curl -w @- http://example.com/
变量
可用的变量通过在字符串中写入%{variable_name}来访问,该变量将被正确的值替换。要输出一个普通的%,请将其写作%%。您还可以使用\n输出换行符,使用\r输出回车符,使用\t输出制表符。
例如,我们可以这样输出 HTTP 传输的 Content-Type 和响应代码,用换行符和一些额外的文本分隔:
curl -w "Type: %{content_type}\nCode: %{response_code}\n" \http://example.com
默认情况下,输出发送到 stdout,所以您可能想确保您没有也将下载的内容发送到 stdout,否则您可能很难分离数据;或者使用%{stderr}将输出发送到 stderr。
HTTP 头部
此选项还提供了一个简单易用的方法来输出最近一次传输的 HTTP 响应头内容。
在字符串中使用%header{name},其中name是头部的无大小写名称(不带尾随冒号)。然后输出头部内容将精确地显示为通过网络发送的内容,前后空白被删除。如下所示:
curl -w "Server: %header{server}\n" http://example.com
输出
默认情况下,此选项使选定的数据输出到 stdout。如果这还不够好,可以使用伪变量%{stderr}将(以下)部分重定向到 stderr,而%{stdout}则将其带回 stdout。
从 curl 8.3.0 版本开始,有一个功能允许用户将 write-out 输出发送到文件:%output{filename}。然后数据将被写入该文件。如果您希望 curl 向该文件追加而不是从头创建它,请将文件名前缀为>>。如下所示:%output{>>filename}。
写出参数可以包括用户认为合适的 stderr、stdout 和文件输出。
Windows
注意: 在 Windows 中,%符号是一个特殊符号,用于展开环境变量。在批处理文件中,使用此选项时,所有%的出现都必须加倍,以正确转义。如果在命令提示符中使用此选项,则%无法转义,可能会发生意外的展开。
可用的–write-out 变量
这些变量中的一些在非常旧的 curl 版本中不可用。
| 变量 | 描述 |
|---|---|
certs |
输出最近一次 TLS 握手的证书链 - 包括详细信息。(自 7.88.0 版本引入) |
content_type |
请求文档的 Content-Type,如果有。 |
errormsg |
传输错误信息。如果没有错误发生,则为空。(自 7.75.0 版本引入) |
exitcode |
传输的数值退出代码。如果没有发生错误,则为 0。(自 7.75.0 版本引入) |
filename_effective |
curl 写入的最终文件名。如果 curl 被指示使用--remote-name或--output选项写入文件,则非常有用。它与--remote-header-name选项结合使用时最有用。 |
ftp_entry_path |
当登录到远程 FTP 服务器时 curl 最终到达的初始路径。 |
http_code |
现在被称为response_code的前一个变量名。 |
http_connect |
在 curl CONNECT 请求的最后响应(来自代理)中找到的数值代码。 |
http_version |
使用的 HTTP 版本。 |
json |
将所有写入变量作为一个单独的 JSON 对象输出。(自 7.72.0 版本引入) |
local_ip |
最近使用连接的本地端 IP 地址 - 可以是 IPv4 或 IPv6 |
local_port |
最近使用连接的本地端口号 |
method |
最近请求使用的 HTTP 方法。(自 7.72.0 版本引入) |
num_certs |
最近 TLS 握手中证书的数量。(自 7.88.0 版本引入) |
num_connects |
在最近传输中建立的新连接数量。 |
num_headers |
最后响应中的响应头数量 |
num_redirects |
请求中跟随的重定向数量。 |
onerror |
如果传输因错误而结束,则显示字符串的其余部分,否则在这里停止。(自 7.75.0 版本引入) |
proxy_ssl_verify_result |
与代理通信时请求的 SSL 对等证书验证的结果。0 表示验证成功。 |
redirect_url |
当进行 HTTP 请求时,如果没有使用 -L 来跟随重定向,将实际重定向到的 URL。 |
remote_ip |
最近使用连接的远程 IP 地址 — 可以是 IPv4 或 IPv6。 |
remote_port |
最近建立的连接的远程端口号。 |
response_code |
在最后传输中找到的数值响应代码。 |
scheme |
在上一个 URL 中使用的方案 |
size_download |
下载的总字节数。 |
size_header |
下载的头的总字节数。 |
size_request |
在 HTTP 请求中发送的总字节数。 |
size_upload |
上传的总字节数。 |
speed_download |
curl 测量的完整下载的平均下载速度,以字节每秒为单位。 |
speed_upload |
curl 测量的完整上传的平均上传速度,以字节每秒为单位。 |
ssl_verify_result |
请求的 SSL 对等证书验证的结果。0 表示验证成功。 |
stderr |
使其余输出写入 stderr。 |
stdout |
使其余输出写入 stdout。 |
time_appconnect |
从开始到与远程主机进行 SSL/SSH 等连接/握手完成所需的时间(以秒为单位)。 |
time_connect |
从开始到与远程主机(或代理)的 TCP 连接完成所需的时间(以秒为单位)。 |
time_namelookup |
从开始到域名解析完成所需的时间(以秒为单位)。 |
time_pretransfer |
从开始到文件传输即将开始所需的时间(以秒为单位)。这包括所有与特定协议(s)相关的预传输命令和协商。 |
time_redirect |
包括名称查找、连接、预传输和传输的所有重定向步骤所需的时间(以秒为单位),直到开始最终事务。time_redirect 是多次重定向的完整执行时间。 |
time_starttransfer |
从开始到第一个字节即将被传输所需的时间(以秒为单位)。这包括 time_pretransfer 以及服务器计算结果所需的时间。 |
time_total |
全部操作持续的总时间(以秒为单位)。时间以毫秒分辨率显示。 |
url |
传输中使用的 URL。(自 7.75.0 版本引入) |
url_effective |
最后被获取的 URL。如果你告诉 curl 跟随 Location:标题(使用-L),这尤其有意义。 |
urlnum |
传输中使用的 URL 的基于 0 的数值索引。(自 7.75.0 版本引入) |
在 curl 8.1.0 版本中,添加了仅输出特定 URL 组件的变量,当url或url_effective变量显示的内容超过所需时使用。
| 变量 | 描述 |
|---|---|
url.scheme |
被获取的 URL 的方案部分。 |
url.user |
被获取的 URL 的用户部分。 |
url.password |
被获取的 URL 的密码部分。 |
url.options |
被获取的 URL 的选项部分。仅适用于某些方案。 |
url.host |
被获取的 URL 的主机名部分。 |
url.path |
被获取的 URL 的路径部分。 |
url.query |
被获取的 URL 的查询部分。 |
url.fragment |
被获取的 URL 的片段部分。 |
url.zoneid |
被获取的 URL 的域 ID 部分。仅当主机名是 IPv6 地址时才可用。 |
urle.scheme |
被获取的有效(最后)URL 的方案部分。 |
urle.user |
被获取的有效(最后)URL 的用户部分。 |
urle.password |
被获取的有效(最后)URL 的密码部分。 |
urle.options |
被获取的有效(最后)URL 的选项部分。仅适用于某些方案。 |
urle.host |
被获取的有效(最后)URL 的主机名部分。 |
urle.path |
被获取的有效(最后)URL 的路径部分。 |
urle.query |
被获取的有效(最后)URL 的查询部分。 |
urle.fragment |
被获取的有效(最后)URL 的片段部分。 |
urle.zoneid |
被获取的有效(最后)URL 的区域 ID 部分。仅在主机名为 IPv6 地址时可用。 |
下载
“下载”意味着从网络上的服务器获取数据,此时服务器显然被认为是“高于”你的。这是将数据从服务器加载到运行 curl 的你的机器上。
下载可能是 curl 最常见的使用场景——将 URL 指向的特定数据检索到你的机器上。
-
下载究竟是什么?
-
存储下载
-
将下载保存到由 URL 命名的文件中
- 使用服务器提供的目标文件名
-
HTML 和字符集
-
压缩
-
shell 重定向
-
多文件下载
-
我的浏览器显示其他内容
-
最大文件大小
-
在文件系统中存储元数据
-
原始数据
-
重试
-
断点续传和范围下载
下载究竟是什么?
您可以通过向 curl 提供 URL 来指定要下载的资源。curl 默认下载 URL,除非另有说明,并且 URL 标识了要下载的内容。在这个例子中,要下载的 URL 是http://example.com:
curl http://example.com
URL 被分解为其各个组成部分([如其他地方所述](../../cmdline/urls/)),联系正确的服务器,然后请求提供特定的资源——通常是文件。服务器随后提供数据,或者拒绝,或者可能是客户端请求了错误的数据,然后提供那些数据。
对资源的请求是协议特定的,所以FTP:// URL 与HTTP:// URL 或SFTP:// URL 的工作方式不同。
没有路径部分的 URL,即只有主机名部分的 URL(如上面提到的http://example.com示例)会在内部附加一个斜杠(‘/’),然后这就是 curl 从服务器请求的资源。
如果您在命令行上指定了多个 URL,curl 会逐个下载每个 URL。它不会在之前的传输完成之前开始第二个传输,等等。
存储下载
如果你尝试上一节中的示例下载,你可能会注意到 curl 将下载的数据输出到 stdout,除非你告诉它做其他事情。将数据输出到 stdout 在你想将其管道传输到另一个程序或类似程序时非常有用,但并不总是处理下载的最佳方式。
使用 -o [filename](将 --output 作为选项的长版本)给 curl 提供一个特定的文件名来保存下载,其中 filename 可以是文件名,到文件名的相对路径或文件的完整路径。
还要注意,你可以将 -o 放在 URL 之前或之后;这没有区别:
curl -o output.html http://example.com/
curl -o /tmp/index.html http://example.com/
curl http://example.com -o ../../folder/savethis.html
当然,这不仅仅限于 http:// URL,无论你下载哪种类型的 URL 都以相同的方式工作:
curl -o file.txt ftp://example.com/path/to/file-name.ext
如果你要求 curl 将输出发送到终端,它会尝试检测并阻止二进制数据被发送到那里,因为这样会严重破坏你的终端(有时甚至会导致其停止工作)。你可以使用 -o - 覆盖 curl 的二进制输出预防并强制输出被发送到 stdout。
curl 有几种其他方式来存储和命名下载的数据。详细信息如下。
覆盖写入
当 curl 将远程资源下载到本地文件名时,如上所述,如果该文件已存在,它会覆盖该文件。它会 覆盖 它。
curl 提供了一种避免这种覆盖的方法:--no-clobber。
当使用此选项时,如果 curl 发现已存在具有给定名称的文件,它会尝试在文件名中添加一个点和一个数字来找到一个尚未使用的名称。它从 1 开始,然后继续尝试数字,直到达到 100 并选择第一个可用的一个。
例如,如果你要求 curl 下载一个 URL 到 picture.png,并且在该目录中已经有两个名为 picture.png 和 picture.png.1 的文件,以下将文件保存为 picture.png.2:
curl --no-clobber https://example.com/image -o picture.png
用户可以使用 –write-out 选项的 %filename_effective 变量来确定最终使用的名称。
错误时的残留
默认情况下,如果 curl 在下载过程中遇到问题并以错误退出,部分传输的文件将保持原样。它可能是预期文件的一小部分,也可能是几乎整个文件。用户必须决定如何处理残留文件。
--remove-on-error 命令行选项改变了这种行为。它告诉 curl 在 curl 因为错误退出时删除任何部分保存的文件。不再有残留文件。
将下载保存到由 URL 命名的文件中
然而,许多 URL 的右端已经包含了文件名部分。curl 允许你使用这个快捷方式,这样你就不必用-o重复它。而不是:
curl -o file.html http://example.com/file.html
你可以用这个命令将远程 URL 资源保存到本地文件file.html中:
curl -O http://example.com/file.html
这是-O(大写字母 O)选项,或者长名称版本的--remote-name。-O选项通过选择你提供的 URL 的文件名部分来选择要使用的本地文件名。这很重要。你指定 URL,curl 会从这个数据中选取名称。如果网站重定向 curl 到其他地方(如果你告诉 curl 跟随重定向),它不会改变 curl 用于存储此内容的文件名。
为所有 URL 使用 URL 的文件名部分
作为对使用一百个 URL 时添加一百个-O选项的反应,我们引入了一个名为--remote-name-all的选项。这使得-O成为所有给定 URL 的默认操作。你仍然可以为 URL 提供单独的“存储指令”,但如果为下载的 URL 省略了它,那么默认操作将从 stdout 切换到-O风格。
使用服务器上的目标文件名
HTTP 服务器有提供名为Content-Disposition:的响应头选项。该头可能包含一个建议的文件名,用于传递的内容,curl 可以被告知使用这个提示来命名其本地文件。-J或--remote-header-name选项启用此功能。如果你还使用了-O选项,curl 默认会使用 URL 中的文件名,并且只有当实际上有有效的Content-Disposition头可用时,它才会切换到使用该名称保存。
-J选项有一些问题和风险,用户需要了解:
-
它只使用建议文件名的最右边部分,因此服务器建议的任何路径或目录都会被删除。
-
由于文件名完全由服务器选择,如果服务器恰好提供了这样的文件名,curl 可能会覆盖你当前目录中任何现有的本地文件(除非你使用了
--no-clobber)。 -
文件名编码和字符集问题。curl 不会以任何方式解码名称,因此你可能会得到一个 URL 编码的文件名,而在浏览器中,它可能会使用合理的字符集将其解码为更易读的格式。
HTML 和字符集
curl 下载服务器发送的确切二进制数据。在例如下载 HTML 页面或其他使用特定字符编码的文本数据,而浏览器按预期显示的情况下,这可能对你很重要。curl 不会转换到达的数据。
一个常见的例子是,当用户下载一个包含如下内容的网页时,这会导致一些令人惊讶的结果:
curl https://example.com/ -o storage.html
…在事后检查storage.html文件后,用户会发现一个或多个字符看起来很奇怪或完全错误。这可能是因为服务器使用字符集 X 发送字符,而你的编辑器和环境使用字符集 Y。在一个理想的世界里,我们都会在所有地方使用 UTF-8,但不幸的是,这还不是事实。
对于这个问题的一个常见解决方案是使用常见的iconv实用程序将文本文件在不同字符集之间进行转换。
压缩
curl 允许你请求 HTTP 和 HTTPS 服务器提供压缩后的数据,并在到达时自动解压缩。在带宽比 CPU 更有限的情况下,这可以帮助你在更短的时间内接收更多数据。
HTTP 压缩可以使用两种不同的机制来完成,一种可能被认为是“正确的方式”,另一种则是大家实际使用且广泛流行的方式。压缩 HTTP 内容的一种常见方式是使用内容编码头。你要求 curl 使用这个选项--compressed:
curl --compressed http://example.com/
启用此选项(如果服务器支持)后,它会以压缩方式提供数据,curl 在保存或发送到 stdout 之前对其进行解压缩。这通常意味着作为用户,你实际上并不真正看到或体验到压缩,除非可能注意到传输速度更快。
--compressed选项请求使用支持的压缩算法之一进行内容编码压缩。还有一种罕见的传输编码方法,这是为这种自动化方法创建的请求头,但从未真正广泛采用。你可以告诉 curl 使用--tr-encoding请求传输编码压缩:
curl --tr-encoding http://example.com/
理论上,没有什么是阻止你在同一命令行中使用两者的,尽管在实践中,你可能发现当被要求以两种不同的方式压缩时,某些服务器会有些困惑。通常来说,只选择一种更安全。
对于 SCP 和 SFTP 传输,有--compressed-ssh选项。它会在两个方向上压缩所有流量。
HTTP 头部
HTTP/1.x 的头部不能被压缩。另一方面,HTTP/2 和 HTTP/3 的头部总是被压缩,并且不能以未压缩的形式发送。然而,为了方便用户,curl 总是以类似于 HTTP/1.x 的风格显示未压缩的头部,以使输出看起来一致。
上传
对于 HTTP,没有标准的压缩方式。上述提到的 HTTP 压缩方法仅适用于下载。
Shell 重定向
当你在 shell 或某些其他命令行提示系统中调用 curl 时,该环境通常会为你提供一组输出重定向能力。在大多数 Linux 和 Unix shell 以及 Windows 的命令提示符中,你可以使用>将标准输出重定向到文件。当然,使用这种方法,-o 或-O 选项就变得多余了。
curl http://example.com/ > example.html
将输出重定向到文件会将 curl 的所有输出重定向到该文件,因此即使你要求将多个 URL 的输出传输到 stdout,重定向输出也会将所有 URL 的输出存储在该单个文件中。
curl http://example.com/1 http://example.com/2 > files
Unix shell 通常允许你单独重定向stderr流。stderr 流通常是一个也会在终端中显示的流,但你可以在不与 stdout 流混淆的情况下单独重定向它。stdout 流用于数据,而 stderr 用于元数据和错误等非数据。你可以像这样使用2>file来重定向 stderr:
curl http://example.com > files.html 2>errors
多次下载
由于 curl 可以告诉它在单个命令行中下载多个 URL,当然有时您希望将这些下载存储在命名良好的本地文件中。
理解这个问题的关键在于,每个下载 URL 都需要自己的“存储指令”。如果没有这个“存储指令”,curl 默认会将数据发送到 stdout。如果您请求了两个 URL,但只告诉 curl 第一个 URL 的保存位置,那么第二个 URL 就会被发送到 stdout。就像这样:
curl -o one.html http://example.com/1 http://example.com/2
“存储指令”的读取和处理顺序与下载 URL 相同,因此它们不必以任何方式紧挨着 URL。您可以首先、最后或与 URL 交织地汇总所有输出选项。您来选择。
这些示例都以相同的方式工作:
curl -o 1.txt -o 2.txt http://example.com/1 http://example.com/2
curl http://example.com/1 http://example.com/2 -o 1.txt -o 2.txt
curl -o 1.txt http://example.com/1 http://example.com/2 -o 2.txt
curl -o 1.txt http://example.com/1 -o 2.txt http://example.com/2
-O同样只是一个针对单个下载的指令,因此如果您下载多个 URL,请使用更多的它们:
curl -O -O http://example.com/1 http://example.com/2
并行
除非另有说明,curl 会按顺序逐个下载所有给定的 URL。通过使用-Z(或--parallel),curl 可以改为并行传输传输:一次多个。
我的浏览器显示的内容不同
一个常见的用例是使用 curl 获取你可以在浏览器地址栏粘贴的 URL。
浏览器在获取 URL 作为输入时,比 curl 做得多得多,并且以多种不同的方式,因此 curl 在终端输出中显示的内容可能与你浏览器窗口中看到的内容完全不同。
客户端差异
Curl 只会获取你要求它获取的内容,它永远不会解析服务器实际提供的数据——即数据。浏览器获取数据后,它会根据认为获取的内容类型激活不同的解析器。例如,如果数据是 HTML,它会解析它以显示网页,并可能下载其他子资源,如图片、JavaScript 和 CSS 文件。当 curl 下载 HTML 时,它只获取那个单一的 HTML 资源,即使当它被浏览器解析时,可能会触发更多的下载。如果你想让 curl 下载任何子资源,你需要将这些 URL 传递给 curl,并要求它获取这些资源,就像其他任何 URL 一样。
客户端在发送请求的方式上也有所不同,一个资源请求的一些方面包括,例如,格式偏好、请求压缩数据,或者只是告诉服务器我们是从哪个页面“来”的。curl 的请求与你的浏览器发送请求的方式略有不同或有很大不同。
服务器差异
接收请求并交付数据的服务器通常根据它认为与之通信的客户端类型而设置成以某种方式行动。有时它可能只是试图为客户端提供最佳内容,有时可能是为了某些客户端隐藏一些内容,甚至试图绕过特定浏览器中已知的问题。当然,还有各种登录系统,可能依赖于 HTTP 身份验证、cookies 或客户端来自预验证的 IP 地址范围。
有时,使用 curl 从服务器获取与浏览器获取相同的响应实际上非常困难。用户通常会使用浏览器的网络工具记录他们的浏览器会话,然后将该记录与 curl 的--trace-ascii选项记录的数据进行比较,然后修改 curl 的请求(通常使用-H/--header),直到服务器开始对两者做出相同的响应。
这类工作可能既耗时又繁琐。你应该始终在服务器所有者或管理员许可的情况下进行。
中间件的篡改
中间件是代理,可以是显式的也可以是隐式的。某些环境强制你使用一个,或者你可能出于各种原因选择使用一个,但还有一些透明的中间件会静默地拦截你的网络流量并为你代理,无论你想要什么。
代理是“中间人”,它们终止流量,然后代表你向远程服务器进行操作。这可能会引入各种显式的过滤,并“拯救”你免受某些内容的影响,甚至“保护”远程服务器免受你试图发送给它的数据,但更重要的是,它引入了另一种软件对协议如何工作以及正确做法的看法。
干扰的中介往往是导致许多头痛和神秘现象的原因,甚至包括对内容进行直接的恶意修改。
我们强烈建议您使用 HTTPS 或其他方式来验证您下载或上传的内容确实是远程服务器发送给您的数据,并且您的宝贵字节最终完整地到达了预期的目的地。
最大文件大小
当你想确保你的 curl 命令行不会下载过大的文件时,可以指示 curl 在开始下载之前停止,如果它在传输开始前知道文件大小。也许这会使用过多的带宽,花费太长时间,或者你的硬盘空间不足:
curl --max-filesize 100000 https://example.com/
给 curl 提供你能够接受的最大的下载字节数,如果 curl 在传输开始前能够计算出文件大小,它会在尝试下载更大的文件之前终止。
有许多情况下 curl 在传输开始时无法确定文件大小。因此,当这些传输实际上达到那个限制时,它们首先会被终止。
在文件系统中存储元数据
当使用 curl 保存下载到文件时,--xattr选项指示 curl 还将某些文件元数据存储在“扩展文件属性”中。这些扩展属性是存储在文件系统中的标准名称/值对,前提是使用了支持的文件系统和操作系统。
目前,URL 存储在xdg.origin.url属性中,对于 HTTP,内容类型存储在mime_type属性中。如果文件系统不支持扩展属性而此选项被设置,则会发出警告。
原始数据
当使用 --raw 选项时,它将禁用所有内部对内容或传输编码的 HTTP 解码,并转而将 curl 传递的原始数据保持不变。
这通常用于您正在编写中间软件,并且希望将内容传递给另一个 HTTP 客户端,并允许它进行解码的情况。
重试
通常,curl 只会尝试一次执行传输,如果失败则返回错误。使用--retry选项,你可以告诉 curl 重试某些失败的传输。
当 curl 尝试执行传输时,如果返回一个短暂错误,它会在放弃之前重试这个次数。将数字设置为 0 会使 curl 不进行重试(这是默认设置)。短暂错误意味着以下情况之一:超时、FTP 4xx 响应代码或 HTTP 5xx 响应代码。
调整你的重试
当 curl 即将重试传输时,它首先等待一秒钟,然后对于所有后续的重试,它会将等待时间加倍,直到达到 10 分钟,然后这是其余重试之间的延迟。使用--retry-delay,你可以禁用这种指数退避算法并设置尝试之间的自己的延迟。使用--retry-max-time,你可以限制允许的重试总时间。--max-time选项仍然指定了这些传输中任何一个允许花费的最长时间。
让 curl 最多重试 5 次,但不超过两分钟:
curl --retry 5 --retry-max-time 120 https://example.com
连接被拒绝
默认的重试机制只对被认为是短暂错误的传输进行重试。这些错误是服务器本身提示并确认现在存在的,但可能在稍后消失。
有时候,作为用户,你对情况了解得更多,你可以帮助 curl 进行更好的重试。首先,你可以告诉 curl 将“连接被拒绝”视为一个短暂错误。也许你知道你正在通信的服务器是不稳定的,或者也许你知道当你尝试从它重新启动或类似的情况下下载时,它有时会出现问题。你可以使用--retry-connrefused来做到这一点。
例如:重试最多 5 次,并将ECONNREFUSED视为重试的理由:
curl --retry 5 --retry-connrefused https://example.com
对任何和所有错误进行重试
最激进的重试形式是针对那些你知道 URL 应该可以正常工作,并且你不能容忍任何失败的情况。使用--retry-all-errors会让 curl 将所有传输失败视为重试的理由。
例如:对所有错误重试最多 12 次:
curl --retry 12 --retry-all-errors https://example.com
恢复和范围
恢复下载意味着首先检查本地已经存在的内容的大小,然后请求服务器发送剩余的部分以便可以追加。curl 还允许在没有任何本地现有内容的情况下从自定义点恢复传输。
curl 支持在多个协议上恢复下载。使用-C, --continue-at选项告诉 curl 从哪里开始传输,该选项可以接受一个纯数字的字节计数器偏移量,或者一个字符串-,这会指示 curl 根据已知信息自行确定。当使用-时,curl 会使用目标文件名来确定已经本地存在的多少数据,并在请求更多数据时使用该数据作为偏移量。
要从字节偏移量 100 开始下载 FTP 文件:
curl --continue-at 100 ftp://example.com/bigfile
继续下载之前已中断的下载:
curl --continue-at - http://example.com/bigfile -O
如果你只想从远程资源传输特定的字节范围,你可以只请求那个范围。例如,当你只想从偏移量 100 开始的 1000 个字节,以避免下载整个巨大的远程文件:
curl --range 100-1099 http://example.com/bigfile
上传
上传是指将数据发送到远程服务器的过程。不同的协议有不同的上传方式,甚至有些协议允许不同的上传数据方式。
允许上传的协议
您可以使用以下协议之一上传数据:FILE、FTP、FTPS、HTTP、HTTPS、IMAP、IMAPS、SCP、SFTP、SMB、SMBS、SMTP、SMTPS 和 TFTP。
HTTP 提供了多种上传方式
HTTP 及其更大的兄弟 HTTPS 提供了多种将数据上传到服务器的方式,curl 提供了简单的命令行选项来完成以下三种最常见的方式,如下所述。
HTTP 的一个有趣细节是,上传也可以是下载,在同一个操作中,实际上许多下载都是通过 HTTP POST 启动的。
POST
POST 是一种 HTTP 方法,旨在向接收的 Web 应用程序发送数据,例如,这是大多数常见的 Web 表单的工作方式。它通常向接收者发送一小块相对较小的数据。
上传通常使用 -d 或 --data 选项进行,但还有一些额外的修改。
在 使用 curl 的 HTTP POST 章节中阅读有关如何使用 curl 进行此操作的详细描述。
多部分表单上传
多部分表单上传也用于网站上的 HTML 表单;通常当涉及文件上传时。这种类型上传也是 HTTP POST,但它发送的数据格式根据一些特殊规则,这就是多部分名称的含义。
由于它以完全不同的格式发送数据,您不能随意选择要使用的 POST 类型,而是完全取决于接收服务器端期望和能够处理的内容。
使用 -F 进行 HTTP 多部分表单上传。请参阅 HTTP 多部分表单上传 章节的详细描述。
PUT
HTTP PUT 是一种上传方法,旨在发送一个完整的资源,该资源将被直接放置在远程站点上,甚至可以替换那里的现有资源。尽管如此,这也是今天在网络上使用最少的 HTTP 上传方法,如果不是大多数的话,许多网络服务器甚至没有启用 PUT。
您可以使用 -T 选项和要上传的文件来发送 HTTP 上传:
curl -T uploadthis http://example.com/
FTP 上传
使用 FTP,您可以看到您正在访问的远程文件系统。您告诉服务器确切地想要将上传放置在哪个目录以及使用哪个文件名。如果您指定带有尾随斜杠的上传 URL,curl 会将本地使用的文件名追加到 URL 中,然后该文件名成为远程存储时使用的文件名:
curl -T uploadthis ftp://example.com/this/directory/
因此,如果您希望远程侧的文件名与本地使用的不同,您可以在 URL 中指定它:
curl -T uploadthis ftp://example.com/this/directory/remotename
在 使用 curl 的 FTP 部分中了解更多关于 FTP 的信息。
SMTP 上传
你可能不会把发送电子邮件视为上传,但对于 curl 来说却是。你将邮件正文上传到 SMTP 服务器。使用 SMTP 时,你还需要在邮件正文中包含所有需要的邮件头信息(如To:, From:, Date:等),因为 curl 不会添加任何邮件头。
curl -T mail smtp://mail.example.com/ --mail-from user@example.com
在发送电子邮件部分了解更多关于 curl 使用 SMTP 的信息。
上传的进度条
通用进度条功能在上传时同样表现良好(参见进度条部分)。需要记住的是,当你将输出发送到标准输出(stdout)时,进度条会自动禁用,并且 curl 支持的多数协议在上传时都能输出一些内容。
因此,你可能需要显式地将下载的数据重定向到一个文件(使用 shell 重定向操作符‘>’,-o或类似操作)来显示上传的进度条。
转移控制
curl 提供了多个不同的旋钮和杠杆来控制传输的执行方式。让它们运行多快,让它们运行多慢,以及如何进行多个传输。
-
停止缓慢的传输
-
速率限制
-
请求速率限制
停止缓慢的传输
对于 curl 操作设置一个固定的最大时间可能会很麻烦,尤其是如果你,例如,进行脚本化的传输,文件大小和传输时间变化很大。这时,就需要设置一个不必要的很高的固定超时值来应对最坏的情况。
作为固定超时时间的替代方案,你可以告诉 curl 在速度低于某个特定值并且持续低于该阈值一段时间后放弃传输。
例如,如果传输速度在 15 秒内低于每秒 1000 字节,则停止它:
curl --speed-time 15 --speed-limit 1000 https://example.com/
速率限制
当 curl 传输数据时,它会尽可能地快速完成。这包括上传和下载。具体传输速度取决于多个因素,包括您电脑的处理能力、您自己的网络连接带宽、您传输到的远程服务器的负载以及到该服务器的延迟。您的 curl 传输也可能与其他用户或同一用户的其他应用程序在数据传输的网络上的传输竞争。
然而,在许多配置中,您可以使用单个 curl 命令行大致饱和自己的网络连接。如果您有每秒 10 兆比特的互联网连接,curl 很可能会使用这 10 兆比特的全部来传输数据。
对于大多数用例,尽可能使用更多的带宽是好事。它使传输更快,使 curl 命令更快完成,并使传输在更短的时间内使用服务器资源。
有时,让 curl 在您的本地网络连接上耗尽其他网络功能可能不方便。在这些情况下,您可能希望告诉 curl 减慢速度,以便其他网络用户也有更好的机会传输他们的数据。使用--limit-rate [speed],您可以告诉 curl 不要超过每秒给定字节数。速率限制值可以使用字母后缀 K、M 和 G 来表示,分别代表千字节、兆字节和吉字节。
要使 curl 的下载速度不超过每秒 200 千字节:
curl https://example.com/ --limit-rate 200K
给定的限制是在几秒钟内允许的最大平均速度。这意味着 curl 可能会在短时间内使用更高的传输速度,但总体上平均速度不会超过给定的速率。
curl 不知道最大可能速度是多少——它只是尽可能地快,并且允许这样做。您可能知道您连接的最大速度,但 curl 不知道。
请求速率限制
当在单个命令行中指示执行多个传输时,有时用户可能更希望这些多个传输以比尽可能快更慢的速度完成。我们称之为请求速率限制。
使用--rate选项,您指定 curl 允许使用的最大传输频率 - 每时间单位内的传输启动次数(有时称为请求速率)。没有此选项时,curl 会尽可能快地启动下一个传输。
如果提供了多个 URL 并且传输完成速度超过允许的速率,curl 将延迟启动下一个传输以保持请求的速率。此选项适用于串行传输,当使用–parallel 时没有效果。
请求速率以N/U提供,其中 N 是一个整数,U 是一个时间单位。支持的单位是s(秒)、m(分钟)、h(小时)和d(天,如 24 小时单位)。如果没有提供/U,默认时间单位是每小时传输次数。
如果 curl 被指示允许每分钟 10 个请求,它不会在上一传输开始后的 6 秒内启动下一个请求。
此功能使用毫秒分辨率。如果允许的频率设置为每秒超过 1000,则它将不受限制地运行。
当使用–retry 启用重试时,将使用单独的重试延迟逻辑,而不是此设置。
如果此选项使用多次,则使用最后一个。
例如,让 curl 下载 100 张图片,但不要快于每秒 2 次传输:
curl --rate 2/s -O https://example.com/[1-100].jpg
让 curl 下载 10 张图片,但不要快于每小时 3 次传输:
curl --rate 3/h -O https://example.com/[1-10].jpg
让 curl 下载 200 张图片,但不要快于每分钟 14 次传输:
curl --rate 14/m -O https://example.com/[1-200].jpg
连接
你在使用 curl 时使用的多数协议都是 TCP。在 TCP 中,客户端如 curl 必须首先找出你想要通信的主机的 IP 地址(或地址),然后连接到它。“连接到它”意味着执行 TCP 协议握手。
对于常规的命令行使用,在处理 URL 时,这些细节在幕后被处理,你大多可以忽略它们。但有时你可能想要调整一些具体设置...
-
名称解析技巧
-
连接超时
-
网络接口
-
本地端口号
-
保持连接
名称解析技巧
curl 提供了许多方法来使其使用不同于它通常连接到的主机。
编辑 hosts 文件
也许你希望命令curl http://example.com连接到你的本地服务器而不是实际的服务器。
你通常可以很容易地通过编辑你的hosts文件(Linux 和 Unix-like 系统上的/etc/hosts)并添加例如127.0.0.1 example.com来重定向主机到你的本地主机。然而,这种编辑需要管理员权限,并且它的缺点是它同时影响了所有其他应用程序。
更改 Host:头
Host:头是 HTTP 客户端告诉 HTTP 服务器它正在与哪个服务器通信的正常方式,因为通常一个 HTTP 服务器使用相同的软件实例为许多不同的名称提供服务。
因此,通过传递一个自定义修改的Host:头,你可以在实际上没有连接到该主机名的情况下让服务器响应网站的内容。
例如,你在本地机器上运行你主站www.example.com的测试实例,并且希望 curl 请求 index html:
curl -H "Host: www.example.com" http://localhost/
当设置自定义Host:头并使用 cookie 时,curl 会提取自定义名称并将其用作匹配 cookie 发送时的主机。
当与 HTTPS 服务器通信时,Host:头是不够的。在 HTTPS 中,TLS 协议中有一个单独的扩展字段,称为 SNI(服务器名称指示),它允许客户端告诉服务器它想要与之通信的服务器名称。curl 只从给定的 URL 中提取 SNI 名称以发送。
为一个名称提供自定义 IP 地址
你是否比 curl 的名称解析器更清楚 curl 应该去哪里?那么你可以自己给 curl 提供一个 IP 地址。如果你想将example.com的 80 端口访问重定向到你的本地主机:
curl --resolve example.com:80:127.0.0.1 http://example.com/
你甚至可以指定多个--resolve开关来提供这种类型的多个重定向,这在处理的 URL 使用 HTTP 重定向或你只想让命令行与多个 URL 一起工作时非常有用。
--resolve将地址插入 curl 的 DNS 缓存中,因此它实际上让 curl 相信这是它在解析名称时得到的地址。
当谈论 HTTPS 时,这会发送 URL 中的名称的 SNI,curl 验证服务器的响应以确保它为 URL 中的名称提供服务。
你在选项中指定的模式需要是一个主机名及其对应的端口号,并且只有当 URL 中使用该精确对时,地址才会被替换。例如,如果你想替换 HTTPS URL 中默认端口号的主机名,你需要告诉 curl 它是 443 端口,如下所示:
curl --resolve example.com:443:192.168.0.1 https://example.com/
提供一个替换名称
作为--resolve选项的近亲,--connect-to选项提供了一个小的变化。它允许你指定 curl 在特定名称和端口号用于连接时在底层使用的替换名称和端口号。
例如,假设你有一个名为 www.example.com 的单一站点,该站点实际上由三个不同的 HTTP 服务器提供服务:load1、load2 和 load3,用于负载均衡。在典型正常流程中,curl 解析主站点并与负载均衡服务器之一(因为它返回一个列表并从中选择一个)进行通信,一切正常。如果你想向负载均衡集中的一个特定服务器发送测试请求(例如 load1.example.com),你可以指示 curl 执行该操作。
如果你知道 load1 的特定 IP 地址,你仍然可以使用 --resolve 来完成这个任务。但无需首先单独解析和修复 IP 地址,你可以告诉 curl:
curl --connect-to www.example.com:80:load1.example.com:80 \http://www.example.com
它将源名称加源端口重定向到目标名称加目标端口。curl 然后解析 load1.example.com 名称并连接,但在所有其他方面仍然假设它正在与 www.example.com 进行通信。
使用 c-ares 的名称解析技巧
正如本书其他地方应详细说明的那样,curl 可以使用几个不同的名称解析后端进行构建。其中之一是由 c-ares 库提供的后端,当 curl 构建为使用 c-ares 时,它获得了一些额外的超级能力,而构建为使用其他名称解析后端的 curl 则没有这些能力。具体来说,它获得了更具体地指示使用哪些 DNS 服务器以及 DNS 流量如何使用网络的能力。
使用 --dns-servers,你可以指定 curl 应该使用哪个 DNS 服务器而不是默认的 DNS 服务器。这让你可以运行自己的实验性服务器,该服务器会以不同的方式回答,或者如果常规服务器不可靠或已死亡,可以使用备份服务器。
使用 --dns-ipv4-addr 和 --dns-ipv6-addr,你要求 curl 将 DNS 通信的本地端“绑定”到特定的 IP 地址,而使用 --dns-interface,你可以指示 curl 使用特定的网络接口来发送 DNS 请求。
这些 --dns-* 选项是高级的,并且仅适用于了解自己在做什么且理解这些选项作用的人。但它们提供了可定制的 DNS 域名解析操作。
连接超时
curl 通常将其网络传输的初始部分作为对主机的 TCP 连接。如果网络条件不稳定或远程服务器存在故障,这种 TCP 连接可能会失败或变慢。
为了减少对您的脚本或其他使用的干扰,您可以设置 curl 允许的连接尝试的最大时间(以秒为单位)。使用--connect-timeout选项,您告诉 curl 允许的最大连接时间,如果 curl 在此时间内未能连接,则返回失败。
连接超时仅限制 curl 连接之前允许花费的时间,一旦 TCP 连接建立,它可能需要更长的时间。有关通用 curl 超时的更多信息,请参阅超时部分。
如果您指定了较低的超时时间,实际上就禁用了 curl 连接到远程服务器、慢速服务器或通过不可靠网络访问的服务器的功能。
连接超时可以指定为小数值以实现亚秒精度。例如,为了允许 2781 毫秒进行连接:
curl --connect-timeout 2.781 https://example.com/
网络接口
在拥有多个网络接口且连接到多个网络的机器上,存在这样的情况:你可以决定你希望出站网络流量使用哪个网络接口。或者,在通信中,使用你拥有的多个 IP 地址中的哪一个作为原始 IP 地址。
使用--interface选项告诉 curl 你想要将通信的本地端“绑定”到哪个网络接口、哪个 IP 地址,甚至是主机名:
curl --interface eth1 https://www.example.com/curl --interface 192.168.0.2 https://www.example.com/curl --interface machine2 https://www.example.com/
本地端口号
在本地端和远程端之间创建一个 TCP 连接,其中一个端是 IP 地址和端口号,另一个端是远程的 IP 地址和端口号。远程端口号可以在 URL 中指定,通常有助于识别您正在针对哪个服务。
本地端口号通常由网络堆栈随机分配给您的 TCP 连接,您通常不需要过多考虑。然而,在某些情况下,您可能会发现自己位于网络设备、防火墙或类似的设置之后,这些设置对可以设置出站连接的源端口号施加了限制。
对于这种情况,您可以指定 curl 应该绑定连接的本地端口。您可以指定一个要使用的单个端口号,或者一个端口号范围。我们建议使用范围,因为端口是稀缺资源,您想要的特定端口可能已经被占用。如果您请求的本地端口号(或范围)curl 无法为您获取,它将失败退出。
此外,在大多数操作系统上,如果没有更高的权限级别(root)您无法绑定低于 1024 的端口号,我们通常建议如果可能的话不要以 root 身份运行 curl。
请配置 curl 在获取此 HTTPS 页面时使用介于 4000 和 4200 之间的本地端口号:
curl --local-port 4000-4200 https://example.com/
Keep alive
当不使用时,TCP 连接在两个方向上都可以完全没有流量。因此,一个完全空闲的连接无法与因网络或服务器问题而完全停滞的连接明显区分开来。
同时,如今许多网络设备,如防火墙或 NAT,都在跟踪 TCP 连接,以便它们可以转换地址、阻止“错误”的传入数据包等。这些设备通常在 N 分钟后将完全空闲的连接视为已死亡,其中 N 在不同设备之间有所不同,有时短至 10 分钟甚至更少。
避免一个真正缓慢的连接(或一个空闲的连接)被错误地视为已死亡并错误地终止的一种方法,是确保使用 TCP keepalive。TCP keepalive 是 TCP 协议中的一个功能,它在其他情况下完全空闲时会发送“ping frames”来回。它帮助空闲连接检测到断开,即使在没有任何流量通过它的情况下,也帮助中间系统不认为连接已死亡。
curl 默认使用 TCP keepalive,原因如下。但有时你可能想禁用 keepalive,或者你可能想改变 TCP “ping” 之间的间隔(curl 默认为 60 秒)。你可以通过以下方式关闭 keepalive:
curl --no-keepalive https://example.com/
或者将间隔改为 5 分钟(300 秒):
curl --keepalive-time 300 https://example.com/
超时
网络操作由于其本质上是相当不可靠或可能脆弱的操作,因为它们依赖于一系列服务和网络保持运行,以便事物能够正常工作。这些服务的可用性可能会来去,它们的性能也可能随时有很大变化。
TCP 的设计甚至允许网络在一段较长的时间内完全断开连接,而无需参与者注意到这一点。
结果是,有时互联网传输需要很长时间。此外,curl 中的大多数操作默认没有超时。
允许花费的最大时间
使用 -m / --max-time 告诉 curl 允许命令行花费的最大时间(以秒为单位),在 curl 退出并带有超时错误代码(28)之前。当设置的时间经过后,curl 将退出,无论当时正在发生什么——包括如果它正在传输数据。这真的是允许的最大时间。
给定的最大时间可以用小数精度指定;0.5 表示 500 毫秒,而 2.37 等于 2370 毫秒。
示例:
curl --max-time 5.5 https://example.com/
(您的区域可能使用除点号以外的其他符号来表示数值分数。)
永远不要花费超过这个时间来连接
--connect-timeout 限制了 curl 尝试连接到主机的时间。在连接被视为完成之前,所有必要的步骤都必须在给定的时间框架内完成。如果在给定时间内无法连接,curl 将退出并带有超时退出代码(28)。
在连接被视为成功之前完成的步骤包括 DNS 查找和随后的 TCP、TLS 或 QUIC 握手。
给定的最大连接时间可以用小数精度指定;0.5 表示 500 毫秒,而 2.37 等于 2370 毫秒:
curl --connect-timeout 2.37 https://example.com/
.netrc
Unix 系统长期以来为用户提供了一种存储远程 FTP 服务器用户名和密码的方法。FTP 客户端已经支持了数十年,这种方式允许用户快速登录到已知的服务器,而无需每次都手动重新输入凭据。.netrc 文件通常存储在用户的家目录中。(在 Windows 上,curl 使用 _netrc 的名称查找它)。
由于这是一个广泛使用且被广泛接受的概念,curl 也支持它——如果你要求它这么做。然而,curl 并不将此功能仅限于 FTP,它可以为任何协议的机器获取凭据。有关如何操作的更多信息,请参阅下文。
.netrc 文件格式
.netrc 文件格式很简单:你指定带有机器名称的行,然后跟随与该机器关联的登录名和密码。
每个字段都提供为以空格或换行符结尾的字母序列。从 7.84.0 版本开始,curl 也支持引号字符串。它们以双引号(")开始和结束,并支持转义的特殊字符 \"、(换行符)、(回车符)和(制表符)。引号字符串是唯一可以在用户名或密码中使用空格的方式。
机器名称
识别远程机器名称。curl 在 .netrc 文件中搜索与 URL 中指定的远程机器匹配的机器令牌。一旦匹配成功,后续的 .netrc 令牌将被处理,直到文件末尾或遇到另一个机器。
默认
这与机器名称相同,只是 default 匹配任何名称。只能有一个默认令牌,并且它必须位于所有机器令牌之后。为了为否则不匹配的主机提供默认匿名登录,请在文件末尾添加类似以下内容的行:
default login anonymous password user@domain
登录名
远程机器的用户名字符串。用户名中不能包含空格。
密码字符串
提供一个密码。如果此令牌存在,curl 将在远程服务器需要密码作为登录过程的一部分时提供指定的字符串。请注意,如果此令牌存在于 .netrc 文件中,你真的应该确保文件除了用户之外对任何人都不可见。输入密码时不能使用空格。
宏名称
定义一个宏。这不被 curl 支持。为了使 .netrc 的其余部分仍然正常工作,curl 会正确跳过它找到的每个使用 macdef 定义的配置。
一个示例 .netrc,用于名为 'daniel' 的用户和密码 'qwerty' 的 example.com 主机,看起来如下所示:
machine example.com
login daniel
password qwerty
它也可以写成一行,同时保持相同的功能:
machine example.com login daniel password qwerty
用户名匹配
当提供一个包含用户名和 .netrc 的 URL 时,curl 会尝试找到该机器和登录组合的匹配密码。
启用 netrc
-n, --netrc 告诉 curl 查找并使用 .netrc 文件。
--netrc-file [文件] 与 --netrc 类似,除了你还需要提供要使用的实际文件的路径。这在你想在另一个目录或使用另一个文件名提供信息时非常有用。
--netrc-optional 与 --netrc 类似,但这个选项使得 .netrc 的使用是可选的,而不是像 --netrc 选项那样是强制的。
代理
代理是一台机器或软件,代表你,即客户端,执行某些操作。
你也可以将其视为位于你与想要交互的服务器之间的中间人,一个你连接到而不是实际远程服务器的中间人。你要求代理为你执行所需的操作,然后它去执行并返回数据给你。
存在几种不同的代理类型,我们将在下面的子节中列出并讨论它们。
-
发现你的代理
-
PAC
-
捕获门户
-
代理类型
-
HTTP 代理
-
SOCKS 代理
-
MITM 代理
-
身份验证
-
HTTPS 代理
-
代理环境变量
-
代理头
-
haproxy
发现你的代理
一些网络配置了必须使用代理才能访问互联网或你可能感兴趣的特殊网络。代理的使用是由运行你的网络的人员和管理员出于政策或技术原因引入的。
在网络空间中,有一些用于自动检测代理及其连接方法的方法,但没有任何一种方法是真正通用的,curl 也不支持任何这些方法。此外,当你通过代理与外界通信时,通常意味着你必须对代理有很大的信任,因为它能够看到并修改你通过它发送或接收的所有非安全网络流量。这种信任并不容易自动假设。
如果你检查浏览器的网络设置,有时在高级设置标签页下,你可以了解你的浏览器配置了哪些代理。当你使用 curl 时,很可能你应该使用相同的代理或代理组。
例如,你可以在“首选项”>“常规”>“网络设置”中找到 Firefox 浏览器的代理设置,如下所示:

Firefox 的代理设置
PAC
一些网络环境提供了在不同情况下应使用的多个不同代理,浏览器支持一种可定制的处理方式。这被称为“代理自动配置”,或 PAC。
PAC 文件包含一个 JavaScript 函数,该函数决定给定的网络连接(URL)应使用哪个代理,甚至决定是否完全不使用代理。浏览器通常从本地网络上的 URL 读取 PAC 文件。
由于 curl 没有 JavaScript 功能,curl 不支持 PAC 文件。如果你的浏览器和网络使用 PAC 文件,通常最简单的途径是手动读取 PAC 文件并找出你需要指定以成功运行 curl 的代理。
门户网关
这些不是代理,但它们阻挡了您与您想要访问的服务器之间的道路。
门户网关是这类在酒店、机场以及其他面向更广泛受众的网络接入中流行的系统之一。门户网关捕获所有网络流量,并引导您到一个登录网页,直到您点击“确定”并确认您已阅读他们的条款,或者甚至确保您已经支付了足够的费用以获得使用网络的权限。
当然,curl 的流量也会被这样的门户网关捕获,通常最好的办法是使用浏览器接受这些条款,并摆脱门户网关,因为从那时起,它们通常会在一段时间内允许来自同一台机器(MAC 地址)的所有其他流量。
通常您也可以使用 curl 提交确认,如果您只是弄清楚如何提交表单以及需要包含哪些字段。如果您最终需要多次这样做,这可能值得探索。
代理类型
curl 支持几种不同的代理类型。
默认的代理类型是 HTTP,所以如果你指定了一个没有方案部分的代理主机名(或 IP 地址)(通常写作http://的部分),curl 会假设它是一个 HTTP 代理。
curl 提供了一些选项来设置代理类型,而不是使用方案前缀。请参阅 SOCKS 部分。
HTTP 代理
HTTP 代理是客户端与它通信以完成传输的代理。curl 默认情况下,假设您使用-x或--proxy指定的主机是 HTTP 代理,除非您也指定端口号,否则默认为端口 1080(而选择该特定端口号的原因纯粹是历史的)。
如果您想通过 192.168.0.1 端口 8080 的代理请求 example.com 网页,命令行可能看起来像这样:
curl -x 192.168.0.1:8080 http://example.com/
回想一下,代理接收您的请求,将其转发到真实服务器,然后从服务器读取响应,并将其返回给客户端。
如果您在与代理通信时启用详细模式-v,它将显示 curl 连接到代理而不是远程服务器,并且可能看到它使用了一个略微不同的请求行。
通过 HTTP 代理的 HTTPS
HTTPS 被设计成允许并从客户端到服务器(以及返回)提供安全且安全的端到端隐私。为了在通过 HTTP 代理进行通信时提供这一点,HTTP 协议有一个 curl 用来设置通过代理隧道的特殊请求。这个 HTTP 方法被称为CONNECT。
当代理在通过 CONNECT 方法设置后通过隧道将加密数据传输到远程服务器时,代理无法看到或修改流量,除非破坏加密:
curl -x proxy.example.com:80 https://example.com/
通过 HTTP 代理的非 HTTP 协议
HTTP 代理意味着代理本身说 HTTP。HTTP 代理主要用于代理 HTTP,但它们也相当普遍地支持其他协议。特别是,FTP 相当普遍地得到支持。
当通过 HTTP 代理进行 FTP 通信时,通常是通过或多或少地假装其他协议像 HTTP 一样工作,并要求代理获取此 URL 来完成的,即使该 URL 不是使用 HTTP。这种区别很重要,因为它意味着当通过这样的 HTTP 代理发送时,curl 实际上并没有真正地说 FTP,尽管给出了 FTP URL;因此 FTP 特定的功能不会工作:
curl -x http://proxy.example.com:80 ftp://ftp.example.com/file.txt
那么,您可以做的另一件事就是通过 HTTP 代理隧道。
HTTP 代理隧道
大多数 HTTP 代理允许客户端通过它隧道到另一侧的服务器。这正是每次您通过 HTTP 代理使用 HTTPS 时所做的。
您可以使用-p或--proxytunnel通过 curl 通过 HTTP 代理隧道。
当您通过代理进行 HTTPS 时,通常连接到默认的 HTTPS 远程 TCP 端口号 443。大多数 HTTP 代理将白名单和允许连接仅限于该端口号上的主机以及可能的一些其他端口号。大多数代理拒绝客户端连接到任何随机端口(原因只有代理管理员知道)。
尽管如此,假设 HTTP 代理允许这样做,您可以要求它通过任何端口号隧道到远程服务器,这样您就可以在隧道时正常进行其他协议。您可以通过以下方式执行 FTP 隧道:
curl -p -x http://proxy.example.com:80 ftp://ftp.example.com/file.txt
您可以使用--proxy1.0 [proxy]而不是-x来告诉 curl 在向 HTTP 代理发出的 CONNECT 请求中使用 HTTP/1.0。
SOCKS 代理
SOCKS 是一种用于代理的协议,curl 支持它。curl 支持 SOCKS 版本 4 和版本 5,两种版本都有两种风味。
您可以通过使用给定的代理主机正确的方案部分来选择要使用的特定 SOCKS 版本,使用-x,或者您可以使用单独的选项来指定它,而不是-x。
SOCKS4 适用于版本 4,但 curl 会解析名称:
curl -x socks4://proxy.example.com http://www.example.com/curl --socks4 proxy.example.com http://www.example.com/
SOCKS4a 适用于版本 4,解析由代理完成:
curl -x socks4a://proxy.example.com http://www.example.com/curl --socks4a proxy.example.com http://www.example.com/
SOCKS5 适用于版本 5,而 SOCKS5-hostname 适用于版本 5 且不本地解析主机名:
curl -x socks5://proxy.example.com http://www.example.com/curl --socks5 proxy.example.com http://www.example.com/
SOCKS5-hostname 版本。这会将主机名发送到代理,因此 curl 在本地不会进行名称解析:
curl -x socks5h://proxy.example.com http://www.example.com/curl --socks5-hostname proxy.example.com http://www.example.com/
有助于确定哪个版本由哪一侧解析名称的有用表格:
| SOCKS | 谁解析名称 | 支持 IPv6 |
|---|---|---|
| 4 | curl | 否 |
| 4a | 代理 | 否 |
| 5 | curl | 是 |
| 5h | 代理 | 是 |
中间人代理
MITM 代表中间人。MITM 代理通常由公司部署在“企业环境”和其他地方,那里的网络所有者希望调查甚至 TLS 加密流量。
为了做到这一点,他们需要用户在客户端安装一个定制的“信任根”(证书颁发机构(CA)证书),然后代理终止来自客户端的所有 TLS 流量,冒充远程服务器并充当代理。代理随后发送一个由定制 CA 签发的生成证书。此类代理设置通常透明地捕获来自客户端到远程机器上 TCP 端口 443 的所有流量。在这样的网络中运行 curl 也会捕获其 HTTPS 流量。
当然,这种做法允许中间人解密并窃听所有 TLS 流量。
代理认证
HTTP 和 SOCKS 代理可能需要认证,因此 curl 需要向代理提供适当的凭据才能使用它。未能这样做(或提供了错误的凭据)将导致代理返回使用代码 407 的 HTTP 响应。
代理认证类似于“正常”的 HTTP 认证。它是独立于服务器认证的,以便客户端可以独立使用正常的宿主认证以及代理认证。
使用 curl 时,你可以通过-U user:password或--proxy-user user:password选项设置代理认证的用户名和密码:
curl -U daniel:secr3t -x myproxy:80 http://example.com
此示例默认使用基本认证方案。一些代理需要其他认证方案(并且当你收到 407 响应时返回的头部信息会告诉你哪种),然后你可以使用--proxy-digest、--proxy-negotiate、--proxy-ntlm请求特定的方法。再次给出上述示例命令,但这次是请求使用代理的 NTLM 认证:
curl -U daniel:secr3t -x myproxy:80 http://example.com --proxy-ntlm
也有一种选项是让 curl 确定代理希望使用并支持的方法,然后使用--proxy-anyauth跟随那个方法(可能会增加额外的往返次数)。让 curl 使用代理想要的任何方法是这样的:
curl -U daniel:secr3t -x myproxy:80 http://example.com --proxy-anyauth
HTTPS 代理
所有其他提到的与代理通信的协议都是明文协议,包括 HTTP 和 SOCKS 版本。使用这些方法可能会允许某人监听您在本地网络中的流量,因为 curl 与代理之间的连接是明文发送数据的。
解决这个问题的方法之一是使用 HTTPS 代理,与代理使用 HTTPS 通信,然后建立一个安全且加密的连接,从而免受简单监视的威胁。
当指定 HTTPS 代理时,该主机默认使用的端口是 443。
在大多数其他方面,HTTPS 代理的工作方式类似于 HTTP 代理。
HTTP/2
当 curl 与 HTTPS 代理通信时,您可以选择使用--proxy-http2来让 curl 尝试使用 HTTP/2 与代理通信。
默认情况下,curl 使用 HTTP/1.1 与 HTTPS 代理通信,但如果使用此选项,curl 将尝试协商并使用 HTTP/2。
代理环境变量
curl 在运行之前检查是否存在特别命名的环境变量,以查看是否请求使用代理。
您可以通过设置一个名为[scheme]_proxy的变量来指定代理主机名(与您使用-x指定主机的方式相同)。如果您想告诉 curl 在访问 HTTP 服务器时使用代理,请设置http_proxy环境变量。如下所示:
http_proxy=http://proxy.example.com:80
curl -v www.example.com
虽然上述示例显示了 HTTP,当然也可以设置ftp_proxy、https_proxy等。除了http_proxy之外,所有这些代理环境变量名称也可以指定为大写,如HTTPS_PROXY。
要设置一个控制所有协议的单个变量,存在ALL_PROXY。如果存在特定的协议变量,则该变量具有优先级。
无代理
有时您会遇到一种情况,其中一个或几个主机名应该被排除在通常使用的代理之外。这可以通过NO_PROXY变量来完成。将其设置为不应使用代理的主机名的逗号分隔列表。可以将NO_PROXY设置为单个星号(*)以匹配所有主机。
如果排除列表中的名称以点(.)开头,则该名称匹配整个域名。例如,.example.com匹配www.example.com和home.example.com,但不匹配nonexample.com。
作为NO_PROXY变量的替代,还有一个--noproxy命令行选项,它具有相同的目的并以相同的方式工作。
自 curl 7.86.0 版本起,用户可以使用 CIDR 表示法排除一个 IP 网络:在 IP 地址后附加一个斜杠和位数来指定网络的位数。例如,通过提供模式192.168.0.0/16来匹配以192.168开头的整个 16 位网络。
http_proxy 仅小写
代理环境变量的 HTTP 版本与其他版本的处理方式不同。由于 CGI 协议,它允许用户在 HTTP 服务器调用时在服务器上运行脚本,因此它只接受其小写版本。当服务器通过 HTTP 服务器调用 CGI 脚本时,它会自动根据请求中的传入头信息为脚本创建环境变量。这些环境变量以前缀大写HTTP_开头。
因此,使用类似Proxy: yada的请求头向 HTTP 服务器发送的请求会在启动 CGI 脚本之前创建一个名为HTTP_PROXY的环境变量,并将其设置为包含yada。如果这样的 CGI 脚本运行 curl,那么 curl 不将其视为要使用的代理是很重要的。
接受此环境变量的大写版本在许多软件中已成为许多安全问题的来源。
代理头信息
当你想添加专门针对 HTTP 或 HTTPS 代理的 HTTP 头信息,而不是针对远程服务器时,--header 选项就不够用了。
例如,如果你通过 HTTP 代理发出 HTTPS 请求,它首先会向代理发出一个 CONNECT 命令,以建立到远程服务器的隧道,然后向该服务器发送请求。这个最初的 CONNECT 命令只针对代理,你可能想确保只有它接收你的特殊头信息,并且向远程服务器发送另一组自定义头信息。
只为代理设置特定的不同 User-Agent:
curl --proxy-header "User-Agent: magic/3000" -x proxy https://example.com/
haproxy
虽然 haproxy 协议的名字中仍有代理,但它与其他代理选项不同,并且不会以其他代理选项的方式与代理一起工作。
这是一种客户端将 IP 地址传递给服务器的方式,无论流量如何到达它:隧道、TCP 代理、负载均衡器、透明代理等等。某些服务会在流量最终到达服务器时更改正在使用的源 IP 地址,使得服务器无法自行确定客户端的 IP 地址。
haproxy 协议很简单。它需要服务器的支持,这意味着用户不能仅仅决定使用它,而服务器不愿意或不合作。如果它确实支持它,你可以告诉 curl 使用它并将自己的 IP 地址传递给服务器。
curl 和 haproxy
curl 只支持 haproxy 协议 v1。
要传递当前正在使用的连接的实际 IP 地址,只需添加布尔标志,如下所示:
curl --haproxy-protocol https://example.com/
如果出于某种原因,这样的命令行没有提供您认为应该传递的 IP 地址,您可以使用 IPv4 或 IPv6 数值地址自行指定确切地址:
curl --haproxy-clientip 10.0.0.1 https://example.com/
curl --haproxy-clientip fe80::fea3:8a22 https://example.com/
TLS
TLS 代表传输层安全性,是之前被称为 SSL 的技术名称。尽管如此,SSL 这个术语并没有真正消失,因此现在 TLS 和 SSL 这两个术语经常被互换使用,以描述同一件事。
TLS 是一种位于 TCP 之上的加密安全层,它通过强大的公钥加密和数字签名确保数据不被篡改,并保证服务器的真实性。
-
加密套件
-
启用 TLS
-
TLS 版本
-
验证服务器证书
-
证书固定
-
OCSP 存档
-
客户端证书
-
TLS 认证
-
TLS 后端
-
SSLKEYLOGFILE
密钥
当 curl 连接到 TLS 服务器时,它会协商如何使用协议,这个协商涉及到双方都需要同意的几个参数和变量。其中一个参数是使用哪种加密算法,也就是所谓的密钥。随着时间的推移,安全研究人员发现了现有密钥中的缺陷和弱点,它们逐渐被淘汰。
使用详细选项-v,你可以获取关于协商中使用的密钥和 TLS 版本的信息。通过使用--ciphers选项,你可以更改在协商中优先选择的密钥,但请注意,这是一个强大的功能,需要知识才能以不会使事情变得更糟的方式使用。
启用 TLS
curl 支持许多协议的 TLS 版本。HTTP 有 HTTPS,FTP 有 FTPS,LDAP 有 LDAPS,POP3 有 POP3S,IMAP 有 IMAPS,SMTP 有 SMTPS。
如果服务器端支持,您可以使用 curl 的这些协议的 TLS 版本。
在协议中使用 TLS 有两种一般方法。其中一种是在第一次连接握手时就使用 TLS,另一种是使用特定协议的指令将连接从纯文本升级到 TLS。
使用 curl,如果您在 URL 中明确指定了协议的 TLS 版本(以 'S' 字符结尾的名称),curl 会尝试从开始就使用 TLS 连接,而如果您在 URL 中指定了非 TLS 版本,您通常可以使用 --ssl 选项将连接升级到基于 TLS 的连接。
支持表看起来如下:
| 清除 | TLS 版本 | –ssl |
|---|---|---|
| HTTP | HTTPS | no |
| LDAP | LDAPS | no |
| FTP | FTPS | yes |
| POP3 | POP3S | yes |
| IMAP | IMAPS | yes |
| SMTP | SMTPS | yes |
所有能够执行 --ssl 的协议都倾向于这种方法。使用 --ssl 意味着 curl 会尝试将连接升级到 TLS,但如果失败,它仍然会使用协议的纯文本版本继续传输。为了使 --ssl 选项要求TLS 继续传输,可以使用 --ssl-reqd 选项,如果 curl 无法成功协商 TLS,则传输会失败。
为您的 FTP 传输要求 TLS 安全性:
curl --ssl-reqd ftp://ftp.example.com/file.txt
建议使用 TLS 进行您的 FTP 传输:
curl --ssl ftp://ftp.example.com/file.txt
直接使用 TLS 连接(到 HTTPS://,LDAPS://,FTPS:// 等)意味着 TLS 是强制性的,如果未协商 TLS,curl 会返回错误。
通过 HTTPS 获取文件:
curl https://www.example.com/
TLS 版本
SSL 是在 90 年代中期发明的,并且从那时起一直在发展。SSL 版本 2 是第一个在互联网上广泛使用的版本,但很久以前就被认为是不安全的。SSL 版本 3 接替了它,它也被认为不足以安全使用。
TLS 版本 1.0 是第一个标准。RFC 2246 于 1999 年发布。TLS 1.1 于 2006 年推出,进一步提高了安全性,随后 TLS 1.2 于 2008 年推出。TLS 1.2 成为 TLS 的黄金标准,持续了十年。
TLS 1.3(RFC 8446)于 2018 年 8 月由 IETF 最终确定并作为标准发布。这是目前最安全、最快的 TLS 版本。然而,由于它非常新,许多软件、工具和库还没有支持它。
curl 默认设计为使用安全的 SSL/TLS 版本。这意味着除非明确告知,否则它不会协商 SSLv2 或 SSLv3,实际上,许多 TLS 库已经不再支持这些协议,所以在许多情况下,除非你做出重大努力,curl 甚至无法使用这些协议版本。
| 选项 | 使用 |
|---|---|
| –sslv2 | SSL 版本 2 |
| –sslv3 | SSL 版本 3 |
| –tlsv1 | TLS >= 版本 1.0 |
| –tlsv1.0 | TLS >= 版本 1.0 |
| –tlsv1.1 | TLS >= 版本 1.1 |
| –tlsv1.2 | TLS >= 版本 1.2 |
| –tlsv1.3 | TLS >= 版本 1.3 |
验证服务器证书
如果你不能确定你正在与正确的宿主进行通信,那么与服务器建立安全连接就没有太大的意义。如果我们不知道这一点,我们可能只是在和一个冒充者交谈,这个冒充者看起来就像是我们认为的那样。
为了检查它与正确的 TLS 服务器通信,curl 使用一个 CA 证书存储库——一组用于验证服务器证书签名的证书。所有服务器都在 TLS 握手过程中向客户端提供一个证书,所有使用公共 TLS 的服务器都从已建立的证书权威机构获得了该证书。
经过一些加密魔术之后,curl 确认服务器确实是获取了 curl 连接到的主机名的证书的正确服务器。未能验证服务器的证书是 TLS 握手失败,curl 会带错误退出。
在罕见的情况下,你可能仍然决定即使证书验证失败,也要与 TLS 服务器进行通信。那么你接受这样一个事实,即你的通信可能会受到中间人攻击。你可以通过 -k 或 --insecure 选项降低你的安全防护。
本地 CA 证书存储库
操作系统如 Windows 和 macOS 通常都有自己的 CA 证书存储库。
如果你使用 Windows 上的 Schannel 运行 curl,curl 默认使用 Windows 的 CA 证书存储库。
如果你使用 macOS 上的 Secure Transport 运行 curl,curl 默认使用 macOS 的 CA 证书存储库。
如果你使用 curl 与 Schannel 或 Secure Transport 之外的任何 TLS 后端,它将使用一个单独的文件或目录中提供的 CA 证书存储库,独立于本地的 CA 证书存储库。然而,对于其中的一些,你仍然可以使用 --ca-native 命令行选项让 curl 优先使用本地的 CA 证书存储库。此选项与 OpenSSL(及其分支)、wolfSSL 和 GnuTLS 兼容。
对于 HTTPS 代理,相应的选项称为 --proxy-ca-native。
文件中的 CA 证书存储库
如果 curl 没有构建为使用你平台本地的 TLS 库(如 Schannel 或 Secure Transport),它必须已经构建为知道本地 CA 证书存储库的位置,或者用户需要在调用 curl 时提供 CA 证书存储库的路径。
你可以使用 --cacert 命令行选项指定用于 TLS 握手的特定 CA 证书包。该包需要是 PEM 格式。你也可以设置环境变量 CURL_CA_BUNDLE 为完整路径。
Windows 上的 CA 证书存储库
在 Windows 上构建的未使用本地 TLS 库(Schannel)的 curl,有额外的序列来查找和使用 CA 证书存储库。
curl 在这些目录中按此顺序搜索名为 curl-ca-bundle.crt 的 CA 证书文件:
-
应用程序目录
-
当前工作目录
-
Windows 系统目录(例如
C:\windows\system32) -
Windows 目录(例如
C:\windows) -
%PATH%中的所有目录
证书固定
TLS 证书固定是一种验证用于签署服务器证书的公钥是否已更改的方法。它是被固定的。
在协商 TLS 或 SSL 连接时,服务器发送一个证书以表明其身份。从这个证书中提取出一个公钥,如果它与提供给此选项的公钥不完全匹配,curl 将在发送或接收任何数据之前终止连接。
你可以向 curl 指定一个文件名来从中读取 sha256 值,或者你可以在命令行中直接使用带有 sha256// 前缀的 base64 编码的哈希值。你可以指定一个或多个这样的哈希值,用分号 (😉 分隔。
curl --pinnedpubkey "sha256//83d34tasd3rt…" https://example.com/
此功能并非所有 TLS 后端都支持。
OCSP stapling
这使用了名为证书状态请求的 TLS 扩展,要求服务器在握手过程中提供来自 CA 的“新鲜”证明,即它返回的证书仍然有效。这是一种确保服务器证书未被吊销的真正方法。
如果服务器不支持此扩展,测试将失败,curl 将返回错误。服务器不支持此扩展的情况仍然很常见。
要求握手使用如下状态请求:
curl --cert-status https://example.com/
此功能仅由 OpenSSL 和 GnuTLS 后端支持。
客户端证书
TLS 客户端证书是客户端通过密码学方式向服务器证明其确实是正确对等方的一种方式(有时也称为双向 TLS 或 mTLS)。使用客户端证书的命令行指定了证书及其相应的密钥,然后它们在 TLS 握手过程中传递给服务器。
在进行此操作时,您需要将客户端证书事先存储在文件中,并且您应该已经通过不同的渠道从正确的实例中获取了它。
密钥通常由密码保护,您需要提供或交互式地提示以获取。
curl 提供了选项,允许您使用--cert指定一个既是客户端证书又是私钥的单一文件,或者您可以使用--key独立指定密钥文件:
curl --cert mycert:mypassword https://example.com
curl --cert mycert:mypassword --key mykey https://example.com
对于某些 TLS 后端,您也可以使用不同的类型传递密钥和证书:
curl --cert mycert:mypassword --cert-type PEM \--key mykey --key-type PEM https://example.com
TLS 认证
TLS 连接提供了一种(很少使用)称为安全远程密码的功能。使用它,您可以使用用户名和密码以及命令行标志来认证服务器的连接,这些标志是--tlsuser <name>和--tlspassword <secret>。就像这样:
curl --tlsuser daniel --tlspassword secret https://example.com
TLS 后端
当 curl 构建时,它会被告知使用特定的 TLS 库。这个 TLS 库是提供 curl 通过有线传输使用 TLS 能力的引擎。我们通常将它们称为不同的“后端”,因为它们可以被看作是 curl 机器的不同可插拔组件。curl 可以被构建为能够使用这些后端中的一个或多个。
有时当 curl 与不同的 TLS 后端一起构建时,其功能和行为会有细微的差异,但开发者们努力使这些差异尽可能小且不明显。
使用 curl –version 显示 curl 版本信息时,输出第一行包括 TLS 库和版本。
多个 TLS 后端
当 curl 与多个 TLS 后端一起构建时,可以在每次启动时指定使用哪一个。除非有特别要求,否则它总是默认使用一个特定的后端。
如果你为具有多个后端的 curl 调用curl --version,它会在最后一行提到MultiSSL作为一个特性。第一行包括所有支持的 TLS 后端,非默认的后端用括号括起来。
要设置一个特定的后端以供使用,请将环境变量CURL_SSL_BACKEND设置为该后端的名字。
SSLKEYLOGFILE

使用 Wireshark 查看网络流量
从很久以前开始,备受尊敬的网络分析工具 Wireshark(如上图所示)就提供了一种在 Firefox 和 Chrome 发送和接收 TLS 流量时解密和检查 TLS 流量的方法。
这同样适用于 curl。
您可以通过让浏览器或 curl 告诉 Wireshark 加密秘密,以便它可以解密它们来完成此操作:
-
在启动浏览器或 curl 之前,将环境变量
SSLKEYLOGFILE设置为您选择的文件名 -
在 Wireshark 的 Master-secret 字段中设置相同的文件名路径。转到首选项->协议->TLS 并编辑如下截图所示的路径。

设置 ssl 密钥文件名
在完成这个简单的操作后,您现在可以在 Wireshark 中检查 curl 或您浏览器的 HTTPS 流量。非常方便且酷炫。
只需记住,如果您记录 TLS 流量并希望稍后分析,您还需要保存包含秘密的文件,这样您才能在稍后解密该流量捕获。
使用 libcurl 的应用程序也是如此
SSLKEYLOGFILE 的支持由 libcurl 本身提供——这使得您能够跟踪和检查任何使用 libcurl 构建的应用的 TLS 网络数据,而不仅仅是 curl 命令行工具。
限制
SSLKEYLOGFILE 的支持要求 curl 是使用支持此功能的 TLS 后端构建的。支持 SSLKEYLOGFILE 的后端有:OpenSSL、libressl、BoringSSL、GnuTLS 和 wolfSSL。
如果 curl 是构建为使用另一个后端,则您不能以这种方式记录您的 curl TLS 流量。
SCP 和 SFTP
如果 curl 在构建时使用了先决的第三方库:libssh2,libssh或wolfSSH,则支持 SCP 和 SFTP 协议。
SCP 和 SFTP 都是建立在 SSH 之上的协议,SSH 是一个安全且加密的数据协议,与 TLS 类似,但在几个重要方面有所不同。例如,SSH 不使用任何类型的证书,而是使用公钥和私钥。当正确使用时,SSH 和 TLS 都提供强大的加密和安全的传输。
SCP 协议通常被认为是两者中的“黑羊”,因为它不可移植,通常只在 Unix 系统之间工作。
URL
SFTP 和 SCP 的 URL 与其他 URL 类似,您可以使用这些协议下载文件,就像使用其他协议一样:
curl sftp://example.com/file.zip -u user
和:
curl scp://example.com/file.zip -u user
SFTP(但不是 SCP)支持在 URL 以斜杠结尾时获取文件列表:
curl sftp://example.com/ -u user
注意,这两个协议都与“用户”一起工作,您不能匿名或使用标准通用名称请求文件。大多数系统要求用户进行认证,如下所述。
当从 SFTP 或 SCP URL 请求文件时,提供的文件路径被认为是远程服务器上的绝对路径,除非您明确要求相对于用户主目录的路径。您可以通过确保路径以/~/开头来实现这一点。这与 FTP URL 的工作方式正好相反,并且是用户中常见的混淆原因。
对于用户daniel,要从他的主目录传输todo.txt,看起来可能像这样:
curl sftp://example.com/~/todo.txt -u daniel
认证
使用 curl 对 SSH 服务器进行认证(当您指定 SCP 或 SFTP URL 时)是这样进行的:
-
curl 连接到服务器并了解该服务器提供的认证方法
-
curl 会依次尝试提供的方法,直到找到一个有效的方法或者都失败
如果服务器提供公钥认证,curl 会尝试使用您在主目录的.ssh子目录中找到的公钥。在这样做的时候,您仍然需要告诉 curl 在服务器上使用哪个用户名。例如,用户‘john’在远程 SFTP 服务器sftp.example.com上列出其主目录的条目:
curl -u john: sftp://sftp.example.com/
如果 curl 由于任何原因无法使用公钥进行认证,它将尝试使用用户名加密码(如果服务器允许并且凭证通过命令行传递)。
例如,上面的同一个用户在远程系统上的密码是RHvxC6wUA,可以通过以下方式使用 SCP 下载文件:
curl -u john:RHvxC6wUA -O scp://ssh.example.com/file.tar.gz
已知主机
一个安全的网络客户端需要确保远程主机正是它认为正在与之通信的主机。基于 TLS 的协议通过客户端验证服务器的证书来实现。
在 SSH 协议中,没有服务器证书,而是每个服务器都可以提供其唯一的密钥。与 TLS 不同,SSH 没有证书颁发机构或类似的东西,因此客户端只需确保主机的密钥与其通过其他方式已知(应该看起来)的密钥匹配即可。
密钥匹配通常是通过密钥的哈希值来完成的,客户端存储已知服务器哈希的文件通常被称为known_hosts,并放置在专用的 SSH 目录中。在 Linux 系统中,这通常称为~/.ssh。
当 curl 连接到 SFTP 和 SCP 主机时,它会确保主机的密钥哈希已经存在于已知主机文件中,否则它会拒绝继续操作,因为它无法信任该服务器是正确的。一旦正确的哈希存在于known_hosts中,curl 就可以执行传输。
要强制 curl 跳过检查和遵守known_hosts文件,您可以使用-k / --insecure命令行选项。您必须非常小心地使用此选项,因为它使得中间人攻击不被检测到成为可能。
阅读邮件
在互联网上,用于从服务器读取/下载邮件(至少如果我们不计入基于 Web 的阅读)有两种主要的协议,它们是 IMAP 和 POP3。前者是稍微更现代的替代品。curl 支持这两种协议。
POP3
列出消息编号和大小:
curl pop3://mail.example.com/
下载第 1 条消息:
curl pop3://mail.example.com/1
删除第 1 条消息:
curl --request DELE pop3://mail.example.com/1
IMAP
使用 UID 57 从邮箱“东西”中获取邮件:
curl imap://server.example.com/stuff;UID=57
相反,从邮箱“有趣”中获取索引为 57 的邮件:
curl imap://server.example.com/fun;MAILINDEX=57
列出邮箱“无聊”中的邮件:
curl imap://server.example.com/boring
列出邮箱“无聊”中的邮件并输入用户名和密码:
curl imap://server.example.com/boring -u user:password
邮件传输层安全性(TLS)
POP3 和 IMAP 都可以通过安全连接进行,并且都可以使用显式或隐式 TLS。其中,“显式”方法可能是最常见的方法,这意味着客户端使用不安全的连接连接到服务器,并在过程中将其升级为 TLS,使用STARTTLS命令。你可以通过--ssl告诉 curl 尝试这样做,或者如果你想坚持使用安全连接,你可以使用--ssl-reqd。如下所示:
curl pop3://mail.example.com/ --ssl-reqd
或者
curl --ssl imap://mail.example.com/inbox
“隐式”SSL 意味着连接在第一次连接时就得到了加密,你可以通过在 URL 中指定使用 SSL 的方案来让 curl 尝试这样做。在这种情况下,可以是pop3s://或imaps://。对于此类连接,curl 坚持从开始就连接并协商 TLS 连接,否则它将失败操作。
之前使用隐式 SSL 的显式示例:
curl pop3s://mail.example.com/
或者
curl imaps://mail.example.com/inbox
发送电子邮件
使用 curl 通过 SMTP 协议发送电子邮件。SMTP 代表简单邮件传输协议。
curl 支持向 SMTP 服务器发送数据,结合正确的命令行选项,可以将电子邮件发送到你选择的接收者。
当使用 curl 发送 SMTP 时,有两个必要的命令行选项必须使用。
-
你需要使用
--mail-rcpt告诉服务器至少一个收件人。你可以多次使用此选项,然后 curl 会告诉服务器所有这些电子邮件地址都应该收到邮件。 -
你需要使用
--mail-from告诉服务器哪个电子邮件地址是邮件的发送者。重要的是要意识到,这个电子邮件地址不一定与电子邮件文本中显示的From:行相同。
然后,你需要提供实际的电子邮件数据。这是一个根据RFC 5322格式化的(文本)文件。它是一组头信息和正文。头信息和正文都需要正确编码。头信息通常包括To:, From:, Subject:, Date:等。
发送电子邮件的基本命令:
curl smtp://mail.example.com --mail-from myself@example.com --mail-rcpt \
receiver@example.com --upload-file email.txt
一个email.txt的例子可能如下所示:
From: John Smith <john@example.com>
To: Joe Smith <smith@example.com>
Subject: an example.com example email
Date: Mon, 7 Nov 2016 08:45:16Dear Joe,
Welcome to this example email. What a lovely day.
安全邮件传输
一些邮件提供商允许或要求使用 SSL 进行 SMTP。它们可能使用一个专用的 SSL 端口,或者允许在明文连接上升级 SSL。
如果你的邮件提供商有一个专用的 SSL 端口,你可以使用 smtps://代替 smtp://,它默认使用 SMTP SSL 端口 465,并要求整个连接都是 SSL。例如 smtps://smtp.gmail.com/。
然而,如果你的提供商允许从明文传输升级到安全传输,你可以使用以下选项之一:
--ssl Try SSL/TLS (FTP, IMAP, POP3, SMTP)
--ssl-reqd Require SSL/TLS (FTP, IMAP, POP3, SMTP)
你可以通过在命令中添加--ssl来告诉 curl尝试但不要求升级到安全传输:
curl --ssl smtp://mail.example.com --mail-from myself@example.com \--mail-rcpt receiver@example.com --upload-file email.txt \--user 'user@your-account.com:your-account-password'
你可以通过在命令中添加--ssl-reqd来告诉 curl要求升级到使用安全传输:
curl --ssl-reqd smtp://mail.example.com --mail-from myself@example.com \--mail-rcpt receiver@example.com --upload-file email.txt \--user 'user@your-account.com:your-account-password'
SMTP URL
SMTP 请求的路径部分指定了在与邮件服务器通信时呈现的主机名。如果省略路径,curl 将尝试确定本地计算机的主机名并使用它。然而,这可能不会返回某些邮件服务器所需的完全限定域名,指定此路径允许你设置一个替代名称,例如从外部函数(如 gethostname 或 getaddrinfo)获得的计算机的完全限定域名。
要连接到mail.example.com上的邮件服务器并将本地计算机的主机名发送到HELO或EHLO命令中:
curl smtp://mail.example.com
你当然可以使用-v选项来查看客户端与服务器之间的通信。
要让 curl 在HELO/EHLO命令中将client.example.com发送到mail.example.com上的邮件服务器,请使用:
curl smtp://mail.example.com/client.example.com
无 MX 查找!
当你使用普通邮件客户端发送电子邮件时,它首先会检查你想要发送电子邮件的特定域的 MX 记录。如果你向joe@example.com发送电子邮件,客户端会获取example.com的 MX 记录,以确定在向 example.com 用户发送电子邮件时应使用哪个邮件服务器(或服务器组)。
curl本身不会执行 MX 查找。如果你想确定向特定域发送电子邮件时应使用哪个服务器,我们建议你首先确定这一点,然后调用curl来使用这些服务器。用于获取 MX 记录的有用命令行工具包括‘dig’和‘nslookup’。
DICT
DICT 是一个用于字典查找的协议。
使用方法
为了娱乐,尝试
curl dict://dict.org/m:curl
curl dict://dict.org/d:heisenbug:jargon
curl dict://dict.org/d:daniel:gcide
‘m’ 的别名是 ‘match’ 和 ‘find’,而 ‘d’ 的别名是 ‘define’ 和 ‘lookup’。例如,
curl dict://dict.org/find:curl
破坏 RFC URL 描述的命令(但不是 DICT 协议)是
curl dict://dict.org/show:db
curl dict://dict.org/show:strat
IPFS
IPFS 是 星际文件系统。curl 仅通过 HTTP 网关 支持使用 IPFS。这意味着当提供给它时,它理解 IPFS URL,但你必须同时提供一个有效的网关 URL,以便 curl 可以使用它来检索内容。curl 本身不支持 IPFS。
网关
--ipfs-gateway 允许用户指定 IPFS HTTP 网关 URL。例如:
curl --ipfs-gateway http://localhost:8080 ipfs://bafybeigagd5nmnn2iys2f3d/
如果你选择使用远程网关,你应该意识到你需要完全信任该网关。在本地网关中这是可以的,因为你自己托管它。在使用远程网关的情况下,可能存在恶意行为者返回与你的请求不匹配的数据,检查或甚至干扰请求。当你使用 curl 获取 IPFS 时,你可能不会注意到这一点。
如果不使用 --ipfs-gateway 选项,curl 会检查 IPFS_GATEWAY 环境变量以获取指导,如果没有设置,则可以使用 ~/.ipfs/gateway 文件来识别网关。
IPFS 支持首次添加到 curl 的版本是 8.4.0。
MQTT
简单的 GET 订阅主题并打印所有发布的消息。执行 POST 将发布数据发布到主题并退出。
订阅由 example.com 发布的 home/bedroom 主题的温度:
curl mqtt://example.com/home/bedroom/temp
将值 75 发送到由 example.com 服务器托管的 home/bedroom/dimmer 主题:
curl -d 75 mqtt://example.com/home/bedroom/dimmer
curl 对订阅的响应是什么
它输出两个字节的主题长度(MSB | LSB),然后是主题和有效载荷。
注意事项
截至 2022 年 9 月 curl 的 MQTT 支持的剩余限制:
-
仅实现了 QoS 级别 0 的发布
-
无法设置发布保留标志
-
不支持 TLS (mqtts)
TELNET
Telnet 是一种古老的应用协议,用于双向 明文 通信。它被设计用于交互式文本通信,Telnet 没有加密或安全版本。
TELNET 并不是 curl 的完美匹配。该协议并没有设计来处理纯上传或下载,因此 curl 的常规范例不得不进行一些调整,以便 curl 能够适当地处理它。
curl 将接收到的数据发送到 stdout,并从 stdin 读取输入以发送。当连接断开或用户按下控制-c 时,传输完成。
历史上的 TELNET
从前,系统提供 telnet 访问用于登录。然后您可以连接到服务器并登录,就像您今天使用 SSH 一样。由于该协议的不安全性,这种做法幸运地现在已经大部分被移入博物馆的陈列柜中。
Telnet 的默认端口号是 23。
使用 TELNET 调试
事实是 TELNET 实际上只是一个简单的明文 TCP 连接到目标主机和端口,这使得它在某些时候对调试其他协议和服务有一定的用处。
例如,连接到您的本地 HTTP 服务器在端口 80,并通过手动输入 GET / 并按两次回车来向它发送一个(损坏的)请求:
curl telnet://localhost:80
您的 Web 服务器可能返回类似以下内容:
HTTP/1.1 400 Bad Request
Date: Tue, 07 Dec 2021 07:41:16 GMT
Server: softeare/7.8.9
Content-Length: 31
Connection: close
Content-Type: text/html[message]
选项
当 curl 设置到服务器的 TELNET 连接时,您可以要求它传递选项。您可以通过 --telnet-option(或 -t)来完成此操作,并且有三个选项可供使用:
-
TTYPE=<term>将会话的“终端类型”设置为<term>。 -
XDISPLOC=<X display>设置 X 显示位置 -
NEW_ENV=<var,val>将远程会话中的环境变量var设置为值val
登录到您本地机器的 telnet 服务器,并告诉它您使用的是 vt100 终端:
curl --telnet-option TTYPE=vt100 telnet://localhost
当提示时,您需要手动输入您的用户名和密码。
TFTP
简单文件传输协议(TFTP)是一种简单的明文协议,允许客户端从远程主机获取文件或将文件放置在远程主机上。
此协议的主要用例是通过本地网络获取引导映像。TFTP 与其他许多协议相比,还有一个特点,那就是它使用 UDP 而不是 TCP(大多数其他协议使用 TCP)。
没有 TFTP 的安全版本或变种。
下载
从选择的 TFTP 服务器下载文件:
curl -O tftp://localserver/file.boot
上传
将文件上传到选择的 TFTP 服务器:
curl -T file.boot tftp://localserver/
TFTP 选项
TFTP 协议通过使用“块”将数据传输到通信的另一端。当设置 TFTP 传输时,双方要么同意使用默认的 512 字节块大小,要么协商使用不同的块大小。curl 支持 8 到 65464 字节的块大小。
您可以使用--tftp-blksize让 curl 使用与默认值不同的块大小。例如,请求 8192 字节的块大小如下:
curl --tftp-blksize 8192 tftp://localserver/file
已有证据表明,有些服务器实现根本不处理选项协商,因此 curl 也有完全关闭设置选项尝试的能力。如果您不幸需要与这样的服务器一起工作,可以使用如下标志:
curl --tftp-no-options tftp://localserver/file
命令行 HTTP
在所有用户调查和 curl 的整个生命周期中,HTTP 都是 curl 支持的最重要且最常使用的协议。本章解释了如何有效地使用 curl 进行 HTTP 传输和一般的调整。
这对于 HTTPS 也大致相同,因为它们在底层实际上是同一件事,因为 HTTPS 是带有额外安全 TLS 层的 HTTP。另请参阅具体的 HTTPS 部分。
-
方法
-
响应
-
身份验证
-
范围
-
HTTP 版本
-
条件
-
HTTPS
-
HTTP POST
-
重定向
-
修改 HTTP 请求
-
HTTP PUT
-
Cookies
-
替代服务
-
HSTS
-
脚本化类似浏览器的任务
方法
在每一个 HTTP 请求中,都有一个方法。有时也被称为动词。最常用的方法有GET、POST、HEAD和PUT。
然而,通常你不会在命令行中指定方法,而是具体使用的方法取决于你使用的特定选项。GET是默认方法,使用-d或-F将其变为POST,使用-I生成HEAD,使用-T发送PUT。
更多关于这个的内容请参阅修改 HTTP 请求部分。
响应
当一个 HTTP 客户端与服务器进行 HTTP 通信时,服务器会以 HTTP 响应消息响应,或者 curl 将其视为错误并返回 52,错误信息为“从服务器收到空回复”。
HTTP 响应的大小
HTTP 响应有一个确定的大小,curl 需要找出它。有几种不同的方式来表示 HTTP 响应的结束,但最基本的方式是使用响应中的Content-Length:头,并指定响应体的确切字节数。
一些早期的 HTTP 服务器实现存在文件大小超过 2GB 的问题,并错误地发送了带有负大小的Content-Length:头或其他错误数据。curl 可以被告知完全忽略Content-Length:头,使用--ignore-content-length。这样做可能会有一些其他负面影响,但至少应该让你得到数据。
HTTP 响应代码
HTTP 传输在第一行响应中会收到一个三位数的响应代码。响应代码是服务器向客户端提供有关请求处理方式的提示的方式。
重要的一点是,即使响应代码表明请求的文档无法提供(或类似情况),curl 也不会将其视为错误。curl 认为成功发送和接收 HTTP 是好的。
HTTP 响应代码的第一位数字是一种错误类别:
-
1xx:短暂响应,还有更多内容
-
2xx:成功
-
3xx:重定向
-
4xx:客户端请求了服务器无法或不愿意提供的内容
-
5xx:服务器存在问题
记住,你可以使用 curl 的--write-out选项来提取响应代码。请参阅–write-out 部分。
要使 curl 对于响应代码>=400 返回错误,你需要使用--fail或--fail-with-body。然后 curl 会以错误代码 22 退出,表示此类情况。
CONNECT 响应代码
由于在同一个 curl 传输中可能存在 HTTP 请求和单独的 CONNECT 请求,我们通常将 CONNECT 响应(来自代理)与远程服务器的 HTTP 响应分开。
CONNECT 也是一个 HTTP 请求,因此它会在相同的数字范围内获得响应代码,你也可以使用--write-out来提取该代码。
分块传输编码
HTTP 1.1 服务器可以选择以分块编码的响应来响应,这是 HTTP 1.0 中不存在的一个特性。
当接收到分块响应时,响应中没有 Content-Length:来指示其大小。相反,有一个Transfer-Encoding: chunked头信息告诉 curl 即将接收分块数据,然后在响应体中,数据以一系列数据块的形式出现。每个单独的数据块以该特定数据块的大小(十六进制表示)开始,然后是一个换行符,接着是数据块的内容。这个过程会一直重复,直到响应结束,响应结束的信号是一个零大小的数据块。这种响应编码的目的是让客户端能够确定响应何时结束,即使服务器在开始发送之前并不知道完整的响应大小。这通常发生在响应是动态生成,且在请求到达时生成的情况下。
客户端如 curl 会解码数据块,但不会向用户显示数据块的大小。
压缩传输
通过 HTTP 发送的响应可以是压缩格式。这通常是在服务器在响应中包含Content-Encoding: gzip时完成的,作为向客户端的提示。当发送静态资源(之前已压缩)或在运行时 CPU 功率大于带宽时,压缩响应非常有意义。发送更少的数据量通常更受欢迎。
您可以要求 curl 请求压缩内容,并在接收到内容编码为 gzip(或实际上 curl 理解的任何其他压缩算法)时自动和透明地解压缩数据,使用--compressed:
curl --compressed http://example.com/
传输编码
与传输编码一起使用的较少见的功能是压缩。
压缩本身很常见。随着时间的推移,HTTP 压缩的主要和兼容网络的方式已经变成了使用上面章节中描述的Content-Encoding。但 HTTP 最初旨在并指定允许透明压缩作为传输编码,curl 支持这一功能。
客户端随后简单地要求服务器执行压缩传输编码,如果服务器接受,它会以一个头信息响应表示它执行了压缩,curl 随后在到达时透明地解压缩这些数据。curl 用户请求压缩传输编码使用--tr-encoding:
curl --tr-encoding http://example.com/
应该注意的是,野外中支持这一功能的 HTTP 服务器并不多。
传递传输编码
在某些情况下,您可能希望将 curl 用作代理或其他中间软件。在这些情况下,curl 处理传输编码头信息并透明解码实际数据的方式可能不受期望,如果最终接收者也期望做同样的事情。
然后,您可以要求 curl 传递接收到的数据,而不对其进行解码。这意味着在分块编码格式或压缩格式中传递大小,当使用压缩传输编码时等。
curl --raw http://example.com/
身份验证
每个 HTTP 请求都可以进行身份验证。如果服务器或代理希望用户提供证明他们拥有访问 URL 或执行操作的正确凭据,它可以发送一个 HTTP 响应代码,告知客户端它需要提供一个正确的 HTTP 身份验证报头才能允许请求。
需要身份验证的服务器会返回一个 401 响应代码,并附带一个 WWW-Authenticate: 报头,其中列出了服务器支持的所有身份验证方法。
需要身份验证的 HTTP 代理会返回一个 407 响应代码,并附带一个 Proxy-Authenticate: 报头,其中列出了代理支持的所有身份验证方法。
值得注意的是,如今的大多数网站在登录等操作中不需要 HTTP 身份验证,而是要求用户在网页上登录,然后浏览器发出包含用户名和密码等的 POST 请求,并随后维护会话的 cookies。
要让 curl 执行身份验证的 HTTP 请求,您可以使用 -u, --user 选项提供用户名和密码(用冒号分隔)。例如:
curl --user daniel:secret http://example.com/
这使得 curl 使用默认的 基本 HTTP 身份验证方法。是的,它实际上被称为基本,并且确实是基本的。要显式请求基本方法,请使用 --basic。
基本身份验证方法会将用户名和密码以明文形式(base64 编码)通过网络发送,应避免用于 HTTP 传输。
当请求使用单一(指定或隐含)的身份验证方法进行 HTTP 传输时,curl 会将身份验证报头插入到第一个网络请求中。
如果您希望 curl 首先测试身份验证是否真的需要,您可以请求 curl 查找并自动使用它所知道的最高安全方法,使用 --anyauth。这使得 curl 尝试未认证的请求,并在必要时切换到身份验证:
curl --anyauth --user daniel:secret http://example.com/
同样的概念也适用于可能需要身份验证的 HTTP 操作:
curl --proxy-anyauth --proxy-user daniel:secret http://example.com/ \--proxy http://proxy.example.com:80/
curl 通常(根据其构建方式略有不同)还支持几种其他身份验证方法,包括摘要(Digest)、协商(Negotiate)和 NTLM。您也可以明确请求这些方法:
curl --digest --user daniel:secret http://example.com/
curl --negotiate --user daniel:secret http://example.com/
curl --ntlm --user daniel:secret http://example.com/
范围
如果客户端只想获取远程资源的前 200 字节,或者可能在中间的某个位置获取 300 字节,怎么办?HTTP 协议允许客户端请求特定的数据范围。客户端通过提供一个起始偏移量和结束偏移量来请求服务器上的特定范围。它甚至可以在同一个请求中组合多个范围,只需将多个片段并列列出即可。当服务器发送多个独立的部分来响应此类请求时,这些部分会通过 MIME 边界字符串分隔,用户应用程序需要相应地处理这些部分。curl 不会进一步分隔此类响应。
然而,字节范围只是一个对服务器的请求。服务器不必遵守这个请求,在许多情况下,比如当服务器在请求时自动生成内容,它可能会简单地拒绝这样做,然后仍然以完整内容作为响应。
你可以使用 curl 的-r或--range选项来请求一个范围。如果你想获取某个内容的前 200 字节:
curl -r 0-199 http://example.com
或者从索引 200 开始文件中的所有内容:
curl -r 200- http://example.com
从索引 0 获取 200 字节,并且从索引 1000 获取 200 字节:
curl -r 0-199,1000-1199 http://example.com/
HTTP 版本
与任何其他互联网协议一样,HTTP 协议在过去的几年里一直在不断发展,现在有客户端和服务器分布在世界各地,随着时间的推移,它们使用不同版本的 HTTP,成功率各不相同。为了使 curl 能够与你的 URL 一起工作,curl 为你提供了指定请求和传输应使用哪个 HTTP 版本的方法。curl 的设计方式是首先尝试使用最常见、最合理(如果你愿意的话)的默认值,但有时这还不够,那么你可能需要指导 curl 如何操作。
curl 默认对 HTTP 服务器使用 HTTP/1.1,但如果连接到 HTTPS 并且你有内置 HTTP/2 功能的 curl,它会自动尝试协商 HTTP/2,或者在协商失败的情况下回退到 1.1。不具备 HTTP/2 功能的 curl 默认通过 HTTPS 获取 1.1。
| 选项 | 描述 |
|---|---|
| –http1.0 | HTTP/1.0 |
| –http1.1 | HTTP/1.1 |
| –http2 | HTTP/2 |
| –http2-prior-knowledge | HTTP/2 |
| –http3 | HTTP/3 |
-
HTTP/0.9
-
HTTP/2
-
HTTP/3
HTTP/0.9
在 HTTP/1.0 之前使用的 HTTP 版本通常被称为 HTTP/0.9。在那些日子里,HTTP 响应没有头部信息,因为它们只会返回一个响应体,然后立即关闭连接。
可以告诉 curl 支持这样的响应,但默认情况下它不会识别它们,出于安全考虑。几乎所有不好的东西在 curl 看起来都像 HTTP/0.9 响应,所以这个选项需要谨慎使用。
curl 的 HTTP/0.9 选项与其他上述提到的 HTTP 版本的命令行选项不同,因为它控制着要接受哪些响应,而其他选项则是关于尝试使用哪个 HTTP 协议版本。
这样告诉 curl 接受 HTTP/0.9 响应:
curl --http0.9 https://example.com/
HTTP/2
假设 curl 是用适当的先决条件构建的,curl 支持 HTTP/2 对于 HTTP:// 和 HTTPS:// URL。它甚至默认使用 HTTP/2,当提供一个 HTTPS URL 时,因为这样做没有惩罚,并且当 curl 与不支持 HTTP/2 的网站一起使用时,请求会协商 HTTP/1.1。
然而,对于 HTTP:// URL,升级到 HTTP/2 是通过一个 Upgrade: 报头完成的,这可能会导致额外的往返,甚至更麻烦的是,相当一部分旧服务器在看到这样的报头时会返回 400 响应。
还应注意的是,一些(大多数?)支持 HTTP/2 的 HTTP:// 服务器(本身并不是所有服务器)不会在 POST 上承认 Upgrade: 报头,例如。
要让服务器使用 HTTP/2,只需:
curl --http2 http://example.com/
如果你的 curl 不支持 HTTP/2,那么这个命令行工具会返回一个错误,说明不支持。运行 curl -V 可以显示你的 curl 版本是否支持它。
如果你偶然知道你的服务器支持 HTTP/2(例如,在你自己的受控环境中,你知道你的机器上运行了什么),你可以通过 --http2-prior-knowledge 来跳过 HTTP/2 的协商。
多路复用
HTTP/2 协议的一个主要特性是能够在相同的物理连接上多路复用多个逻辑流。curl 命令行工具可以利用这个特性,当进行并行传输时。
HTTP/3
HTTP/3 在几个方面与其前辈不同。最明显的是,HTTP/3 不能像 HTTP/2 那样在相同的连接上进行协商。由于 HTTP/3 使用不同的传输协议,它必须为它设置和协商一个专用的连接。
QUIC
HTTP/3 是专为在 QUIC 上通信而设计的 HTTP 版本。对于大多数特定目的而言,QUIC 可以被视为 TCP+TLS 的替代品。
因此,所有使用 HTTP/3 的传输都不使用 TCP。它们使用 QUIC。QUIC 是在 UDP 上构建的可靠传输协议。HTTP/3 意味着使用 QUIC。
HTTPS 仅限
HTTP/3 是在 QUIC 上执行的,QUIC 始终使用 TLS,因此 HTTP/3 在定义上始终是加密和安全的。因此,curl 仅对HTTPS:// URL 使用 HTTP/3。
启用
作为直接访问 HTTP/3 的快捷方式,要使 curl 尝试直接连接到给定的主机名和端口号进行 QUIC 连接,请使用--http3。如下所示:
curl --http3 https://example.com/
通常,如果没有--http3选项,HTTPS:// URL 意味着客户端需要使用 TCP(和 TLS)连接到它。
复用
HTTP/3 协议的一个主要特性是能够在相同的物理连接上复用多个逻辑流。curl 命令行工具可以在进行并行传输时利用这一特性。
Alt-svc:
将 HTTP/3 切换为 alt-svc 的方法是服务器启动 HTTP/3 的官方方式。
注意,你需要该功能内置,并且它不会为当前请求切换到 HTTP/3,除非 alt-svc 缓存已经填充,但它而不是存储信息以供在下一个请求到该主机时使用。
当 QUIC 被拒绝
由于许多网络和主机阻止或限制流量,一定数量的 QUIC 连接尝试会失败。
当使用--http3时,curl 在启动 QUIC 连接后大约几百毫秒开始第二次传输尝试,该连接使用 HTTP/2 或 HTTP/1,这样如果 QUIC 连接尝试失败或变得无法忍受地慢,使用较旧 HTTP 版本的连接仍然可以成功并执行传输。这使用户可以有一定信心地使用--http3。
--http3-only选项用于明确不尝试任何较旧版本并行传输,但因此如果无法建立 QUIC 连接,传输将立即失败。
条件性
有时用户希望避免在文件可能前一天已经下载的情况下再次下载文件。这可以通过使 HTTP 传输条件化来实现。curl 支持两种不同的条件:文件时间戳和 etag。
通过修改日期进行检查
使用 -z 或 --time-cond 选项仅下载比特定日期更新的文件:
curl -z "Jan 10, 2017" https://example.com/file -O
或者相反,如果文件比特定时间旧,可以通过在日期前加一个连字符来获取文件:
curl --time-cond "-Jan 10, 2017" https://example.com/file -O
日期解析器很宽松,接受大多数你可以用日期写出的格式,你也可以指定它包括时间:
curl --time-cond "Sun, 12 Sep 2004 15:05:58 -0700" \https://www.example.org/file.html
-z 选项还可以从本地文件中提取并使用时间戳,这对于仅在文件被远程更新时下载文件来说很方便:
curl -z file.html https://example.com/file.html -O
通常将 -z 与 --remote-time 标志结合使用非常有用,该标志将本地创建的文件的时间设置为与远程文件相同的时戳:
curl -z file.html -o file.html --remote-time https://example.com/file.html
通过内容修改进行检查
HTTP 服务器可以为给定资源版本返回一个特定的 ETag。如果给定 URL 的资源发生变化,必须生成新的 Etag 值,以便客户端知道只要 ETag 保持不变,内容就没有发生变化。
使用 ETags,curl 可以在无需依赖时间或文件日期的情况下检查远程更新。它还使得检查能够检测到亚秒级的变化,这是基于时间戳的检查所无法做到的。
使用 curl 可以下载远程文件并将其 ETag(如果有的话)保存在单独的缓存中,通过使用 --etag-save 命令行选项。例如:
curl --etag-save etags.txt https://example.com/file -o output
随后的命令行可以使用之前保存的 etag,并确保只有在文件发生变化时才再次下载文件,如下所示:
curl --etag-compare etag.txt https://example.com/file -o output
这两个 etag 选项也可以在同一个命令行中结合使用,这样如果文件实际上已经更新,curl 会再次将更新 ETag 保存到文件中:
curl --etag-compare etag.txt --etag-save etag.txt \https://example.com/file -o output
HTTPS
HTTPS 实际上是安全 HTTP。安全部分意味着通过使用 TLS,TCP 传输层被增强以提供身份验证、隐私(加密)和数据完整性。
请参阅使用 TLS 部分,以获取如何修改和调整 HTTPS 传输中 TLS 详细信息的深入细节。
HTTP POST
POST 是一种 HTTP 方法,它被发明用来向接收的 Web 应用程序发送数据,并且它是大多数常见的 Web 表单工作方式。它通常向接收者发送一小块相对较小的数据。
-
简单 POST
-
内容类型
-
上传二进制数据
-
JSON
-
URL 编码
-
转换为 GET
-
期望 100-continue
-
分块编码的 POST 请求
-
隐藏表单字段
-
弄清楚浏览器发送的内容
-
JavaScript 和表单
-
多部分表单提交
-
-d 与 -F 的区别
简单的 POST
要发送表单数据,浏览器会将它编码为一系列由和号(&)符号分隔的name=value对。生成的字符串作为 POST 请求的主体发送。要在 curl 中执行相同的操作,使用-d(或--data)参数,如下所示:
curl -d 'name=admin&shoesize=12' http://example.com/
当在命令行上指定多个-d选项时,curl 会将它们连接起来,并在它们之间插入和号,因此上述示例也可以这样写:
curl -d name=admin -d shoesize=12 http://example.com/
如果要发送的数据量太大,无法在命令行上的简单字符串中处理,您也可以按照标准 curl 风格从文件名中读取它:
curl -d @filename http://example.com
虽然服务器可能假设数据是以某种特殊方式编码的,但 curl 不会对您告诉它发送的数据进行编码或更改。curl 发送您给出的确切字节(除了从文件读取时。-d会跳过回车符和换行符,因此如果您希望它们包含在数据中,则需要使用--data-binary)。
要发送以@符号开始的 POST 主体,以避免 curl 尝试将其作为文件名加载,请使用--data-raw。此选项没有文件加载功能:
curl --data-raw '@string' https://example.com
Content-Type
使用 curl 的-d选项进行 POST 操作时,它会包含一个默认的头部信息,看起来像Content-Type: application/x-www-form-urlencoded。这就是你典型的浏览器用于普通 POST 请求的方式。
许多接收 POST 数据的接收者并不关心或检查Content-Type头部信息。
如果这个头部信息对你来说不够好,你应该当然地替换它,并提供正确的一个。例如,如果你向服务器发送 JSON 数据,并希望更准确地告诉服务器内容类型:
curl -d '{json}' -H 'Content-Type: application/json' https://example.com
发布二进制
当从文件读取数据以发布时,-d 会去除回车和换行符。如果你想让 curl 读取并使用给定的文件作为二进制文件,则使用 --data-binary:
curl --data-binary @filename http://example.com/
JSON
curl 7.82.0 版本引入了--json选项,作为将 JSON 格式数据通过 POST 方式发送到 HTTP 服务器的新方法。此选项作为一个快捷方式,提供了一个单一选项来替代以下三个选项:
--data [arg]
--header "Content-Type: application/json"
--header "Accept: application/json"
此选项并不使 curl 真正理解或知道它发送的 JSON 数据,但它使发送它变得更容易。curl 不会触摸或解析它发送的数据,因此你需要自己确保它是有效的 JSON。
向服务器发送一个基本的 JSON 对象:
curl --json '{"tool": "curl"}' https://example.com/
从本地文件发送 JSON:
curl --json @json.txt https://example.com/
从 curl 的 stdin 发送 JSON:
echo '{"a":"b"}' | curl --json @- https://example.com/
你可以在同一个命令行中使用多个--json选项。这使得 curl 将选项的内容连接起来,一次性将所有数据发送到服务器。请注意,这种连接是基于纯文本的,并且它不会按照 JSON 格式合并 JSON 对象。
从文件发送 JSON 并将字符串连接到末尾:
curl --json @json.txt --json ', "end": "true"}' https://example.com/
构建要发送的 JSON
JSON 数据中使用的引号有时会使在 shell 和脚本中编写和使用它变得有些困难和繁琐。
使用一个专门的工具来完成这个目的可能会使事情对你来说更容易,特别是可能帮助你完成这个任务的工具是jo。
使用 jo 和--json选项向服务器发送一个基本的 JSON 对象。
jo -p name=jo n=17 parser=false | curl --json @- https://example.com/
接收 JSON
curl 本身并不知道或理解它发送或接收的内容,包括当服务器在响应中返回 JSON 时。
使用一个专门的工具来解析或格式化 JSON 响应可能会使事情对你来说更容易,特别是可能帮助你完成这个任务的工具是jq。
向服务器发送一个基本的 JSON 对象,并格式化输出 JSON 响应:
curl --json '{"tool": "curl"}' https://example.com/ | jq
使用jo发送 JSON,使用jq打印响应:
jo -p name=jo n=17 | curl --json @- https://example.com/ | jq
jq 是一个功能强大且功能丰富的工具,用于提取、过滤和管理 JSON 内容,其功能远不止于格式化输出。
URL 编码数据
百分比编码,也称为 URL 编码,在技术上是一种编码数据的方式,以便它可以在 URL 中出现。这种编码通常用于发送 application/x-www-form-urlencoded 内容类型的 POST,例如 curl 通过 --data 和 --data-binary 等发送的。
上文提到的命令行选项都需要你提供正确编码的数据,你需要确保这些数据已经以正确的格式存在。虽然这给了你很大的自由度,但在某些时候也会有些不便。
为了帮助你发送尚未编码的数据,curl 提供了 --data-urlencode 选项。此选项提供了多种方法来对给定的数据进行 URL 编码。
你可以使用与其它 –data 选项相同的风格使用它,例如 --data-urlencode data。为了符合 CGI 标准,data 部分应该以一个名称开头,后面跟着一个分隔符和一个内容指定。data 部分可以通过以下语法之一传递给 curl:
-
content:对内容进行 URL 编码并传递。只需小心确保内容中不包含任何=或@符号,因为这会使语法与以下其他情况之一匹配。 -
=content:对内容进行 URL 编码并传递。初始的=符号不包括在数据中。 -
name=content:对内容部分进行 URL 编码并传递。请注意,预期名称部分已经进行了 URL 编码。 -
@filename:从指定的文件中加载数据(包括任何换行符),对数据进行 URL 编码,并在 POST 中传递。 -
name@filename:从指定的文件中加载数据(包括任何换行符),对数据进行 URL 编码,并在 POST 中传递。名称部分会附加一个等号,结果为name=urlencoded-file-content。请注意,预期名称已经进行了 URL 编码。
例如,你可以通过 POST 发送一个名称,让 curl 进行编码:
curl --data-urlencode "name=John Doe (Junior)" http://example.com
…这将实际请求体中发送以下数据:
name=John%20Doe%20%28Junior%29
如果你将字符串 John Doe (Junior) 存储在名为 contents.txt 的文件中,你可以告诉 curl 使用字段名 ‘user’ 以这种方式发送该内容 URL 编码:
curl --data-urlencode user@contents.txt http://example.com
在上述两个示例中,字段名没有进行 URL 编码,而是直接传递。如果你想对字段名也进行 URL 编码,比如你想传递一个名为 user name 的字段名,你可以要求 curl 对整个字符串进行编码,通过在前面加上一个等号(该等号不会被发送):
curl --data-urlencode "=user name=John Doe (Junior)" http://example.com
转换为 GET
在这个上下文中可以提及的一个小便利功能是 -G 或 --get 选项,它接受您使用不同的 -d 变体指定的所有数据,并将这些数据附加到输入的 URL 上,例如 http://example.com,用问号分隔,然后让 curl 发送一个 GET 请求。
此选项使得在 POST 和 GET 表单之间切换变得容易,例如。
一个示例,将编码后的数据作为查询添加到 URL 中:
curl -G --data-urlencode "name=daniel stenberg" https://example.com/
期待 100-continue
HTTP/1 没有一种适当的方式来停止正在进行的传输(在任何方向上)同时保持连接。因此,如果我们确定传输应该在传输开始后停止,那么只有两种方法可以继续:切断连接并支付重新建立连接的代价,或者继续传输并浪费带宽,但下次能够重用连接。
这种情况的一个例子是,当您通过 HTTP 发送一个大型文件时,却发现服务器需要认证,并立即发送回 401 响应代码。
为了使这种情况不那么频繁,存在一种缓解措施,即 curl 传递一个额外的头Expect: 100-continue,这给了服务器在发送大量数据之前拒绝请求的机会。如果 curl 执行的 POST 操作已知或怀疑大于一兆字节,curl 会默认发送这个Expect:头。curl 也会为 PUT 请求执行此操作。
当服务器收到一个带有 100-continue 的请求并认为请求是合理的,它会用一个使客户端继续的 100 响应来响应。如果服务器不喜欢该请求,它会发送回它认为的错误响应代码。
不幸的是,世界上许多服务器都没有正确支持 Expect:头,或者没有正确处理它,因此 curl 在继续之前只等待 1000 毫秒的第一个响应。
您可以使用--expect100-timeout <seconds>来更改 curl 等待 Expect 响应的时间。您可以通过使用-H Expect:来完全避免等待,从而移除该头:
curl -H Expect: -d "payload to send" http://example.com
在某些情况下,如果 curl 即将发送的内容很小(小于一兆字节),它会抑制使用Expect头,因为浪费如此小的数据块被认为不是什么大问题。
HTTP/2 及以后版本
HTTP/2 及 HTTP 的后续版本可以在不关闭连接的情况下停止正在进行的传输,这使得Expect:变得没有意义。
分块编码的 POST
当与 HTTP 1.1 服务器通信时,你可以告诉 curl 在发送请求体之前不使用 Content-Length: 头部来指定 POST 的确切大小。通过坚持让 curl 使用分块传输编码,curl 会以特殊的方式逐块发送 POST,同时也会在发送过程中发送每个这样的块的大小。
你可以使用 curl 发送分块 POST,如下所示:
curl -H "Transfer-Encoding: chunked" -d @file http://example.com
注意事项
这假设你知道你是在与一个 HTTP/1.1 服务器进行交互。在 1.1 之前,没有分块编码,而在 1.1 版本之后,分块编码已经被弃用。
隐藏表单字段
使用 -d 选项发送邮件相当于浏览器在填写并提交 HTML 表单时的操作。
使用 curl 提交此类表单是一个常见的操作;实际上,curl 可以以自动化的方式填写网页表单。
如果你想要使用 curl 提交表单并且让它看起来像是用浏览器完成的,那么提供表单中的所有输入字段是很重要的。网页的一个常见做法是为表单设置几个隐藏的输入字段,并在 HTML 中直接为它们赋值。当然,成功的表单提交也包括这些字段,为了自动完成这一过程,你可能被迫首先下载包含表单的 HTML 页面,解析它,并提取隐藏字段的值,以便你可以使用 curl 将它们发送出去。
确定浏览器发送的内容
一个常见的快捷方法是简单地使用浏览器填写表单并提交,然后在浏览器的网络开发工具中检查它发送了什么。
另一种稍微不同的方法是保存包含表单的 HTML 页面,然后编辑该 HTML 页面,将表单的‘action=’部分重定向到你的服务器或一个仅输出它接收到的内容的测试服务器。完成该表单提交将显示浏览器是如何发送它的。
当然,第三种选择是使用网络抓包工具,例如 Wireshark,来检查通过电线发送的确切内容。如果你在使用 HTTPS,你无法在电线上以明文形式看到表单提交,但你需要确保 Wireshark 可以从你的浏览器中提取你的 TLS 私钥。有关如何操作的详细信息,请参阅 SSLKEYLOGFILE 部分。
JavaScript 和表单
针对使用 curl 的自动化代理或脚本,一种常见的缓解措施是在包含 HTML 表单的页面上使用 JavaScript 设置某些输入字段的值,通常是一个隐藏字段。通常,有一些 JavaScript 代码会在页面加载时或提交按钮被按下时执行,设置一个魔法值,服务器随后可以验证这个值,在考虑提交有效之前。
你通常可以通过阅读 JavaScript 代码并在你的脚本中重新实现那个逻辑来解决这个问题。使用 确定浏览器发送的内容 中的技巧来检查浏览器确实发送了什么,也是一种很好的帮助。
多部分表单
多部分表单是当 HTML 表单提交时,enctype设置为multipart/form-data时,HTTP 客户端发送的内容。这是一个带有特殊格式化的请求体,作为一系列部分发送的 HTTP POST 请求,这些部分由 MIME 边界分隔。
一个 HTML 示例可能如下所示:
<form action="submit.cgi" method="post" enctype="multipart/form-data">Name: <input type="text" name="person"><br>File: <input type="file" name="secret"><br><input type="submit" value="Submit">
</form>
在网页浏览器中,它可能看起来像这样:

一个多部分表单
用户可以在“名称”字段中填写文本,通过按下浏览按钮选择一个本地文件,当按下提交按钮时,该文件将被上传。
使用 curl 发送此类表单
使用 curl,你可以通过一个-F(或--form)标志添加每个单独的多部分,然后继续添加一个-F标志,为表单中你想要发送的每个输入字段添加一个。
上述简短示例表单有两个部分,一个名为“person”的纯文本字段和一个名为“secret”的文件。
按照如下方式将数据发送到该表单:
curl -F person=anonymous -F secret=@file.txt http://example.com/submit.cgi
生成的 HTTP 请求
动作指定了 POST 请求发送的位置。方法说明这是一个 POST 请求,而编码类型告诉我们这是一个多部分表单。
如上所示,curl 根据填写的字段生成并发送这些 HTTP 请求头到主机 example.com:
POST /submit.cgi HTTP/1.1
Host: example.com
User-Agent: curl/7.46.0
Accept: */*
Content-Length: 313
Expect: 100-continue
Content-Type: multipart/form-data; boundary=------------d74496d66958873e
内容长度当然告诉服务器期望多少数据。这个示例的 313 字节非常小。
Expect头信息在 Expect 100 continue 章节中有解释。
内容类型头信息有些特殊。它表明这是一个多部分表单,然后设置边界字符串。边界字符串是一行字符,其中包含一些随机的数字,用作提交表单的不同部分之间的分隔符。在这个示例中,你看到的特定边界包含随机部分d74496d66958873e,但当你运行 curl(或使用浏览器提交此类表单时),你当然会得到不同的结果。
因此,在初始的一组头信息之后是请求体。
--------------------------d74496d66958873e
Content-Disposition: form-data; name="person"anonymous
--------------------------d74496d66958873e
Content-Disposition: form-data; name="secret"; filename="file.txt"
Content-Type: text/plaincontents of the file
--------------------------d74496d66958873e--
在这里,你可以清楚地看到发送的两个部分,它们由边界字符串分隔。每个部分都以一个或多个头信息开始,描述了该部分的内容,包括其名称和可能的一些其他细节。然后在该部分的头信息之后是实际的数据,没有任何编码。
最后的边界字符串附加了两个额外的破折号--,以表示结束。
内容类型
使用 curl 的-F选项会使其请求包含一个默认的Content-Type头信息,如上述示例所示。这表明multipart/form-data,然后指定 MIME 边界字符串。这个Content-Type是多部分表单的默认值,但你当然可以修改它以适应自己的命令。如果你这样做,curl 足够聪明,仍然会在替换的头信息中附加边界魔法。你实际上无法更改边界字符串,因为 curl 需要它来生成 POST 流。
要替换头信息,使用-H选项,如下所示:
curl -F 'name=Dan' -H 'Content-Type: multipart/magic' https://example.com
转换网页表单
有几种不同的方法可以确定如何编写一个 curl 命令行来提交一个如 HTML 中所见的多部分表单。
-
将 HTML 本地保存,在本地运行
nc监听所选的端口号,将actionURL 更改为提交 POST 到你的本地nc实例。提交表单并观察nc如何显示它。然后将其转换为 curl 命令行。 -
使用你最喜欢的浏览器的开发工具,在网络标签中检查你提交后的 POST 请求。然后将那些 HTTP 数据转换为 curl 命令行。不幸的是,浏览器中的 复制为 curl 功能通常并不特别擅长处理多部分表单的 POST。
-
检查源 HTML 并直接将其转换为 curl 命令行。
从 <form> 到 -F
在使用 enctype="multipart/form-data" 的 <form> 中,第一步是找到 action= 属性,因为它告诉 POST 的目标。你需要将其转换为 curl 命令行的完整 URL。
一个示例动作看起来像这样:
<form action="submit.cgi" method="post" enctype="multipart/form-data">
如果表单位于一个 URL 如 https://example.com/user/login 的网页上,那么 action=submit.cgi 是表单本身所在目录内的相对路径。因此,提交此表单的完整 URL 变为 https://example.com/user/submit.cgi。这就是 curl 命令行中要使用的 URL。
接下来,你必须识别表单中使用的每个 <input> 标签,包括标记为隐藏的标签。隐藏只是意味着它们不会显示在网页上,但它们应该仍然在 POST 中发送。
表单中的每个 <input> 都应该在命令行中有一个相应的 -F。
文本输入
一个使用类似文本类型的常规标签
<input type="text" name="person">
应设置字段名称,内容如下:
curl -F "person=Mr Smith" https://example.com/
文件输入
当输入类型设置为文件时,如:
<input type="file" name="image">
你通过指定文件名来提供这部分文件,并使用 @ 和文件的路径来包含:
curl -F image=@funnycat.gif https://example.com/
隐藏输入
将状态从表单传递过去的一个常见技术是将多个 <input> 标签设置为 type="hidden"。这基本上与已经填写好的表单字段相同,所以你可以通过使用名称和值将其转换为命令行。例如:
<input type="hidden" name="username" value="bob123">
这与正常文本字段的转换方式相同,并且你知道内容应该是什么:
curl -F "username=bob123" https://example.com/
所有字段同时
如果我们假设上面示例中显示的所有三个不同的 <input> 标签都在同一个 <form> 中使用,那么一个完整的 curl 命令行,包括上面提取的正确 URL,将看起来像这样:
curl -F "person=Mr Smith" -F image=@funnycat.gif -F "username=bob123" \https://example.com/user/submit.cgi
-d与-F
前几章讨论了常规 POST 和 multipart formpost,在你的典型命令行中,你可以使用-d或-F来完成它们。
何时使用哪一个?
如上所述章节中描述的,这两种选项都将指定数据发送到服务器。区别在于数据在网络中的格式化方式。大多数情况下,接收端被编写为期望特定的格式,并期望发送端正确地格式化和发送数据。客户端不能随意选择自己的格式。
HTML 网页表单
当我们谈论浏览器和 HTML 时,标准方式是提供一个表单给用户,当表单填写完毕后发送数据。<form>标签是使这些表单出现在网页上的标签。该标签指示浏览器如何格式化其 POST。如果表单标签包含enctype=multipart/form-data,它告诉浏览器使用 curl 的-F选项发送数据作为 multipart formpost。这种方法通常用于表单包含<input type=file>标签,用于文件上传。
表单默认使用的enctype,由于它是默认的,所以在 HTML 中很少明确写出,是application/x-www-form-urlencoded。它使得浏览器将输入作为 name=value 对进行 URL 编码,以避免不安全的字符。我们通常将其称为常规 POST,而你使用 curl 的-d和其他选项执行它。
HTML 之外的 POST
POST 是一种常规的 HTTP 方法,没有要求它必须由 HTML 触发或涉及浏览器。如今,许多服务、API 和其他系统允许你传递数据以完成任务。
如果这些服务期望纯原始数据或可能是 JSON 或类似格式的数据,你希望采用常规 POST 方法。curl 的-d选项根本不改变或编码数据,只是发送你告诉它的内容。只需注意,-d设置了一个默认的Content-Type:,这可能不是你想要的。
重定向
“重定向”是 HTTP 协议的基本部分。这个概念在 1996 年发布的第一个规范(RFC 1945)中就已经存在,并且有记录,自那时起一直被广泛使用。
重定向就是其字面意思。服务器向客户端发送指令,而不是返回客户端请求的内容。服务器会说“去找这里看看你请求的那个东西”。
重定向并不完全相同。重定向是永久的吗?客户端在下一个请求中应该使用什么请求方法?
所有重定向都需要发送回一个Location:头,包含请求的新 URI,可以是绝对路径或相对路径。
永久和临时
重定向是打算持续存在还是只是暂时有效?如果你想通过另一个 GET 永久性地将用户重定向到资源 B,请发送回 301。这也意味着用户代理(浏览器)打算缓存此操作,并从现在开始,当请求原始 URI 时,继续访问新的 URI。
临时替代方案是 302。目前服务器希望客户端向 B 发送 GET 请求,但它不应该缓存这个请求,而应该在下次被指引到原始 URI 时继续尝试。
注意,301 和 302 都会让浏览器在下一个请求中执行 GET 操作,这可能意味着如果它以 POST 开始(并且仅限于 POST),则需要更改方法。将 HTTP 方法从 GET 更改为 301 和 302 响应的说法是“出于历史原因”,但浏览器仍然这样做,因此大多数公共网络都表现出这种行为。
实际上,303 代码与 302 类似。它不会被缓存,并且它让客户端在下一个请求中发出 GET 请求。302 和 303 之间的区别很微妙,但 303 似乎是为对原始请求的“间接响应”而设计的,而不仅仅是重定向。
这三个代码是 HTTP/1.0 规范中唯一的重定向代码。
然而,curl 根本不记住或缓存任何重定向,因此对于它来说,永久重定向和临时重定向之间实际上没有区别。
告诉 curl 跟随重定向
在 curl 的传统中,除非你告诉它不同,否则它默认不跟随 HTTP 重定向。使用-L, --location选项来告诉它这样做。
当启用跟随重定向时,curl 默认跟随最多 30 个重定向。这个最大限制主要是为了避免陷入无限循环的风险。如果你觉得 30 不够用,你可以通过--max-redirs选项更改要跟随的最大重定向数。
GET 或 POST?
这三个响应代码,301 和 302/303,都假设客户端发送 GET 来获取新的 URI,即使客户端可能在第一个请求中发送了 POST。这很重要,至少如果你做的是不使用 GET 的事情。
如果服务器希望将客户端重定向到新的 URI,并希望它在第二次请求中发送与第一次相同的方法,比如如果它首先发送了 POST,它希望它在下一个请求中再次发送 POST,服务器将使用不同的响应代码。
要告诉客户“你发送 POST 请求的 URI 已永久重定向到 B,你应该现在以及未来发送 POST 请求”,服务器会响应 308 状态码。更复杂的是,308 状态码是最近才定义的(规范于 2014 年 6 月发布),因此较旧的客户端可能无法正确处理它。如果是这样,那么你剩下的唯一响应代码就是……
告诉客户端在下一个请求中也发送 POST 的(较旧的)响应代码是 307。尽管如此,客户端不会缓存此重定向,因此如果再次请求,它将再次向 A 发送 POST。307 代码是在 HTTP/1.1 中引入的。
哦,并且重定向在 HTTP/2 中与 HTTP/1.1 中的工作方式相同。
| 永久 | 临时 | |
|---|---|---|
| 切换到 GET | 301 | 302 和 303 |
| 保持原始方法 | 308 | 307 |
决定在重定向中使用的请求方法
结果表明,世界上确实存在一些希望将 POST 请求发送到原始 URL 的 Web 服务,但它们会以 HTTP 重定向的形式响应,使用 301、302 或 303 响应代码,并且仍然希望 HTTP 客户端将下一个请求作为 POST 发送。如上所述,浏览器不会这样做,curl 默认也不会这样做。
由于这些设置存在,而且实际上并不罕见,curl 提供了选项来改变其行为。
你可以通过使用专门为此目的提供的选项来告诉 curl 在 30x 响应后不要将非 GET 请求方法更改为 GET:--post301、--post302和--post303。如果你正在编写基于 libcurl 的应用程序,你可以通过CURLOPT_POSTREDIR选项来控制该行为。
重定向到其他主机名
当你使用 curl 时,你可以为特定站点提供用户名和密码等凭证,但由于 HTTP 重定向可能会移动到不同的主机,curl 限制了它发送到原始传输中其他主机的数据。
所以,如果你想将凭证也发送到后续的主机名,即使它们与原始主机名不同——可能是因为你信任它们,并且知道这样做没有害处——你可以通过使用--location-trusted选项告诉 curl 这样做是可以的。
非 HTTP 重定向
浏览器支持多种重定向方式,这些方式有时会让 curl 用户的生活变得复杂,因为这些方法不被 curl 支持或识别。
HTML 重定向
如果上述内容还不够,网络世界还提供了一种通过纯 HTML 重定向浏览器的方法。请参见下面的示例 <meta> 标签。这对于 curl 来说有些复杂,因为 curl 从不解析 HTML,因此对这些类型的重定向没有了解。
<meta http-equiv="refresh" content="0; url=http://example.com/">
JavaScript 重定向
现代网络充满了 JavaScript,正如你所知,JavaScript 是一种语言,也是一个完整的运行时环境,允许代码在访问网站时在浏览器中执行。
JavaScript 还提供了让浏览器跳转到另一个网站的手段——如果你愿意,可以称之为重定向。
修改 HTTP 请求
如前所述,每个 HTTP 传输都是从 curl 发送一个 HTTP 请求开始的。这个请求由一个请求行和多个请求头部组成,本章详细介绍了如何修改所有这些内容。
-
请求方法
-
请求目标
-
片段
-
自定义头部
-
引用者
-
用户代理
当然,更改 HTTP 版本 也是改变请求的另一种方法。
请求方法
HTTP 请求的第一行包括方法——有时也被称为动词。当你执行一个简单的 GET 请求,就像这个命令行会做的那样:
curl http://example.com/file
…初始请求行看起来像这样:
GET /file HTTP/1.1
你可以通过使用-X或--request命令行选项后跟实际的方法名称来告诉 curl 更改方法。例如,你可以像这样发送DELETE而不是GET:
curl http://example.com/file -X DELETE
此命令行选项仅更改输出请求中的文本,它不会改变任何行为。这一点尤其重要,例如,如果你要求 curl 使用-X发送 HEAD 请求,因为 HEAD 指定发送 GET 响应会获取的所有头信息,但永远不发送响应体,即使头信息暗示了可能会有响应体。因此,在原本应该执行 GET 请求的命令行中添加-X HEAD会导致 curl 挂起,等待不会到来的响应体。
当使用 curl 执行 HTTP 传输时,它会根据选项选择正确的方法,因此你很少需要显式使用-X来请求它。还应注意的是,当 curl 按照-L选项的要求进行重定向时,即使是在后续的重定向中,也会发送使用-X设置的请求方法。
请求目标
当给定一个如 http://example.com/file 的输入 URL 时,URL 的路径部分会被提取出来,并在 HTTP 请求行中转换为 /file。在 HTTP 协议中,这个协议项被称为 请求目标。这就是请求与之交互的资源。通常,这个请求目标是从 URL 中提取出来的,然后用于请求中,作为用户,你不需要考虑它。
在一些罕见的情况下,用户可能想要创新并改变这个请求目标,以这种方式改变它,而 URL 并不允许这样做。例如,HTTP OPTIONS 方法有一个专门定义的请求目标,用于与 服务器 相关的魔法,而不是特定的路径,它使用 * 来表示。是的,一个单独的星号。无法指定 URL,因此如果你想在请求目标中传递一个星号到服务器,比如用于 OPTIONS,你必须这样做:
curl -X OPTIONS --request-target "*" http://example.com/
那个示例命令行使得发出的 HTTP 请求的第一行看起来像这样:
OPTIONS * HTTP/1.1
–path-as-is
URL 的路径部分是从主机名后的第一个斜杠开始的部分,它要么在 URL 的末尾结束,要么在 ‘?’ 或 ‘#’(大致上)处结束。
如果你将包括 /../ 或 /./ 的子字符串包含在路径中,curl 会自动在将路径发送到服务器之前将其压缩,这是根据标准以及这些字符串在本地文件系统中通常的工作方式所决定的。/../ 序列会移除前面的部分,因此 /hello/sir/../ 最终变为 /hello/,而 /./ 则被简单地移除,因此 /hello/./sir/ 变为 /hello/sir/。
为了 防止 curl 在将那些魔法序列发送到服务器之前压缩它们,从而允许它们通过,存在 --path-as-is 选项。
欺骗服务器交付其 /etc/passwd 文件的拙劣尝试:
curl --path-as-is https://example.com/../../etc/passwd
片段
一个 URL 可能包含一个锚点,也称为片段,它以 URL 末尾的井号(#)和字符串的形式书写。例如,http://example.com/foo.html#here-it-is。这个片段部分,从井号/哈希符号到 URL 末尾的所有内容,仅用于本地,不会通过网络发送。curl 会简单地移除这些数据并丢弃它们。
自定义头部
在 HTTP 请求中,在初始请求行之后,通常跟随一系列请求头部。这是一组以空白行结束的name: value对,该空白行将头部与随后的请求体(有时为空)分开。
curl默认在其请求中传递一些默认头部,出于其自身的账户,例如Host:, Accept:, User-Agent:以及一些可能取决于用户要求curl执行的操作的其他头部。
用户可以替换curl自己设置的所有头部。你只需告诉curl的-H或--header使用新的头部,如果头部字段与这些头部之一匹配,它就会替换内部的头部,或者将指定的头部添加到请求中要发送的头部列表中。
要更改Host:头部,请这样做:
curl -H "Host: test.example" http://example.com/
要添加Elevator: floor-9头部,请这样做:
curl -H "Elevator: floor-9" http://example.com/
如果你只想删除一个内部生成的头部,只需将其提供给curl而不带值,即在冒号右边什么也不写。
要关闭User-Agent:头部,请这样做:
curl -H "User-Agent:" http://example.com/
最后,如果你确实想要添加一个冒号右边没有内容的头部(这很少见),那个神奇的标记是让头部字段名以一个分号结尾。就像这样:
curl -H "Empty;" http://example.com
引用者
当用户在网页上点击链接,浏览器将用户带到下一个 URL 时,它会向新的 URL 发送一个包含Referer:头部的请求,告知其来源。这就是引用者头部。Referer:拼写错误,但这就是它应有的样子。
使用 curl,你可以通过-e或--referer设置引用者头部,如下所示:
curl --referer http://comes-from.example.com https://www.example.com/
User-agent
User-Agent 是一个头部,每个客户端都可以在请求中设置,以通知服务器它使用的用户代理。有时服务器会查看这个头部并根据其内容决定如何行动。
默认的头部值是‘curl/版本’,例如对于 curl 版本 7.54.1,显示为User-Agent: curl/7.54.1。
您可以设置任何喜欢的值,使用选项-A或--user-agent加上要使用的字符串,或者,因为它只是一个头部,可以使用-H "User-Agent: foobar/2000"。
作为比较,Linux 机器上 Firefox 的一个测试版本曾经发送过这个 User-Agent 头部:
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0
HTTP PUT
PUT 和 POST 之间的区别很微妙。它们在传输上几乎相同,只是方法字符串不同。POST 旨在将数据传递给远程资源,而 PUT 则应该是该资源的新版本。
在这方面,PUT 与在其他协议中找到的古老标准文件上传类似。您使用 PUT 上传资源的最新版本。URL 标识资源,您指出要放置的本地文件:
curl -T localfile http://example.com/new/resource/file
-T 表示一个 PUT 操作,并告诉 curl 发送哪个文件。但 POST 和 PUT 之间的相似性也允许您通过使用常规 curl POST 机制(使用-d)发送一个 PUT 请求,但要求使用 PUT 方法:
curl -d "data to PUT" -X PUT http://example.com/new/resource/file
Cookies
HTTP Cookies 是客户端代表服务器存储的键/值对。它们在后续请求中发送回来,以便客户端能够在请求之间保持状态。记住,HTTP 协议本身没有状态,而是客户端必须在后续请求中重新发送所有它希望服务器知道的数据。
服务器通过Set-Cookie:头设置 Cookies,并且每个 Cookies 都会发送一些额外的属性,客户端需要匹配这些属性才能将 Cookies 发送回服务器。这些属性包括域名、路径,也许最重要的是 Cookies 应该存活的时间。
Cookies 的过期时间要么设置为未来的固定时间(或存活一定秒数),要么根本不设置过期时间。没有过期时间的 Cookies 被称为会话 Cookies,意味着它在会话期间存在,但不会更久。在这个意义上,会话通常被认为是用于查看网站的浏览器的生命周期。当你关闭浏览器时,你就结束了会话。使用支持 Cookies 的命令行客户端进行 HTTP 操作时,会引发一个疑问:会话何时真正结束…
Cookies 引擎
curl 的一般概念是除非你告知它不同,否则只做最基本的工作,这使得它默认不识别 Cookies。你需要开启 Cookies 引擎来让 curl 跟踪它接收到的 Cookies,并在具有匹配 Cookies 的请求中发送它们。
你可以通过让 curl 读取或写入 Cookies 来开启 Cookies 引擎。如果你告诉 curl 从一个空命名的文件中读取 Cookies,你只是开启了引擎,但开始时内部 Cookies 存储是空的:
curl -b "" http://example.com
只开启 Cookies 引擎,获取单个资源然后退出是没有意义的,因为 curl 没有机会发送它接收到的任何 Cookies。假设在这个示例中,网站会设置 Cookies 然后进行重定向,我们会这样做:
curl -L -b "" http://example.com
从文件中读取 Cookies
从一个空的 Cookies 存储开始可能不是最佳选择。为什么不从之前获取或以其他方式获得的 Cookies 开始呢?curl 用于 Cookies 的文件格式被称为 Netscape Cookies 格式,因为它曾经是浏览器使用的文件格式,然后你可以轻松地告诉 curl 使用浏览器的 Cookies。
作为便利,curl 还支持将 Cookies 文件作为设置 Cookies 的 HTTP 头集合。这是一个次优格式,但可能是你唯一拥有的。
告诉 curl 从哪个文件中读取初始 Cookies:
curl -L -b cookies.txt http://example.com
记住,这只是为了从文件中读取。如果服务器在其响应中更新 Cookies,curl 会在其内存存储中更新该 Cookies,但最终在退出时会丢弃所有 Cookies。当后续调用相同的输入文件时,会再次使用原始的 Cookies 内容。
将 Cookies 写入文件
存储 cookies 的地方有时被称为 cookie jar。当你启用 curl 的 cookie 引擎并且它已经接收到了 cookies,你可以指示 curl 在退出之前将其所有已知的 cookies 写入一个文件,即 cookie jar。重要的是要记住,curl 只在退出时更新输出 cookie jar,而不是在其生命周期内,无论处理给定输入需要多长时间。
你可以使用 -c 来指定 cookie jar 的输出:
curl -c cookie-jar.txt http://example.com
-c 是将 cookies 写入 文件的指令,-b 是从文件 读取 cookies 的指令。通常你两者都需要。
当 curl 将 cookies 写入此文件时,它会保存所有已知的 cookies,包括那些会话 cookies(没有指定生存期)。curl 本身没有会话的概念,也不知道会话何时结束,因此除非你告诉它,否则它不会刷新会话 cookies。
新的 Cookie 会话
curl 不是通过告诉 curl 何时会话结束来处理会话的,它提供了一个选项,让用户决定何时开始一个新的会话 开始。
新的 cookie 会话意味着所有旧的会话 cookies 都会被丢弃。这相当于关闭浏览器并重新启动。
使用 -j, --junk-session-cookies 告诉 curl 一个新的 Cookie 会话开始:
curl -j -b cookies.txt http://example.com/
- Cookie 文件格式
Cookie 文件格式
Netscape 曾经创建了一种文件格式,用于在磁盘上存储 cookie,以便它们能够在浏览器重启后继续存在。curl 采用了这种文件格式,以便与浏览器共享 cookie,但很快看到浏览器开始远离这种格式。现代浏览器不再使用它,而 curl 仍然使用。
Netscape 的 cookie 文件格式在文件中每行存储一个 cookie,并附带一些关联的元数据,每个字段由制表符分隔。在 curl 术语中,该文件被称为 cookiejar。
当 libcurl 保存 cookiejar 时,它会创建一个包含 URL 提及的文件头,该提及链接到该文档的网络版本。
文件格式
Cookie 文件格式是基于文本的,每行存储一个 cookie。以#开头的行被视为注释。
每一行指定单个 cookie,由七个文本字段组成,字段之间用制表符(ASCII 八进制 9)分隔。有效的行必须以换行符结束。
文件中的字段
字段编号、类型、示例数据和其含义:
-
字符串
example.com- 域名 -
布尔值
FALSE- 包含子域名 -
字符串
/foobar/- 路径 -
布尔值
TRUE- 仅通过 HTTPS 发送/接收 -
数字
1462299217- 过期时间 - 自 1970 年 1 月 1 日起的秒数,或 0 -
字符串
person- cookie 的名称 -
字符串
daniel- cookie 的值
替代服务
RFC 7838定义了一个 HTTP 头信息,允许服务器通过使用Alt-Svc:响应头告诉客户端,该服务器在另一个位置有一个或多个替代方案。
服务器建议的替代方案可能包括在同一主机上运行的其他端口的另一个服务器,在完全不同的主机名上,甚至可能通过另一个协议提供该服务。
启用
要让 curl 考虑提供的替代方案,告诉 curl 使用特定的 alt-svc 缓存文件,如下所示:
curl --alt-svc altcache.txt https://example.com/
然后,curl 在启动时从文件中加载现有的替代服务条目,并在进行 HTTP 请求时考虑这些条目;如果服务器发送新的或更新的Alt-Svc:头信息,curl 会在退出时将这些信息存储在缓存中。
alt-svc 缓存
alt-svc 缓存类似于一个 cookie jar。它是一个基于文本的文件,每行存储一个替代方案,每个条目还有一个有效期,表示该特定替代方案的有效时长。
仅 HTTPS
Alt-Svc:只有在通过 HTTPS 连接到服务器时才被信任和解析。
HTTP/3
截至 2019 年 8 月,使用Alt-Svc:头信息是启动客户端和服务器使用 HTTP/3 的唯一定义方式。然后服务器通过 HTTP/1 或 HTTP/2 向客户端暗示它也通过 HTTP/3 可用,如果 alt-svc 缓存这样指示,curl 可以在后续请求中使用 HTTP/3 连接到它。
HSTS
HTTP 严格传输安全,HSTS,是一种协议机制,有助于保护 HTTPS 服务器免受中间人攻击,如协议降级攻击和 cookie 劫持。它允许 HTTPS 服务器声明客户端应自动使用仅 HTTPS 连接与该主机名交互 - 并且明确不使用明文协议与它交互。
HSTS 缓存
某个服务器名称的 HSTS 状态设置在响应头中,并有一个过期时间。每个 HSTS 主机名的状态都需要保存在一个文件中,以便 curl 能够获取它并更新状态和过期时间。
调用 curl 并告诉它使用哪个文件作为 hsts 缓存:
curl --hsts hsts.txt https://example.com
curl 仅在通过安全传输读取头信息时更新 hsts 信息,因此不是在明文协议上完成时更新。
使用 HSTS 更新不安全的协议
如果缓存文件现在包含给定主机名的条目,即使你尝试使用不安全的协议连接到它,它也会自动切换到安全协议:
curl --hsts hsts.txt http://example.com
脚本化类似浏览器的任务
curl 可以执行几乎所有的 HTTP 操作,并且可以像你最喜欢的浏览器一样传输。实际上,它还可以做更多的事情,但在这个章节中,我们关注的是你可以使用 curl 来重现或脚本化你否则必须手动使用浏览器执行的操作。
这里有一些技巧和建议,关于在执行此操作时如何进行。
确定浏览器做了什么
这实际上是一个必要的第一步。猜测它做了什么可能会让你陷入错误的问题陷阱。对这个问题的科学方法几乎要求你首先了解浏览器做了什么。
要了解浏览器如何执行特定任务,你可以阅读你操作的 HTML 页面,如果你有足够的知识,你可以看到浏览器为了完成它会如何操作,然后开始尝试用 curl 做同样的事情。
一种稍微有效的方法,即使在页面充满了混淆的 JavaScript 的情况下也能工作,是运行浏览器并监控它执行的 HTTP 操作。
复制为 curl 部分描述了如何记录浏览器的请求并将其轻松转换为 curl 命令行。
虽然复制 curl 命令行通常还不够好,因为它们倾向于精确地复制那个请求,而你可能更希望它更加动态,以便你可以重现相同的操作,而不仅仅是重新发送原始请求。
Cookies
当今许多网站都使用某个地方的用户名和密码登录提示。在许多情况下,你甚至很久以前就已经用浏览器登录过了,但它保持了状态,并保持你登录的状态。
登录状态几乎总是通过使用 cookies 来完成的。一个常见的操作是首先登录并将返回的 cookies 保存到文件中,然后当使用 curl 遍历网站时,让网站在后续的命令行中更新 cookies。
网络登录和会话
网址为 https://example.com/的网站有一个登录提示。该网站的登录是一个 HTML 表单,你向其发送一个 HTTP POST 请求。保存响应 cookies 和响应(HTML)输出。
尽管登录页面在 https://example.com/上是可见的(如果你使用浏览器),但该页面的 HTML 表单标签会告诉你应该将 POST 发送到哪个确切的 URL,使用action参数。
在我们的假设案例中,表单标签看起来像这样:
<form action="login.cgi" method="POST"><input type="text" name="user"><input type="password" name="secret"><input type="hidden" name="id" value="bc76">
</form>
有三个重要的字段。text、secret和id。最后一个,即 id,被标记为hidden,这意味着它不会在浏览器中显示,并且它不是一个用户需要填写的字段。它由网站本身生成,为了你的 curl 登录成功,你需要提取该值,并在 POST 提交中使用该值以及其余数据。
将正确的内容发送到正确的目标 URL 的字段:
curl -d user=daniel -d secret=qwerty -d id=bc76 \https://example.com/login.cgi -o out
许多登录页面甚至在你展示登录时就已经发送了会话 cookie,而且由于你通常需要从<form>标签中提取隐藏字段,你可以先这样做:
curl -c cookies https://example.com/ -o loginform
你通常会需要一个 HTML 解析器或某种脚本语言来从那里提取 id 字段,然后你可以继续按照上述方法登录,但增加了 cookie 加载(我将这一行分成两行以提高可读性):
curl -d user=daniel -d secret=qwerty -d id=bc76 \https://example.com/login.cgi -b cookies -c cookies -o out
你可以看到它同时使用了-b来从文件中读取 cookies,以及-c来存储 cookies,以便在服务器发送回更新的 cookies 时使用。
总是,总是,在处理细节时添加-v到命令行。有关更多详细信息,请参阅详细输出部分。
重定向
服务器在响应登录 POST 请求时使用重定向是很常见的。如此常见,以至于我可能会说,如果不通过重定向解决,那是非常罕见的。
然后,你只需记住 curl 不会自动跟随重定向。你需要通过添加-L命令行选项来指示它这样做。将此添加到之前的命令行后,完整的命令行看起来像这样:
curl -d user=daniel -d secret=qwerty -d id=bc76 \https://example.com/login.cgi -b cookies -c cookies -L -o out
登录后
在上述示例命令行中,我们将登录响应输出保存到名为‘out’的文件中,在你的脚本中,你可能需要验证它包含一些文本或确认登录成功的某些内容。
一旦成功登录,获取所需的文件或执行 HTTP 操作,并记得在命令行上继续使用-b和-c来使用和更新 cookies。
引用者
一些网站在请求某些内容或登录或类似操作时,会验证Referer:是否确实标识了合法的父 URL。然后,你可以通过使用-e https://example.com/等来通知服务器你从哪个 URL 到达的。将此添加到之前的登录尝试后,它看起来像这样:
curl -d user=daniel -d secret=qwerty -d id=bc76 \https://example.com/login.cgi \-b cookies -c cookies -L -e "https://example.com/" -o out
TLS 指纹识别
现今,反机器人检测通常使用 TLS 指纹识别来确定请求是否来自浏览器。Curl 的指纹可能会根据你的环境而变化,并且很可能是与浏览器不同的。Curl 的 CLI 没有选项来更改指纹的各个部分,但是高级用户可以通过使用 libcurl 并自行编译 curl 来自定义指纹。
命令行 FTP
FTP,即文件传输协议,可能是 curl 支持的最古老的网络协议——它是在 20 世纪 70 年代初期创建的。官方规范仍然是首选的文档,即 RFC 959,发布于 1985 年,比第一个 curl 发布早十多年。
FTP 是在互联网和计算机的另一个时代创建的,因此它的工作方式与大多数其他协议略有不同。这些差异通常可以忽略,事情也能正常工作,但在某些情况下,了解这些差异也很重要,因为事情可能不会按计划运行。
Ping-pong
FTP 协议是一种命令和响应协议;客户端发送命令,服务器响应。如果你使用 curl 的 -v 选项,你可以在传输过程中看到所有的命令和响应。
对于普通传输,大约需要 5 到 8 个命令来发送,以及同样多的响应等待读取。也许不必说,如果服务器位于远程位置,在文件传输可以设置并开始之前,需要等待 ping pong 的时间可能会很长。对于小文件,初始命令可能比实际数据传输花费更长的时间。
传输模式
当 FTP 客户端即将传输数据时,它会向服务器指定希望使用的传输模式。curl 支持的两种传输模式是 ‘ASCII’ 和 ‘BINARY’。Ascii 用于文本,通常意味着服务器在发送文件时转换换行符,而 binary 则表示发送未经修改的数据,并假设文件不是文本。
curl 默认为 FTP 使用二进制传输模式,你可以通过 -B, --use-ascii 选项或确保 URL 以 ;type=A 结尾来请求 ASCII 模式。
认证
FTP 是一种通常需要用户名和密码才能访问的协议。对于允许匿名 FTP 访问的系统,你可以使用几乎任何你喜欢的名字和密码登录。当在 FTP URL 上使用 curl 进行传输且没有提供用户名或密码时,它会使用名字 anonymous 和密码 ftp@example.com。
如果你想要提供另一个用户名和密码,你可以通过 -u, --user 选项将它们传递给 curl,或者将信息嵌入到 URL 中:
curl --user daniel:secret ftp://example.com/downloadcurl ftp://daniel:secret@example.com/download
-
FTP 目录列表
-
使用 FTP 上传
-
自定义 FTP 命令
-
两个连接
-
目录遍历
-
FTPS
FTP 目录列表
您可以使用 curl 列出远程 FTP 目录,只需确保 URL 以反斜杠结尾。如果 URL 以斜杠结尾,curl 假定您想要列出的是一个目录。如果不是实际的目录,您可能会收到错误。
curl ftp://ftp.example.com/directory/
在 FTP 中,没有用于返回此类命令的标准目录输出语法,该命令使用标准的 FTP 命令LIST。列表通常是可读的,并且完全理解,但不同的服务器可能会使用略微不同的布局来返回列表。
要仅获取目录中所有名称的列表,从而避免常规目录列表的特殊格式,可以告诉 curl 使用--list-only(或简称-l)。然后 curl 会发出NLST FTP 命令:
curl --list-only ftp://ftp.example.com/directory/
NLST 有其独特的特点,因为一些 FTP 服务器在响应 NLST 时只列出实际的文件;它们不包括目录和符号链接。
使用 FTP 上传
要上传到 FTP 服务器,您需要在 URL 中指定完整的目标文件路径和名称,并且使用-T, --upload-file指定要上传的本地文件名。可选地,您可以在目标 URL 的末尾加上一个斜杠,然后 curl 会将本地路径中的文件组件附加为远程文件名。
例如:
curl -T localfile ftp://ftp.example.com/dir/path/remote-file
或者使用本地文件名作为远程名称:
curl -T localfile ftp://ftp.example.com/dir/path/
curl 也支持在-T参数中使用通配符,这样您可以轻松上传一系列文件:
curl -T 'image[1-99].jpg' ftp://ftp.example.com/upload/
或者一系列文件:
curl -T '{file1,file2}' ftp://ftp.example.com/upload/
或者
curl -T '{Huey,Dewey,Louie}.jpg' ftp://ftp.example.com/nephews/
自定义 FTP 命令
FTP 协议提供了一系列不同的命令,允许客户端执行除了 curl 专注于的普通文件传输之外的操作。
curl 用户可以将这些额外的(自定义)命令作为文件传输序列中的一步传递给服务器。curl 甚至提供在处理过程中的不同点运行这些命令的选项。
引号
在过去,标准的旧 ftp 客户端有一个名为 quote 的命令。它用于将命令逐字发送到服务器。curl 使用相同的名称来提供几乎相同的功能:将指定的命令逐字发送到服务器。实际上是一个或多个命令。-Q 或 --quote。
要了解可以发送到服务器的哪些命令,您需要了解一些关于 FTP 协议的知识,并可能阅读一下 RFC 959 的细节。
要在传输开始前发送一个简单的 NOOP 到服务器(它什么都不做),像这样提供给 curl:
curl -Q NOOP ftp://example.com/file
要在传输后立即发送相同的命令,请在 FTP 命令前加一个短横线(-):
curl -Q -NOOP ftp://example.com/file
curl 还提供在更改工作目录后发送命令的选项,就在启动传输的命令之前。要发送命令,请在命令前加一个加号(+)。
一系列命令
实际上,您可以通过在命令行中使用多个 -Q 来在所有三个不同时间发送命令。您也可以通过使用更多的 -Q 选项在同一位置发送多个命令。
默认情况下,如果任何给定的命令从服务器返回错误,curl 会停止其操作,如果发生错误在传输开始之前,则中止传输,并且不会发送更多的自定义命令。
例如,先重命名一个文件然后进行传输:
curl -Q "RNFR original" -Q "RNTO newname" ftp://example.com/newname
可失败的命令
您可以选择发送允许失败的单独引号命令,以从服务器返回错误,而不会导致一切停止。
您可以通过在命令前加星号(*)使其成为可失败的命令。例如,在传输后发送删除(DELE)命令并允许其失败:
curl -Q "-*DELE file" ftp://example.com/moo
两个连接
FTP 使用两个 TCP 连接。第一个连接是由客户端在连接到 FTP 服务器时设置的,被称为控制连接。作为初始连接,它负责处理认证以及更改远程服务器上的正确目录等。当客户端准备好传输文件时,会建立第二个 TCP 连接,数据通过该连接传输。
建立第二个连接的原因有几个,这会导致麻烦和头痛。
主动连接
客户端可以选择要求服务器连接到客户端以进行设置,这被称为主动连接。这是通过 PORT 或 EPRT 命令完成的。允许远程主机连接回客户端打开的端口需要中间没有防火墙或其他网络设备拒绝该连接,但这并不总是情况。您可以使用curl -P [arg](也称为--ftp-port的长格式)请求一个主动传输,虽然该选项允许您指定确切要使用的地址,但将地址设置为与您连接的地址几乎总是正确的选择,您可以通过-P -这样做,如下请求文件的方式:
curl -P - ftp://example.com/foobar.txt
您还可以显式要求 curl 不使用 EPRT(这是一个比 PORT 稍微新一点的命令)通过--no-eprt命令行选项。
被动连接
Curl 默认请求一个被动连接,这意味着它会向服务器发送 PASV 或 EPSV 命令,然后服务器打开一个新的端口用于第二个连接,curl 随后连接到该端口。对于终端用户和客户端来说,向新端口发出的连接通常更容易且限制较少,但需要服务器端的网络允许这样做。
默认情况下,被动连接已被启用,但如果您之前已经开启了主动连接,您可以使用--ftp-pasv将其切换回被动连接。
您还可以显式要求 curl 不使用 EPSV(这是一个比 PASV 稍微新一点的命令)通过--no-epsv命令行选项。
有时服务器运行的是一个古怪的配置,当 curl 发出 PASV 命令且服务器响应一个 curl 需要连接的 IP 地址时,该地址是错误的,然后 curl 无法设置数据连接。对于这种(罕见)情况,您可以要求 curl 忽略 PASV 响应中提到的 IP 地址(--ftp-skip-pasv-ip),而使用与控制连接相同的 IP 地址,即使是第二个连接也是如此。
防火墙问题
使用主动或被动传输时,网络路径中现有的防火墙基本上必须对 FTP 流量进行状态检查,以确定要打开的新端口并接受第二个连接。
目录遍历
当执行 FTP 命令遍历远程文件系统时,curl 可以采取几种不同的方式来达到目标文件,即用户想要传输的文件。
multicwd
curl 可以为文件树层次结构中的每个单独目录执行一个更改目录(CWD)命令。如果完整路径是one/two/three/file.txt,那么这种方法意味着在请求传输file.txt文件之前执行三个CWD命令。因此,如果路径有很多层,这种方法会创建相当多的命令。这种方法由早期规范(RFC 1738)规定,并且是 curl 默认的行为:
curl --ftp-method multicwd ftp://example.com/one/two/three/file.txt
这就等于这个 FTP 命令/响应序列(简化版):
> CWD one
< 250 OK. Current directory is /one
> CWD two
< 250 OK. Current directory is /one/two
> CWD three
< 250 OK. Current directory is /one/two/three
> RETR file.txt
nocwd
与为每个目录部分执行一个CWD命令相反,是根本不更改目录。这种方法一次性使用整个路径请求服务器,因此速度快。偶尔服务器会对此有问题,并且它并不完全符合标准:
curl --ftp-method nocwd ftp://example.com/one/two/three/file.txt
这就等于这个 FTP 命令/响应序列(简化版):
> RETR one/two/three/file.txt
singlecwd
这位于其他两种 FTP 方法之间。它向目标目录发送一个单独的CWD命令,然后请求指定的文件:
curl --ftp-method singlecwd ftp://example.com/one/two/three/file.txt
这就等于这个 FTP 命令/响应序列(简化版):
> CWD one/two/three
< 250 OK. Current directory is /one/two/three
> RETR file.txt
FTPS
FTPS 是通过 TLS 加密的 FTP。它协商完全安全的 TLS 连接,而普通的 FTP 使用明文不安全的连接。
使用 curl 进行 FTPS 有两种方式:隐式 方式和 显式 方式。(这些术语源自 FTPS RFC)。通常,你合作的服务器会决定你可以和应该使用哪种方法。
隐式 FTPS
隐式 方式是指你在 URL 中使用 ftps://。这使得 curl 立即连接到主机并执行 TLS 握手,而不进行任何明文操作。如果 URL 中未指定端口号,curl 将使用端口号 990。这通常不是 FTPS 的做法。
显式 FTPS
进行 FTPS 的 显式 方式是继续使用 ftp:// URL,但指示 curl 使用 AUTH TLS FTP 命令将连接升级为安全连接。
你可以告诉 curl 使用 --ssl 尝试升级并继续正常操作,如果升级失败,或者你可以使用 --ssl-reqd 强制 curl 在升级失败时整个操作失败。我们强烈建议使用后者,这样你可以确保进行安全传输——如果有的话。
常见 FTPS 问题
FTPS 最常见的问题源于 FTP 协议(FTPS 依赖的协议)使用一个单独的连接设置进行数据传输。这个连接是到另一个端口,当 FTP 通过明文(非 FTPS)进行时,防火墙和网络检查器等可以确定这是正在进行的 FTP,他们可以为此新连接调整事物和规则。
当 FTP 控制通道使用 TLS 加密时,防火墙无法看到正在发生的事情,任何外部人员都无法根据此动态调整网络规则或权限。
libcurl
curl 命令行工具中的引擎是 libcurl。libcurl 同样也是今天成千上万的工具、服务和应用程序的引擎,执行它们的互联网数据传输。
C API
libcurl 是一个提供 C API 的函数库,用于编写 C 语言的应用程序。你也可以很容易地从 C++ 中使用它,只需考虑几个因素(参见 libcurl for C++ programmers)。对于其他语言,存在 绑定,它们作为 libcurl 库和特定语言对应函数之间的中间层。
传输导向
我们设计 libcurl 以传输为导向,通常不强迫用户成为协议专家,或者实际上对网络或涉及的协议了解很多。你可以用尽可能多的细节和具体信息设置一个传输,然后告诉 libcurl 执行该传输。
话虽如此,网络和协议是存在许多陷阱和特殊情况的领域,所以你对这些事情了解得越多,你对 libcurl 的选项和工作方式的理解就越深入。更不用说,这种知识在调试时非常有价值,当你遇到问题需要理解下一步该做什么时。
最基本的 libcurl 使用应用程序可能只有几行代码那么小,但当然,大多数应用程序都需要比这更多的代码。
默认简单,按需扩展
libcurl 通常默认执行简单的和基本的数据传输,如果你想要添加更高级的功能,你可以通过设置正确的选项来实现。例如,libcurl 默认不支持 HTTP cookies,但一旦你告诉它,它就支持了。
这使得 libcurl 的行为更容易预测和依赖,同时也使得维护旧行为和添加新功能更容易。只有真正请求和使用新功能的应用程序才会获得这种行为。
-
头文件
-
全局初始化
-
API 兼容性
-
–libcurl
-
多线程
-
CURLcode 返回代码
-
详细操作
-
缓存
-
性能
-
针对 C++ 程序员
头文件
你的 libcurl 使用应用程序只需要包含一个头文件:
#include <curl/curl.h>
那个文件反过来又包含了一些其他的公共头文件,但你可以假装它们不存在。(从历史的角度来看,我们最初略有不同,但经过一段时间,我们已经稳定在只使用一个用于包含的形式。)
全局初始化
在你的程序中进行任何与 libcurl 相关的操作之前,你应该使用 curl_global_init() 做一个全局的 libcurl 初始化调用。这是必要的,因为 libcurl 可能使用的某些底层库需要提前调用以正确设置和初始化。
curl_global_init() 很不幸,不是线程安全的,所以你必须确保只调用一次,并且永远不要与其他调用同时进行。它初始化全局状态,因此你应该只调用一次,一旦你的程序完全使用完 libcurl,你可以调用 curl_global_cleanup() 来释放和清理由初始化调用分配的关联全局资源。
libcurl 是构建来处理你跳过 curl_global_init() 调用的情况的,但是它是通过自己调用它来做到这一点的(如果你在开始任何实际文件传输之前没有这样做)然后它使用自己的默认值。但是请注意,即使在这种情况下,它仍然不是线程安全的,所以它可能会给你带来一些“有趣”的副作用。最好是以受控的方式自己调用 curl_global_init()。
API 兼容性
libcurl 承诺 API 稳定性,并保证您今天编写的程序在未来仍然可以工作。我们不破坏兼容性。
随着时间的推移,我们在 API 中添加了新功能、新选项和新函数,但我们不会以不兼容的方式更改行为或删除函数。
我们上一次以不兼容的方式更改 API 是在 2006 年的 7.16.0 版本,我们计划永远不再这样做。
版本号
Curl 和 libcurl 分别进行版本控制,但它们通常非常接近。
版本编号始终使用相同的系统构建:
X.Y.Z
-
X 是主版本号
-
Y 是发布号
-
Z 是补丁号
增加数字
每个新的版本中,这些 X.Y.Z 数字中的一个都会增加。增加的数字右侧的数字都重置为 0。
当进行真正重大的、世界级的变化时,主版本号 X 会增加。当进行更改或添加功能时,发布号 Y 会增加。当更改仅仅是修复错误时,补丁号 Z 会增加。
这意味着在发布 1.2.3 之后,如果进行了真正重大的改进,我们可以发布 2.0.0,如果没有那么大的更改,我们可以发布 1.3.0,如果主要是修复了错误,我们可以发布 1.2.4。
增加,即通过加 1 来增加数字,无条件地只影响一个数字(及其右侧的数字设置为 0)。1 变为 2,3 变为 4,9 变为 10,88 变为 89,99 变为 100。因此,在 1.2.9 之后是 1.2.10。在 3.99.3 之后,可能是 3.100.0。
所有原始 curl 源发布存档都是根据 libcurl 版本命名的(而不是根据前面提到的可能不同的 curl 客户端版本)。
哪个 libcurl 版本
作为一项服务,任何可能希望支持新的 libcurl 功能同时仍然能够使用旧版本构建的应用程序,所有发布版本都在 curl/curlver.h 文件中存储了 libcurl 版本,使用一个静态编号方案,可用于比较。版本号定义为:
#define LIBCURL_VERSION_NUM 0xXXYYZZ
其中 XX、YY 和 ZZ 分别是十六进制表示的主版本号、发布号和补丁号。所有三个数字字段始终使用两位数字(每个八位)表示。1.2.0 会显示为 0x010200,而版本 9.11.7 会显示为 0x090b07。
这个 6 位十六进制数在较新的版本中总是更大的数字。这使得使用大于和小于进行比较变得可行。
这个数字也作为三个单独的定义提供:LIBCURL_VERSION_MAJOR、LIBCURL_VERSION_MINOR 和 LIBCURL_VERSION_PATCH。
这些定义当然仅适用于确定刚刚构建的版本号,并不能帮助您确定三年后运行时使用的 libcurl 版本。
哪个 libcurl 版本在运行
为了确定您的应用程序当前正在使用哪个 libcurl 版本,curl_version_info() 函数可供您使用。
应用程序应使用此函数来判断是否可以进行某些操作,而不是使用编译时检查,因为动态/DLL 库可以在不依赖于应用程序的情况下进行更改。
curl_version_info() 返回一个指向包含版本号和 libcurl 运行版本中各种功能的 struct 的指针。您通过提供一个特殊的年龄计数器来调用它,这样 libcurl 就知道调用它的 libcurl 的年龄。年龄是一个名为 CURLVERSION_NOW 的宏,它是一个在整个 curl 开发过程中以不规则间隔增加的计数器。年龄数字告诉 libcurl 它可以返回哪个 struct 集合。
您可以这样调用该函数:
curl_version_info_data *version = curl_version_info( CURLVERSION_NOW );
数据随后指向一个具有以下布局的 struct:
struct {CURLversion age; /* see description below *//* when 'age' is 0 or higher, the members below also exist: */const char *version; /* human readable string */unsigned int version_num; /* numeric representation */const char *host; /* human readable string */int features; /* bitmask, see below */char *ssl_version; /* human readable string */long ssl_version_num; /* not used, always zero */const char *libz_version; /* human readable string */const char * const *protocols; /* protocols *//* when 'age' is 1 or higher, the members below also exist: */const char *ares; /* human readable string */int ares_num; /* number *//* when 'age' is 2 or higher, the member below also exists: */const char *libidn; /* human readable string *//* when 'age' is 3 or higher (7.16.1 or later), the members below alsoexist */int iconv_ver_num; /* '_libiconv_version' if iconv enabled */const char *libssh_version; /* human readable string *//* when 'age' is 4 or higher, the member below also exists: */unsigned int brotli_ver_num; /* Numeric Brotli version(MAJOR << 24) | (MINOR << 12) | PATCH */const char *brotli_version; /* human readable string. *//* when 'age' is 5 or higher, the member below also exists: */unsigned int nghttp2_ver_num; /* Numeric nghttp2 version(MAJOR << 16) | (MINOR << 8) | PATCH */const char *nghttp2_version; /* human readable string. */const char *quic_version; /* human readable quic (+ HTTP/3) library +version or NULL *//* when 'age' is 6 or higher, the member below also exists: */const char *cainfo; /* built-in default CURLOPT_CAINFO, mightbe NULL */const char *capath; /* built-in default CURLOPT_CAPATH, mightbe NULL *//* when 'age' is 7 or higher, the member below also exists: */unsigned int zstd_ver_num; /* Numeric Zstd version(MAJOR << 24) | (MINOR << 12) | PATCH */const char *zstd_version; /* human readable string. *//* when 'age' is 8 or higher, the member below also exists: */const char *hyper_version; /* human readable string. */} curl_version_info_data;
–libcurl
我们积极鼓励用户首先尝试使用 curl 命令行工具执行他们想要的传输,一旦它大致按照你的期望工作,你就在命令行中添加 --libcurl [filename] 选项并再次运行它。
--libcurl 命令行选项会在提供的文件名中创建一个 C 程序。这个 C 程序是一个使用 libcurl 来执行你刚刚用 curl 命令行工具完成的传输的应用程序。有一些例外,并不总是完全匹配,但你可能会发现它可以作为一个极好的灵感来源,了解你想要或可以使用哪些 libcurl 选项,以及需要为它们提供哪些附加参数。
如果你将文件名指定为单个短横线,如 --libcurl -,则程序将被写入 stdout 而不是文件。
例如,我们运行一个命令来获取 http://example.com:
curl http://example.com --libcurl example.c
这将在当前目录中创建 example.c 文件,看起来类似于这样:
/********* Sample code generated by the curl command-line tool *********** All curl_easy_setopt() options are documented at:* https://curl.se/libcurl/c/curl_easy_setopt.html************************************************************************/
#include <curl/curl.h>int main(int argc, char *argv[])
{CURLcode ret;CURL *hnd;hnd = curl_easy_init();curl_easy_setopt(hnd, CURLOPT_URL, "http://example.com");curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L);curl_easy_setopt(hnd, CURLOPT_USERAGENT, "curl/7.45.0");curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L);curl_easy_setopt(hnd, CURLOPT_SSH_KNOWNHOSTS,"/home/daniel/.ssh/known_hosts");curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L);/* Here is a list of options the curl code used that cannot getgenerated as source easily. You may select to either not use them orimplement them yourself.CURLOPT_WRITEDATA set to a objectpointerCURLOPT_WRITEFUNCTION set to a functionpointerCURLOPT_READDATA set to a objectpointerCURLOPT_READFUNCTION set to a functionpointerCURLOPT_SEEKDATA set to a objectpointerCURLOPT_SEEKFUNCTION set to a functionpointerCURLOPT_ERRORBUFFER set to a objectpointerCURLOPT_STDERR set to a objectpointerCURLOPT_HEADERFUNCTION set to a functionpointerCURLOPT_HEADERDATA set to a objectpointer*/ret = curl_easy_perform(hnd);curl_easy_cleanup(hnd);hnd = NULL;return (int)ret;
}
/**** End of sample code ****/
多线程
libcurl 是线程安全的,但没有内部线程同步。你可能需要提供自己的锁定机制或更改选项以正确使用 libcurl 的多线程功能。具体需要什么取决于 libcurl 的构建方式。请参阅 libcurl 线程安全 页面,其中包含最新信息。
CURLcode 返回代码
许多 libcurl 函数返回一个 CURLcode。这是一个特殊的 libcurl 类型定义变量,用于错误代码。如果没有问题,它返回CURLE_OK(其值为零),如果检测到问题,则返回非零数字。目前有近一百个CURLcode错误在使用中,您可以在curl/curl.h头文件中找到它们,并在 libcurl-errors 手册页中进行了文档化。
您可以使用curl_easy_strerror()函数将 CURLcode 转换为人类可读的字符串——但请注意,这些错误很少以适合在 UI 中公开或向最终用户展示的方式措辞:
const char *str = curl_easy_strerror( error );
printf("libcurl said %s\n", str);
在出现错误的情况下,获取稍微更好的错误文本的另一种方法是设置CURLOPT_ERRORBUFFER选项来指向程序中的一个缓冲区,然后 libcurl 在返回错误之前将相关错误消息存储在那里:
char error[CURL_ERROR_SIZE]; /* needs to be at least this big */
CURLcode ret = curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error);
详细操作
好的,我们刚刚展示了如何将错误作为可读文本获取,这对于确定特定传输中出了什么问题非常有帮助,通常解释了为什么可以这样做或目前的问题是什么。
当编写 libcurl 应用程序时,每个人都需要了解并广泛使用的一个救星,至少在开发 libcurl 应用程序或调试 libcurl 本身时,是启用详细模式CURLOPT_VERBOSE:
CURLcode ret = curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L);
当 libcurl 被要求详细输出时,它在传输进行时将传输相关的细节和信息输出到 stderr。这对于找出为什么失败以及了解当您要求它做不同的事情时 libcurl 到底做了什么非常有用。您可以通过更改 stderr 使用CURLOPT_STDERR将输出重定向到其他地方,或者您可以通过调试回调(在后面的部分中进一步解释)以更花哨的方式获取更多信息。
跟踪一切
详细模式当然很好,但有时您需要更多。libcurl 还提供了一个跟踪回调,除了显示详细模式所做的所有内容外,它还传递发送和接收的所有数据,以便您的应用程序获得完整的跟踪。
传递给跟踪回调的发送和接收数据以未加密的形式提供给回调,这在处理基于 TLS 或 SSH 的协议时很有用,当从网络上捕获数据以进行调试不切实际时。
当您设置CURLOPT_DEBUGFUNCTION选项时,您仍然需要启用CURLOPT_VERBOSE,但将跟踪回调设置为 libcurl 使用该回调而不是其内部处理。
跟踪回调应与以下原型匹配:
int my_trace(CURL *handle, curl_infotype type, char *data, size_t size,void *user);
handle是相关的简单句柄,type描述传递给回调的特定数据(数据输入/输出、头部输入/输出、TLS 数据输入/输出和文本),data是一个指向数据的指针,该数据是size字节数。user是您使用CURLOPT_DEBUGDATA设置的定制指针。
data指向的数据不是 null 终止的,但正好与size参数告诉的大小相同。
回调必须返回 0,否则 libcurl 将其视为错误并终止传输。
在 curl 网站上,我们托管了一个名为debug.c的示例,其中包含一个简单的跟踪函数以供参考。
在CURLOPT_DEBUGFUNCTION 手册页中还有额外的详细信息。
传输和连接标识符
尽管您的应用程序可能让 libcurl 使用大量单独的连接和不同的传输,但跟踪信息流传递给调试回调时是一个连续的流,有时您想查看各种信息属于哪些特定的传输或连接。为了更好地理解跟踪输出。
您可以在回调内部获取传输和连接标识符:
curl_off_t conn_id;
curl_off_t xfer_id;
res = curl_easy_getinfo(curl, CURLINFO_CONN_ID, &conn_id);
res = curl_easy_getinfo(curl, CURLINFO_XFER_ID, &xfer_id);
它们是两个独立的标识符,因为连接可以被重用,多个传输可以使用相同的连接。使用这些标识符(实际上是数字),你可以看到哪些日志与哪些传输和连接相关联。
跟踪更多
如果传递给调试回调函数的默认跟踪数据量不足。例如,当你怀疑并想要在更基础的底层协议级别调试问题时,libcurl 为你提供了 curl_global_trace() 函数。
使用此函数,你告诉 libcurl 也包括关于它默认不包含的组件的详细日志。例如,关于 TLS、HTTP/2 或 HTTP/3 协议的详细信息。
curl_global_trace() 函数接受一个参数,其中你指定一个字符串,包含你想要它跟踪的区域以逗号分隔的列表。例如,包括 TLS 和 HTTP/2 的详细信息:
/* log details of HTTP/2 and SSL handling */
curl_global_trace("http/2,ssl");
具体的选项集可能会有所不同,但这里有一些可以尝试的选项:
| area | 描述 |
|---|---|
all |
显示所有可能的内容 |
tls |
TLS 协议交换细节 |
http/2 |
HTTP/2 帧信息 |
http/3 |
HTTP/3 帧信息 |
* |
未来版本中的附加选项 |
使用 all 快速运行通常是一个很好的方法来查看哪些特定区域被显示,因为这样你可以进行后续的运行,并设置更具体的区域。
缓存
libcurl 缓存不同的信息,以帮助后续传输更快地执行。有四个关键缓存:DNS、连接、TLS 会话和 CA 证书。
当使用多接口时,这些缓存默认情况下会共享给添加到单个多接口的所有 easy handles,而当使用 easy 接口时,它们将保留在该 handle 中。
您可以指示 libcurl 与共享接口共享一些缓存。
DNS 缓存
当 libcurl 将主机名解析为一个或多个 IP 地址时,这些地址会存储在 DNS 缓存中,以便在近期内的后续传输中无需再次进行相同的解析。名称解析可能需要几百毫秒,有时甚至更长。
默认情况下,每个此类主机名都会在缓存中存储 60 秒(可以通过CURLOPT_DNS_CACHE_TIMEOUT进行更改)。
实际上,libcurl 通常不知道 DNS 条目的 TTL(生存时间)值,因为通常在它用于此目的的系统功能调用中不会暴露该值,因此增加此值会带来风险,即 libcurl 可能比必要的更长的时间继续使用过时的地址。
连接缓存
也被称为连接池。这是一个之前使用的连接集合,而不是在使用后关闭,而是保持活跃状态,以便后续针对相同主机名且具有其他几个匹配的检查的传输可以使用它们,而不是创建新的连接。
重新使用的连接通常可以节省进行 DNS 查找、设置 TCP 连接、执行 TLS 握手等操作。
只有当名称相同的情况下才会重用连接。即使两个不同的主机名解析到相同的 IP 地址,它们仍然始终使用 libcurl 的两个单独的连接。
由于连接重用基于主机名,并且当重用连接进行传输时,DNS 解析阶段完全跳过,因此 libcurl 不知道主机名在 DNS 中的当前状态,因为实际上 IP 地址可能会随时间变化,而连接可能会在原始 IP 地址上继续被重用。
连接缓存的大小——要保留的活跃连接数量——可以通过CURLOPT_MAXCONNECTS(默认为 5)为 easy handles 设置,对于多 handles 使用CURLMOPT_MAXCONNECTS。多 handles 的默认大小是 easy handles 数量的 4 倍。
TLS 会话缓存
TLS 会话 ID 和票据是特殊的 TLS 机制,客户端可以将它们传递给服务器,以简化与之前已建立连接的服务器之间的后续 TLS 握手。
libcurl 缓存与主机名和端口号相关的会话 ID 和票据,因此如果后续连接尝试连接到 libcurl 已缓存的 ID 或票据的主机,使用它们可以大大减少 TLS 握手过程,从而减少完成所需的时间。
CA 证书缓存
对于 curl 支持的某些 TLS 后端(OpenSSL 和 Schannel),它会在内存中构建 CA 证书存储缓存,并在后续传输中使用它。这使得传输可以跳过加载和处理有时相当大的 CA 证书包所带来的不必要的加载和解析时间。
由于 CA 证书包可能会更新,缓存的生命周期默认设置为 24 小时,这样长时间运行的应用程序至少每天刷新缓存并重新加载文件一次——以便能够加载和使用存储的新版本。
如果默认设置不够好,应用程序可以通过CURLOPT_CA_CACHE_TIMEOUT选项更改 CA 证书缓存超时时间。
性能
本节收集了关于作为应用程序作者你可以做什么以从 libcurl 中获得最大性能的一般建议。
libcurl 默认设计为尽可能快地运行。你期望在不做任何额外操作的情况下就能获得最佳性能。然而,有一些常见的事情需要注意或者可能需要避免的错误。
重复使用句柄
这是在讨论 libcurl 时的一个通用箴言。如果你使用简单接口,那么提高性能的主要关键是后续传输时重用句柄。这允许 libcurl 重用连接、重用 TLS 会话、尽可能多地使用其 DNS 缓存等等。
缓冲区大小
如果你下载数据,将CURLOPT_BUFFERSIZE设置为合适的大小。它从开始就相对较小,尤其是在高速传输中,你可能通过增加其大小从 libcurl 中获得更多。我们鼓励你在基准测试中尝试几个大小以适应你的用例。
同样,如果你上传数据,你可能需要出于相同的原因调整CURLOPT_UPLOAD_BUFFERSIZE。
池大小
你通过CURLOPT_MAXCONNECTS设置的连接池中保持的活动连接数可能值得调整。当然,这取决于你的应用程序如何使用连接,但如果它例如在短时间内迭代 N 个域名,确保 libcurl 可以保持所有这些连接活跃可能是有意义的。
尽可能使回调尽可能快
在高速数据下载中,写入回调会被多次调用。如果这个函数没有以尽可能快的方式编写,那么这个函数本身可能会使所有传输比它们本应慢。
当然,对于上传的读取回调也是如此。
避免在 libcurl 回调中执行复杂的逻辑或使用锁/互斥锁。
共享数据
如果你使用多个简单句柄,你仍然可以在它们之间共享数据和缓存以提高性能。更详细地了解共享 API。
线程
如果你的传输线程最终消耗了 100%的 CPU,那么你可能从将负载分配到多个线程以增加带宽中受益。
通常情况下,你希望每个线程尽可能独立地进行传输,以避免它们相互干扰性能或因共享句柄而出现线程安全的问题。尽量让相同的域名在同一个线程上传输,以便可以优化连接重用。
curl_multi_socket_action
如果你的应用程序执行许多并行传输,比如超过一百个并发传输,那么你必须考虑切换到curl_multi_socket_action()和基于事件的 API,而不是“常规”的多线程 API。这允许并鼓励你使用基于事件的策略,让你的应用程序避免使用poll()和select(),这对于高性能和高并发度至关重要。
对于 C++ 程序员
libcurl 提供了一个 C API。C 和 C++ 类似但并不相同。在使用 libcurl 进行 C++ 开发时,有一些事情需要记住。
字符串是 C 字符串,而不是 C++ 字符串对象
当你将字符串传递给接受 char * 的 libcurl API 时,这意味着你不能将这些函数传递 C++ 字符串或对象。
例如,如果你使用 C++ 构建一个字符串,然后希望该字符串用作 URL:
std::string url = "https://example.com/foo.asp?name=" + i;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
回调注意事项
由于 libcurl 是一个 C 库,它对 C++ 成员函数或对象一无所知。你可以通过使用静态成员函数并传递指向类的指针来相对容易地克服这个限制。
这里有一个使用 C++ 方法作为回调的写回调示例:
// f is the pointer to your object.
static size_t YourClass::func(void *buffer, size_t sz, size_t n, void *f)
{// Call non-static member function.static_cast<YourClass*>(f)->nonStaticFunction();
}// This is how you pass pointer to the static function:
curl_easy_setopt(hcurl, CURLOPT_WRITEFUNCTION, YourClass::func);
curl_easy_setopt(hcurl, CURLOPT_WRITEDATA, this);
libcurl 传输
在本章中,我们将介绍如何使用 libcurl 进行互联网传输的步骤。核心功能。
-
易于处理
-
curl 简单选项
-
驱动传输
-
回调函数
-
连接控制
-
传输控制
-
清理
-
传输后信息
简单句柄
你需要使用 libcurl 学习的 fundamentals:
首先,你创建一个“简单句柄”,这实际上是你的传输句柄:
CURL *easy_handle = curl_easy_init();
然后,你在这个句柄上设置选项以控制即将进行的传输。此示例设置了 URL:
/* set URL to operate on */
res = curl_easy_setopt(easy_handle, CURLOPT_URL, "http://example.com/");
如果curl_easy_setopt()返回CURLE_OK,我们知道它已正确存储了选项。
创建简单句柄并在其上设置选项并不会触发任何传输,通常也不会导致 libcurl 做更多的事情,除了存储你希望在传输实际发生时使用的愿望。很多语法检查和输入验证也可以推迟,所以即使curl_easy_setopt没有抱怨,并不意味着输入是正确和有效的;你可能会在之后得到一个错误返回。
在其单独的章节中了解更多关于简单选项的信息。
当你完成对简单句柄的选项设置后,你可以启动实际的传输。
实际执行传输可以使用不同的方法和函数调用,这取决于你希望在应用程序中实现的行为以及 libcurl 如何最佳地集成到你的架构中。这些内容将在本章后面进一步描述。
在传输进行过程中,libcurl 会调用你指定的函数——称为回调函数——来传递数据、读取数据以及执行各种操作。
传输完成后,你可以确定它是否成功,并可以从简单句柄中提取 libcurl 在传输期间收集的统计信息和其它信息。请参阅传输后信息。
重复使用
简单句柄旨在并设计为可重复使用。当你使用简单句柄完成一次传输后,你可以立即再次使用它进行下一次传输。这样做可以带来很多好处。
所有选项都是“粘性的”。如果你使用同一个句柄进行第二次传输,将使用相同的选项。它们将保留在句柄中,直到你再次更改它们或调用句柄上的curl_easy_reset()。
重置
通过调用curl_easy_reset(),给定简单句柄的所有选项都会重置并恢复到其默认值。这些值与句柄最初创建时选项的值相同。缓存保持完整。
复制
使用curl_easy_duphandle()可以复制一个包含所有当前设置选项的简单句柄。它返回传递给它的句柄的副本。
缓存和其他状态信息不会被传递。
curl 简单选项
你在 easy 处理句柄中设置选项来控制如何执行该传输,或者在某些情况下,你实际上可以在传输进行中设置选项并修改传输的行为。你使用 curl_easy_setopt() 来设置选项,并提供句柄、你想要设置的选项以及选项的参数。所有选项都恰好接受一个参数,并且你必须始终向 curl_easy_setopt() 调用传递恰好三个参数。
由于 curl_easy_setopt() 调用接受数百种不同的选项,并且各种选项接受各种不同类型的参数,因此了解具体细节并准确提供特定选项支持的参数类型非常重要。传递错误类型的参数可能导致意外的副作用或难以理解的故障。
每个传输可能最重要的选项是 URL。libcurl 无法在没有知道它涉及哪个 URL 的情况下执行传输,因此你必须告诉它。URL 选项名称是 CURLOPT_URL,因为所有选项都以 CURLOPT_ 为前缀,然后是描述性的名称——全部使用大写字母。一个设置 URL 以获取 http://example.com HTTP 内容的示例行可能如下所示:
CURLcode ret = curl_easy_setopt(easy, CURLOPT_URL, "http://example.com");
再次强调:这仅设置句柄中的选项。它并不执行实际的传输或任何其他操作。它只是告诉 libcurl 复制给定的字符串,如果成功则返回 OK。
当然,检查返回码以确认没有出错是良好的做法。
获取选项
无法提取之前使用 curl_easy_setopt() 设置的值。如果你需要能够再次提取你之前设置的信息,我们鼓励你在你的应用程序中自行跟踪这些数据。
-
设置数值选项
-
设置字符串选项
-
TLS 选项
-
所有选项
-
获取选项信息
设置数值选项
由于 curl_easy_setopt() 是一个 vararg 函数,其中第 3 个参数可以根据情况使用不同类型,因此不能进行常规的 C 语言类型转换。您必须确保如果文档中这样说明,您确实传递了一个 long 而不是一个 int。在它们大小相同的架构上,您可能不会遇到任何问题,但并非所有的工作方式都如此。同样,对于接受 curl_off_t 类型的选项,您使用该类型传递参数是至关重要的,而不是其他任何类型。
强制使用 long:
curl_easy_setopt(handle, CURLOPT_TIMEOUT, 5L); /* 5 seconds timeout */
强制使用 curl_off_t:
curl_off_t no_larger_than = 0x50000;
curl_easy_setopt(handle, CURLOPT_MAXFILESIZE_LARGE, no_larger_than);
设置字符串选项
目前curl_easy_setopt()函数有超过 80 个选项接受字符串作为其第三个参数。
当在句柄中设置字符串时,libcurl 会立即复制该数据,这样应用程序就不必在传输过程中保留数据 - 有一个显著的例外:CURLOPT_POSTFIELDS。
在句柄中设置 URL:
curl_easy_setopt(handle, CURLOPT_URL, "https://example.com");
CURLOPT_POSTFIELDS
不同于 libcurl 总是复制数据的规则,CURLOPT_POSTFIELDS只存储数据的指针,这意味着使用此选项的应用程序必须在整个传输过程中保留内存。
如果这成问题,一个替代方案是使用CURLOPT_COPYPOSTFIELDS来复制数据。如果数据是二进制且不以第一个 null 字节结束,确保在使用此选项之前设置CURLOPT_POSTFIELDSIZE。
为什么?
CURLOPT_POSTFIELDS是一个例外的原因是历史遗留问题。最初(在 curl 7.17.0 之前),libcurl 不复制任何字符串参数,当引入当前行为时,此选项无法在不破坏行为的情况下转换,因此它必须像以前一样继续工作。现在这显得有些突出,因为其他选项都没有这样做...
C++
如果你从 C++程序中使用 libcurl,重要的是要记住你不能传递 libcurl 期望的字符串对象。它必须是一个以 null 结尾的 C 字符串。通常你可以使用c_str()方法来实现这一点。
例如,将 URL 保存在字符串对象中,并在句柄中设置该对象:
std::string url("https://example.com/");
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
TLS 选项
在撰写本文时,curl_easy_setopt 至少有四十个不同的选项是专门用于控制 libcurl 如何进行 SSL 和 TLS。
使用 TLS 进行的传输使用安全默认值,但由于 curl 被用于许多不同的场景和配置中,您可能会遇到想要更改这些行为的情况。
协议版本
使用CURLOPT_SSLVERSION和CURLOPT_PROXY_SSLVERSION,您可以指定您可接受的 SSL 或 TLS 协议范围。传统上,SSL 和 TLS 协议版本随着时间的推移被发现检测不合适,即使 curl 本身随着时间的推移提高了默认的较低版本,您可能只想使用最新和最安全的协议版本。
这些选项接受最低可接受版本和可选的最高版本。如果服务器无法根据这些条件协商连接,传输将失败。
示例:
curl_easy_setopt(easy, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
协议细节和行为
您可以通过设置CURLOPT_SSL_CIPHER_LIST和CURLOPT_PROXY_SSL_CIPHER_LIST来选择要使用的加密套件。
您可以使用CURLOPT_SSL_FALSESTART请求启用 SSL“False Start”,并且还有其他一些行为变化可以通过CURLOPT_SSL_OPTIONS进行调整。
验证
使用 TLS 的客户端需要验证它与服务器通信的是正确且受信任的。这是通过验证服务器的证书是否由 curl 具有公钥的证书机构(CA)签名,并且证书包含服务器的名称来完成的。任何这些检查失败都会导致传输失败。
为了开发目的和实验,curl 允许应用程序关闭服务器或 HTTPS 代理的任一或两个检查。
-
CURLOPT_SSL_VERIFYPEER控制检查证书是否由受信任的 CA 签名。 -
CURLOPT_SSL_VERIFYHOST控制对证书中名称的检查。 -
CURLOPT_PROXY_SSL_VERIFYPEER是CURLOPT_SSL_VERIFYPEER的代理版本。 -
CURLOPT_PROXY_SSL_VERIFYHOST是CURLOPT_SSL_VERIFYHOST的代理版本。
可选地,您可以通过使用CURLOPT_PINNEDPUBLICKEY或CURLOPT_PROXY_PINNEDPUBLICKEY告诉 curl 验证证书的公钥与已知散列相匹配。在这里,不匹配也会导致传输失败。
认证
TLS 客户端证书
当使用 TLS 并且服务器要求客户端使用证书进行认证时,您通常使用CURLOPT_SSLKEY和CURLOPT_SSLCERT指定私钥和相应的客户端证书。通常还需要设置密钥的密码,使用CURLOPT_SSLKEYPASSWD。
再次强调,对于连接到 HTTPS 代理的选项,存在一组独立的选项:CURLOPT_PROXY_SSLKEY、CURLOPT_PROXY_SSLCERT等。
TLS 认证
TLS 连接提供了一个(很少使用)称为安全远程密码的功能。使用此功能,您可以使用名称和密码对服务器连接进行认证,选项称为CURLOPT_TLSAUTH_USERNAME和CURLOPT_TLSAUTH_PASSWORD。
STARTTLS
对于使用 STARTTLS 方法升级连接到 TLS 的协议(如 FTP、IMAP、POP3 和 SMTP),你通常在指定 URL 时告诉 curl 使用该协议的非 TLS 版本,然后请求 curl 使用CURLOPT_USE_SSL选项启用 TLS。
此选项允许客户端在无法升级到 TLS 时让 curl 继续执行,但这并不是推荐的做法,因为这样你可能会在不经意间使用一个不安全的协议。
/* require use of SSL for this, or fail */
curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
所有选项
这是curl_easy_setopt()可用的选项表。
| 选项 | 目的 |
|---|---|
CURLOPT_ABSTRACT_UNIX_SOCKET |
抽象 Unix 域套接字 |
CURLOPT_ACCEPT_ENCODING |
自动解压缩 HTTP 下载 |
CURLOPT_ACCEPTTIMEOUT_MS |
等待 FTP 服务器回连的超时时间 |
CURLOPT_ADDRESS_SCOPE |
IPv6 地址的作用域 ID |
CURLOPT_ALTSVC_CTRL |
控制 alt-svc 行为 |
CURLOPT_ALTSVC |
alt-svc 缓存文件名 |
CURLOPT_APPEND |
追加到远程文件 |
CURLOPT_AUTOREFERER |
自动更新引用头 |
CURLOPT_AWS_SIGV4 |
V4 签名 |
CURLOPT_BUFFERSIZE |
接收缓冲区大小 |
CURLOPT_CA_CACHE_TIMEOUT |
缓存证书存储的生命周期 |
CURLOPT_CAINFO_BLOB |
PEM 格式的证书颁发机构(CA)包 |
CURLOPT_CAINFO |
证书颁发机构(CA)包的路径 |
CURLOPT_CAPATH |
存放 CA 证书的目录 |
CURLOPT_CERTINFO |
请求 SSL 证书信息 |
CURLOPT_CHUNK_BGN_FUNCTION |
FTP 通配符匹配传输前的回调 |
CURLOPT_CHUNK_DATA |
传递给 FTP 块回调的指针 |
CURLOPT_CHUNK_END_FUNCTION |
FTP 通配符匹配传输后的回调 |
CURLOPT_CLOSESOCKETDATA |
传递给套接字关闭回调的指针 |
CURLOPT_CLOSESOCKETFUNCTION |
替换套接字关闭的回调 |
CURLOPT_CONNECT_ONLY |
连接到目标服务器后停止 |
CURLOPT_CONNECT_TO |
连接到指定的主机和端口,而不是 URL 的主机和端口 |
CURLOPT_CONNECTTIMEOUT_MS |
连接阶段的超时时间 |
CURLOPT_CONNECTTIMEOUT |
连接阶段超时时间 |
CURLOPT_CONV_FROM_NETWORK_FUNCTION |
将数据从网络编码转换为主机编码 |
CURLOPT_CONV_FROM_UTF8_FUNCTION |
将数据从 UTF8 转换为主机编码 |
CURLOPT_CONV_TO_NETWORK_FUNCTION |
将数据从主机编码转换为网络编码 |
CURLOPT_COOKIE |
HTTP Cookie 头 |
CURLOPT_COOKIEFILE |
从中读取 cookie 的文件名 |
CURLOPT_COOKIEJAR |
存储 cookie 的文件名 |
CURLOPT_COOKIELIST |
添加或操作内存中持有的 cookie |
CURLOPT_COOKIESESSION |
开始一个新的 cookie 会话 |
CURLOPT_COPYPOSTFIELDS |
让 libcurl 将数据复制到 POST |
CURLOPT_CRLF |
CRLF 转换 |
CURLOPT_CRLFILE |
证书吊销列表文件 |
CURLOPT_CURLU |
CURLU *格式的 URL |
CURLOPT_CUSTOMREQUEST |
自定义请求方法 |
CURLOPT_DEBUGDATA |
传递给调试回调的指针 |
CURLOPT_DEBUGFUNCTION |
调试回调函数 |
CURLOPT_DEFAULT_PROTOCOL |
如果 URL 缺少协议,则使用默认协议 |
CURLOPT_DIRLISTONLY |
仅请求目录列表中的名称 |
CURLOPT_DISALLOW_USERNAME_IN_URL |
禁止在 URL 中指定用户名 |
CURLOPT_DNS_CACHE_TIMEOUT |
DNS 缓存条目的生命周期 |
CURLOPT_DNS_INTERFACE |
在其上说话 DNS 的接口 |
CURLOPT_DNS_LOCAL_IP4 |
绑定 DNS 解析到的 IPv4 地址 |
CURLOPT_DNS_LOCAL_IP6 |
绑定 DNS 解析到的 IPv6 地址 |
CURLOPT_DNS_SERVERS |
要使用的 DNS 服务器 |
CURLOPT_DNS_SHUFFLE_ADDRESSES |
打乱主机名的 IP 地址 |
CURLOPT_DNS_USE_GLOBAL_CACHE |
全局 DNS 缓存 |
CURLOPT_DOH_SSL_VERIFYHOST |
验证 DoH SSL 证书中的主机名 |
CURLOPT_DOH_SSL_VERIFYPEER |
验证 DoH SSL 证书 |
CURLOPT_DOH_SSL_VERIFYSTATUS |
验证 DoH SSL 证书的状态 |
CURLOPT_DOH_URL |
提供 DNS-over-HTTPS URL |
CURLOPT_EGDSOCKET |
EGD 套接字路径 |
CURLOPT_ERRORBUFFER |
错误消息的错误缓冲区 |
CURLOPT_EXPECT_100_TIMEOUT_MS |
Expect: 100-continue 响应的超时时间 |
CURLOPT_FAILONERROR |
HTTP 响应 >= 400 时请求失败 |
CURLOPT_FILETIME |
获取远程资源的修改时间 |
CURLOPT_FNMATCH_DATA |
传递给 fnmatch 回调的指针 |
CURLOPT_FNMATCH_FUNCTION |
通配符匹配回调 |
CURLOPT_FOLLOWLOCATION |
跟随 HTTP 3xx 重定向 |
CURLOPT_FORBID_REUSE |
使用后立即关闭连接 |
CURLOPT_FRESH_CONNECT |
强制使用新的连接 |
CURLOPT_FTP_ACCOUNT |
FTP 的账户信息 |
CURLOPT_FTP_ALTERNATIVE_TO_USER |
用于 FTP 的替代 USER 命令 |
CURLOPT_FTP_CREATE_MISSING_DIRS |
为 FTP 和 SFTP 创建缺失的目录 |
CURLOPT_FTP_FILEMETHOD |
选择 FTP 目录遍历方法 |
CURLOPT_FTP_RESPONSE_TIMEOUT |
等待 FTP 响应的时间 |
CURLOPT_FTP_SKIP_PASV_IP |
忽略 PASV 响应中的 IP 地址 |
CURLOPT_FTP_SSL_CCC |
在 FTP 身份验证后再次关闭 SSL |
CURLOPT_FTP_USE_EPRT |
为 FTP 使用 EPRT |
CURLOPT_FTP_USE_EPSV |
为 FTP 使用 EPSV |
CURLOPT_FTP_USE_PRET |
为 FTP 使用 PRET |
CURLOPT_FTPPORT |
使 FTP 传输处于活动状态 |
CURLOPT_FTPSSLAUTH |
尝试 TLS 与 SSL 的顺序 |
CURLOPT_GSSAPI_DELEGATION |
允许 GSS-API 委派 |
CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS |
为 happy eyeballs 的 ipv6 提供提前启动 |
CURLOPT_HAPROXY_CLIENT_IP |
设置 HAProxy PROXY 协议客户端 IP |
CURLOPT_HAPROXYPROTOCOL |
发送 HAProxy PROXY 协议 v1 头部 |
CURLOPT_HEADER |
将头部传递到数据流 |
CURLOPT_HEADERDATA |
传递给头部回调的指针 |
CURLOPT_HEADERFUNCTION |
接收头部数据的回调 |
CURLOPT_HEADEROPT |
将 HTTP 头部发送到代理和主机或分别发送 |
CURLOPT_HSTS_CTRL |
控制 HSTS 行为 |
CURLOPT_HSTS |
HSTS 缓存文件名 |
CURLOPT_HSTSREADDATA |
传递给 HSTS 读取回调的指针 |
CURLOPT_HSTSREADFUNCTION |
HSTS 主机的读取回调 |
CURLOPT_HSTSWRITEDATA |
传递给 HSTS 写入回调的指针 |
CURLOPT_HSTSWRITEFUNCTION |
HSTS 主机的写入回调 |
CURLOPT_HTTP09_ALLOWED |
允许 HTTP/0.9 响应 |
CURLOPT_HTTP200ALIASES |
HTTP 200 OK 的替代匹配 |
CURLOPT_HTTP_CONTENT_DECODING |
HTTP 内容解码控制 |
CURLOPT_HTTP_TRANSFER_DECODING |
HTTP 传输解码控制 |
CURLOPT_HTTP_VERSION |
要使用的 HTTP 协议版本 |
CURLOPT_HTTPAUTH |
尝试的 HTTP 服务器认证方法 |
CURLOPT_HTTPGET |
请求 HTTP GET 请求 |
CURLOPT_HTTPHEADER |
HTTP 头集合 |
CURLOPT_HTTPPOST |
表单 post 内容的多部分 |
CURLOPT_HTTPPROXYTUNNEL |
通过 HTTP 代理隧道 |
CURLOPT_IGNORE_CONTENT_LENGTH |
忽略内容长度 |
CURLOPT_INFILESIZE_LARGE |
要发送的输入文件的大小 |
CURLOPT_INFILESIZE |
要发送的输入文件的大小 |
CURLOPT_INTERFACE |
出站流量的源接口 |
CURLOPT_INTERLEAVEDATA |
传递给 RTSP 交错回调的指针 |
CURLOPT_INTERLEAVEFUNCTION |
RTSP 交错数据的回调 |
CURLOPT_IOCTLDATA |
指针传递给 I/O 回调 |
CURLOPT_IOCTLFUNCTION |
I/O 操作的回调 |
CURLOPT_IPRESOLVE |
要使用的 IP 协议版本 |
CURLOPT_ISSUERCERT_BLOB |
从内存 blob 中获取的发行者 SSL 证书 |
CURLOPT_ISSUERCERT |
发行者 SSL 证书文件名 |
CURLOPT_KEEP_SENDING_ON_ERROR |
在早期 HTTP 响应 >= 300 时继续发送 |
CURLOPT_KEYPASSWD |
私钥的密码短语 |
CURLOPT_KRBLEVEL |
FTP Kerberos 安全级别 |
CURLOPT_LOCALPORT |
用于套接字的本地端口号 |
CURLOPT_LOCALPORTRANGE |
尝试的附加本地端口号数量 |
CURLOPT_LOGIN_OPTIONS |
登录选项 |
CURLOPT_LOW_SPEED_LIMIT |
每秒字节数的低速限制 |
CURLOPT_LOW_SPEED_TIME |
低速限制时间周期 |
CURLOPT_MAIL_AUTH |
SMTP 认证地址 |
CURLOPT_MAIL_FROM |
SMTP 发件人地址 |
CURLOPT_MAIL_RCPT_ALLOWFAILS |
允许 RCPT TO 命令对某些收件人失败 |
CURLOPT_MAIL_RCPT |
SMTP 邮件收件人列表 |
CURLOPT_MAX_RECV_SPEED_LARGE |
数据下载速度限制率 |
CURLOPT_MAX_SEND_SPEED_LARGE |
数据上传速度限制率 |
CURLOPT_MAXAGE_CONN |
允许重用连接的最大空闲时间 |
CURLOPT_MAXCONNECTS |
最大连接缓存大小 |
CURLOPT_MAXFILESIZE_LARGE |
允许下载的最大文件大小 |
CURLOPT_MAXFILESIZE |
允许下载的最大文件大小 |
CURLOPT_MAXLIFETIME_CONN |
允许重用连接的最大生命周期(自创建以来) |
CURLOPT_MAXREDIRS |
允许的最大重定向次数 |
CURLOPT_MIME_OPTIONS |
设置 MIME 选项标志 |
CURLOPT_MIMEPOST |
从 MIME 结构发送数据 |
CURLOPT_NETRC_FILE |
从中读取 .netrc 信息的文件名 |
CURLOPT_NETRC |
启用 .netrc 的使用 |
CURLOPT_NEW_DIRECTORY_PERMS |
远程创建目录的权限 |
CURLOPT_NEW_FILE_PERMS |
远程创建文件的权限 |
CURLOPT_NOBODY |
执行下载请求而不获取主体 |
CURLOPT_NOPROGRESS |
关闭进度表 |
CURLOPT_NOPROXY |
禁用特定主机的代理使用 |
CURLOPT_NOSIGNAL |
跳过所有信号处理 |
CURLOPT_OPENSOCKETDATA |
传递给打开套接字回调的指针 |
CURLOPT_OPENSOCKETFUNCTION |
打开套接字的回调 |
CURLOPT_PASSWORD |
用于认证的密码 |
CURLOPT_PATH_AS_IS |
不处理点号序列 |
CURLOPT_PINNEDPUBLICKEY |
固定公钥 |
CURLOPT_PIPEWAIT |
等待管道/多路复用 |
CURLOPT_PORT |
要连接的远程端口号 |
CURLOPT_POST |
执行 HTTP POST |
CURLOPT_POSTFIELDS |
要发送到服务器的数据 |
CURLOPT_POSTFIELDSIZE_LARGE |
指向的 POST 数据的大小 |
CURLOPT_POSTFIELDSIZE |
指向的 POST 数据的大小 |
CURLOPT_POSTQUOTE |
传输后运行的(S)FTP 命令 |
CURLOPT_POSTREDIR |
对 HTTP POST 重定向的处理方式 |
CURLOPT_PRE_PROXY |
要使用的预代理主机 |
CURLOPT_PREQUOTE |
FTP 传输前的命令 |
CURLOPT_PREREQDATA |
传递给预请求回调的指针 |
CURLOPT_PREREQFUNCTION |
当连接建立时用户回调函数被调用 |
CURLOPT_PRIVATE |
存储一个私有指针 |
CURLOPT_PROGRESSDATA |
传递给进度回调的指针 |
CURLOPT_PROGRESSFUNCTION |
进度表回调 |
CURLOPT_PROTOCOLS_STR |
允许的协议 |
CURLOPT_PROTOCOLS |
允许的协议 |
CURLOPT_PROXY_CAINFO_BLOB |
以 PEM 格式存储的代理证书颁发机构(CA)证书包 |
CURLOPT_PROXY_CAINFO |
代理证书颁发机构(CA)证书包的路径 |
CURLOPT_PROXY_CAPATH |
存放 HTTPS 代理 CA 证书的目录 |
CURLOPT_PROXY_CRLFILE |
HTTPS 代理证书吊销列表文件 |
CURLOPT_PROXY_ISSUERCERT_BLOB |
从内存 blob 中获取的代理发行者 SSL 证书 |
CURLOPT_PROXY_ISSUERCERT |
代理发行者 SSL 证书的文件名 |
CURLOPT_PROXY_KEYPASSWD |
代理私钥的密码短语 |
CURLOPT_PROXY_PINNEDPUBLICKEY |
https 代理的固定公钥 |
CURLOPT_PROXY_SERVICE_NAME |
代理认证服务名称 |
CURLOPT_PROXY_SSL_CIPHER_LIST |
用于 HTTPS 代理的加密套件 |
CURLOPT_PROXY_SSL_OPTIONS |
HTTPS 代理 SSL 行为选项 |
CURLOPT_PROXY_SSL_VERIFYHOST |
将代理证书的名称与主机进行验证 |
CURLOPT_PROXY_SSL_VERIFYPEER |
验证代理的 SSL 证书 |
CURLOPT_PROXY_SSLCERT_BLOB |
从内存 blob 中获取的 SSL 代理客户端证书 |
CURLOPT_PROXY_SSLCERT |
HTTPS 代理客户端证书 |
CURLOPT_PROXY_SSLCERTTYPE |
代理客户端 SSL 证书的类型 |
CURLOPT_PROXY_SSLKEY_BLOB |
从内存 blob 中获取的代理证书的私钥 |
CURLOPT_PROXY_SSLKEY |
HTTPS 代理客户端证书的私钥文件 |
CURLOPT_PROXY_SSLKEYTYPE |
代理私钥文件的类型 |
CURLOPT_PROXY_SSLVERSION |
偏好的 HTTPS 代理 TLS 版本 |
CURLOPT_PROXY_TLS13_CIPHERS |
代理 TLS 1.3 的加密套件 |
CURLOPT_PROXY_TLSAUTH_PASSWORD |
用于代理 TLS 认证的密码 |
CURLOPT_PROXY_TLSAUTH_TYPE |
HTTPS 代理 TLS 认证方法 |
CURLOPT_PROXY_TLSAUTH_USERNAME |
用于代理 TLS 认证的用户名 |
CURLOPT_PROXY_TRANSFER_MODE |
将 FTP 传输模式附加到代理 URL |
CURLOPT_PROXY |
要使用的代理 |
CURLOPT_PROXYAUTH |
HTTP 代理认证方法 |
CURLOPT_PROXYHEADER |
要传递给代理的 HTTP 头部集合 |
CURLOPT_PROXYPASSWORD |
用于代理认证的密码 |
CURLOPT_PROXYPORT |
代理监听的端口号 |
CURLOPT_PROXYTYPE |
代理协议类型 |
CURLOPT_PROXYUSERNAME |
用于代理认证的用户名 |
CURLOPT_PROXYUSERPWD |
用于代理认证的用户名和密码 |
CURLOPT_PUT |
发送 HTTP PUT 请求 |
CURLOPT_QUICK_EXIT |
允许快速退出 |
CURLOPT_QUOTE |
传输前运行的 (S)FTP 命令 |
CURLOPT_RANDOM_FILE |
从中读取随机数据的文件 |
CURLOPT_RANGE |
请求的字节范围 |
CURLOPT_READDATA |
传递给读取回调的指针 |
CURLOPT_READFUNCTION |
数据上传的读取回调 |
CURLOPT_REDIR_PROTOCOLS_STR |
允许重定向到的协议 |
CURLOPT_REDIR_PROTOCOLS |
允许重定向到的协议 |
CURLOPT_REFERER |
HTTP 引用头 |
CURLOPT_REQUEST_TARGET |
此请求的替代目标 |
CURLOPT_RESOLVE |
提供自定义主机名到 IP 地址解析 |
CURLOPT_RESOLVER_START_DATA |
传递给 resolver start 回调的指针 |
CURLOPT_RESOLVER_START_FUNCTION |
在开始新的名称解析之前调用的回调 |
CURLOPT_RESUME_FROM_LARGE |
从何处恢复传输的偏移量 |
CURLOPT_RESUME_FROM |
从何处恢复传输的偏移量 |
CURLOPT_RTSP_CLIENT_CSEQ |
RTSP 客户端 CSEQ 数字 |
CURLOPT_RTSP_REQUEST |
RTSP 请求 |
CURLOPT_RTSP_SERVER_CSEQ |
RTSP 服务器 CSEQ 数字 |
CURLOPT_RTSP_SESSION_ID |
RTSP 会话 ID |
CURLOPT_RTSP_STREAM_URI |
RTSP 流 URI |
CURLOPT_RTSP_TRANSPORT |
RTSP 传输:头部 |
CURLOPT_SASL_AUTHZID |
授权身份(要充当的身份) |
CURLOPT_SASL_IR |
在第一个数据包中发送初始响应 |
CURLOPT_SEEKDATA |
传递给 seek 回调的指针 |
CURLOPT_SEEKFUNCTION |
输入流中查找的用户回调 |
CURLOPT_SERVER_RESPONSE_TIMEOUT |
允许等待服务器响应的秒数 |
CURLOPT_SERVER_RESPONSE_TIMEOUT_MS |
允许等待服务器响应的毫秒数 |
CURLOPT_SERVICE_NAME |
认证服务名称 |
CURLOPT_SHARE |
要使用的共享句柄 |
CURLOPT_SOCKOPTDATA |
传递给 sockopt 回调的指针 |
CURLOPT_SOCKOPTFUNCTION |
设置套接字选项的回调 |
CURLOPT_SOCKS5_AUTH |
SOCKS5 代理认证方法 |
CURLOPT_SOCKS5_GSSAPI_NEC |
socks 代理 gssapi 协商保护 |
CURLOPT_SOCKS5_GSSAPI_SERVICE |
SOCKS5 代理认证服务名称 |
CURLOPT_SSH_AUTH_TYPES |
SFTP 和 SCP 的认证类型 |
CURLOPT_SSH_COMPRESSION |
启用 SSH 压缩 |
CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 |
SSH 服务器公钥的 MD5 校验和 |
CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 |
SSH 服务器公钥的 SHA256 哈希值 |
CURLOPT_SSH_HOSTKEYDATA |
传递给 SSH 主机密钥回调的指针 |
CURLOPT_SSH_HOSTKEYFUNCTION |
检查主机密钥的回调 |
CURLOPT_SSH_KEYDATA |
传递给 SSH 密钥回调的指针 |
CURLOPT_SSH_KEYFUNCTION |
已知主机匹配逻辑的回调 |
CURLOPT_SSH_KNOWNHOSTS |
包含 SSH 已知主机的文件名 |
CURLOPT_SSH_PRIVATE_KEYFILE |
SSH 认证的私钥文件 |
CURLOPT_SSH_PUBLIC_KEYFILE |
SSH 认证的公钥文件 |
CURLOPT_SSL_CIPHER_LIST |
用于 TLS 的加密套件 |
CURLOPT_SSL_CTX_DATA |
传递给 ssl_ctx 回调的指针 |
CURLOPT_SSL_CTX_FUNCTION |
OpenSSL、wolfSSL 或 mbedTLS 的 SSL 上下文回调 |
CURLOPT_SSL_EC_CURVES |
密钥交换曲线 |
CURLOPT_SSL_ENABLE_ALPN |
应用层协议协商 |
CURLOPT_SSL_ENABLE_NPN |
使用 NPN |
CURLOPT_SSL_FALSESTART |
TLS false start |
CURLOPT_SSL_OPTIONS |
SSL 行为选项 |
CURLOPT_SSL_SESSIONID_CACHE |
使用 SSL 会话 ID 缓存 |
CURLOPT_SSL_VERIFYHOST |
将证书的名称与主机进行验证 |
CURLOPT_SSL_VERIFYPEER |
验证对等方的 SSL 证书 |
CURLOPT_SSL_VERIFYSTATUS |
验证证书的状态 |
CURLOPT_SSLCERT_BLOB |
从内存 blob 中获取 SSL 客户端证书 |
CURLOPT_SSLCERT |
SSL 客户端证书 |
CURLOPT_SSLCERTTYPE |
客户端 SSL 证书的类型 |
CURLOPT_SSLENGINE_DEFAULT |
设置 SSL 引擎为默认 |
CURLOPT_SSLENGINE |
SSL 引擎标识符 |
CURLOPT_SSLKEY_BLOB |
从内存 blob 中获取客户端证书的私钥 |
CURLOPT_SSLKEY |
TLS 和 SSL 客户端证书的私钥文件 |
CURLOPT_SSLKEYTYPE |
私钥文件的类型 |
CURLOPT_SSLVERSION |
优先的 TLS/SSL 版本 |
CURLOPT_STDERR |
将 stderr 重定向到另一个流 |
CURLOPT_STREAM_DEPENDS_E |
此传输专一依赖的流 |
CURLOPT_STREAM_DEPENDS |
此传输依赖的流 |
CURLOPT_STREAM_WEIGHT |
数值流权重 |
CURLOPT_SUPPRESS_CONNECT_HEADERS |
从用户回调中抑制代理 CONNECT 响应头 |
CURLOPT_TCP_FASTOPEN |
TCP 快速打开 |
CURLOPT_TCP_KEEPALIVE |
TCP 保活探测 |
CURLOPT_TCP_KEEPIDLE |
TCP 保活空闲时间等待 |
CURLOPT_TCP_KEEPINTVL |
TCP 保活间隔 |
CURLOPT_TCP_NODELAY |
TCP_NODELAY 选项 |
CURLOPT_TELNETOPTIONS |
一组 telnet 选项 |
CURLOPT_TFTP_BLKSIZE |
TFTP 块大小 |
CURLOPT_TFTP_NO_OPTIONS |
不发送 TFTP 选项请求 |
CURLOPT_TIMECONDITION |
时间请求的选取条件 |
CURLOPT_TIMEOUT_MS |
允许传输完成的最大时间 |
CURLOPT_TIMEOUT |
允许传输完成的最大时间 |
CURLOPT_TIMEVALUE_LARGE |
条件性时间值 |
CURLOPT_TIMEVALUE |
条件性时间值 |
CURLOPT_TLS13_CIPHERS |
用于 TLS 1.3 的加密套件 |
CURLOPT_TLSAUTH_PASSWORD |
用于 TLS 身份验证的密码 |
CURLOPT_TLSAUTH_TYPE |
TLS 身份验证方法 |
CURLOPT_TLSAUTH_USERNAME |
用于 TLS 身份验证的用户名 |
CURLOPT_TRAILERDATA |
传递给尾部头回调的指针 |
CURLOPT_TRAILERFUNCTION |
发送尾部头的回调函数 |
CURLOPT_TRANSFER_ENCODING |
请求 HTTP 传输编码 |
CURLOPT_TRANSFERTEXT |
请求基于文本的 FTP 传输 |
CURLOPT_UNIX_SOCKET_PATH |
Unix 域套接字 |
CURLOPT_UNRESTRICTED_AUTH |
向其他主机发送凭据 |
CURLOPT_UPKEEP_INTERVAL_MS |
连接维护间隔 |
CURLOPT_UPLOAD_BUFFERSIZE |
上传缓冲区大小 |
CURLOPT_UPLOAD |
数据上传 |
CURLOPT_URL |
此传输的 URL |
CURLOPT_USE_SSL |
使用 SSL / TLS 进行传输请求 |
CURLOPT_USERAGENT |
HTTP 用户代理头 |
CURLOPT_USERNAME |
用于身份验证的用户名 |
CURLOPT_USERPWD |
用于身份验证的用户名和密码 |
CURLOPT_VERBOSE |
详细模式 |
CURLOPT_WILDCARDMATCH |
目录通配符传输 |
CURLOPT_WRITEDATA |
传递给写回调的指针 |
CURLOPT_WRITEFUNCTION |
写入接收数据的回调函数 |
CURLOPT_WS_OPTIONS |
WebSocket 行为选项 |
CURLOPT_XFERINFODATA |
传递给进度回调的指针 |
CURLOPT_XFERINFOFUNCTION |
进度计回调函数 |
CURLOPT_XOAUTH2_BEARER |
OAuth 2.0 访问令牌 |
获取选项信息
libcurl 提供了一个 API,实际上是一组函数,允许应用程序获取有关所有当前支持的简单选项的信息。它不返回选项的值,而是提供有关名称、ID 和类型的信息。
遍历所有选项
现代 libcurl 支持超过 300 个不同的选项。通过使用curl_easy_option_by_next(),应用程序可以遍历所有已知选项,并返回一个指向struct curl_easyoption的指针。
此函数仅返回此特定 libcurl 构建所知的选项信息。其他选项可能存在于较新的 libcurl 构建中,或者在不同的构建时启用/禁用选项的构建中。
示例,遍历所有可用选项:
const struct curl_easyoption *opt;
opt = curl_easy_option_by_next(NULL);
while(opt) {printf("Name: %s\n", opt->name);opt = curl_easy_option_by_next(opt);
}
通过名称查找特定选项
给定一个特定的简单选项名称,你可以要求 libcurl 返回一个指向struct curl_easyoption的指针。名称应提供,不带CURLOPT_前缀。
例如,一个应用程序可以这样询问 libcurl 关于CURLOPT_VERBOSE选项:
const struct curl_easyoption *opt = curl_easy_option_by_name("VERBOSE");
if(opt) {printf("This option wants CURLoption %x\n", (int)opt->id);
}
通过 ID 查找特定选项
给定一个特定的简单选项 ID,你可以要求 libcurl 返回一个指向struct curl_easyoption的指针。该“ID”是公共curl/curl.h头文件中提供的CURLOPT_前缀符号。
一个应用程序可以这样询问 libcurlCURLOPT_VERBOSE选项的名称:
const struct curl_easyoption *opt =curl_easy_option_by_id(CURLOPT_VERBOSE);
if(opt) {printf("This option has the name: %s\n", opt->name);
}
curl_easyoption结构体
struct curl_easyoption {const char *name;CURLoption id;curl_easytype type;unsigned int flags;
};
在‘flags’中只有一个具有定义意义的位:如果CURLOT_FLAG_ALIAS被设置,则表示该选项是一个“别名”。这是一个为了向后兼容而提供的名称,如今更常用另一个名称的选项。如果你查找别名的 ID,你会得到该选项的新规范名称。
驱动传输
libcurl 提供了三种不同的方式进行传输。在你的情况下使用哪种方式完全取决于你和你所需的内容。
-
“简单”接口允许你以同步方式执行单个传输。libcurl 完成整个传输并将控制权返回到你的应用程序,无论传输成功还是失败。
-
“多任务”接口用于当你想要同时进行多个传输,或者你只需要一个非阻塞传输时。
-
“多套接字”接口是常规多任务接口的一个小变体,但它基于事件,如果你打算将同时进行的传输数量扩展到数百或数千,那么它实际上是建议使用的 API。
让我们逐一更详细地看看它们...
-
使用简单驱动
-
使用多任务驱动
-
使用多套接字驱动
使用简单方式驾驶
“简单”这个名字被选中,仅仅是因为这确实是使用 libcurl 的简单方法,而且当然,使用简单方法也有一些限制。例如,它一次只能进行一个传输,并且在一个函数调用中完成整个传输,并在完成后返回:
res = curl_easy_perform( easy_handle );
如果服务器速度慢,如果传输量大,或者你在网络或类似情况下遇到一些不愉快的超时,这个函数调用可能会花费很长时间。当然,你可以设置超时,以防止它花费超过 N 秒,但这仍然可能意味着根据特定条件需要大量时间。
如果你想在 libcurl 使用简单接口进行传输的同时,让应用程序做其他事情,你需要使用多个线程。如果你想在使用简单接口时进行多个同时传输,你需要在每个传输中执行其自己的线程。
使用多接口驱动
“多”这个名字是表示多个,比如多个并行传输,所有这些都在同一个单独的线程中完成。多 API 是非阻塞的,因此也可以用于单次传输。
传输仍然设置在如上所述的“简单”的CURL *句柄中,但使用多接口时,你还需要创建一个多CURLM *句柄,并使用它来驱动所有单个传输。多句柄可以“持有”一个或多个简单句柄:
CURLM *multi_handle = curl_multi_init();
多句柄也可以设置某些选项,这通过curl_multi_setopt()完成,但在最简单的情况下,你可能没有什么可以设置的。
要驱动多接口传输,你首先需要将所有应该传输的单个简单句柄添加到多句柄中。你可以在任何时候将它们添加到多句柄中,也可以随时将它们移除。从多句柄中移除简单句柄会取消关联,并且那个特定的传输会立即停止。
将简单句柄添加到多句柄中很简单:
curl_multi_add_handle( multi_handle, easy_handle );
移除一个也很简单:
curl_multi_remove_handle( multi_handle, easy_handle );
添加了代表你想要执行传输的简单句柄后,你编写传输循环。使用多接口,你进行循环,这样你可以要求 libcurl 提供一组文件描述符和一个超时值,然后你自己进行select()调用,或者你可以使用稍微简化一点的版本,它为我们做这件事,使用curl_multi_wait。最简单的循环可能看起来像这样:(注意,实际应用会检查返回代码)
int transfers_running;
do {curl_multi_wait ( multi_handle, NULL, 0, 1000, NULL);curl_multi_perform ( multi_handle, &transfers_running );
} while (transfers_running);
curl_multi_wait的第四个参数,在上面的例子中设置为 1000,是一个以毫秒为单位的超时值。这是函数在返回之前等待任何活动可能的最长时间。你不想在再次调用curl_multi_perform之前锁定太长时间,因为存在超时、进度回调等,这样做可能会失去精度。
要自行执行 select(),我们像这样从 libcurl 中提取文件描述符和超时值(注意,实际应用会检查返回代码):
int transfers_running;
do {fd_set fdread;fd_set fdwrite;fd_set fdexcep;int maxfd = -1;long timeout;/* extract timeout value */curl_multi_timeout(multi_handle, &timeout);if (timeout < 0)timeout = 1000;/* convert to struct usable by select */timeout.tv_sec = timeout / 1000;timeout.tv_usec = (timeout % 1000) * 1000;FD_ZERO(&fdread);FD_ZERO(&fdwrite);FD_ZERO(&fdexcep);/* get file descriptors from the transfers */mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite,&fdexcep, &maxfd);if (maxfd == -1) {SHORT_SLEEP;}elseselect(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);/* timeout or readable/writable sockets */curl_multi_perform(multi_handle, &transfers_running);
} while ( transfers_running );
这两个循环都让你可以使用一个或多个自己的文件描述符来等待,比如如果你从自己的套接字或管道或类似的东西中读取。
再次强调,你可以在循环的任何时刻添加和移除简单句柄到多句柄。在传输过程中移除句柄会中止该传输。
单次传输何时完成?
如上例所示,程序可以通过查看transfers_running变量减少来检测单个传输何时完成。
它还可以调用curl_multi_info_read(),如果传输结束,它会返回一个指向结构体(一个“消息”)的指针,然后你可以使用该结构体找出该传输的结果。
当你进行多个并行传输时,当然可能会有多个传输在同一个 curl_multi_perform 调用中完成,然后你可能需要多次调用 curl_multi_info_read 来获取每个已完成的传输的信息。
使用 multi_socket 驱动
multi_socket 是常规多接口的增强版本,专为事件驱动应用程序设计。请确保您首先阅读使用多接口驱动部分。
multi_socket 支持多个并行传输——所有这些都在同一个单线程中完成——并且已被用于在单个应用程序中运行数万个传输。如果您进行大量并行传输(>100 个左右),通常这是最有意义的 API。
在这种情况下,事件驱动意味着您的应用程序使用一个系统级库或设置,它订阅了多个套接字,并在其中一个套接字可读或可写时通知您的应用程序,并确切地告诉您是哪一个。
这种设置允许客户端将同时传输的数量扩展得比其他系统高得多,同时仍然保持良好的性能。常规 API 否则会浪费太多时间扫描所有套接字列表。
选择一个
在外部有众多基于事件的选择,libcurl 对您使用哪一个完全无偏见。libevent、libev 和 libuv 是三个流行的选择,但您也可以直接使用操作系统的原生解决方案,如 epoll、kqueue、/dev/poll、pollset 或 Event Completion。
许多简单句柄
就像使用常规的多接口一样,您可以使用curl_multi_add_handle()将简单句柄添加到多句柄中。每个句柄对应您想要执行的一个传输。
您可以在传输运行期间随时添加它们,您也可以使用curl_multi_remove_handle调用在任意时间移除简单句柄。不过,通常您只在传输完成后才移除句柄。
多套接字回调
如上所述,这种基于事件的机制依赖于应用程序知道 libcurl 使用的套接字以及 libcurl 在这些套接字上等待的活动:如果它等待套接字变为可读、可写或两者。
应用程序还需要在超时时间到期时通知 libcurl,因为它控制着 libcurl 无法自行完成的一切。libcurl 在需要时立即通知应用程序更新的超时值。
套接字回调
libcurl 通过一个名为CURLMOPT_SOCKETFUNCTION的回调函数通知应用程序等待的套接字活动。您的应用程序需要实现这样一个函数:
int socket_callback(CURL *easy, /* easy handle */curl_socket_t s, /* socket */int what, /* what to wait for */void *userp, /* private callback pointer */void *socketp) /* private socket pointer */
{/* told about the socket 's' */
}/* set the callback in the multi handle */
curl_multi_setopt(multi_handle, CURLMOPT_SOCKETFUNCTION, socket_callback);
使用此方法,libcurl 设置和删除应用程序应监视的套接字。您的应用程序告诉底层基于事件的系统等待套接字。如果有多个套接字要等待,此回调会被多次调用,当状态改变并且您可能需要从等待可写套接字切换到等待它变为可读时,它会被再次调用。
当应用程序代表监控的 libcurl 上的一个套接字注册它变得可读或可写时,你通过调用 curl_multi_socket_action() 并传递受影响的套接字以及一个相关的位掩码来告诉 libcurl,该位掩码指定了已注册的套接字活动:
int running_handles;
ret = curl_multi_socket_action(multi_handle,sockfd, /* the socket with activity */ev_bitmask, /* the specific activity */&running_handles);
timer_callback
应用程序处于控制状态,等待套接字活动。但即使没有套接字活动,libcurl 也需要做一些事情。比如处理超时,调用进度回调,重新尝试或失败耗时过长的传输等。为了使这些工作正常进行,应用程序还必须确保处理 libcurl 设置的单一超时。
libcurl 使用 timer_callback CURLMOPT_TIMERFUNCTION 设置超时:
int timer_callback(multi_handle, /* multi handle */timeout_ms, /* milliseconds to wait */userp) /* private callback pointer */
{/* the new time-out value to wait for is in 'timeout_ms' */
}/* set the callback in the multi handle */
curl_multi_setopt(multi_handle, CURLMOPT_TIMERFUNCTION, timer_callback);
对于整个多句柄,应用程序只需处理一个超时,无论添加了多少个单独的简单句柄或正在进行多少传输。定时器回调会更新为等待的当前最近时间间隔。如果由于套接字活动在超时到期之前调用 libcurl,它可能会在超时到期之前再次更新超时值。
当你选择的的事件系统最终告诉你定时器已超时时,你需要通知 libcurl:
curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running);
…在许多情况下,这会使 libcurl 再次调用 timer_callback 并设置下一个到期周期的超时。
如何开始一切
当你在多句柄中添加了一个或多个简单句柄,并设置了句柄和定时器回调函数后,你就可以开始传输了。
要启动整个过程,你告诉 libcurl 它已超时(因为所有简单句柄最初都有一个短的超时时间),这将使 libcurl 调用回调来设置事情,从那时起你就可以让你的事件系统驱动:
/* all easy handles and callbacks are setup */curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running);/* now the callbacks should have been called and we have sockets to waitfor and possibly a timeout, too. Make the event system do its magic */event_base_dispatch(event_base); /* libevent2 has this API *//* at this point we have exited the event loop */
它何时完成?
curl_multi_socket_action 返回的 ‘running_handles’ 计数器保持当前未完成的传输数量。当这个数字达到零时,我们知道没有传输正在进行。
每当 ‘running_handles’ 计数器发生变化时,curl_multi_info_read() 返回有关已完成的特定传输的信息。
回调
libcurl 内部有很多操作是通过使用回调来控制的。回调是一个提供给 libcurl 的函数指针,libcurl 在某个时刻会调用这个函数指针以完成特定的任务。
每个回调都有其特定的文档化目的,并且它要求你使用确切的函数原型来接受正确的参数,并返回文档化的返回代码和返回值,以便 libcurl 按你想要的方式执行。
每个回调选项还有一个配套选项,用于设置关联的用户指针。这个用户指针是一个 libcurl 不会触摸或关心的指针,但只是将其作为参数传递给回调。这允许你,例如,将指向本地数据的指针传递到你的回调函数中。
除非在 libcurl 函数文档中明确指出,否则在 libcurl 回调中调用 libcurl 函数是不合法的。
-
写入数据
-
读取数据
-
进度信息
-
标题数据
-
调试
-
sockopt
-
SSL 上下文
-
查找和 ioctl
-
网络数据转换
-
打开套接字和关闭套接字
-
SSH 密钥
-
RTSP 交错数据
-
FTP 通配符匹配
-
解析器启动
-
发送尾部信息
-
HSTS
-
先决条件
写入数据
写入回调通过CURLOPT_WRITEFUNCTION设置:
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback);
write_callback函数必须匹配此原型:
size_t write_callback(char *ptr, size_t size, size_t nmemb,void *userdata);
当 libcurl 接收到需要保存的数据时,会调用此回调函数。ptr指向传递的数据,该数据的大小是size乘以nmemb。
如果此回调未设置,libcurl 将默认使用fwrite。
写入回调在所有调用中尽可能传递尽可能多的数据,但它不能做出任何假设。它可能是一个字节,也可能是数千字节。传递给写入回调的最大正文数据量在 curl.h 头文件中定义:CURL_MAX_WRITE_SIZE(通常默认为 16KB)。如果为此传输启用了CURLOPT_HEADER,这将使头部数据传递给写入回调,你可以得到多达CURL_MAX_HTTP_HEADER字节的头部数据传递给它。这通常意味着 100KB。
如果传输的文件为空,此函数可能会以零字节数据调用。
传递给此函数的数据不会被空终止。例如,你不能使用 printf 的%s操作符来显示内容,也不能使用 strcpy 来复制它。
此回调应返回实际处理的字节数。如果该数字与传递给回调函数的数字不同,则向库发出错误条件信号。这会导致传输被中止,并且使用的 libcurl 函数返回CURLE_WRITE_ERROR。
在userdata参数中传递给回调的用户指针通过CURLOPT_WRITEDATA设置:
curl_easy_setopt(handle, CURLOPT_WRITEDATA, custom_pointer);
存储到内存
有一个普遍的需求是将检索到的响应存储在内存中,上述回调支持这一点。在这样做的时候,请务必小心,因为响应可能非常大。
你应以类似以下方式实现回调:
struct response {char *memory;size_t size;
};static size_t
mem_cb(void *contents, size_t size, size_t nmemb, void *userp)
{size_t realsize = size * nmemb;struct response *mem = (struct response *)userp;char *ptr = realloc(mem->memory, mem->size + realsize + 1);if(!ptr) {/* out of memory! */printf("not enough memory (realloc returned NULL)\n");return 0;}mem->memory = ptr;memcpy(&(mem->memory[mem->size]), contents, realsize);mem->size += realsize;mem->memory[mem->size] = 0;return realsize;
}int main()
{struct response chunk = {.memory = malloc(0),.size = 0};/* send all data to this function */curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, mem_cb);/* we pass our 'chunk' to the callback function */curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);free(chunk.memory);
}
读取数据
读取回调是通过 CURLOPT_READFUNCTION 设置的:
curl_easy_setopt(handle, CURLOPT_READFUNCTION, read_callback);
read_callback 函数必须匹配以下原型:
size_t read_callback(char *buffer, size_t size, size_t nitems,void *stream);
当 libcurl 想要向服务器发送数据时,会调用此回调函数。这是一个您已设置用于上传数据或以其他方式将其发送到服务器的传输。此回调会在所有数据已发送或传输失败之前反复调用。
stream 指针指向通过 CURLOPT_READDATA 设置的私有数据集:
curl_easy_setopt(handle, CURLOPT_READDATA, custom_pointer);
如果没有设置此回调,libcurl 默认使用 'fread'。
指针 buffer 所指向的数据区域应由您的函数填充最多 size 乘以 nitems 个字节的字节。然后回调应返回它存储在该内存区域中的字节数,或者如果已达到数据末尾,则返回 0。回调还可以返回一些“魔法”返回代码,以使 libcurl 立即返回失败或暂停特定的传输。有关详细信息,请参阅 CURLOPT_READFUNCTION 手册页。
进度信息
进度回调是在整个传输过程中,对于每次传输都会定期和重复调用的。旧的回调是通过CURLOPT_PROGRESSFUNCTION设置的,但现代且首选的回调是通过CURLOPT_XFERINFOFUNCTION设置的:
curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, xfer_callback);
xfer_callback函数必须匹配以下原型:
int xfer_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow,curl_off_t ultotal, curl_off_t ulnow);
如果设置了此选项且CURLOPT_NOPROGRESS设置为 0(零),则 libcurl 会以频繁的间隔调用此回调函数。在数据传输过程中,它会被频繁调用,而在没有数据传输的缓慢时期,它可以慢到大约每秒调用一次。
clientp指针指向通过CURLOPT_XFERINFODATA设置的私有数据:
curl_easy_setopt(handle, CURLOPT_XFERINFODATA, custom_pointer);
回调会被告知 libcurl 即将传输和已传输的数据量,以字节数表示:
-
dltotal是 libcurl 在此传输中预期下载的总字节数。 -
dlnow是到目前为止已下载的字节数。 -
ultotal是 libcurl 在此传输中预期上传的总字节数。 -
ulnow是到目前为止已上传的字节数。
传递给回调的未知/未使用参数值被设置为零(例如,如果您只下载数据,上传大小保持为零)。很多时候,在知道数据大小之前,回调会被调用一次或多次,因此必须编写程序来处理这种情况。
从此回调返回非零值会导致 libcurl 终止传输并返回CURLE_ABORTED_BY_CALLBACK。
如果您使用多接口传输数据,除非您调用执行传输的适当 libcurl 函数,否则在空闲期间不会调用此函数。
(已弃用的回调CURLOPT_PROGRESSFUNCTION工作方式相同,但它使用的是double类型,而不是curl_off_t类型。)
标头数据
标头回调是通过 CURLOPT_HEADERFUNCTION 设置的:
curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, header_callback);
header_callback 函数必须匹配以下原型:
size_t header_callback(char *ptr, size_t size, size_t nmemb,void *userdata);
这个回调函数会在 libcurl 接收到标头后立即被调用。ptr 指向传递的数据,该数据的大小是 size 乘以 nmemb。libcurl 缓存标头,并逐个将“完整”的标头传递给这个回调。
传递给这个函数的数据不会被空终止。例如,你不能使用 printf 的 %s 操作符来显示内容,也不能使用 strcpy 来复制它。
这个回调应该返回实际处理的字节数。如果这个数字与传递给回调函数的数字不同,它将向库发出错误条件信号。这会导致传输中断,并且使用的 libcurl 函数返回 CURLE_WRITE_ERROR。
在 userdata 参数中传递给回调的用户指针是通过 CURLOPT_HEADERDATA 设置的:
curl_easy_setopt(handle, CURLOPT_HEADERDATA, custom_pointer);
调试
通过CURLOPT_DEBUGFUNCTION设置调试回调:
curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, debug_callback);
debug_callback函数必须匹配此原型:
int debug_callback(CURL *handle,curl_infotype type,char *data,size_t size,void *userdata);
此回调函数替换了库中的默认详细输出函数,并在所有调试和跟踪消息中被调用,以帮助应用程序理解正在发生的事情。类型参数解释了提供的数据类型:标题、数据或 SSL 数据以及其流动方向。
此回调的常见用途是获取 libcurl 发送和接收的所有数据的完整跟踪。发送到此回调的数据始终是未加密版本,即使使用 HTTPS 或其他加密协议也是如此。
此回调必须返回零或使用错误代码停止传输。
在userdata参数中传递给回调的用户指针通过CURLOPT_DEBUGDATA设置:
curl_easy_setopt(handle, CURLOPT_DEBUGDATA, custom_pointer);
sockopt
sockopt 回调通过 CURLOPT_SOCKOPTFUNCTION 设置:
curl_easy_setopt(handle, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
sockopt_callback 函数必须匹配此原型:
int sockopt_callback(void *clientp,curl_socket_t curlfd,curlsocktype purpose);
当 libcurl 创建了一个新的套接字但在连接调用之前,这个回调函数会被调用,以便应用程序更改特定的套接字选项。
clientp 指针指向使用 CURLOPT_SOCKOPTDATA 设置的私有数据集:
curl_easy_setopt(handle, CURLOPT_SOCKOPTDATA, custom_pointer);
此回调应返回:
-
CURL_SOCKOPT_OK 表示成功
-
CURL_SOCKOPT_ERROR 用于向 libcurl 信号不可恢复的错误
-
CURL_SOCKOPT_ALREADY_CONNECTED 表示成功,同时也表明套接字实际上已经连接到目标
SSL 上下文
libcurl 提供了一个特殊的 TLS 相关回调,称为CURLOPT_SSL_CTX_FUNCTION。此选项仅适用于由 OpenSSL、wolfSSL 或 mbedTLS 提供的 libcurl,如果 libcurl 是用其他 TLS 后端构建的,则此选项不起作用。
此回调在 libcurl 处理完所有其他 TLS 相关选项之后,在初始化 TLS 连接之前被调用,以便给应用程序最后一次修改 TLS 初始化行为的机会。传递给回调的第二个参数中的ssl_ctx 参数实际上是 OpenSSL 或 wolfSSL 的 SSL 库的SSL_CTX的指针,以及 mbedTLS 的mbedtls_ssl_config的指针。如果回调返回错误,则不会尝试建立连接,并且操作返回回调的错误代码。使用CURLOPT_SSL_CTX_DATA选项设置userptr参数。
此函数在服务器上建立的所有新连接期间被调用,在 TLS 协商过程中。每次 TLS 上下文都指向一个新初始化的对象。
寻找和 ioctl
此回调函数通过CURLOPT_SEEKFUNCTION设置。
回调函数由 libcurl 调用,用于在输入流中定位到特定位置,并可用于在恢复上传时快速前进文件(而不是使用常规的读取函数/回调读取所有已上传的字节)。它还用于在已向服务器发送数据并需要再次发送数据时回滚流。这可能在执行带有多阶段认证方法的 HTTP PUT 或 POST 时发生,或者当现有的 HTTP 连接太晚重用时服务器关闭连接。该函数应像 fseek(3)或 lseek(3)一样工作,并接收SEEK_SET、SEEK_CUR或SEEK_END作为原点的参数,尽管 libcurl 当前只传递SEEK_SET。
发送到回调函数的userp是您使用CURLOPT_SEEKDATA设置的指针。
回调函数在成功时必须返回CURL_SEEKFUNC_OK,在导致上传操作失败时返回CURL_SEEKFUNC_FAIL,或在寻求失败时返回CURL_SEEKFUNC_CANTSEEK以指示虽然寻求失败,但如果可能,libcurl 可以自由地绕过问题。后者有时可以通过从输入读取或类似的方式完成。
如果您直接将输入参数传递给 fseek(3)或 lseek(3),请注意,偏移量的数据类型与许多系统上定义的curl_off_t不同。
网络数据转换
直到 libcurl 版本 7.82.0,这些回调被提供以使非 ASCII 平台上的事物能够工作。自那时起,对这些回调的支持已被移除。
以下文档暂时保留在此处,描述了它们过去是如何工作的。它将在未来的某个日期从本书中移除。
转换到和从网络回调
对于非 ASCII 平台,提供了 CURLOPT_CONV_FROM_NETWORK_FUNCTION。此函数应将网络编码转换为主机编码。
CURLOPT_CONV_TO_NETWORK_FUNCTION 应将主机编码转换为网络编码。它在通过网络发送命令或 ASCII 数据时使用。
从 UTF-8 回调转换
CURLOPT_CONV_FROM_UTF8_FUNCTION 应将 UTF-8 编码转换为主机编码。它仅适用于 SSL 处理。
Opensocket 和 closesocket
有时你可能会遇到这样的情况,你希望你的应用程序能够更精确地控制 libcurl 操作所使用的套接字库。libcurl 提供了一对回调,用于替换 libcurl 对socket()的调用以及随后对相同文件描述符的close()操作。
提供一个文件描述符
通过设置CURLOPT_OPENSOCKETFUNCTION回调,你可以提供一个自定义函数,以便 libcurl 返回一个文件描述符:
curl_easy_setopt(handle, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);
opensocket_callback函数必须匹配以下原型:
curl_socket_t opensocket_callback(void *clientp,curlsocktype purpose,struct curl_sockaddr *address);
回调将clientp作为第一个参数,它是一个简单的不可见指针,你使用CURLOPT_OPENSOCKETDATA设置它。
其他两个参数传递的数据用于标识套接字将被用于什么目的和地址。目的是一个 typedef,其值为CURLSOCKTYPE_IPCXN或CURLSOCKTYPE_ACCEPT,用于标识套接字是在哪种情况下创建的。在 libcurl 用于接受 FTP 主动模式下的传入 FTP 连接时,“接受”情况发生,而在 libcurl 创建套接字用于其自己的出站连接的所有其他情况下,传递的值是IPCXN。
地址指针指向一个struct curl_sockaddr,它描述了为创建此套接字而创建的网络目标 IP 地址。例如,你的回调可以使用这些信息来白名单或黑名单特定的地址或地址范围。
如果你想在结构体中修改目标地址,以便提供某种网络过滤器或转换层,socketopen 回调也被明确允许。
回调应该返回一个文件描述符或CURL_SOCKET_BAD,这将导致 libcurl 内部发生不可恢复的错误,并从其 perform 函数返回CURLE_COULDNT_CONNECT。
如果你希望返回一个已经连接到服务器的文件描述符,那么你也必须设置 sockopt 回调并确保它返回正确的返回值。
curl_sockaddress结构看起来像这样:
struct curl_sockaddr {int family;int socktype;int protocol;unsigned int addrlen;struct sockaddr addr;
};
Socket 关闭回调
当然,打开套接字的对应回调是关闭套接字。通常,当你提供一种自定义方式来提供文件描述符时,你希望提供自己的清理版本:
curl_easy_setopt(handle, CURLOPT_CLOSESOCKETFUNCTION,closesocket_callback);
closesocket_callback函数必须匹配以下原型:
int closesocket_callback(void *clientp, curl_socket_t item);
SSH 密钥
此回调函数通过CURLOPT_SSH_KEYFUNCTION设置。
当known_host匹配完成时会被调用,以便应用程序可以行动并决定 libcurl 如何继续。如果CURLOPT_SSH_KNOWNHOSTS也被设置,则会调用回调函数。
回调函数的参数是旧密钥和新密钥,并且期望回调函数返回一个返回码,告诉 libcurl 如何行动:
CURLKHSTAT_FINE_REPLACE - 新的主机+密钥被接受,libcurl 在继续连接之前将旧的主机+密钥替换到 known_hosts 文件中。如果新主机+密钥组合尚未存在于内存中的 known_host 池中,它也会添加到该池中。向文件添加数据是通过完全用新副本替换文件来完成的,因此文件权限必须允许这样做。
CURLKHSTAT_FINE_ADD_TO_FILE - 主机+密钥被接受,libcurl 在继续连接之前将其追加到 known_hosts 文件中。如果主机+密钥组合尚未存在于内存中的 known_host 池中,它也会添加到该池中。向文件添加数据是通过完全用新副本替换文件来完成的,因此文件权限必须允许这样做。
CURLKHSTAT_FINE - 主机+密钥被接受,libcurl 继续连接。如果主机+密钥组合尚未存在于内存中的 known_host 池中,它也会添加到该池中。
CURLKHSTAT_REJECT - 主机+密钥被拒绝。libcurl 拒绝连接继续并关闭。
CURLKHSTAT_DEFER - 主机+密钥被拒绝,但请求保持 SSH 连接活跃。当应用程序想要以某种方式返回并处理主机+密钥情况,然后重试而不需要从头设置开销时,可以使用此功能。
RTSP 交织数据
带有 CURLOPT_INTERLEAVEFUNCTION 选项的回调。
当进行 RTSP 传输时,libcurl 一旦收到交织的 RTP 数据就会调用此回调。它对每个 $ 块进行调用,因此恰好包含一个上层协议单元(例如,一个 RTP 数据包)。libcurl 为每个调用写入交织的头部以及包含的数据。第一个字节始终是 ASCII 美元符号。美元符号后面跟着一个字节通道标识符,然后是一个 2 字节整数长度,以网络字节序表示。有关 RTP 交织行为的信息,请参阅 RFC2326 第 10.12 节。如果未设置或设置为 NULL,curl 将使用默认的写入函数。
CURLOPT_INTERLEAVEDATA 指针在回调函数的 userdata 参数中传递。
FTP 通配符匹配
libcurl 支持 FTP 通配符匹配。您通过将CURLOPT_WILDCARDMATCH设置为1L并然后在 URL 的文件名部分使用“通配符模式”来使用此功能。
通配符模式
默认的 libcurl 通配符匹配函数支持:
* - 星号
ftp://example.com/some/path/*.txt
要匹配目录some/path中的所有 txt 文件。在同一模式字符串中只允许使用两个星号。
? - 问号
一个问号匹配任何(确切的一个)字符。例如,如果您有名为photo1.jpeg和photo7.jpeg的文件,此模式可以匹配它们:
ftp://example.com/some/path/photo?.jpeg
[ - 括号表达式
左括号开启一个括号表达式。在括号表达式中,问号和星号没有特殊含义。每个括号表达式以右括号(])结束,并匹配一个确切字符。以下是一些示例:
[a-zA-Z0-9]或[f-gF-G] - 字符区间
[abc] - 字符枚举
[^abc]或[!abc] - 否定
[[:name:]] 类表达式。支持的类有 alnum、lower、space、alpha、digit、print、upper、blank、graph、xdigit。
[][-!^] - 特殊情况,仅匹配-、]、[、!或^。
[\\[\\]\\\\] - 转义语法。匹配[、]或\。
使用上述规则,可以构建一个文件名模式:
ftp://example.com/some/path/[a-z[:upper:]\\\\].jpeg
FTP 块回调
当使用 FTP 通配符匹配时,在开始传输匹配的文件之前,会调用CURLOPT_CHUNK_BGN_FUNCTION回调函数。
回调函数可以选择返回以下这些返回代码之一,以告诉 libcurl 如何处理该文件:
-
CURL_CHUNK_BGN_FUNC_OK传输文件 -
CURL_CHUNK_BGN_FUNC_SKIP -
CURL_CHUNK_BGN_FUNC_FAIL由于错误而停止
在匹配的文件传输或跳过后,会调用CURLOPT_CHUNK_END_FUNCTION回调函数。
结束块回调函数只能返回成功或错误。
FTP 匹配回调
如果默认的模式匹配函数不符合您的喜好,您可以通过设置CURLOPT_FNMATCH_FUNCTION选项为您的替代函数来提供自己的替换函数。
解析器启动
这个回调函数,通过CURLOPT_RESOLVER_START_FUNCTION设置,每次在 libcurl 开始一个新的解析请求之前都会被调用,并指定解析是为哪个CURL *句柄进行的。
发送拖车
“拖车”是 HTTP/1 的一个特性,其中可以在传输的末尾传递头信息。此回调用于在你想要在执行上传操作后使用 curl 发送拖车时。一种分块编码的 POST 形式的上传。
使用CURLOPT_TRAILERFUNCTION设置的回调会被调用,然后该函数可以将头信息追加到列表中。一个或多个。完成时,libcurl 会将这些作为拖车发送到服务器。
HSTS
对于 HSTS(HTTP 严格传输安全),libcurl 提供了两个回调函数,允许分配实现规则的存储。然后,这些回调函数被设置为从持久存储中读取和/或写入 HSTS 策略。
使用 CURLOPT_HSTSREADFUNCTION,应用程序提供了一个函数,通过该函数将 HSTS 数据读入 libcurl。CURLOPT_HSTSWRITEFUNCTION 是 libcurl 调用来写入数据的对应函数。
Prereq
“Prereq”在这里的意思是在请求发出之前。那就是这个回调被调用的时刻。
使用CURLOPT_PREREQFUNCTION设置函数,它会被调用并传递使用的 IP 地址和端口号作为参数。这允许应用程序在传输开始之前就了解传输情况,同时也允许它在需要时取消这个特定的传输。
连接控制
在使用 libcurl 进行传输时,通常涉及一个 连接。使用 TCP 或 QUIC 等互联网传输协议完成的连接。传输是在连接上进行的,libcurl 提供了许多关于连接的概念和选项,以控制其如何与它们一起工作。
-
libcurl 如何连接
-
本地地址和端口号
-
连接重用
-
名称解析
-
代理
libcurl 的连接方式
当 libcurl 即将进行互联网传输时,它首先解析主机名以获取主机的 IP 地址数量。主机名至少需要有一个地址,libcurl 才能连接到它。
一个主机名可以同时具有 IPv4 地址和 IPv6 地址,并且它们可以有一组。
如果主机只返回了单个 IP 族的地址,libcurl 会遍历每个地址并尝试连接。如果某个 IP 的连接尝试失败,libcurl 将继续尝试下一个条目,直到列表中的所有条目都尝试完毕。
应用程序可以通过设置CURLOPT_IPRESOLVE来限制 libcurl 使用的 IP 版本。
Happy Eyeballs
当它已经收到了主机的 IPv4 和 IPv6 地址,libcurl 首先尝试连接到 IPv6 地址,然后经过短暂的延迟,它尝试连接到第一个 IPv4 地址——同时并行进行。一旦其中一个尝试成功,其他尝试将被丢弃。这种同时使用两个家族尝试连接的方法被称为Happy Eyeballs,并且是互联网客户端广泛接受的最佳实践。
应用程序可以通过使用CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS来设置 Happy Eyeballs 过程中第二个家族连接尝试的延迟。
超时和减半
连接阶段有一个最大允许时间(通过CURLOPT_CONNECTTIMEOUT_MS设置),默认为 300 秒。如果在那个时间内没有成功连接,整个连接过程被认为是失败的。
当 libcurl 还有多个地址需要尝试连接,并且剩余时间超过 600 毫秒时,它最多允许这次尝试使用剩余时间的一半。这是为了避免单个陷阱地址让 libcurl 在错误条目上消耗其整个超时时间。
例如:如果剩余超时时间为 1000 毫秒,并且还有两个 IP 地址需要尝试连接,那么 libcurl 在下一个尝试中只允许 500 毫秒。
如果只剩下 600 毫秒的超时时间,并且还有两个 IP 地址需要尝试连接,libcurl 允许在下一个尝试中使用整个剩余的超时时间,以避免超时时间太短而无法成功。只有在剩余时间超过 600 毫秒的情况下,才会执行超时减半的方法。
HTTP/3
对于要求 libcurl 使用 HTTP/3 的应用程序,它会在 Happy Eyeballs 的基础上增加另一层。HTTP/3 在 QUIC 上工作,而 QUIC 是一种与 TCP 不同的传输协议,有时可能会被阻止或不如 TCP 工作得那么好。为了平滑这种问题带来的影响,libcurl 除了上述不同 IP 版本连接外,还会并行执行 QUIC 连接和常规 TCP 连接。
当 libcurl 获取到主机的 IPv4 和 IPv6 地址,并且它想要与该主机进行 HTTP/3 通信时,它将按以下方式进行:
-
开始一个 IPv6 QUIC 连接尝试,遍历 IPv6 地址
-
短暂延迟后,开始一个 IPv4 QUIC 连接尝试,遍历 IPv4 地址
-
短暂延迟后,开始一个 IPv6 TCP 连接尝试,遍历 IPv6 地址
-
短暂延迟后,开始一个 IPv4 TCP 连接尝试,遍历 IPv4 地址
一旦连接尝试成功,其他所有尝试将立即被丢弃。
当 libcurl 被要求使用 CURL_HTTP_VERSION_3 而不是设置为 CURL_HTTP_VERSION_3ONLY 时,才会进行 HTTP/3 happy eyeballing。
本地地址和端口号
在几乎所有情况下,您都希望让系统在设置连接时选择默认的源 IP 地址和本地端口号。
对于那些不够好的罕见情况,libcurl 提供了覆盖功能。
本地地址
由 libcurl 创建的连接需要有一个能够路由回此主机的源 IP 地址。应用程序不能随意选择任何它想要的 IP 地址并期望它能够工作。机器中的网络接口需要分配 IP 地址。
您可以使用CURLOPT_INTERFACE让 libcurl 使用非默认的 IP 地址。正如其名所示,它是设计用来接收一个命名的网络接口作为输入,然后它会尝试使用该接口的 IP 地址进行出站流量。
然而,名称也可以是一个 IP 地址或主机名,尽管我们不推荐使用这些版本。
为了防止 libcurl 猜测提供的输入类型,请在接口名称前加上“if!”前缀,以确保名称不会被误认为是主机名。
类似地,您可以通过在提供的名称前加上“host!”前缀来坚持地址是一个主机名或 IP 号码。
本地端口号
默认情况下,连接使用 16 位随机源本地端口号,来自所谓的临时端口范围。应用程序可以通过CURLOPT_LOCALPORT和CURLOPT_LOCALPORTRANGE选项请求使用特定的端口号范围。由于端口号是有限的资源,缩小可选择的端口范围会增加连接无法设置的风险,如果尝试的端口号当前都不可用。
连接重用
libcurl 保持着一组旧连接的活跃状态。当一个传输完成时,它会在连接池中保持 N 个连接的活跃状态(有时也称为连接缓存),以便后续的传输如果能够重用现有连接之一,就可以使用它而不是创建一个新的连接。重用连接而不是创建新连接在速度和所需资源方面提供了显著的好处。
当 libcurl 即将为传输目的创建新的连接时,它会首先检查连接池中是否存在可以重用的现有连接。连接重用检查在使用任何 DNS 或其他名称解析机制之前进行,因此它是基于主机名的。如果存在到正确主机名的现有活动连接,还会检查许多其他属性(端口号、协议等),以确保它可以被使用。
Easy API 连接池
当您使用 easy API,或者更具体地说,curl_easy_perform()时,libcurl 将连接池与特定的 easy 句柄相关联。然后重用相同的 easy 句柄确保 libcurl 可以重用其连接。
多线程 API 连接池
当您使用多线程 API 时,连接池将与多线程句柄相关联。这允许您自由地清理和重新创建 easy 句柄,而不会丢失连接池,并允许一个 easy 句柄使用的连接在后续传输中被另一个单独的句柄重用。只需重用多线程句柄即可。
共享连接缓存
自从 libcurl 7.57.0 版本起,应用程序可以使用共享接口让原本独立的传输共享相同的连接池。
当连接没有按预期重用时
libcurl 会自动且始终尝试重用连接,除非明确告知不要这样做。然而,有几种原因会导致连接不用于后续传输。
-
服务器表示此传输后连接将关闭。例如,通过使用
Connection: closeHTTP 响应头或 HTTP/2 或 HTTP/3 的“离开”帧。 -
传输的 HTTP/1 响应是以这种方式发送的,即连接关闭是检测身体结束的唯一方式。或者只是一个使 curl 认为它不能再安全地重用连接的 HTTP/1 接收错误。
-
当 libcurl 尝试重用连接时,该连接被视为“死亡”。这可能发生在服务器端在先前的传输完成后关闭了连接。也可能发生在有状态防火墙/NAT 或网络路径中的其他部分断开了连接,或者在无人看管的情况下,连接上存在 HTTP/2 或 HTTP/3 流量(如 PING 帧)。
-
前一个传输被认为太旧,无法重用。如果设置了
CURLOPT_MAXLIFETIME_CONN,libcurl 将不会重用超过设定秒数的旧连接。 -
之前的传输被认为空闲时间过长。默认情况下,libcurl 从不尝试重用空闲时间超过 118 秒的连接。这个时间可以通过
CURLOPT_MAXAGE_CONN来更改。 -
如果在传输结束时连接池已满,并且即将有一个新连接存储在那里,则池中最旧的空闲连接将被关闭并丢弃,因此不能再重用。根据您使用的 API,可以通过
CURLMOPT_MAXCONNECTS或CURLOPT_MAXCONNECTS来增加连接池的大小。 -
当使用多接口时,如果下一个传输开始时之前的传输尚未结束,并且之前的连接不能用于多路复用。
等等。通常,您可以通过启用 CURLOPT_VERBOSE 并检查 libcurl 向应用程序通知的内容来了解原因。
名称解析
大多数 libcurl 可以执行的数据传输都涉及一个首先需要解析为互联网地址的名称。这就是名称解析。在 URL 中直接使用数值 IP 地址通常可以避免名称解析阶段,但在许多情况下,手动将名称替换为 IP 地址并不容易。
libcurl 尽力重用现有连接而不是创建一个新的连接。检查现有连接以使用的函数仅基于名称,并在尝试任何名称解析之前执行。这就是重用如此之快的一个原因。使用重用连接的传输不会再次解析主机名。
如果无法重用连接,libcurl 将将主机名解析为它解析到的地址集。通常这意味着请求 IPv4 和 IPv6 地址,并且可能有一整套这些地址返回给 libcurl。然后尝试这一组地址,直到找到一个可以工作的,或者返回失败。
应用程序可以通过将 CURLOPT_IPRESOLVE 设置为首选值来强制 libcurl 只使用 IPv4 或 IPv6 解析的地址。例如,请求只使用 IPv6 地址:
curl_easy_setopt(easy, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
名称解析后端
libcurl 可以构建为以三种不同方式之一进行名称解析,具体取决于使用的后端方式,它将获得略有不同的功能集和有时修改后的行为。
-
默认后端是在新的辅助线程中调用正常的 libc 解析器函数,这样它仍然可以在需要时执行细粒度超时,并且没有阻塞调用。
-
在较旧的系统上,libcurl 使用标准的同步名称解析函数。不幸的是,它们在其操作期间会在多处理块中执行所有传输,这使得优雅地超时变得非常困难。
-
还支持使用 c-ares 第三方库进行解析,该库支持不使用线程的异步名称解析。这对于大量并行传输的扩展性更好,但它并不总是与原生名称解析功能完全兼容。
DNS over HTTPS
不论 libcurl 构建时使用的是哪种解析器后端,从 7.62.0 版本开始,它还提供了一个让用户请求特定 DoH(DNS over HTTPS)服务器获取名称地址的方法。这避免了使用正常的、原生的解析器方法和服务器,而是请求一个专门的独立服务器。
使用 CURLOPT_DOH_URL 选项指定一个完整的 URL 作为 DoH 服务器,例如:
curl_easy_setopt(easy, CURLOPT_DOH_URL, "https://example.com/doh");
传递给此选项的 URL 必须 使用 https://,并且通常建议您启用 HTTP/2 支持,以便 libcurl 可以在连接到 DoH 服务器的连接上多路复用执行多个 DoH 请求。
缓存
当一个名称被解析后,结果将存储在 libcurl 的内存缓存中,以便后续对该名称的解析可以即时完成,只要该名称保留在 DNS 缓存中。默认情况下,每个条目在缓存中保留 60 秒,但可以使用CURLOPT_DNS_CACHE_TIMEOUT更改此值。
当使用curl_easy_perform时,DNS 缓存保留在 easy 句柄中,或者当使用多接口时,保留在 multi 句柄中。还可以使用共享接口在多个 easy 句柄之间共享 DNS 缓存。
主机的自定义地址
有时提供虚假的自定义地址以代替真实的主机名很有用,这样 libcurl 就会连接到不同的地址,而不是实际名称解析所建议的地址。
通过使用CURLOPT_RESOLVE选项,应用程序可以预先填充 libcurl 的 DNS 缓存,为特定的主机名和端口号指定一个自定义地址。
要使 libcurl 在请求 example.com 的 443 端口时连接到 127.0.0.1,应用程序可以执行以下操作:
struct curl_slist *dns;
dns = curl_slist_append(NULL, "example.com:443:127.0.0.1");
curl_easy_setopt(curl, CURLOPT_RESOLVE, dns);
由于这会将虚假地址放入 DNS 缓存中,因此即使在跟随重定向等情况下也能正常工作。
名称服务器选项
对于构建为使用 c-ares 的 libcurl,有一些选项可供选择,这些选项提供了对要使用的 DNS 服务器及其使用方式的精细控制。这仅限于纯 c-ares 构建,因为这些功能在标准系统调用用于名称解析时不可用。
-
使用
CURLOPT_DNS_SERVERS,应用程序可以选择使用一组专用的 DNS 服务器。 -
使用
CURLOPT_DNS_INTERFACE,它可以告诉 libcurl 使用哪个网络接口来代替默认的 DNS 接口。 -
使用
CURLOPT_DNS_LOCAL_IP4和CURLOPT_DNS_LOCAL_IP6,应用程序可以指定要将 DNS 解析绑定到哪个特定的网络地址。
无全局 DNS 缓存
之前名为CURLOPT_DNS_USE_GLOBAL_CACHE的选项曾指示 curl 使用全局 DNS 缓存。自 7.65.0 版本以来,此功能已被移除,因此尽管此选项仍然存在,但它不再执行任何操作。
代理
在网络环境中,代理是一个中间人,位于您作为客户端和您想要与之通信的远程服务器之间的服务器。客户端联系中间人,然后中间人继续为您联系远程服务器。
这种代理使用方式有时被公司和组织使用,在这种情况下,您通常需要使用它们来访问目标服务器。
在与代理通信时,有几种不同的代理类型和不同的协议,libcurl 支持其中一些最常用的代理协议。重要的是要认识到,用于代理的协议不一定与用于远程服务器的协议相同。
当使用 libcurl 设置传输时,您需要指定代理服务器的名称和端口号。您可能会发现,您喜欢的浏览器在这一点上可能比 libcurl 更高级,我们将在后面的章节中详细介绍这些细节。
代理类型
libcurl 支持两种主要的代理类型:SOCKS 和 HTTP 代理。更具体地说,它支持带有或不带有远程名称查找的 SOCKS4 和 SOCKS5,以及到本地代理的 HTTP 和 HTTPS。
指定您正在与之通信的代理类型的简单方法是将代理主机名字符串的方案部分(CURLOPT_PROXY)设置为匹配它:
socks4://proxy.example.com:12345/
socks4a://proxy.example.com:12345/
socks5://proxy.example.com:12345/
socks5h://proxy.example.com:12345/
http://proxy.example.com:12345/
https://proxy.example.com:12345/
socks4 - 表示带有本地名称解析的 SOCKS4
socks4a - 表示带有代理名称解析的 SOCKS4
socks5 - 表示带有本地名称解析的 SOCKS5
socks5h - 表示带有代理名称解析的 SOCKS5
http - 表示 HTTP,这总是让代理解析名称
https - 表示代理的 HTTPS,这总是让代理解析名称。
如果您更喜欢只设置主机名,可以选择使用单独的选项设置代理类型,使用CURLOPT_PROXYTYPE。同样,您可以使用CURLOPT_PROXYPORT设置要使用的代理端口号。
本地或代理名称解析
在上面的某个部分中,您可以看到不同的代理设置允许不同的参与方在传输中执行名称解析。在几种情况下,您可以选择让客户端解析服务器主机名并将 IP 地址传递给代理以连接,当然这假设名称查找在客户端系统上工作准确无误 - 或者您可以将名称交给代理,让代理解析名称;将其转换为要连接的 IP 地址。
当您使用 HTTP 或 HTTPS 代理时,您始终需要将名称提供给代理以进行解析。
哪个代理?
如果您的网络连接需要使用代理来访问目标,您必须找出这一点,并告诉 libcurl 使用正确的代理。libcurl 没有自动确定或检测代理的支持。
当使用浏览器时,提供代理 PAC 脚本或其他方式是很常见的,但 libcurl 不识别这些方式。
代理环境变量
如果没有设置代理选项,libcurl 在执行传输之前会检查是否存在特别命名的环境变量,以查看是否请求使用代理。
你可以通过设置一个名为 [scheme]_proxy 的变量来指定代理主机名(与使用 -x 指定主机的方式相同)。如果你想告诉 curl 在访问 HTTP 服务器时使用代理,你设置 http_proxy 环境变量。如下所示:
http_proxy=http://proxy.example.com:80
上面的代理示例是针对 HTTP 的,但当然也可以为想要代理的特定协议设置 ftp_proxy、https_proxy 等等。除了 http_proxy 之外,所有这些代理环境变量名称也可以用大写指定,例如 HTTPS_PROXY。
要设置一个控制所有协议的单个变量,存在 ALL_PROXY。如果存在特定的协议变量,则该变量具有优先级。
当使用环境变量设置代理时,你可能会遇到一种情况,即一个或几个主机名应该被排除在代理之外。这可以通过 NO_PROXY 变量或相应的 CURLOPT_NOPROXY libcurl 选项来实现。将 NO_PROXY 设置为一个以逗号分隔的主机名列表,这些主机名在访问时不应使用代理。你可以将 NO_PROXY 设置为单个星号(‘*’)以匹配所有主机。
HTTP 代理
HTTP 协议详细说明了如何使用 HTTP 代理。客户端(libcurl)不是将请求发送到实际远程服务器,而是请求代理提供特定资源。与 HTTP 代理的连接使用未加密的普通 HTTP。
如果请求 HTTPS 资源,libcurl 将会向代理发送一个 CONNECT 请求。这样的请求通过代理打开一个隧道,其中数据通过隧道传输而不被理解。这样,即使存在 HTTP 代理,libcurl 也能建立安全的端到端 TLS 连接。
你可以在 HTTP 代理上代理非 HTTP 协议,但这是通过 CONNECT 方法通过它进行隧道传输来完成的,因此需要代理配置为允许客户端连接到那些特定的远程端口号。许多 HTTP 代理被设置为禁止连接到 80 和 443 之外的端口号。
HTTPS 代理
HTTPS 代理类似于 HTTP 代理,但允许客户端使用安全的 HTTPS 连接连接到它。由于在这种情况下代理连接与远程站点的连接是分开的,即使在这种情况下,HTTPS 到远程站点的连接也是通过代理的 HTTPS 连接进行隧道传输的,因此 libcurl 为代理连接提供了一整套与远程主机连接分开的 TLS 选项。
例如,CURLOPT_PROXY_CAINFO 对于 HTTPS 代理的功能与 CURLOPT_CAINFO 对于远程主机相同。CURLOPT_PROXY_SSL_VERIFYPEER 是 CURLOPT_SSL_VERIFYPEER 的代理版本,等等。
到今天为止,HTTPS 代理在组织和公司中仍然相当不常见。
代理认证
使用代理进行身份验证意味着您需要在与代理本身的握手协商中提供有效的凭据。然后,代理身份验证是在可能的远程主机身份验证或缺乏身份验证的基础上额外进行的。
libcurl 支持 HTTP、HTTPS 和 SOCKS5 代理的身份验证。关键选项是 CURLOPT_PROXYUSERPWD,它设置要使用的用户名和密码 - 除非您在 CURLOPT_PROXY 字符串中设置它。
HTTP 代理头部
使用 HTTP 或 HTTP 代理时,libcurl 会向代理发送一个包含一组头部的请求。当然,应用程序可以修改这些头部,就像向服务器发送请求一样。
libcurl 提供了 CURLOPT_PROXYHEADER 以控制发送到代理的头部 当向服务器发送单独的请求时。这通常意味着发送到代理的初始 CONNECT 请求,用于通过代理设置隧道。
转移控制
可以通过多种方式控制正在进行的传输。以下页面将描述更多详细信息:
-
停止
-
停止慢速传输
-
速率限制
-
进度计
-
进度回调
停止
互联网传输可能很短暂,但也可能需要很长时间。甚至可能是无限长的时间。
libcurl 通常执行传输,直到它们完成或发生错误。如果没有这些事件发生,传输将继续。
有时,你可能想在 libcurl 传输本应停止之前停止它。
简单 API
如其他地方所述,curl_easy_perform()函数是一个同步函数调用。它在返回之前完成整个传输。
在传输本应结束之前停止传输有几种不同的方法:
-
从回调返回一个错误
-
设置一个选项,使传输在固定时间段后停止
每个 callback 都可以返回一个错误,并且当这些函数之一返回错误时,整个传输将停止。例如,读取、写入或进度回调。
第二种方法是设置超时或其他选项,在一段时间后或达到特定条件时停止传输。例如以下之一:
-
CURLOPT_TIMEOUT- 设置整个传输可能的最大时间 -
CURLOPT_CONNECTTIMEOUT- 设置“连接阶段”可能的最大时间 -
CURLOPT_LOW_SPEED_LIMIT- 设置最低可接受的传输速度。如果传输速度低于此速度CURLOPT_LOW_SPEED_TIME秒数,则传输停止。
没有提供函数允许你的应用程序从另一个线程停止正在进行的curl_easy_perform()调用。因此,常见的建议是私下发出这个意图,你可以在回调中检测到它,并在发生时让该回调返回错误。
多个 API
多接口通常是一个非阻塞 API,所以在大多数情况下,你可以通过使用curl_multi_remove_handle()从多句柄中移除相应的简单句柄来停止传输。
当你使用多 API 时,你可能会调用 libcurl 来等待与 libcurl 一起工作的套接字上的活动或流量。例如,curl_multi_poll()这样的调用可能会在等待某个事件发生(或超时到期)时阻塞。
应用程序可以通过从另一个线程调用curl_multi_wakeup()来使阻塞的curl_multi_poll()调用唤醒并强制立即返回。
停止缓慢传输
默认情况下,传输可能会停滞或以极慢的速度传输数据,而这种情况并不算错误。
如果在M秒内传输速度低于N字节/秒,则停止传输。使用CURLOPT_LOW_SPEED_LIMIT设置N,使用CURLOPT_LOW_SPEED_TIME设置M。
在实际代码中使用这些选项可能看起来像这样:
#include <stdio.h>
#include <curl/curl.h>int main(void)
{CURL *curl;CURLcode res = CURLE_OK;curl = curl_easy_init();if(curl) {/* abort if slower than 30 bytes/sec during 60 seconds */curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 60L);curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 30L);curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se/");res = curl_easy_perform(curl);curl_easy_cleanup(curl);}return (int)res;
}
速率限制
允许应用程序设置速度上限。不要以每秒超过设定字节数的速度传输数据。libcurl 随后会尝试在多个秒内将平均速度保持在给定的阈值以下。
有单独的选项用于接收(CURLOPT_MAX_RECV_SPEED_LARGE)和发送(CURLOPT_MAX_SEND_SPEED_LARGE)。
下面是一个示例源代码,展示了其使用方法:
#include <stdio.h>
#include <curl/curl.h>int main(void)
{CURL *curl;CURLcode res = CURLE_OK;curl = curl_easy_init();if(curl) {curl_off_t maxrecv = 31415;curl_off_t maxsend = 67954;curl_easy_setopt(curl, CURLOPT_MAX_RECV_SPEED_LARGE, maxrecv);curl_easy_setopt(curl, CURLOPT_MAX_SEND_SPEED_LARGE, maxsend);curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se/");res = curl_easy_perform(curl);curl_easy_cleanup(curl);}return (int)res;
}
进度条
libcurl 可以在标准错误输出中显示进度条。这个功能默认是禁用的,并且是那些名称中带有尴尬否定词的选项之一:CURLOPT_NOPROGRESS - 将其设置为 1L 以 禁用 进度条。设置为 0L 以启用它。
返回错误以停止传输
在代码中,它可能看起来像这样:
#include <stdio.h>
#include <curl/curl.h>int main(void)
{CURL *curl;CURLcode res = CURLE_OK;curl = curl_easy_init();if(curl) {/* enable progress meter */curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se/");res = curl_easy_perform(curl);curl_easy_cleanup(curl);}return (int)res;
}
进度回调
这个回调允许应用程序跟踪传输进度。它也通过简单接口在空闲时被调用,并且是让 libcurl 通过返回错误来停止传输的常见方法。
有关所有详细信息,请参阅进度回调部分。
清理
在前面的章节中,我们讨论了如何设置 handles 以及如何驱动传输。所有的传输最终都会结束,无论是成功还是失败。
多 API
当你使用多 API 完成单个传输后,你使用curl_multi_info_read()来识别哪个 easy handle 已经完成,然后使用curl_multi_remove_handle()将其从 multi handle 中移除。
如果你从 multi handle 中移除了最后一个 easy handle,使得没有更多的传输在进行,你可以这样关闭 multi handle:
curl_multi_cleanup( multi_handle );
easy handle
当 easy handle 完成其服务目的后,你可以关闭它。然而,如果你打算进行另一个传输,建议你重用 handle 而不是关闭它并创建一个新的。
如果你没有打算使用 easy handle 进行另一个传输,你只需请求 libcurl 进行清理:
curl_easy_cleanup( easy_handle );
传输后信息
记住 libcurl 传输与 easy 处理句柄 的关联。每个传输都有一个这样的句柄,当传输完成时,在句柄被清理或用于另一个传输之前,它可以用来从之前的操作中提取信息。
你的朋友 curl_easy_getinfo() 可以做这件事,你告诉它你感兴趣的具体信息,如果它能提供,它就会返回。
当你使用此函数时,你需要传入 easy 处理句柄、所需信息和用于存储答案的变量的指针。你必须传入正确类型的变量指针,否则可能会出现意外情况。这些信息值旨在在传输完成后提供。
你接收到的数据可以是一个长整型、char *、struct curl_slist*、一个双精度浮点数或一个套接字。
这就是如何从之前的 HTTP 传输中提取 Content-Type: 值:
CURLcode res;
char *content_type;
res = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type);
如果你想提取在该连接中使用过的本地端口号:
CURLcode res;
long port_number;
res = curl_easy_getinfo(curl, CURLINFO_LOCAL_PORT, &port_number);
可用信息
| 获取信息选项 | 类型 | 描述 |
|---|---|---|
CURLINFO_ACTIVESOCKET |
curl_socket_t |
会话的活跃套接字 |
CURLINFO_APPCONNECT_TIME |
double |
从开始到 SSL/SSH 握手完成的时刻 |
CURLINFO_APPCONNECT_TIME_T |
curl_off_t |
从开始到 SSL/SSH 握手完成的时刻(以微秒为单位) |
CURLINFO_CAINFO |
char * |
libcurl 构建时使用的默认 CA 文件路径 |
CURLINFO_CAPATH |
char * |
libcurl 构建时使用的 CA 目录路径 |
CURLINFO_CERTINFO |
struct curl_slist * |
证书链 |
CURLINFO_CONDITION_UNMET |
long |
是否满足时间条件 |
CURLINFO_CONNECT_TIME |
double |
从开始到远程主机或代理完成的时间 |
CURLINFO_CONNECT_TIME_T |
curl_off_t |
从开始到远程主机或代理完成的时间(以微秒为单位) |
CURLINFO_CONN_ID |
curl_off_t |
当前连接的数值 ID(用于回调) |
CURLINFO_CONTENT_LENGTH_DOWNLOAD |
double |
从 Content-Length 报头获取的内容长度 |
CURLINFO_CONTENT_LENGTH_DOWNLOAD_T |
curl_off_t |
从 Content-Length 报头获取的内容长度 |
CURLINFO_CONTENT_LENGTH_UPLOAD |
double |
上传大小 |
CURLINFO_CONTENT_LENGTH_UPLOAD_T |
curl_off_t |
上传大小 |
CURLINFO_CONTENT_TYPE |
char * |
从 Content-Type 报头获取的内容类型 |
CURLINFO_COOKIELIST |
struct curl_slist * |
所有已知 cookie 的列表 |
CURLINFO_EFFECTIVE_METHOD |
char * |
最后使用的 HTTP 请求方法 |
CURLINFO_EFFECTIVE_URL |
char * |
最后使用的 URL |
CURLINFO_FILETIME |
long |
获取的文档的远程时间 |
CURLINFO_FILETIME_T |
curl_off_t |
获取的文档的远程时间 |
CURLINFO_FTP_ENTRY_PATH |
char * |
登录 FTP 服务器后的入口路径 |
CURLINFO_HEADER_SIZE |
long |
接收到的所有报头字节数 |
CURLINFO_HTTP_CONNECTCODE |
long |
最后代理 CONNECT 响应代码 |
CURLINFO_HTTP_VERSION |
long |
连接中使用的 HTTP 版本 |
CURLINFO_HTTPAUTH_AVAIL |
long |
可用的 HTTP 身份验证方法(位掩码) |
CURLINFO_LASTSOCKET |
long |
最后使用的套接字 |
CURLINFO_LOCAL_IP |
char * |
上次连接的本地端 IP 地址 |
CURLINFO_LOCAL_PORT |
long |
上次连接的本地端端口 |
CURLINFO_NAMELOOKUP_TIME |
double |
从开始到名称解析完成的时间 |
CURLINFO_NAMELOOKUP_TIME_T |
curl_off_t |
从开始到名称解析完成的时间(微秒) |
CURLINFO_NUM_CONNECTS |
long |
用于上次传输的新成功连接数 |
CURLINFO_OS_ERRNO |
long |
上次连接失败的 errno |
CURLINFO_PRETRANSFER_TIME |
double |
从开始到传输开始之前的时间 |
CURLINFO_PRETRANSFER_TIME_T |
curl_off_t |
从开始到传输开始之前的时间(微秒) |
CURLINFO_PRIMARY_IP |
char * |
上次连接的 IP 地址 |
CURLINFO_PRIMARY_PORT |
long |
上次连接的端口 |
CURLINFO_PRIVATE |
char * |
用户私有数据指针 |
CURLINFO_PROTOCOL |
long |
用于连接的协议 |
CURLINFO_PROXY_ERROR |
long |
如果从传输返回CURLE_PROXY,则详细的(SOCKS)代理错误 |
CURLINFO_PROXY_SSL_VERIFYRESULT |
long |
代理证书验证结果 |
CURLINFO_PROXYAUTH_AVAIL |
long |
可用的 HTTP 代理身份验证方法 |
CURLINFO_QUEUE_TIME_T |
curl_off_t |
此传输在队列中等待开始的时间(微秒) |
CURLINFO_REDIRECT_COUNT |
long |
跟随的重定向总数 |
CURLINFO_REDIRECT_TIME |
double |
在最终传输之前所有重定向步骤所需的时间 |
CURLINFO_REDIRECT_TIME_T |
curl_off_t |
在最终传输之前所有重定向步骤所需的时间(微秒) |
CURLINFO_REDIRECT_URL |
char * |
重定向会带你去到的 URL,如果你启用了重定向 |
CURLINFO_REFERER |
char * |
使用的请求Referer:头 |
CURLINFO_REQUEST_SIZE |
long |
发出的 HTTP 请求中发送的字节数 |
CURLINFO_RESPONSE_CODE |
long |
最后接收到的响应代码 |
CURLINFO_RETRY_AFTER |
curl_off_t |
从响应Retry-After:头中获取的值 |
CURLINFO_RTSP_CLIENT_CSEQ |
long |
RTSP 下一次期望的客户 CSeq |
CURLINFO_RTSP_CSEQ_RECV |
long |
RTSP 最后接收 |
CURLINFO_RTSP_SERVER_CSEQ |
long |
RTSP 下一次期望的服务器 CSeq |
CURLINFO_RTSP_SESSION_ID |
char * |
RTSP 会话 ID |
CURLINFO_SCHEME |
char * |
用于连接的方案 |
CURLINFO_SIZE_DOWNLOAD |
double |
下载的字节数 |
CURLINFO_SIZE_DOWNLOAD_T |
curl_off_t |
下载的字节数 |
CURLINFO_SIZE_UPLOAD |
double |
上传的字节数 |
CURLINFO_SIZE_UPLOAD_T |
curl_off_t |
上传的字节数 |
CURLINFO_SPEED_DOWNLOAD |
double |
平均下载速度 |
CURLINFO_SPEED_DOWNLOAD_T |
curl_off_t |
平均下载速度 |
CURLINFO_SPEED_UPLOAD |
double |
平均上传速度 |
CURLINFO_SPEED_UPLOAD_T |
curl_off_t |
平均上传速度 |
CURLINFO_SSL_ENGINES |
struct curl_slist * |
OpenSSL 加密引擎列表 |
CURLINFO_SSL_VERIFYRESULT |
long |
证书验证结果 |
CURLINFO_STARTTRANSFER_TIME |
double |
从开始到接收到第一个字节的时刻的时间 |
CURLINFO_STARTTRANSFER_TIME_T |
curl_off_t |
从开始到接收到第一个字节的时刻的时间(以微秒为单位) |
CURLINFO_TLS_SSL_PTR |
struct curl_slist * |
可用于进一步处理的 TLS 会话信息 |
CURLINFO_TOTAL_TIME |
double |
上一次传输的总时间 |
CURLINFO_TOTAL_TIME_T |
curl_off_t |
上一次传输的总时间(以微秒为单位) |
CURLINFO_XFER_ID |
curl_off_t |
当前传输的数值 ID(用于回调) |
libcurl HTTP
HTTP 是 libcurl 用户最常用的协议,libcurl 提供了无数种修改此类传输的方式。有关 HTTP 协议如何工作的基础知识,请参阅 HTTP 协议基础。
HTTPS
执行 HTTPS 通常与 HTTP 相同,因为额外的安全层和服务器验证等操作默认情况下是自动且透明的。只需在 URL 中使用 https:// 方案即可。
HTTPS 是在 TLS 之上的 HTTP。有关 TLS 传输选项的更多信息,请参阅传输选项部分。
HTTP 代理
请参阅使用 libcurl 的代理
部分
-
HTTP 响应
-
HTTP 请求
-
HTTP 版本
-
HTTP 范围
-
HTTP 认证
-
使用 libcurl 的 Cookies
-
下载
-
上传
-
多路复用
-
HSTS
-
alt-svc
响应
每个 HTTP 请求都包含一个 HTTP 响应。HTTP 响应是一组元数据和响应体,其中体有时可以是零字节,因此不存在。然而,HTTP 响应总是有响应头。
响应体
响应体传递给写入回调,响应头传递给头部回调。
几乎所有使用 libcurl 的应用程序都需要设置至少一个回调,指示 libcurl 如何处理接收到的头和数据。
响应元数据
libcurl 提供了curl_easy_getinfo()函数,允许应用程序查询 libcurl 关于之前执行传输的信息。
有时候一个应用程序只想知道数据的大小。响应的大小如服务器头部所述可以通过curl_easy_getinfo()提取,如下所示:
curl_off_t size;
curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &size);
如果你可以在传输完成后等待,这通常是一个更可靠的方法,因为并非所有 URL 都提前提供大小(例如,对于按需生成内容的服务器),你可以改为请求最近传输中下载的数据量。
curl_off_t size;
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &size);
HTTP 响应代码
每个 HTTP 响应都以一行开始,该行包含 HTTP 响应代码。这是一个三位数,包含服务器对请求状态的想法。这些数字在 HTTP 标准规范中有详细说明,但它们被分为如下范围的类别:
| 代码 | 含义 |
|---|---|
| 1xx | 临时代码,后面跟一个新代码 |
| 2xx | 事情正常 |
| 3xx | 内容在其他地方 |
| 4xx | 由于客户端问题而失败 |
| 5xx | 由于服务器问题而失败 |
你可以在传输后像这样提取响应代码
long code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
关于 HTTP 响应代码“错误”
虽然响应代码数字可以包括(在 4xx 和 5xx 范围内)服务器用来表示请求处理过程中出现错误的数字,但重要的是要认识到这并不意味着 libcurl 会返回错误。
当 libcurl 被要求执行 HTTP 传输时,如果该 HTTP 传输失败,它会返回一个错误。然而,收到 HTTP 404 或类似错误对 libcurl 来说并不是问题。这不是 HTTP 传输错误。用户可能正在编写一个客户端来测试服务器的 HTTP 响应。
如果你坚持让 curl 将 HTTP 响应代码从 400 及以上视为错误,libcurl 提供了CURLOPT_FAILONERROR选项,如果设置,将指示 curl 在这种情况下返回CURLE_HTTP_RETURNED_ERROR。然后它尽快返回错误,并且不传递响应体。
请求
HTTP 请求是 curl 向服务器发送以告知服务器要执行的操作。当它想要获取数据或发送数据时,所有涉及 HTTP 的传输都以 HTTP 请求开始。
一个 HTTP 请求包含一个方法、一个路径、HTTP 版本和一组请求头。使用 libcurl 的应用程序可以调整所有这些字段。
请求方法
每个 HTTP 请求都包含一个“方法”,有时也称为“动词”。它通常是 GET、HEAD、POST 或 PUT 等内容,但也有更专业的如 DELETE、PATCH 和 OPTIONS。
通常,当您使用 libcurl 设置和执行传输时,特定的请求方法由您使用的选项隐含。如果您只是请求一个 URL,这意味着方法是 GET,而如果您设置例如 CURLOPT_POSTFIELDS,这将使 libcurl 使用 POST 方法。如果您将 CURLOPT_UPLOAD 设置为 true,libcurl 在其 HTTP 请求中发送一个 PUT 方法,依此类推。请求 CURLOPT_NOBODY 将使 libcurl 使用 HEAD。
然而,有时那些默认的 HTTP 方法不足以满足需求,或者根本不是您想要的传输方法。然后您可以指示 libcurl 使用您喜欢的特定方法,通过 CURLOPT_CUSTOMREQUEST。例如,您想向您选择的 URL 发送一个 DELETE 方法:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/file.txt");
CURLOPT_CUSTOMREQUEST 设置应仅是作为 HTTP 请求行中方法的单个关键字。如果您想更改或添加额外的 HTTP 请求头,请参阅以下部分。
自定义 HTTP 请求头
当 libcurl 在执行您请求的数据传输时发出 HTTP 请求,它会使用一组适合完成分配给它的任务的 HTTP 头信息发送请求。
如果只提供了 http://localhost/file1.txt 这个 URL,libcurl 将向服务器发送以下请求:
GET /file1.txt HTTP/1.1
Host: localhost
Accept: */*
如果您指示应用程序也将 CURLOPT_POSTFIELDS 设置为字符串 “foobar”(6 个字母,引号仅用于视觉分隔符),它将发送以下头信息:
POST /file1.txt HTTP/1.1
Host: localhost
Accept: */*
Content-Length: 6
Content-Type: application/x-www-form-urlencoded
如果您对 libcurl 发送的默认头信息集不满意,应用程序有权在 HTTP 请求中添加、更改或删除头信息。
添加一个头
要添加一个在请求中不会出现的头,使用 CURLOPT_HTTPHEADER 添加它。假设您想要一个名为 Name: 的头,它包含 Mr. Smith:
struct curl_slist *list = NULL;
list = curl_slist_append(list, "Name: Mr Smith");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_perform(curl);
curl_slist_free_all(list); /* free the list again */
更改一个头
如果默认的标题之一不符合您的期望,您可以修改它们。例如,如果您认为默认的 Host: 标题是错误的(即使它是从您提供的 libcurl URL 中派生出来的),您可以告诉 libcurl 使用您自己的:
struct curl_slist *list = NULL;
list = curl_slist_append(list, "Host: Alternative");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_perform(curl);
curl_slist_free_all(list); /* free the list again */
移除一个头
当您认为 libcurl 在请求中使用了一个您认为不应该使用的头时,您可以轻松地告诉它从请求中移除它。例如,如果您想移除 Accept: 头。只需提供头名称,冒号右边不提供任何内容:
struct curl_slist *list = NULL;
list = curl_slist_append(list, "Accept:");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_perform(curl);
curl_slist_free_all(list); /* free the list again */
提供一个无内容的头
如您在上文各节中可能已经注意到的,如果您尝试在冒号右侧添加没有内容的标题,它会被视为删除指令,从而完全阻止该标题被发送。如果您确实真正地想要发送一个右侧内容为零的标题,您需要使用一个特殊的标记。您必须使用分号而不是正确的冒号来提供标题。例如Header;。如果您想要向发出的 HTTP 请求添加一个只有Moo:而没有冒号后跟内容的标题,您可以这样写:
struct curl_slist *list = NULL;
list = curl_slist_append(list, "Moo;");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_perform(curl);
curl_slist_free_all(list); /* free the list again */
引用者
Referer:标题(是的,它拼写错误)是一个标准的 HTTP 标题,它告诉服务器用户代理是从哪个 URL 被引导到它现在请求的 URL 的。它是一个正常的标题,因此您可以使用上面显示的CURLOPT_HEADER方法自己设置它,或者您可以使用称为CURLOPT_REFERER的快捷方式。就像这样:
curl_easy_setopt(curl, CURLOPT_REFERER, "https://example.com/fromhere/");
curl_easy_perform(curl);
自动引用者
当 libcurl 被要求使用CURLOPT_FOLLOWLOCATION选项自行跟随重定向,并且您仍然希望将Referer:标题设置为它进行重定向的正确的前一个 URL,您可以要求 libcurl 自行设置该值:
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1L);
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/redirected.cgi");
curl_easy_perform(curl);
版本
像所有互联网协议一样,HTTP 协议在多年中一直在不断发展,现在有客户端和服务器遍布全球,随着时间的推移,它们使用不同版本的协议,成功率各不相同。为了使 libcurl 能够与您传递的 URL 一起工作,libcurl 提供了您指定要使用哪个 HTTP 版本的方法。libcurl 的设计方式是首先尝试使用最常见、最合理的默认值,但有时这还不够,然后您可能需要指示 libcurl 如何操作。
如果您使用具有内置 HTTP/2 功能的 libcurl 构建,libcurl 默认使用 HTTP/2 进行 HTTPS 服务器。然后 libcurl 尝试自动使用 HTTP/2,或者在协商失败的情况下回退到 1.1。不具备 HTTP/2 功能的 libcurl 默认通过 HTTPS 使用 HTTP/1.1。纯 HTTP 请求默认使用 HTTP/1.1。
如果默认行为不足以满足您的传输需求,CURLOPT_HTTP_VERSION 选项可供您使用。
| 选项 | 描述 |
|---|---|
| CURL_HTTP_VERSION_NONE | 重置回默认行为 |
| CURL_HTTP_VERSION_1_0 | 强制使用旧的 HTTP/1.0 协议版本 |
| CURL_HTTP_VERSION_1_1 | 使用 HTTP/1.1 协议版本进行请求 |
| CURL_HTTP_VERSION_2_0 | 尝试使用 HTTP/2 |
| CURL_HTTP_VERSION_2TLS | 仅在 HTTPS 连接上尝试使用 HTTP/2,否则使用 HTTP/1.1 |
| CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE | 直接使用 HTTP/2 而不是从 1.1 升级。这要求您知道该服务器可以接受这一点。 |
| CURL_HTTP_VERSION_3 | 尝试使用 HTTP/3,允许回退到较旧版本。 |
| CURL_HTTP_VERSION_3ONLY | 使用 HTTP/3,如果不可能则失败 |
版本 2 不是强制性的
当您要求 libcurl 使用 HTTP/2 时,这是一个请求而不是要求。然后 libcurl 允许服务器选择使用 HTTP/1.1 或 HTTP/2,这决定了最终使用的协议。
版本 3 可以为强制
当您要求 libcurl 使用 CURL_HTTP_VERSION_3 选项进行 HTTP/3 时,它会使 libcurl 进行第二次并行但略微延迟的连接尝试,这样如果 HTTP/3 连接失败,它仍然可以尝试使用较旧的 HTTP 版本。
使用 CURL_HTTP_VERSION_3ONLY 表示不使用回退机制,失败的 QUIC 连接将完全失败传输。
范围
如果客户端只想从远程资源中获取前 200 字节,或者可能在中间的某个地方获取 300 字节,怎么办?HTTP 协议允许客户端请求特定的数据范围。客户端通过提供一个起始偏移量和结束偏移量向服务器请求特定的范围。它甚至可以组合这些请求,通过并列列出多个范围来在同一个请求中请求多个范围。当服务器发送多个独立的部分来回答这样的请求时,这些部分会通过 MIME 边界字符串分隔,用户应用程序需要相应地处理这些部分。curl 不会进一步分隔这样的响应。
然而,字节范围只是一个对服务器的请求。服务器不必遵守这个请求,在许多情况下,比如当服务器在请求时自动生成内容,它可能会简单地拒绝这样做,然后仍然以完整内容作为响应。
你可以使用 CURLOPT_RANGE 让 libcurl 请求一个范围。比如,如果你想要某个东西的前 200 字节:
curl_easy_setopt(curl, CURLOPT_RANGE, "0-199");
或者从索引 200 开始文件中的所有内容:
curl_easy_setopt(curl, CURLOPT_RANGE, "200-");
从索引 0 获取 200 字节,并且从索引 1000 获取 200 字节:
curl_easy_setopt(curl, CURLOPT_RANGE, "0-199,1000-199");
认证
libcurl 支持多种 HTTP 认证方案。
注意,这种认证方式与今天广泛使用的在 Web 上通过 HTTP POST 执行认证并在 cookies 中保持状态的方案不同。有关如何操作的详细信息,请参阅 libcurl 的 Cookies。
用户名和密码
libcurl 在没有给定用户名的情况下不会尝试任何 HTTP 认证。设置一个如下:
curl_easy_setopt(curl, CURLOPT_USERNAME, "joe");
当然,大多数认证还需要一个单独设置的密码:
curl_easy_setopt(curl, CURLOPT_PASSWORD, "secret");
这就是你需要做的。这使得 libcurl 为这次传输切换到其默认的认证方法:HTTP Basic。
认证要求
客户端本身不会决定发送一个认证请求。这是服务器要求的。当服务器有一个受保护的资源并需要认证时,它会以 401 HTTP 响应和一个WWW-Authenticate:头来响应。该头包括关于它为该资源接受哪些特定认证方法的详细信息。
Basic
Basic 是默认的 HTTP 认证方法,正如其名所示,它确实是基本的。它接受用户名和密码,用冒号分隔它们,然后在将其整个放入请求中的Authorization: HTTP 头之前进行 base64 编码。
如果像上面示例中那样设置了用户名和密码,精确的输出头看起来像这样:
Authorization: Basic am9lOnNlY3JldA==
这种认证方法在 HTTP 上完全不安全,因为凭证以明文形式在网络中发送。
你可以明确告诉 libcurl 为特定的传输使用 Basic 方法,如下所示:
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
Digest
另一种 HTTP 认证方法被称为 Digest。与 Basic 相比,这种方法的一个优点是它不会在明文中将密码发送到线路上。然而,这是一个很少被浏览器提及的认证方法,因此也不是一个常用的方法。
你可以明确告诉 libcurl 为特定的传输使用 Digest 方法,如下所示(它仍然需要设置用户名和密码):
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
NTLM
另一种 HTTP 认证方法被称为 NTLM。
你可以明确告诉 libcurl 为特定的传输使用 NTLM 方法,如下所示(它仍然需要设置用户名和密码):
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
Negotiate
另一种 HTTP 认证方法被称为 Negotiate。
你可以明确告诉 libcurl 为特定的传输使用 Negotiate 方法,如下所示(它仍然需要设置用户名和密码):
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_NEGOTIATE);
Bearer
要在请求中传递 OAuth 2.0 Bearer 访问令牌,例如使用CURLOPT_XOAUTH2_BEARER:
CURL *curl = curl_easy_init();
if(curl) {curl_easy_setopt(curl, CURLOPT_URL, "pop3://example.com/");curl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, "1ab9cb22ba269a7");ret = curl_easy_perform(curl);curl_easy_cleanup(curl);
}
尝试第一个
一些 HTTP 服务器允许使用多种认证方法中的一种,在某些情况下,你可能发现自己作为客户端,在事先无法选择单一特定方法,而对于另一个子集的情况,你的应用程序甚至不知道请求的 URL 是否需要认证。
libcurl 也涵盖了所有这些情况。
你可以要求 libcurl 使用多种方法,在这样做的时候,你暗示 curl 首先尝试不进行任何身份验证的请求,然后根据返回的 HTTP 响应,选择服务器和你的应用程序都允许的方法之一。如果有多种方法都可以工作,curl 会根据这些方法被认为有多安全来选择一个顺序,选择最安全的方法。
告诉 libcurl 通过按位或操作接受多种方法,就像这样:
curl_easy_setopt(curl, CURLOPT_HTTPAUTH,CURLAUTH_BASIC | CURLAUTH_DIGEST);
如果你希望 libcurl 只允许使用一种特定的方法,但仍然希望它首先探测是否可以在不使用身份验证的情况下发送请求,你可以通过在掩码中添加CURLAUTH_ONLY来强制这种行为。
请求使用摘要,但除了摘要之外不使用其他任何方法,并且只有在证明确实有必要时:
curl_easy_setopt(curl, CURLOPT_HTTPAUTH,CURLAUTH_DIGEST | CURLAUTH_ONLY);
Cookies
默认情况下,libcurl 将传输设置为尽可能简单,需要启用功能才能使用。其中一个功能就是 HTTP cookie,更通俗地称为 cookie。
Cookies 是由服务器(使用Set-Cookie:头)发送的名称/值对,用于存储在客户端,并且应该在请求中再次发送,以匹配服务器(使用Cookie:头)指定的主机和路径要求。在今天的现代网络中,网站有时会使用大量的 cookie。
Cookie 引擎
当您为特定的简单句柄启用 cookie 引擎时,这意味着它记录传入的 cookie,将它们存储在与简单句柄关联的内存中 cookie 存储中,并在发出匹配的 HTTP 请求时发送适当的 cookie。
有两种方法可以开启 cookie 引擎:
使用读取启用 cookie 引擎
使用CURLOPT_COOKIEFILE选项请求 libcurl 从给定的文件名导入 cookie:
curl_easy_setopt(easy, CURLOPT_COOKIEFILE, "cookies.txt");
一个常见的技巧是只指定一个不存在的文件名或空字符串"",以便它只使用空白的 cookie 存储来启动 cookie 引擎。
此选项可以设置多次,然后读取每个给定的文件。
使用写入启用 cookie 引擎
使用CURLOPT_COOKIEJAR选项请求将接收到的 cookie 存储在文件中:
curl_easy_setopt(easy, CURLOPT_COOKIEJAR, "cookies.txt");
当稍后使用curl_easy_cleanup()关闭简单句柄时,所有已知的 cookie 都会存储在给定的文件中。文件格式是众所周知的 Netscape cookie 文件格式,浏览器也曾使用过。
设置自定义 cookie
一个更简单、更直接的方法是只需在请求中传递一组特定的 cookie,这不会向 cookie 存储添加任何 cookie,甚至不会激活 cookie 引擎,只需使用CURLOPT_COOKIE设置即可:
curl_easy_setopt(easy, CURLOPT_COOKIE, "name=daniel; present=yes;");
您设置的字符串是将在 HTTP 请求中发送的原始字符串,应该格式为重复的NAME=VALUE;序列 - 包括分号分隔符。
导入导出
内存中的 cookie 存储可以保存大量 cookie,libcurl 为应用程序提供了非常强大的方式来操作它们。您可以设置新的 cookie,可以替换现有的 cookie,也可以提取现有的 cookie。
向 cookie 存储中添加 cookie
通过将新的 cookie 传递给 curl 并使用CURLOPT_COOKIELIST选项,可以简单地向 cookie 存储中添加新的 cookie。输入格式是 cookie 文件格式中的一行,或者格式化为Set-Cookie:响应头,但我们推荐使用 cookie 文件风格:
#define SEP "\t" /* Tab separates the fields */char *my_cookie ="example.com" /* Hostname */SEP "FALSE" /* Include subdomains */SEP "/" /* Path */SEP "FALSE" /* Secure */SEP "0" /* Expiry in epoch time format. 0 == Session */SEP "foo" /* Name */SEP "bar"; /* Value */curl_easy_setopt(curl, CURLOPT_COOKIELIST, my_cookie);
如果给定的 cookie 与已存在的 cookie(具有相同的域名和路径等)匹配,它将用新内容覆盖旧的 cookie。
从 cookie 存储中获取所有 cookie
有时在关闭句柄时写入 cookie 文件是不够的,然后您的应用程序可以选择像这样从存储中提取所有当前已知的 cookie:
struct curl_slist *cookies
curl_easy_getinfo(easy, CURLINFO_COOKIELIST, &cookies);
这返回一个指向 cookie 链表的指针,每个 cookie 都(再次)指定为 cookie 文件格式的一行。列表为您分配,因此当应用程序完成信息处理时,请务必调用 curl_slist_free_all。
Cookie 存储命令
如果设置和提取 cookie 不够,您还可以以更多方式干扰 cookie 存储:
使用以下命令清除整个内存存储:
curl_easy_setopt(curl, CURLOPT_COOKIELIST, "ALL");
从内存中擦除所有会话 cookie(没有过期日期的 cookie):
curl_easy_setopt(curl, CURLOPT_COOKIELIST, "SESS");
强制将所有 cookie 写入之前通过 CURLOPT_COOKIEJAR 指定的文件名:
curl_easy_setopt(curl, CURLOPT_COOKIELIST, "FLUSH");
强制从之前通过 CURLOPT_COOKIEFILE 指定的文件名重新加载 cookie:
curl_easy_setopt(curl, CURLOPT_COOKIELIST, "RELOAD");
Cookie 文件格式
Cookie 文件格式是基于文本的,每行存储一个 cookie。以 # 开头的行被视为注释。
每行指定一个单个 cookie,由制表符字符分隔的七个文本字段组成。
| 字段 | 示例 | 含义 |
|---|---|---|
| 0 | example.com | 域名 |
| 1 | FALSE | 包含子域布尔值 |
| 2 | /foobar/ | 路径 |
| 3 | FALSE | 通过安全传输设置 |
| 4 | 1462299217 | 过期时间 – 自 1970 年 1 月 1 日起的秒数,或 0 |
| 5 | person | Cookie 的名称 |
| 6 | daniel | Cookie 的值 |
下载
GET 方法是 libcurl 在请求 HTTP URL 且未指定特定其他方法时的默认方法。它请求服务器提供特定资源——标准的 HTTP 下载请求:
easy = curl_easy_init();
curl_easy_setopt(easy, CURLOPT_URL, "http://example.com/");
curl_easy_perform(easy);
由于在 easy 处理程序中设置的选项是粘性的,并且会保持直到被更改,因此可能会有这样的情况:你请求了另一种请求方法而不是 GET,然后又想切换回 GET 以进行后续请求。为此,存在 CURLOPT_HTTPGET 选项:
curl_easy_setopt(easy, CURLOPT_HTTPGET, 1L);
下载头部信息
HTTP 传输还包括一组响应头部。响应头部是与实际有效载荷(称为响应正文)关联的元数据。所有下载都会获得一组头部信息,但当你使用 libcurl 时,你可以选择是否希望下载(查看)它们。
你可以通过使用 CURLOPT_HEADER 来请求 libcurl 将头部信息传递到与常规正文相同的流中:
easy = curl_easy_init();
curl_easy_setopt(easy, CURLOPT_HEADER, 1L);
curl_easy_setopt(easy, CURLOPT_URL, "http://example.com/");
curl_easy_perform(easy);
或者,你也可以选择将头部信息存储在单独的下载文件中,这依赖于 write 和 header callbacks 的默认行为:
easy = curl_easy_init();
FILE *file = fopen("headers", "wb");
curl_easy_setopt(easy, CURLOPT_HEADERDATA, file);
curl_easy_setopt(easy, CURLOPT_URL, "http://example.com/");
curl_easy_perform(easy);
fclose(file);
如果你只想随意浏览头部信息,那么在开发时仅设置详细模式可能就足够让你满意了,因为这样会在标准错误输出中显示发送的出站和入站头部信息:
curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L);
上传
通过 HTTP 进行上传可以以许多不同的方式完成,并且注意这些差异非常重要。它们可以使用不同的方法,如 POST 或 PUT,并且在使用 POST 时,主体格式可能不同。
除了这些 HTTP 差异之外,libcurl 还提供了不同的方式来提供上传的数据。
HTTP POST
POST 通常是传递数据到远程 Web 应用的 HTTP 方法。在浏览器中,通常通过填写 HTML 表单并按提交按钮来实现这一点。这是 HTTP 请求传递数据到服务器的标准方式。使用 libcurl,你通常提供数据作为指针和长度:
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, dataptr);
curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE, (long)datalength);
或者,你可以告诉 libcurl 这是一个 POST 操作,但更希望它通过使用常规读取回调来获取数据:
curl_easy_setopt(easy, CURLOPT_POST, 1L);
curl_easy_setopt(easy, CURLOPT_READFUNCTION, read_callback);
这个“正常”的 POST 也设置了请求头Content-Type: application/x-www-form-urlencoded。
HTTP 多部分表单
多部分表单仍然使用相同的 HTTP 方法 POST;区别仅在于请求体的格式。多部分表单是一系列分开的“部分”,由 MIME 风格的边界字符串分隔。你可以发送的部分数量没有限制。
每个这样的部分都有一个名称、一组头和一些其他属性。
libcurl 提供了一套便利函数来构建这样的部分序列并将其发送到服务器,所有这些函数都以curl_mime为前缀。创建一个多部分表单,对于数据中的每个部分,你设置名称、数据和可能的其他元数据。一个基本的设置可能看起来像这样:
/* Create the form */
form = curl_mime_init(curl);/* Fill in the file upload field */
field = curl_mime_addpart(form);
curl_mime_name(field, "sendfile");
curl_mime_filedata(field, "photo.jpg");
然后你将这个帖子传递给 libcurl,如下所示:
curl_easy_setopt(easy, CURLOPT_MIMEPOST, form);
(curl_formadd是之前用于构建多部分表单的 API,但我们不再推荐使用它)
HTTP PUT
使用 libcurl 进行 PUT 操作时,它假定你通过读取回调传递数据给它,因为这是 libcurl 使用的典型“文件上传”模式,并且提供了该模式。你设置回调,请求 PUT(通过请求CURLOPT_UPLOAD),设置上传的大小,并设置目标 URL:
curl_easy_setopt(easy, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(easy, CURLOPT_INFILESIZE_LARGE, (curl_off_t) size);
curl_easy_setopt(easy, CURLOPT_READFUNCTION, read_callback);
curl_easy_setopt(easy, CURLOPT_URL, "https://example.com/handle/put");
如果你在传输开始之前不知道上传的大小,并且你正在使用 HTTP 1.1,你可以通过 CURLOPT_HTTPHEADER 添加一个Transfer-Encoding: chunked头。对于 HTTP 1.0,你必须事先提供大小,而对于 HTTP 2 及以后版本,既不需要大小也不需要额外的头。
期望:标题
在使用 HTTP 1.1 进行 HTTP 上传时,在某些情况下,libcurl 会插入一个Expect: 100-continue头。此头为服务器提供了一种在早期拒绝传输的方式,从而避免客户端在服务器有机会拒绝之前发送大量数据。
如果使用CURLOPT_UPLOAD进行 HTTP 上传,或者如果要求进行 HTTP POST(其中主体大小未知或已知大于 1024 字节),libcurl 会添加一个头。
使用 libcurl 的客户端可以通过 CURLOPT_HTTPHEADER 选项显式禁用Expect:头部的使用。
在 HTTP/2 或 HTTP/3 中不使用此头部。
上传也会下载
HTTP 是一种即使你向其上传数据也能返回内容的协议 - 这取决于服务器来决定。响应数据甚至可能在上传完成之前就开始发送回客户端。
多路复用
HTTP 版本 2 和 3 提供了“多路复用”功能。使用此协议特性,HTTP 客户端可以在同一个单一连接上对服务器进行多个并发传输。这一特性在 HTTP 协议的早期版本中并不存在。在早期的 HTTP 版本中,客户端要么必须创建多个连接,要么必须按顺序逐个进行传输。
libcurl 支持 HTTP/2 和 HTTP/3 的 HTTP 多路复用。
确保您使用多接口对支持 HTTP 多路复用的服务器进行多个传输。libcurl 只能在后续传输使用相同主机名时进行多路复用。
对于所有实际用途和 API 行为,应用程序无需关心是否进行了多路复用。
libcurl 默认启用多路复用,但如果您同时开始多个传输,它们会优先考虑短期速度,因此可能会打开新的连接,而不是等待另一个传输创建连接以便进行多路复用。要告诉 libcurl 优先考虑多路复用,请使用curl_easy_setopt()为传输设置CURLOPT_PIPEWAIT选项。
使用curl_multi_setopt()的选项CURLMOPT_PIPELINING,您可以禁用特定多处理句柄的多路复用。
HSTS
HSTS 是 HTTP Strict-Transport-Security 的缩写。这是一种服务器告知客户端,客户端应优先使用 HTTPS 与该站点进行通信,并在未来指定时间段内使用该站点的定义方式。
下面是如何使用 libcurl 与 HSTS 一起使用的方法。
内存缓存
libcurl 主要具有用于 HSTS 主机的内存缓存功能,这样后续只针对 HTTP 的请求到缓存中存在的域名将被内部“重定向”到 HTTPS 版本。假设你已启用此功能。
为句柄启用 HSTS
通过使用 CURLOPT_HSTS_CTRL 选项并调用 curl_easy_setopt() 来设置正确的掩码位,可以启用 HSTS。掩码位有两个独立的标志可以使用,但 CURLHSTS_ENABLE 是主要的一个。如果设置了该标志,则此简单句柄已启用 HSTS 支持。
此选项可用的第二个标志是 CURLHSTS_READONLYFILE,如果设置此标志,则告诉 libcurl,你指定的用于作为 HSTS 缓存的文件名仅用于读取,不应写入任何内容。
设置 HSTS 缓存文件
如果你想在磁盘上持久化 HSTS 缓存,则使用 CURLOPT_HSTS 选项设置一个文件名。libcurl 在传输开始时从该文件读取,并在简单句柄关闭时写入它(除非它被设置为只读)。
alt-svc
替代服务,即 alt-svc,是一个 HTTP 头部,允许服务器通过使用 Alt-Svc: 响应头部告诉客户端,该服务器在另一个位置有一个或多个 替代方案。
服务器建议的 替代方案 可以包括在同一主机上运行的其他端口的服务器,也可以是在完全不同的主机名上运行的服务器,它还可以通过 另一种协议 提供服务。
启用
要让 libcurl 考虑服务器提供的任何替代方案,您必须首先在句柄中启用它。您可以通过将正确的掩码设置到 CURLOPT_ALTSVC_CTRL 选项来实现。掩码允许应用程序限制允许的 HTTP 版本,并确定磁盘上的缓存文件是否仅用于读取(不写入)。
启用 alt-svc 并允许其切换到 HTTP/1 或 HTTP/2:
curl_easy_setopt(curl, CURLOPT_ALTSVC_CTRL, CURLALTSVC_H1|CURLALTSVC_H2);
通过以下方式告诉 libcurl 使用特定的 alt-svc 缓存文件:
curl_easy_setopt(curl, CURLOPT_ALTSVC, "altsvc-cache.txt");
libcurl 将替代方案列表存储在基于内存的缓存中,但在启动时加载所有现有的替代服务条目从 alt-svc 文件,并在后续的 HTTP 请求时考虑这些条目。如果服务器响应新的或更新的 Alt-Svc: 头部,libcurl 将这些存储在退出时的缓存文件中(除非设置了 CURLALTSVC_READONLYFILE 位)。
alt-svc 缓存
alt-svc 缓存类似于一个 cookie jar。它是一个基于文本的文件,每行存储一个替代方案,每个条目还有一个有效期,表示该特定替代方案的有效时长。
仅 HTTPS
Alt-Svc: 只有在通过 HTTPS 连接时才被信任并解析来自服务器的信息。
HTTP/3
截至 2022 年 3 月,使用 Alt-Svc: 头部仍然是启动客户端和服务器使用 HTTP/3 的唯一定义方式。然后服务器通过 HTTP/1 或 HTTP/2 向客户端暗示它也支持 HTTP/3,如果 alt-svc 缓存允许,curl 可以在后续请求中使用 HTTP/3 连接到它。
libcurl 辅助工具
进行传输是好的,但为了进行有效的传输,应用程序通常需要一些额外的 API 和超级功能。
-
在句柄之间共享数据
-
URL API
-
WebSocket
-
标题 API
在句柄之间共享数据
有时应用程序需要在传输之间共享数据。添加到同一多句柄的所有简单句柄会自动在同一个多句柄中的句柄之间完成大量的共享,但有时这并不是你想要的。
多句柄
所有添加到同一多句柄的简单句柄自动共享 连接缓存 和 dns 缓存。
简单句柄之间的共享
libcurl 有一个通用的“共享接口”,其中应用程序创建一个“共享对象”,然后可以由任意数量的简单句柄共享数据。数据随后从共享对象中存储和读取,而不是保留在共享数据的句柄中。
CURLSH *share = curl_share_init();
共享对象可以设置为共享所有或任何 cookie、连接缓存、dns 缓存和 SSL 会话 ID 缓存。
例如,设置共享以保存 cookie 和 dns 缓存:
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
然后设置相应的传输以使用此共享对象:
curl_easy_setopt(curl, CURLOPT_SHARE, share);
使用此 curl 句柄完成的传输会将其 cookie 和 dns 信息存储在 share 句柄中。你可以设置多个简单句柄以共享相同的共享对象。
要共享的内容
CURL_LOCK_DATA_COOKIE - 设置此位以共享 cookie jar。请注意,每个简单句柄仍然需要正确启动其 cookie “引擎”才能开始使用 cookie。
CURL_LOCK_DATA_DNS - DNS 缓存是 libcurl 存储解析的主机名的地址的地方,以便在后续查找中更快。
CURL_LOCK_DATA_SSL_SESSION - SSL 会话 ID 缓存是 libcurl 存储用于 SSL 连接的恢复信息的地方,以便能够更快地恢复之前的连接。
CURL_LOCK_DATA_CONNECT - 当设置时,此句柄使用共享连接缓存,因此更有可能找到现有的连接以重新使用等,这可能在以串行方式对同一主机进行多次传输时提高性能。
锁定
如果你想在多线程环境中让传输共享共享对象。也许你有一个具有许多核心的 CPU,你希望每个核心运行其自己的线程并传输数据,但你仍然希望不同的传输共享数据。那么你需要设置互斥回调。
如果你没有使用线程并且 知道 你以串行方式逐个访问共享对象,则不需要设置任何锁。但如果同时有多个传输访问共享对象,则需要设置互斥回调以防止数据损坏甚至崩溃。
由于 libcurl 本身不知道如何锁定事物或甚至不知道你使用的是哪种线程模型,你必须确保只允许一次访问的互斥锁。一个用于 pthreads 应用程序的锁回调可能如下所示:
static void lock_cb(CURL *handle, curl_lock_data data,curl_lock_access access, void *userptr)
{pthread_mutex_lock(&lock[data]); /* uses a global lock array */
}
curl_share_setopt(share, CURLSHOPT_LOCKFUNC, lock_cb);
相应的解锁回调可能如下所示:
static void unlock_cb(CURL *handle, curl_lock_data data,void *userptr)
{pthread_mutex_unlock(&lock[data]); /* uses a global lock array */
}
curl_share_setopt(share, CURLSHOPT_UNLOCKFUNC, unlock_cb);
取消共享
在传输过程中,传输使用共享对象,并将该对象被指定共享的内容与其他共享相同对象的句柄共享。
在后续传输中,可以将CURLOPT_SHARE设置为 NULL 以防止传输继续共享。在这种情况下,句柄可能以空缓存开始下一次传输,这些缓存是之前共享的数据。
在两次传输之间,共享对象也可以更新以共享不同的属性集,这样共享该对象的句柄在下一次共享时将共享不同的数据集。当取消共享 DNS 数据时,可以使用 curl_share_setopt()的CURLSHOPT_UNSHARE选项从共享对象中移除要共享的项目:
curl_share_setopt(share, CURLSHOPT_UNSHARE, CURL_LOCK_DATA_DNS);
URL API
libcurl 提供了解析、更新和生成 URL 的 API。使用它,应用程序可以利用 libcurl 的 URL 解析器为自己的目的服务。通过使用相同的解析器,可以避免因不同解释而引起的安全问题。
-
包含文件
-
创建、清理、复制
-
解析 URL
-
重定向到 URL
-
获取 URL
-
获取 URL 部分
-
设置 URL 部分
-
追加到查询
-
CURLOPT_CURLU
包含文件
当你想要使用 URL API 时,在你的代码中包含 <curl/curl.h>。
#include <curl/curl.h>CURLU *h = curl_url();
rc = curl_url_set(h, CURLUPART_URL, "ftp://example.com/no/where", 0);
创建、清理、复制
使用此 API 的第一步是创建一个CURLU *句柄,该句柄包含 URL 信息和资源。句柄是对一个关联数据对象的引用,该对象包含有关单个 URL 及其所有不同组件的信息。
该 API 允许您分别或作为一个完整的 URL 设置或获取每个 URL 组件。
创建一个类似于这样的 URL 句柄:
CURLU *h = curl_url();
当您完成操作后,请进行清理:
curl_url_cleanup(h);
当您需要一个句柄的副本时,只需复制它:
CURLU *nh = curl_url_dup(h);
解析 URL
通过在句柄中设置 CURLUPART_URL部分来解析完整 URL:
CURLU *h = curl_url();
rc = curl_url_set(h, CURLUPART_URL,"https://example.com:449/foo/bar?name=moo", 0);
如果成功,rc包含CURLUE_OK,并且不同的 URL 组件保留在句柄中。这意味着从 libcurl 的角度来看,URL 是有效的。
函数调用的第四个参数是一个掩码。设置该掩码中的零位、一位或多位以改变解析器的行为:
CURLU_NON_SUPPORT_SCHEME
使curl_url_set()接受非支持的方案。如果没有设置,则唯一可接受的方案是 libcurl 知道并具有内置支持的协议。
CURLU_URLENCODE
如果路径中的任何字节从编码中受益,则使函数对路径部分进行 URL 编码:如空格或“控制字符”。
CURLU_DEFAULT_SCHEME
如果传入的字符串未使用方案,则假定默认方案被意图使用。默认方案是 HTTPS。如果未设置,则不接受没有方案部分的 URL 作为有效 URL。如果两者都设置了,则覆盖CURLU_GUESS_SCHEME选项。
CURLU_GUESS_SCHEME
使 libcurl 允许设置没有方案的 URL,并且它根据主机名“猜测”所意图的方案。如果最外层的子域名与 DICT、FTP、IMAP、LDAP、POP3 或 SMTP 匹配,则使用该方案,否则选择 HTTP。与CURLU_DEFAULT_SCHEME选项冲突,如果两者都设置了,则优先级更高。
CURLU_NO_AUTHORITY
跳过权限检查。RFC 允许个别方案省略主机部分(通常是权限的唯一必需部分),但 libcurl 无法知道这是否允许自定义方案。指定该标志允许空权限部分,类似于处理文件方案的方式。实际上,仅在与CURLU_NON_SUPPORT_SCHEME结合使用时才可用。
CURLU_PATH_AS_IS
使 libcurl 跳过路径的正常化。这是 curl 通常删除点斜杠序列(如点点和点点等)的程序。用于传输的相同选项称为CURLOPT_PATH_AS_IS。
CURLU_ALLOW_SPACE
使 URL 解析器尽可能允许空格(ASCII 32)。URL 语法通常不允许任何地方有空格,但它们应编码为%20或+。当允许空格时,它们在方案中仍然不允许。当在 URL 中使用并允许空格时,除非也设置了CURLU_URLENCODE,否则将按原样存储。这会影响随后使用curl_url_get()提取完整 URL 或单个部分时 URL 的构建方式。
重定向到 URL
当句柄已经解析了一个 URL 时,设置第二个相对 URL 会使它“重定向”以适应它。
例如,首先设置原始 URL,然后设置我们要“重定向”到的 URL:
CURLU *h = curl_url();
rc = curl_url_set(h, CURLUPART_URL,"https://example.com/foo/bar?name=moo", 0);rc = curl_url_set(h, CURLUPART_URL, "../test?another", 0);
获取一个 URL
CURLU * 处理符代表一个单独的 URL,你可以使用 curl_url_get 函数轻松提取完整的 URL 或其各个部分:
char *url;
rc = curl_url_get(h, CURLUPART_URL, &url, CURLU_NO_DEFAULT_PORT);
curl_free(url);
如果处理符没有足够的信息来返回所请求的部分,它将返回错误。
在使用完毕后,必须使用 curl_free() 函数释放返回的字符串。
标志
当使用 curl_url_get() 检索 URL 部分时,API 提供了一些不同的切换选项,以更好地指定应如何返回该内容。它们设置在 flags 位掩码参数中,这是函数的第四个参数。你可以设置零、一个或多个位。
CURLU_DEFAULT_PORT
如果 URL 句柄没有存储端口号,此选项使 curl_url_get() 返回用于该方案的默认端口号。
CURLU_DEFAULT_SCHEME
如果句柄没有存储方案,此选项使 curl_url_get() 返回默认方案而不是错误。
CURLU_NO_DEFAULT_PORT
指示 curl_url_get() 在生成的 URL 中不使用端口号,如果该端口号与用于方案的默认端口号匹配。例如,如果端口号设置为 443,方案为 https,则提取的 URL 不包括端口号。
CURLU_URLENCODE
此标志使 curl_url_get() 在检索完整 URL 时对主机名部分进行 URL 编码。如果没有设置(默认),libcurl 以“raw”形式返回 URL,以支持 IDN 名称以原始形式出现。IDN 主机名通常使用非 ASCII 字节,否则将进行百分编码。
注意,即使不请求 URL 编码,%(字节 37)在主机名中也是 URL 编码的,以确保主机名保持有效。
CURLU_URLDECODE
告诉 curl_url_get() 在返回之前对内容进行 URL 解码。它确实尝试解码方案、端口号或完整 URL。当设置此位时,查询组件还会获得加号到空格的转换作为额外的好处。请注意,此 URL 解码是无字符集意识的,你将获得一个以零终止的字符串,其中包含可能旨在特定编码的数据。如果解码字符串中存在任何小于 32 的字节值,则获取操作将返回错误。
CURLU_PUNYCODE
如果设置且未设置 CURLU_URLENCODE,并且请求检索 CURLUPART_HOST 或 CURLUPART_URL 部分,当主机名包含任何非 ASCII 八进制数(并且是 IDN 名称)时,libcurl 返回其 punycode 版本。如果 libcurl 没有构建 IDN 功能,使用此位使 curl_url_get() 在主机名包含 ASCII 范围之外的任何内容时返回 CURLUE_LACKS_IDN。
获取 URL 部分
CURLU句柄存储 URL 的各个部分,应用程序可以在任何时间从句柄中单独提取这些部分。如果它们被设置。
curl_url_get()的第二个参数指定了您想要提取的部分。它们都被提取为空终止的char *数据,因此您需要传递一个指向此类变量的指针。
char *host;
rc = curl_url_get(h, CURLUPART_HOST, &host, 0);char *scheme;
rc = curl_url_get(h, CURLUPART_SCHEME, &scheme, 0);char *user;
rc = curl_url_get(h, CURLUPART_USER, &user, 0);char *password;
rc = curl_url_get(h, CURLUPART_PASSWORD, &password, 0);char *port;
rc = curl_url_get(h, CURLUPART_PORT, &port, 0);char *path;
rc = curl_url_get(h, CURLUPART_PATH, &path, 0);char *query;
rc = curl_url_get(h, CURLUPART_QUERY, &query, 0);char *fragment;
rc = curl_url_get(h, CURLUPART_FRAGMENT, &fragment, 0);char *zoneid;
rc = curl_url_get(h, CURLUPART_ZONEID, &zoneid, 0);
记得使用curl_free释放返回的字符串,当你完成使用时!
提取的部分除非用户使用CURLU_URLDECODE标志请求,否则不会进行 URL 解码。
URL 部分
不同的部分是根据它们在 URL 中的角色命名的。想象一个看起来像这样的 URL:
http://joe:7Hbz@example.com:8080/images?id=5445#footer
当这个 URL 被 curl 解析时,它会以这种方式存储不同的组件:
| 文本 | 部分 |
|---|---|
http |
CURLUPART_SCHEME |
joe |
CURLUPART_USER |
7Hbz |
CURLUPART_PASSWORD |
example.com |
CURLUPART_HOST |
8080 |
CURLUPART_PORT |
/images |
CURLUPART_PATH |
id=5445 |
CURLUPART_QUERY |
footer |
CURLUPART_FRAGMENT |
区域 ID
可能会稍微突出一点的是区域 ID。它是一个额外的限定符,可以用于 IPv6 数值地址,并且仅适用于此类地址。它的使用方式如下,其中设置为eth0:
http://[2a04:4e42:e00::347%25eth0]/
对于这个 URL,curl 提取:
| 文本 | 部分 |
|---|---|
http |
CURLUPART_SCHEME |
2a04:4e42:e00::347 |
CURLUPART_HOST |
eth0 |
CURLUPART_ZONEID |
/ |
CURLUPART_PATH |
请求任何其他组件都会返回非零值,因为它们缺失。
设置 URL 部分
API 允许应用程序通过 CURLU 处理器设置 URL 的各个部分,无论是解析了完整的 URL 之后,还是不解析这样的 URL。
rc = curl_url_set(urlp, CURLUPART_HOST, "www.example.com", 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_PASSWORD, "doe", 0);
rc = curl_url_set(urlp, CURLUPART_PORT, "443", 0);
rc = curl_url_set(urlp, CURLUPART_PATH, "/index.html", 0);
rc = curl_url_set(urlp, CURLUPART_QUERY, "name=john", 0);
rc = curl_url_set(urlp, CURLUPART_FRAGMENT, "anchor", 0);
rc = curl_url_set(urlp, CURLUPART_ZONEID, "25", 0);
API 总是期望在第三个参数中提供一个以空字符终止的 char * 字符串,或者 NULL 以清除字段。请注意,端口号也是以这种方式作为字符串提供的。
除非用户在第四个参数中使用 CURLU_URLENCODE 标志请求它,否则设置的各部分不会进行 URL 编码。
更新部分
通过设置单个部分,例如,你可以首先设置一个完整的 URL,然后更新该 URL 的单个组件,然后提取该 URL 的更新版本。
例如,假设我们有一个这样的 URL
const char *url="http://joe:7Hbz@example.com:8080/images?id=5445#footer";
我们希望将那个 URL 中的主机名更改为 example.net,可以这样做:
CURLU *h = curl_url();
rc = curl_url_set(h, CURLUPART_URL, url, 0);
然后更改主机名部分:
rc = curl_url_set(h, CURLUPART_HOST, "example.net", 0);
然后,现在这个 URL 如下所示:
http://joe:7Hbz@example.net:8080/images?id=5445#footer
如果你继续并将路径部分更改为 /foo,就像这样:
rc = curl_url_set(h, CURLUPART_PATH, "/foo", 0);
然后,URL 处理器现在持有这个 URL:
http://joe:7Hbz@example.net:8080/foo?id=5445#footer
等等……
添加到查询
应用程序可以使用 CURLU_APPENDQUERY 标志将字符串添加到现有查询部分的右侧。
考虑一个持有 URL https://example.com/?shoes=2 的句柄。应用程序可以像这样将字符串 hat=1 添加到查询部分:
rc = curl_url_set(urlp, CURLUPART_QUERY, "hat=1", CURLU_APPENDQUERY);
它甚至注意到了缺少 & 分隔符,因此也注入了一个,句柄的完整 URL 因此变为 https://example.com/?shoes=2&hat=1。
添加的字符串当然也可以在添加时进行 URL 编码,如果需要,编码会跳过 = 字符。例如,将 candy=M&M 添加到我们已有的内容中,并将其 URL 编码以处理数据中的 & 符号:
rc = curl_url_set(urlp, CURLUPART_QUERY, "candy=M&M",CURLU_APPENDQUERY | CURLU_URLENCODE);
现在 URL 看起来像这样:https://example.com/?shoes=2&hat=1&candy=M%26M。
CURLOPT_CURLU
为了方便应用程序,它们可以将已经解析的 URL 传递给 libcurl 进行处理,作为CURLOPT_URL的替代方案。
使用CURLOPT_CURLU选项,您传递一个CURLU句柄而不是 URL 字符串。
示例:
CURLU *h = curl_url();
rc = curl_url_set(h, CURLUPART_URL, "https://example.com/", 0);CURL *easy = curl_easy_init();
curl_easy_setopt(easy, CURLOPT_CURLU, h);
WebSocket
WebSocket 是一种在 HTTP 之上完成的传输协议,它提供了一个通用的双向字节流。该协议是为了不仅仅是简单的上传和下载而创建的,并且更类似于 HTTP 上的 TCP。
WebSocket 客户端应用程序通过一个升级为 WebSocket 的 HTTP 请求来建立连接 - 一旦升级,相关各方将通过该连接使用 WebSocket 进行通信,直到完成并关闭连接。
-
支持
-
URLs
-
概念
-
选项
-
读取
-
元数据
-
写入
支持
WebSocket 是 libcurl 7.86.0 及更高版本中存在的一个 实验性 功能。由于它是实验性的,您需要在构建时显式启用它,才能使其存在并可用。
要确定您的 libcurl 安装是否支持 WebSocket,您可以调用 curl_version_info() 函数(见 ch192.xhtml#libcurl__api__md),并检查返回的结构体中的 ->protocols 字段。它应该包含 ws 以表明其存在,可能还会包含 wss。
网址
客户端通过使用带有ws或wss方案的 URL 与 libcurl 启动 WebSocket 通信。例如,wss://websocket.example.com/traffic-lights。
wss变体用于使用 TLS 安全连接,而ws变体则是通过不安全的明文进行。
概念
A libcurl application can do WebSocket using one of these two different approaches below.
1. 回调方法
It can decide to use the regular write callback to receive incoming data, and respond to that data in or outside of the callback with curl_ws_send. Thereby treating the entire session as a form of download from the server.
Within the write callback, an application can call curl_ws_meta() to retrieve information about the incoming WebSocket data.
2. 仅连接的方法
The other way to do it, if using the write callback is not suitable, is to set CURLOPT_CONNECT_ONLY to the value 2L and let libcurl do a transfer that only sets up the connection to the server, does the WebSocket upgrade and then is considered complete. After that connect-only transfer, the application can use curl_ws_recv() and curl_ws_send() to receive and send WebSocket data over the connection.
升级或死亡
Doing a transfer with a ws:// or wss:// URL implies that libcurl makes a successful upgrade to the WebSocket protocol or an error is returned. An HTTP 200 response code which for example is considered fine in a normal HTTP transfer is therefore considered an error when asking for a WebSocket transfer.
自动 PONG
If not using raw mode, libcurl automatically responds with the appropriate PONG response for incoming PING frames and does not expose them in the API.
选项
应用程序有一个专门的 setopt 选项来控制 WebSocket 通信:CURLOPT_WS_OPTIONS。
此选项设置一个标志位的掩码给 libcurl,但到目前为止,只有一个位被使用。
原始模式
通过在掩码中设置 CURLWS_RAW_MODE 位,libcurl 将所有 WebSocket 流量以原始形式传递给写入回调,而不是自己解析 WebSocket 流量。这种原始模式适用于可能已经实现了 WebSocket 处理的应用程序,它们只想迁移到使用 libcurl 进行传输并保持自己的 WebSocket 逻辑。
在原始模式下,libcurl 也不会自动处理任何 PING 流量。
读取
应用程序可以通过以下两种方法之一接收和读取传入的 WebSocket 流量:
写入回调
当未设置 CURLOPT_CONNECT_ONLY 选项时,WebSocket 数据将传递给写入回调。
在默认帧模式(与原始模式相对),libcurl 在数据到达时将 WebSocket 数据片段的部分传递给回调。然后应用程序可以调用 curl_ws_meta() 来获取传递给回调的特定帧的信息。
libcurl 可以传递完整的片段或部分片段,这取决于何时通过网络传输的内容。每个 WebSocket 片段的大小可以达到 63 位。
curl_ws_recv
如果设置了仅连接选项,则在将 WebSocket 设置到远程主机后,传输结束,从那时起应用程序需要调用 curl_ws_recv() 来读取 WebSocket 数据,并调用 curl_ws_send() 来发送它。
curl_ws_recv 函数具有以下原型:
CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,size_t *recv, struct curl_ws_frame **meta);
curl - 转移的句柄
buffer - 指向一个缓冲区的指针,用于接收 WebSocket 数据
buflen - 缓冲区的大小(以字节为单位)
recv - 返回时存储在 buffer 中的数据的大小(以字节为单位)
meta - 获取一个指向包含 接收帧信息 的结构的指针。
元数据
curl_ws_recv()和curl_ws_meta()都返回一个指向curl_ws_frame结构的指针,该结构提供了关于传入 WebSocket 数据的信息。在这种情况下,WebSocket“帧”是 WebSocket 片段的一部分。它可以是一个完整的片段,但也可能只是其中的一部分。curl_ws_frame包含有关帧的信息,以告知您详细信息。
struct curl_ws_frame {int age; /* zero */int flags; /* See the CURLWS_* defines */curl_off_t offset; /* the offset of this data into the frame */curl_off_t bytesleft; /* number of pending bytes left of the payload */
};
age
这只是一个标识此结构年龄的数字。现在它始终为 0,但将来可能会增加,那时结构可能会增长。
flags
flags字段是一个位掩码,描述了数据的详细信息。
CURLWS_TEXT
缓冲区包含文本数据。请注意,这会对 WebSocket 产生影响,但 libcurl 本身并不验证内容或采取任何预防措施来确保您接收到的确实是有效的 UTF-8 内容。
CURLWS_BINARY
这是二进制数据。
CURLWS_FINAL
这是消息的最后一个片段,如果未设置,则表示还有另一个片段作为同一消息的一部分即将到来。
CURLWS_CLOSE
此传输现在已关闭。
CURLWS_PING
这是一个接收到的 ping 消息,它期望得到一个 pong 响应。
offset
当传递的数据只是更大片段的一部分时,这标识了此片段属于更大片段的字节数偏移量。
bytesleft
在此帧之后,剩余未完成此片段的未处理负载字节数。
WebSocket 片段的最大大小为 63 位。
写入
应用程序可以通过两种不同的方式接收 WebSocket 数据,但发送数据的方式只有一种:curl_ws_send()函数。
curl_ws_send()
CURLcode curl_ws_send(CURL *curl, const void *buffer, size_t buflen,size_t *sent, curl_off_t fragsize,unsigned int sendflags);
curl - 传输句柄
buffer - 指向要发送的帧数据的指针
buflen - buffer中数据的长度(以字节为单位)
fragsize - 整个片段的总大小,用于发送较大片段的一部分。
sent - 发送的字节数
flags - 描述数据的位掩码。请参见下面的位描述。
完整片段与部分片段
要发送完整的 WebSocket 片段,将fragsize设置为零,并为所有其他参数提供数据。
要以更小的片段发送片段:使用fragsize设置为总片段大小发送第一部分。你必须知道并提供整个片段的大小,然后才能发送它。在随后的curl_ws_send()调用中,使用fragsize设置为零但在flags参数中设置CURLWS_OFFSET位发送片段的下一部分。重复此操作,直到发送构成整个片段的所有部分。
标志
CURLWS_TEXT
缓冲区包含文本数据。请注意,这会对 WebSocket 产生影响,但 libcurl 本身并不对内容进行任何验证,也不会采取任何预防措施来确保你发送的是有效的 UTF-8 内容。
CURLWS_BINARY
这是二进制数据。
CURLWS_CONT
这不是消息的最终片段,这暗示了作为同一消息的一部分,还有另一个片段即将到来,其中此位未设置。
CURLWS_CLOSE
关闭此传输。
CURLWS_PING
这作为 ping。
CURLWS_PONG
这作为回显。
CURLWS_OFFSET
提供的数据仅是部分片段,还有更多数据将在随后的curl_ws_send()调用中到来。当仅以这种方式发送片段的一部分时,fragsize必须在第一次调用中提供总预期的帧大小,并且在后续调用中需要设置为零。
当CURLWS_OFFSET被设置时,不应设置其他标志位,因为这将是之前发送的延续,并且当时已设置了描述片段的位。
头部 API
libcurl 提供了一个 API,用于遍历所有接收到的 HTTP 头部信息,并从特定的头部中提取内容。
当返回头部内容时,libcurl 会去除前导和尾随空格,但不会以任何其他方式修改或更改内容。
此 API 从 libcurl 7.84.0 版本开始正式提供。
头部来源
HTTP 头部是键值对,在传输过程中,服务器从几个不同的 来源 发送这些键值对。libcurl 收集所有头部信息,并为应用程序提供方便的访问。
HTTP 头部可以以以下方式到达
-
CURLH_HEADER - 在常规响应内容之前。
-
CURLH_TRAILER - 在响应内容 之后 到达的字段
-
CURLH_CONNECT - 在实际服务器请求之前可能完成的代理
CONNECT请求的响应头部 -
CURLH_1XX - 可能先于后续 >= 2xx 响应代码的潜在 1xx HTTP 响应中的头部。
-
CURLH_PSEUDO - 以冒号 (
:) 开头的 HTTP/2 和 HTTP/3 层级头部
请求编号
使用 libcurl 完成的单个 HTTP 传输可能由一系列 HTTP 请求组成,而头部 API 函数的 request 参数允许你指定你想要从哪个特定的单个请求中获取头部信息。0 表示第一个请求,然后数字随着进一步的重定向或使用多状态身份验证而增加。传入 -1 是到系列中最后一个请求的快捷方式,不受实际请求数量影响。
头部折叠
HTTP/1 头部支持一个已弃用的格式,称为 折叠,这意味着在头部之后有一个续行,使得行折叠。
头部 API 支持折叠头部,并返回展开的内容 - 其中不同部分由单个空格字符分隔。
当
在传输过程中,可以在任何时间调用两个头部 API 函数调用,无论是从回调内部还是外部。然而,重要的是要记住,API 只返回在调用时的头部状态信息,这可能不是最终状态,如果你在传输仍在进行时调用它。
-
头部结构
-
获取头部
-
遍历头部
Header 结构体
头部结构指针是头部 API 函数返回的,指向与 easy 句柄关联的内存,后续对该函数的调用会破坏该结构。如果应用程序想要保留数据,则需要复制数据。用于结构体的内存会在调用curl_easy_cleanup()时释放。
结构体
struct curl_header {char *name;char *value;size_t amount;size_t index;unsigned int origin;void *anchor;
};
名称是报头的名称。它使用报头首次出现时使用的首字母大小写。
值是内容。它正好像通过网络传递的那样,但去除了前导和尾随空格以及换行符。数据总是以空字符终止。
数量是指使用此名称的报头在请求的源和请求上下文中的存在数量。
索引是此特定报头名称的基于零的条目编号,如果此报头在请求范围内被使用超过一次,则可能大于 0,但总是小于数量。
源设置了(正好)一个源位,指示报头从哪里起源。
锚点是 libcurl 内部使用的私有句柄。不要修改。不要对此有任何假设。
获取标题
CURLHcode curl_easy_header(CURL *easy,const char *name,size_t index,unsigned int origin,int request,struct curl_header **hout);
此函数返回有关具有特定名称的字段的信息,并且您要求该函数在一个或多个来源中搜索它。
index 参数是当您想要请求标题的第 n 次出现时;当有多个可用时。将 index 设置为 0 返回第一个实例 - 在许多情况下,这可能是唯一的。
request 参数告诉 libcurl 您想要从哪个请求中获取标题。
应用程序需要在最后一个参数中传递一个指向 struct curl_header * 的指针,因为当没有错误返回时,会从那里返回一个指针。有关成功调用的 out 结果的详细信息,请参阅标题结构。
如果给定的名称与给定来源中接收到的任何标题都不匹配,则函数返回 CURLHE_MISSING,或者如果没有接收任何标题,则返回 CURLHE_NOHEADERS。
遍历头信息
struct curl_header *curl_easy_nextheader(CURL *easy,unsigned int origin,int request,struct curl_header *previous);
此函数允许应用程序遍历给定来源中所有可用的头信息。
请求参数告诉 libcurl 从哪个请求中获取头信息。
如果previous设置为 NULL,此函数将返回第一个头信息的指针。然后应用程序可以使用该指针作为下一个调用的参数,遍历同一来源和请求上下文中的所有可用头信息。
当此函数返回 NULL 时,上下文中没有更多的头信息。
请参阅头结构,了解该函数返回的curl_header结构体的详细信息。
libcurl 示例
libcurl 的本地 API 是用 C 语言编写的,因此本章主要关注用 C 语言编写的示例。但由于许多 libcurl 的语言绑定都很薄,它们通常暴露或多或少相同的函数,因此对于其他语言的用户来说,它们仍然是有趣且具有教育意义的。
-
获取简单的 HTTP 页面
-
在内存中获取页面
-
通过 HTTP 提交登录表单
-
获取 FTP 目录列表
-
非阻塞 HTTP 表单提交
获取简单的 HTTP 页面
此示例仅从给定的 URL 获取 HTML 并将其发送到 stdout。可能是你可以编写的最简单的 libcurl 程序。
通过替换 URL,这也可以获取其他支持的协议的内容。
将输出发送到 stdout 是默认行为,通常不是你真正想要的。大多数应用程序会安装一个 写入回调 来接收到达的数据。
#include <stdio.h>
#include <curl/curl.h>int main(void)
{CURL *curl;CURLcode res;curl = curl_easy_init();if(curl) {curl_easy_setopt(curl, CURLOPT_URL, "http://example.com/");/* Perform the request, 'res' holds the return code */res = curl_easy_perform(curl);/* Check for errors */if(res != CURLE_OK)fprintf(stderr, "curl_easy_perform() failed: %s\n",curl_easy_strerror(res));/* always cleanup */curl_easy_cleanup(curl);}return 0;
}
将响应存储到内存中
这个例子是前者的一个变体,它不是将接收到的数据发送到标准输出(这通常不是你想要的),而是将传入的数据存储在内存缓冲区中,随着传入数据的增长,该缓冲区也会相应扩大。
它通过使用一个写入回调来接收数据来实现这一点。
这个例子使用了一个固定的 URL 字符串和固定的 URL 方案,但你当然可以更改它以使用任何其他支持的协议,然后从该协议获取资源。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <curl/curl.h>struct MemoryStruct {char *memory;size_t size;
};static size_t
mem_cb(void *contents, size_t size, size_t nmemb, void *userp)
{size_t realsize = size * nmemb;struct MemoryStruct *mem = (struct MemoryStruct *)userp;mem->memory = realloc(mem->memory, mem->size + realsize + 1);if(mem->memory == NULL) {/* out of memory */printf("not enough memory (realloc returned NULL)\n");return 0;}memcpy(&(mem->memory[mem->size]), contents, realsize);mem->size += realsize;mem->memory[mem->size] = 0;return realsize;
}int main(void)
{CURL *curl_handle;CURLcode res;struct MemoryStruct chunk;chunk.memory = malloc(1); /* grown as needed by the realloc above */chunk.size = 0; /* no data at this point */curl_global_init(CURL_GLOBAL_ALL);/* init the curl session */curl_handle = curl_easy_init();/* specify URL to get */curl_easy_setopt(curl_handle, CURLOPT_URL, "https://www.example.com/");/* send all data to this function */curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, mem_cb);/* we pass our 'chunk' struct to the callback function */curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);/* some servers do not like requests that are made without a user-agentfield, so we provide one */curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");/* get it! */res = curl_easy_perform(curl_handle);/* check for errors */if(res != CURLE_OK) {fprintf(stderr, "curl_easy_perform() failed: %s\n",curl_easy_strerror(res));}else {/** Now, our chunk.memory points to a memory block that is chunk.size* bytes big and contains the remote file.** Do something nice with it*/printf("%lu bytes retrieved\n", (long)chunk.size);}/* cleanup curl stuff */curl_easy_cleanup(curl_handle);free(chunk.memory);/* we are done with libcurl, so clean it up */curl_global_cleanup();return 0;
}
通过 HTTP 提交登录表单
通过 HTTP 提交登录通常是一个确定在 POST 中提交哪些数据以及将其发送到哪个目标 URL 的问题。
登录成功后,如果使用了正确的 cookies,可以获取目标 URL。由于许多登录系统使用 HTTP 重定向,我们要求 libcurl 在收到重定向时跟随。
一些登录表单使得过程更加复杂,并要求您从显示登录表单的页面获取 cookies 等,因此如果您需要这些,可能需要稍微扩展一下此代码。
通过传递一个不存在的 cookie 文件,此示例启用了 cookie 解析器,以便在登录响应的响应到达时存储传入的 cookies,然后后续的资源请求使用这些 cookies 并向服务器证明我们确实已经正确登录。
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>int main(void)
{CURL *curl;CURLcode res;static const char *postthis = "user=daniel&password=monkey123";curl = curl_easy_init();if(curl) {curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/login.cgi");curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postthis);curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); /* redirects */curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); /* no file */res = curl_easy_perform(curl);/* Check for errors */if(res != CURLE_OK)fprintf(stderr, "curl_easy_perform() failed: %s\n",curl_easy_strerror(res));else {/** After the login POST, we have received the new cookies. Switch* over to a GET and ask for the login-protected URL.*/curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/file");curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); /* no more POST */res = curl_easy_perform(curl);/* Check for errors */if(res != CURLE_OK)fprintf(stderr, "second curl_easy_perform() failed: %s\n",curl_easy_strerror(res));}/* always cleanup */curl_easy_cleanup(curl);}return 0;
}
获取 FTP 目录列表
此示例仅从给定的 URL 获取 FTP 目录输出并将其发送到 stdout。URL 中的尾部斜杠使得 libcurl 将其视为目录。
#include <curl/curl.h>int main(void)
{CURL *curl;CURLcode res;curl_global_init(CURL_GLOBAL_DEFAULT);curl = curl_easy_init();if(curl) {/** Make the URL end with a trailing slash*/curl_easy_setopt(curl, CURLOPT_URL, "ftp://ftp.example.com/");res = curl_easy_perform(curl);/* always cleanup */curl_easy_cleanup(curl);if(CURLE_OK != res) {/* we failed */fprintf(stderr, "curl told us %d\n", res);}}curl_global_cleanup();return 0;
}
非阻塞 HTTP 表单-post
这个示例使用多接口创建了一个多部分表单-post。
#include <stdio.h>
#include <string.h>
#include <sys/time.h>#include <curl/curl.h>int main(void)
{CURL *curl;CURLM *multi_handle;int still_running = 0;curl_mime *form = NULL;curl_mimepart *field = NULL;struct curl_slist *headerlist = NULL;static const char buf[] = "Expect:";curl = curl_easy_init();multi_handle = curl_multi_init();if(curl && multi_handle) {/* Create the form */form = curl_mime_init(curl);/* Fill in the file upload field */field = curl_mime_addpart(form);curl_mime_name(field, "sendfile");curl_mime_filedata(field, "multi-post.c");/* Fill in the filename field */field = curl_mime_addpart(form);curl_mime_name(field, "filename");curl_mime_data(field, "multi-post.c", CURL_ZERO_TERMINATED);/* Fill in the submit field too, even if this is rarely needed */field = curl_mime_addpart(form);curl_mime_name(field, "submit");curl_mime_data(field, "send", CURL_ZERO_TERMINATED);/* initialize custom header list (stating that Expect: 100-continue isnot wanted */headerlist = curl_slist_append(headerlist, buf);/* what URL that receives this POST */curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/upload.cgi");curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);curl_easy_setopt(curl, CURLOPT_MIMEPOST, form);curl_multi_add_handle(multi_handle, curl);do {CURLMcode mc = curl_multi_perform(multi_handle, &still_running);if(still_running)/* wait for activity, timeout or "nothing" */mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);if(mc)break;} while(still_running);curl_multi_cleanup(multi_handle);/* always cleanup */curl_easy_cleanup(curl);/* then cleanup the form */curl_mime_free(form);/* free slist */curl_slist_free_all(headerlist);}return 0;
}
libcurl 绑定
创意人士为各种环境和编程语言编写了绑定或接口。使用其中之一可以让您在您喜欢的语言或系统中利用 curl 的强大功能。以下是截至本文写作时所有已知接口的列表。
列出的绑定不属于 curl/libcurl 分发存档的一部分。它们必须单独下载和安装。
| 语言 | 网站 | 作者(s) |
|---|---|---|
| Script Basic | scriptbasic.com/ |
Peter Verhas |
| C++ | www.curlpp.org/ |
Jean-Philippe, Barrette-LaPierre |
| C++ | github.com/JosephP91/curlcpp |
Giuseppe Persico |
| C++ | github.com/libcpr/cpr |
Huu Nguyen |
| Ch/C++ | chcurl.sourceforge.io/ |
Stephen Nestinger, Jonathan Rogado |
| Cocoa (BBHTTP) | github.com/biasedbit/BBHTTP |
Bruno de Carvalho |
| Cocoa (CURLHandle) | github.com/karelia/curlhandle/ |
Dan Wood |
| Clojure | github.com/lsevero/clj-curl |
Lucas Severo |
| D | dlang.org/library/std/net/curl.html |
Kenneth Bogert |
| Delphi | github.com/Mercury13/curl4delphi |
Mikhail Merkuryev |
| Dylan | opendylan.org/ |
Chris Double |
| Eiffel | iron.eiffel.com/repository/20.11/package/ABEF6975-37AC-45FD-9C67-52D10BA0669B |
Eiffel Software |
| Erlang | github.com/puzza007/katipo |
Paul Oliver |
| Falcon | www.falconpl.org/project_docs/curl/ |
Falcon |
| Gambas | gambas.sourceforge.io/ |
Gambas |
| glib/GTK+ | web.archive.org/web/20230204213618/atterer.org/glibcurl |
Richard Atterer |
| Go | github.com/andelf/go-curl |
ShuYu Wang |
| Guile | web.archive.org/web/20210417020142/www.lonelycactus.com/guile-curl.html |
Michael L. Gran |
| Harbour | github.com/vszakats/harbour-core/tree/master/contrib/hbcurl |
Viktor Szakáts |
| Haskell | hackage.haskell.org/package/curl |
Galois, Inc |
| Java | github.com/pjlegato/curl-java |
Paul Legato |
| Julia | github.com/JuliaWeb/LibCURL.jl |
Amit Murthy |
| Lisp | common-lisp.net/project/cl-curl/ |
Liam Healy |
| Lua-cURL | github.com/Lua-cURL/Lua-cURLv3 |
Jürgen Hötzel, Alexey Melnichuk |
| .NET | github.com/masroore/CurlSharp |
Masroor Ehsan Choudhury, Jeffrey Phillips |
| Nim | nimble.directory/pkg/libcurl |
Andreas Rumpf |
| NodeJS | github.com/JCMais/node-libcurl |
Jonathan Cardoso Machado |
| OCaml | ygrek.org/p/ocurl/ |
Lars Nilsson |
| Pascal/Delphi/Kylix | curlpas.sourceforge.io/curlpas/ |
Jeffrey Pohlmeyer |
| Perl | github.com/szbalint/WWW--Curl |
Cris Bailiff and Bálint Szilakszi |
| Perl | metacpan.org/pod/Net::Curl |
Przemyslaw Iskra |
| Perl6 | github.com/azawawi/perl6-net-curl |
Ahmad M. Zawawi |
| PHP | php.net/curl |
Sterling Hughes |
| PostgreSQL | github.com/pramsey/pgsql-http |
Paul Ramsey |
| PostgreSQL | github.com/RekGRpth/pg_curl |
RekGRpth |
| PureBasic | www.purebasic.com/documentation/http/ |
PureBasic |
| Python (mcurl) | pypi.org/project/pymcurl/ |
Ganesh Viswanathan |
| Python (PycURL) | github.com/pycurl/pycurl |
Kjetil Jacobsen |
| R | cran.r-project.org/package=curl |
Jeroen Ooms, Hadley Wickham, RStudio |
| Rexx | rexxcurl.sourceforge.io/ |
Mark Hessling |
| Ring | ring-lang.sourceforge.io/doc1.3/libcurl.html |
Mahmoud Fayed |
| RPG | github.com/curl/curl/blob/master/packages/OS400/README.OS400 |
Patrick Monnerat |
| Ruby (curb) | github.com/taf2/curb |
Ross Bamford |
| Ruby (ruby-curl-multi) | github.com/kball/curl_multi.rb |
Kristjan Petursson and Keith Rarick |
| Rust (curl-rust) | github.com/alexcrichton/curl-rust |
Carl Lerche |
| Scheme Bigloo | www.metapaper.net/lisovsky/web/curl/ |
Kirill Lisovsky |
| Scilab | help.scilab.org/docs/current/fr_FR/getURL.html |
西尔维斯特尔·莱德鲁 |
| S-Lang | www.jedsoft.org/slang/modules/curl.html |
约翰·E·戴维斯 |
| Smalltalk | www.squeaksource.com/CurlPlugin/ |
丹尼尔·奥西普丘克 |
| SP-Forth | sourceforge.net/p/spf/spf/ci/master/tree/devel/~ac/lib/lin/curl/ |
安德烈·切列佐夫 |
| Tcl | mirror.yellow5.com/tclcurl/ |
安德烈斯·加西亚 |
| Visual Basic | sourceforge.net/projects/libcurl-vb/ |
杰弗里·菲利普斯 |
| wxWidgets | wxcode.sourceforge.io/components/wxcurl/ |
凯西·奥唐奈尔 |
| Xojo | github.com/charonn0/RB-libcURL |
安德鲁·兰伯特 |
libcurl 内部
libcurl 永远不会完成,它不仅仅是一个现成的产品。它是一个活生生的项目,几乎每天都在改进和修改。我们依赖于熟练且感兴趣的黑客来修复错误和添加功能。
本章旨在描述内部细节,以帮助热衷于 libcurl 的黑客学习一些关于 libcurl 内部工作方式的基本概念,从而可能找到问题所在或在你想要让库执行新功能时知道如何添加内容。
-
简单句柄和连接
-
一切都是多任务
-
状态机
-
协议处理器
-
后端
-
缓存和状态
-
超时
-
Windows 与 Unix
-
内存调试
-
内容编码
-
结构体
-
解析主机名
-
测试
简单句柄和连接
在阅读源代码时,有一些有用的基础知识是值得了解并记住的:
-
‘data’ 是我们在整个程序中使用来引用正在处理的传输的简单句柄(
struct Curl_easy)的变量名。不应使用其他名称来表示这个句柄,并且没有其他东西应该使用这个名称。简单句柄是识别传输的主要对象。传输通常在某个时刻使用一个连接,并且通常一次只使用一个。存在一个data->conn指针,用于标识当前由这个传输使用的连接。单个连接可以在一段时间内被使用,甚至在使用多路复用连接时,可以被多个传输(以及简单句柄)同时使用。 -
conn是我们在内部使用来引用当前代码正在工作的 连接(struct connectdata)的变量名。 -
result是我们通常用于存储函数返回值的CURLcode变量的名称,如果该返回值不等于零,则表示错误,函数应该清理并返回(通常将相同的错误代码传递给其父函数)。
一切都是多任务
libcurl 提供了几个不同的 API 来进行传输;其中主要区别在于同步的简单接口与非阻塞的多接口。多接口本身可以通过使用事件驱动的套接字接口或正常的执行接口进一步使用。
然而,内部一切都是为事件驱动的接口编写的。所有内容都需要以非阻塞方式编写,以确保函数在循环或类似操作中永远不会等待数据。除非它们是具有明确功能的外部函数。
curl_easy_perform() 函数执行单个同步传输,它本身只是一个包装函数,在内部设置并使用多接口本身。
状态机
为了在整个过程中实现非阻塞行为,curl 源中充满了状态机。处理尽可能多的数据,并根据可用情况驱动状态机到达可以继续的位置,允许函数在之后有更多数据到达时从该点继续执行。
对于给定的传输,存在许多不同级别的状态,并且每个特定协议的代码可能都有自己的状态机集合。
mstate
其中一个主要状态是 easy 句柄持有的主要传输“模式”,这表示当前传输是否正在解析、等待解析、连接、等待连接、发送请求、进行传输等(参见lib/multihandle.h中的CURLMstate枚举)。使用 libcurl 完成的每个传输都有一个关联的 easy 句柄,每个 easy 句柄都会执行该状态机。
下面的图像显示了所有状态和可能的状态转换。详见以下解释。

libcurl 传输状态机
所有传输都从初始化状态开始,并以发送消息状态结束。
黄色:初始设置状态
蓝色:解析名称和设置连接
绿色:启动和设置传输
白色:传输过程
红色:传输后
所有位于条纹区域内的状态都关联着一个连接。
协议处理程序
libcurl 是一个多协议传输库。代码的核心是一组通用的函数,用于一般的传输,并且对于所有协议来说大多数情况下工作方式相同。上面描述的主要状态机就在那里,并且适用于所有协议——即使某些协议可能不会在所有传输中使用所有状态。
然而,curl 支持的每种不同协议都有其独特的特性和专长。为了避免代码中充斥着“如果协议是 XYZ,那么做……”这样的条件语句,我们引入了Curl_handler的概念。每个支持的协议在lib/url.c中定义了一个这样的处理程序,那里有一个指向这些处理程序的指针数组protocols[]。
当一个传输即将进行时,libcurl 会解析它即将操作的 URL,并确定要使用哪种协议。通常,这可以通过查看 URL 的方案部分来完成。对于https://example.com,这是https,对于imaps://example.com,它是imaps。使用提供的方案,libcurl 将conn->handler指针设置为处理该 URL 的协议的处理程序结构体。

libcurl 协议处理程序
处理程序结构体包含一组函数指针,可以是 NULL 或设置为指向特定于协议的函数,以执行该协议在传输中工作所需的事情。不是所有其他协议都需要的事情。处理程序结构体还设置了协议的名称,并使用位掩码描述其功能集。
libcurl 的传输是围绕一组不同的动作构建的,处理程序可以扩展每一个动作。以下是一些该结构体中的示例函数指针及其使用方式:
设置连接
如果一个连接不能用于传输,它需要设置一个连接到 URL 中给出的主机,并且当它这样做时,它也可以调用该协议处理程序的功能。如下所示:
if(conn->handler->setup_connection)result = conn->handler->setup_connection(data, conn);
连接
在连接建立之后,这个函数会被调用
if(conn->handler->connect_it)result = conn->handler->connect_it(data, &done);
Do
Do 简单地是发出对 URL 标识的特定资源的请求的动作。所有协议都有一个 do 动作,因此必须提供这个函数:
result = conn->handler->do_it(data, &done);
Done
当传输完成时,执行 done 动作:
result = conn->handler->done(data, status, premature);
断开连接
连接即将断开。
result = conn->handler->disconnect(data, conn, dead_connection);
后端
curl 中的后端是一个构建时可选的替代实现。
当你构建 curl 时,你可以为几个不同的事情选择替代实现。相同功能集的不同提供者。当你构建 curl 时,你选择使用哪个后端或后端(复数)。
-
后端是可选的,也可以取消选择
-
通常与平台相关
-
功能可能不同
-
第三方许可证可能不同
-
成熟度可能不同
-
内部 API 永远不会对外暴露
不同的后端
在 libcurl 源代码中,存在内部 API 以提供功能。在这些不同的区域有多个不同的提供者:
-
IDN
-
名称解析
-
TLS
-
SSH
-
HTTP/1 和 HTTP/2
-
HTTP/3
-
HTTP 内容编码
后端可视化

libcurl 后端
应用程序(在上方的黄色云中)通过公共 API 访问 libcurl。API 是固定和稳定的。
内部,libcurl 的核心使用内部 API 来完成它需要执行的不同任务。每个内部 API 都由替代实现提供动力,很多时候是由不同的第三方库提供动力。
上面的图像展示了不同第三方库为不同的内部 API 提供动力。紫色方框表示一个或多个,深灰色方框表示“这些中的一个”。
缓存和状态
当 libcurl 用于互联网传输时,它将数据存储在缓存中,并将状态存储在状态存储中,以便更快、更好地执行后续传输。
缓存与CURL或CURLM句柄相关联,具体取决于使用的 libcurl API 是简单模式还是多线程模式。
DNS 缓存
当 libcurl 解析主机名的 IP 地址时,它将结果存储在其 DNS 缓存中(默认生存时间为 60 秒),以便后续查找可以立即使用缓存数据,而不是再次执行(可能较慢)的解析操作。此缓存仅存在于内存中。
连接缓存
也称为连接池。这是 curl 在传输完成后将活动连接放置的地方,以便后续传输可能能够使用已经存在的连接而不是必须设置一个新的连接。当连接被重用时,curl 避免名称查找、TLS 握手等操作。此缓存仅存在于内存中。
TLS 会话 ID 缓存
当 curl 使用 TLS 时,它将会话 ID保存在缓存中。当后续传输需要与具有缓存会话 ID 的主机重新执行 TLS 握手时,握手可以更快地完成。此缓存仅存在于内存中。
CA 存储缓存
当 curl 创建新的连接并执行 TLS 握手时,它需要加载和解析CA 存储以用于验证远程服务器提供的证书。CA 存储缓存将解析的 CA 存储在内存中一段时间(默认为 24 小时),以便后续握手通过避免重新解析可能的大量数据而更快地完成。此缓存仅存在于内存中。自 7.87.0 版本添加。
HSTS
HSTS 是 HTTP 严格传输安全。HTTPS 服务器可以通知客户端,它们希望客户端今后仅使用 HTTPS 连接到其主机名,而不是 HTTP,即使使用HTTP:// URL。curl 将此连接升级信息保存在内存中,并可以指示从磁盘加载它并将它保存到磁盘。
Alt-Svc
Alt-Svc:是一个 HTTP 响应头,通知客户端有关同一服务也提供的替代主机名、端口号和协议版本。curl 将此替代服务信息保存在内存中,并可以指示从磁盘加载它并将它保存到磁盘。
Cookies
Cookies 是由 HTTP 服务器发送到客户端的名称值对,旨在在后续请求中发送回匹配条件。curl 将所有 Cookies 保存在内存中,并可以指示从磁盘加载它们以及将它们保存到磁盘。
超时
所有内部操作都需要以非阻塞方式编写,不能只是挂起并等待事件发生。同时,多接口允许用户在几乎任何时间调用 libcurl,即使没有发生任何操作或超时触发。
仅向应用暴露单个超时
在外部 API 中,libcurl 每次只提供一个超时,无论有多少并发传输以及设置了哪些选项。应用程序可以通过curl_multi_timeout()或CURLMOPT_TIMERFUNCTION回调函数来获取超时值,具体取决于它想使用哪个 API。
在内部,操作如下:
-
每个简单句柄都保留一个超时数组,按顺序排列。时间最接近(下次超时)的位于列表之首。
-
所有简单句柄都被放入一个伸展树中,这是一个二叉自平衡搜索树,使得根据超时插入和删除节点变得快速。
-
一旦任何句柄的下次超时时间发生变化,伸展树就会重新平衡。
提取带有过期超时的简单句柄是一个快速操作。
设置超时
用于设置超时的内部函数称为Curl_expire()。它要求 libcurl 在未来的一定毫秒数后再次调用此句柄。通过特定的 ID 设置超时,以确保它覆盖了之前为同一超时设置的值等。现有的超时 ID 是有限的,并且是硬编码的。
可以使用Curl_expire_clear()再次移除超时,这将从给定简单句柄的超时列表中移除该超时。
过期的超时
超时到期意味着应用程序知道它需要再次调用 libcurl。当使用socket_action API 时,它甚至知道需要再次为超时到期的特定简单句柄调用 libcurl。
当超时到期时,除了调用 perform 函数外,没有其他特殊动作或活动发生。每个状态或内部函数都需要知道要检查哪些时间或状态,并在被调用时(再次)相应地执行。
Windows 与 Unix
与 Windows 方式相比,Unix 方式编程 curl 存在一些差异。可能最显著的四个细节是:
套接字操作的不同函数名
在 curl 中,这是通过定义和宏来解决的,因此除了定义它们的头文件之外,源代码在所有地方看起来都是一样的。使用的宏是 sclose()、sread() 和 swrite()。
初始化调用
Windows 需要对套接字进行几个初始化调用。
这由 curl_global_init() 调用处理,但如果其他库也这样做等,可能存在应用程序需要改变该行为的原因。
我们需要 WinSock 版本 2.2,并在全局初始化期间加载此版本。
文件描述符
网络通信和文件操作的文件描述符不像在 Unix 中那样容易互换。
我们通过不在文件描述符上尝试任何奇怪技巧来避免这种情况。
标准输出
当向标准输出写入数据时,Windows 会以 DOS 方式处理行结束符,从而破坏二进制数据,尽管如果你处理的是文本数据,你可能希望进行这种转换……(叹息)
在 Windows 下,我们将标准输出设置为二进制
Ifdefs
在源代码内部,我们努力避免使用 #ifdef [Your OS]。所有处理功能的条件语句应该以 #ifdef HAVE_THAT_WEIRD_FUNCTION 的格式出现。由于 Windows 无法运行配置脚本,我们在 lib 目录中维护一个 curl_config-win32.h 文件,该文件应该与 Windows 机器上的 curl_config.h 文件看起来完全一样。
一般而言:curl 经常在几十种操作系统上编译。不要走钢丝。
内存调试
文件 lib/memdebug.c 包含了一些函数的调试版本。例如 malloc()、free()、fopen()、fclose() 等等,这些函数以某种方式处理可能会给我们带来问题的资源。memdebug 系统中的函数不做任何花哨的事情,它们执行正常功能,然后记录它们刚刚做了什么的信息。这些记录的数据可以在完整会话后进行分析,
memanalyze.pl 是位于 tests/ 中的 perl 脚本,它分析由内存跟踪系统生成的日志文件。它检测资源是否已分配但从未释放,以及其他与资源管理相关的错误。
在内部,预处理器符号 DEBUGBUILD 的定义限制了仅针对调试启用构建编译的代码。符号 CURLDEBUG 用于区分仅用于内存跟踪/调试的代码。
在编译时使用 -DCURLDEBUG 以启用内存调试,这也可以通过运行配置脚本并使用 --enable-curldebug 来启用。在编译时使用 -DDEBUGBUILD 以启用调试构建,或者运行配置脚本并使用 --enable-debug。
curl --version 列出了调试启用构建的 Debug 功能,以及 curl 调试内存跟踪功能 TrackMemory。这些功能是独立的,可以在运行配置脚本时进行控制。当提供 --enable-debug 时,这两个功能都会被启用,除非某些限制阻止了内存跟踪的使用。
跟踪内存泄漏
…使用内存调试系统。一般来说,我们建议首先使用 valgrind。
单线程
请注意,此内存泄漏系统尚未调整以在多个线程中工作。如果您想在多线程应用程序中使用它,请相应地进行调整。
构建
使用 -DCURLDEBUG 重新构建 libcurl(通常,重新运行配置脚本并使用 --enable-debug 可以解决这个问题)。首先执行 make clean,然后执行 make 以确保所有文件都正确地重新构建。此外,使用带有调试选项(通常是编译器的 -g)构建 libcurl 也是有意义的,这样如果实际上在库中找到泄漏,调试会更加容易。
这构建了一个启用了内存调试的库。
修改您的应用程序
在您的应用程序代码中添加一行:
curl_dbg_memdebug("dump");
这使得 malloc 调试系统输出所有资源使用函数到指定的文件名。确保您重新构建程序,并且与上述描述中为该目的构建的相同 libcurl 链接。
运行您的应用程序
按照常规运行您的程序。观察指定的内存跟踪文件增长。
让您的程序退出并使用适当的 libcurl 清理函数等,以确保所有非泄漏都返回/释放正确。
分析流程
使用 tests/memanalyze.pl perl 脚本分析转储文件:
$ tests/memanalyze.pl dump
现在会输出关于已分配但从未释放的资源等的报告。这个报告适合发布到列表中。
如果没有产生任何输出,libcurl 中未检测到泄漏。那么泄漏很可能是出现在您的代码中。
内容编码
关于内容编码
[HTTP/1.1][4] 规定,客户端可以请求服务器对响应进行编码。这通常用于使用一组常用压缩技术之一(或多个)压缩响应。这些方案包括 deflate(zlib 算法)、gzip、br(brotli)和 compress。客户端通过在请求文档中包含 Accept-Encoding 报头来请求服务器执行编码。报头的值应该是已识别的令牌之一 deflate、…(有一种方法可以注册新的方案/令牌,请参阅规范的第 3.5 节)。服务器可以尊重客户端的编码请求。当响应被编码时,服务器会在响应中包含一个 Content-Encoding 报头。Content-Encoding 报头的值指示使用了哪些编码来编码数据,以及它们应用的顺序。
客户端还可以对不同方案附加优先级,以便服务器知道它更喜欢哪一个。有关 Accept-Encoding 报头的信息,请参阅 RFC 2616 的第 14.3 节。有关 Content-Encoding 报头的信息,请参阅 RFC 7231 的第 3.1.2.2 节。
支持的内容编码
libcurl 支持 deflate、gzip、zstd 和 br 内容编码。常规和分块传输都能正常工作。deflate 和 gzip 编码需要 zlib 库,br 编码需要 brotli 解码库,而 libzstd 用于 zstd。
libcurl 接口
要使 libcurl 请求内容编码,请使用:
使用 curl_easy_setopt1
其中 string 是 Accept-Encoding 报头的预期值。
目前,libcurl 支持多种编码,但仅了解如何处理使用 deflate、gzip、zstd 和/或 br 内容编码的响应,因此除了 identity(它什么都不做)之外,CURLOPT_ACCEPT_ENCODING[5] 的唯一有效值是 deflate、gzip、zstd 和 br。如果响应使用 compress 或其他方法编码,libcurl 会返回一个错误,指示响应无法解码。如果 <string> 为 NULL,则不生成 Accept-Encoding 报头。如果 <string> 是零长度字符串,则生成包含所有支持编码的 Accept-Encoding 报头。
必须将 CURLOPT_ACCEPT_ENCODING[5] 设置为任何非空值,以便自动解码内容。如果没有设置,并且服务器仍然发送编码内容(尽管没有请求),则数据以原始形式返回,并且不检查 Content-Encoding 类型。
curl 接口
使用 curl 的 --compressed[6] 选项来指示它请求服务器使用 curl 支持的任何格式压缩响应。
结构体
本节记录了内部结构体。由于它们确实是内部的,我们偶尔会更改它们,这可能会使本节在某些时候略显过时。
Curl_easy
Curl_easy 结构体是在外部 API 中以不透明的 CURL * 形式返回的。这个指针通常在 API 文档和示例中被称为简单句柄。
与实际连接相关的信息和状态在 connectdata 结构体中。当即将进行传输时,libcurl 要么创建一个新的连接,要么重用现有的一个。当前由这个句柄使用的当前连接数据由 Curl_easy->conn 指出。
与这个特定单次传输相关的数据和信息被放入 SingleRequest 子结构体中。
当 Curl_easy 结构体被添加到多句柄中,为了执行任何传输,它必须这样做,->multi 成员指向它所属的 Curl_multi 结构体。->prev 和 ->next 成员随后被多代码用来保持添加到同一多句柄的 Curl_easy 结构体的链表。libcurl 始终使用多句柄,因此在传输进行时 ->multi 指向一个 Curl_multi。
->mstate 是这个特定 Curl_easy 的多状态。当调用 multi_runsingle() 时,它根据当前状态对此句柄进行操作。mstate 还告诉在调用 curl_multi_fdset() 等时,为特定的 Curl_easy 返回哪些套接字。
libcurl 源代码通常使用 data 这个名字来命名指向 Curl_easy 结构体的局部变量。
在进行多路复用的 HTTP/2 传输时,每个 Curl_easy 都与一个单独的流相关联,共享相同的连接数据结构。多路复用使得保持与正确事物关联变得更加重要。
connectdata
在 libcurl 中,一个普遍的想法是在连接使用后将其保留在连接缓存中,以防再次使用,然后重用现有的连接而不是创建一个新的,因为这可以显著提高性能。
每个 connectdata 结构体标识了对服务器的单个物理连接。如果连接不能保持活动状态,则在使用后关闭连接,然后可以从缓存中删除此结构体并释放它。
因此,同一个 Curl_easy 可以被多次使用,每次可以选择另一个 connectdata 结构体来用于连接。请记住这一点,因为这很重要,需要考虑选项或选择是基于连接还是 Curl_easy。
作为一种特殊的复杂性,libcurl 支持的一些协议需要一种特殊的断开连接程序,这不仅仅是关闭套接字。这可能涉及到在这样做之前向服务器发送一个或多个命令。由于连接在使用后被保留在连接缓存中,因此在关闭特定连接的时候,原始的Curl_easy可能已经不再存在。为此,libcurl 在Curl_multi结构中保留了一个特殊的虚拟closure_handle Curl_easy,以便在需要时使用。
FTP 使用两个 TCP 连接进行典型传输,但它将这两个连接都保留在这个单个结构中,因此可以被认为是大多数内部关注的一个单一连接。
libcurl 的源代码通常使用conn这个名字来表示指向 connectdata 的局部变量。
Curl_multi
在内部,easy 接口被实现为多接口函数的包装器。这使得一切都是多接口。
Curl_multi是多句柄结构,在外部 API 中以不透明的CURLM *暴露。
这个结构包含了一个Curl_easy结构体的列表,这些结构体是通过curl_multi_add_handle()[13]添加到这个句柄的。列表的起始点是->easyp,而->num_easy是添加的Curl_easy的计数器。
->msglist是一个消息链表,当调用curl_multi_info_read()[14]时用来发送回消息。基本上,当单个Curl_easy的传输完成时,就会向该列表添加一个节点。
->hostcache指向名称缓存。这是一个用于查找名称到 IP 的散列表。节点在那里有有限的生存期,这个缓存旨在减少在短时间内需要相同名称的时间。
->timetree指向一个Curl_easy的树,按剩余时间直到应该检查排序——通常是某种超时。每个Curl_easy在树中有一个节点。
->sockhash是一个散列表,允许快速查找Curl_easy使用的套接字描述符。这对于multi_socket API 是必要的。
->conn_cache指向连接缓存。它跟踪所有在使用后被保留的连接。缓存有一个最大大小。
->closure_handle在connectdata部分有描述。
libcurl 的源代码通常使用multi这个名字来表示指向Curl_multi结构的变量。
Curl_handler
libcurl 支持的每个独特协议都需要提供一个至少包含一个Curl_handler结构的实现。它定义了协议的名称以及主代码应该调用哪些函数来处理特定协议的问题。通常,有一个名为[protocol].c的源文件,其中声明了一个struct Curl_handler Curl_handler_[protocol]结构。在url.c中,有一个主数组,其中包含所有指向单个数组的Curl_handler结构体,当给 libcurl 一个 URL 进行处理时,会扫描这个数组。
具体的函数指针原型可以在lib/urldata.h中找到。
-
->scheme是 URL 方案名称,通常用大写字母拼写。例如 HTTP 或 FTP 等。协议的 SSL 版本需要自己的Curl_handler设置,因此 HTTPS 与 HTTP 分开。 -
->setup_connection被调用以允许协议代码分配特定于协议的数据,这些数据随后与整个传输过程中的Curl_easy关联。在传输结束时再次释放。它在选择/创建传输的connectdata之前被调用。大多数协议在这里分配其私有的struct [PROTOCOL]并将其分配给Curl_easy->req.p.[protocol]。 -
->connect_it允许协议在 TCP 连接完成后执行一些特定操作,这些操作仍可被视为连接阶段的一部分。一些协议在此函数中更改connectdata->recv[]和connectdata->send[]函数指针。 -
->connecting是一个函数,只要协议认为它仍然处于连接阶段,就会不断被调用。 -
->do_it是调用以发出传输请求的函数。我们内部称之为 DO 操作。如果 DO 不足以完成整个 DO 序列,则通常还提供->doing。每个需要执行多个命令或类似操作的协议都需要实现自己的状态机(参见 SCP、SFTP、FTP)。一些协议(只有 FTP,并且仅由于历史原因)有一个单独的 DO 状态部分,称为DO_MORE。 -
->doing在发出传输请求命令时不断被调用 -
->done在传输完成并标记为完成时被调用。即在主要数据传输之后。 -
->do_more在DO_MORE状态期间被调用。FTP 协议在设置第二个连接时使用此状态。 -
->proto_getsock、->doing_getsock、->domore_getsock、->perform_getsock函数返回套接字信息。在特定多状态期间等待哪个套接字进行哪些 I/O 操作。 -
->disconnect在关闭 TCP 连接之前立即被调用。 -
->readwrite在传输期间被调用,允许协议执行额外的读取/写入操作 -
->attach将传输附加到连接上。 -
->defport是此协议使用的默认报告 TCP 或 UDP 端口 -
->protocol是CURLPROTO_*集中的一个或多个位。SSL 版本具有基本协议集和 SSL 变体。例如HTTP|HTTPS。 -
->flags是一个掩码,包含有关协议的附加信息,这使得通用引擎以不同的方式处理它:-
PROTOPT_SSL- 使其连接并协商 SSL -
PROTOPT_DUAL- 此协议使用两个连接 -
PROTOPT_CLOSEACTION- 此协议在关闭连接之前执行操作。此标志不再由代码使用,但仍被许多协议处理程序设置。 -
PROTOPT_DIRLOCK- 方向锁。SSH 协议设置这个位以限制主引擎关注的套接字动作的方向。 -
PROTOPT_NONETWORK- 不使用网络的协议(读取file:) -
PROTOPT_NEEDSPWD- 这个协议需要一个密码,如果没有提供,则使用默认密码 -
PROTOPT_NOURLQUERY- 这个协议无法处理 URL 上的查询部分(?foo=bar)
-
conncache
这是一个带有连接以供以后重用的哈希表。每个 Curl_easy 都有一个指向其连接缓存的指针。每个多处理句柄默认情况下都会设置一个连接缓存,所有添加的 Curl_easys 都会共享这个缓存。
Curl_share
libcurl 共享 API 分配一个 Curl_share 结构,作为 CURLSH * 暴露给外部 API。
理念是,这个结构可以有自己的缓存和池的版本集,然后通过在 CURLOPT_SHARE 选项中提供这个结构,那些特定的 Curl_easys 就会使用这个共享句柄所持有的缓存/池。
然后,可以单独的 Curl_easy 结构被设置为共享特定的东西,否则它们不会共享,比如 cookies。
Curl_share 结构目前可以存储 cookies、DNS 缓存和 SSL 会话缓存。
CookieInfo
这是主要的 cookie 结构。它包含所有已知的 cookies 和相关信息。即使它们被添加到多处理句柄中,每个 Curl_easy 也有自己的私有 CookieInfo。可以通过使用共享 API 使它们共享 cookies。
解析主机名
即 hostip.c 解释
在阅读 host*.c 源文件时需要记住的主要编译时定义是这些:
CURLRES_IPV6
此主机具有 getaddrinfo() 和家族,因此我们使用它。该主机可能无法解析 IPv6,但我们实际上不必考虑这一点。未启用 IPv6 的主机定义了 CURLRES_IPV4。
CURLRES_ARES
如果 libcurl 构建为使用 c-ares 进行异步名称解析,则定义。这可能是在 Windows 或 *nix 上。
CURLRES_THREADED
如果 libcurl 构建为使用线程进行异步名称解析,则定义。名称解析在新的线程中完成,支持的异步 API 与 ares 构建相同。这是在 (本地) Windows 下的默认设置。
如果定义了上述两个中的任何一个,则也会定义 CURLRES_ASYNCH。如果 libcurl 没有构建为使用异步解析器,则定义 CURLRES_SYNCH。
host*.c 源文件
host*.c 源文件是这样分割的:
-
hostip.c- 通用解析函数和实用函数 -
hostasyn.c- 异步名称解析的函数 -
hostsyn.c- 同步名称解析的函数 -
asyn-ares.c- 使用 c-ares 进行异步名称解析的函数 -
asyn-thread.c- 使用线程进行异步名称解析的函数 -
hostip4.c- IPv4 特定函数 -
hostip6.c- IPv6 特定函数
hostip.h 是所有这些内容的单一联合头文件。它基于 config*.h 和 curl_setup.h 的定义来定义 CURLRES_* 定义。
测试
curl 测试套件是我们开发过程中的一个基本基石。它帮助我们验证现有功能是否仍然如以前那样存在,并且我们用它来检查新功能是否按预期行为。
随着每个错误修复和新特性的引入,我们理想情况下也会创建一个或多个测试用例。
测试套件是专门定制的,针对我们自己的目的,以便我们可以从我们认为需要的每一个可能的角度测试 curl。它不依赖于任何第三方测试框架。
测试旨在能够在几乎所有可用的平台上构建和运行。
-
测试文件格式
-
构建测试
-
运行测试
-
调试构建
-
测试服务器
-
curl 测试
-
libcurl 测试
-
单元测试
-
Valgrind
-
持续集成
-
自动构建
-
折磨测试
测试文件格式
每个 curl 测试都使用单个文本文件设计,格式类似于 XML。
标签标记了所有部分的开始和结束,并且每个标签必须单独一行。注释可以是 XML 风格(用 <!-- 和 --> 包围)或 shell 脚本风格(以 # 开头)并且必须单独一行,不能与实际测试数据并列。大多数测试数据文件是语法有效的 XML,尽管一些文件不是(不支持字符实体和保留行尾的回车和换行符是最大的区别)。
所有测试都必须以 <testcase> 标签开始,它包含文件的其余部分。下面将介绍其他标签。
每个测试文件都命名为 tests/data/testNUMBER,其中 NUMBER 是唯一的数值测试标识符。每个测试都必须使用自己的专用编号。这个编号除了标识测试外,没有其他意义。
测试文件定义了确切要运行的命令行或工具、要调用的测试服务器以及它们应该如何响应、应该发生的确切协议交换、预期的输出和返回代码以及更多内容。
当设置名称时,所有内容都写入它们各自的标签中,如下所示:
<name>
HTTP with host name written backwards
</name>
关键字
每个测试在文件顶部设置一个或多个 <keywords>。它们被用作“标签”,以标识由此测试用例测试的功能和协议。runtests.pl 可以被配置为仅运行匹配(或不匹配)此类关键字的测试。
预处理
在启动时,每个测试输入文件都由 runtests.pl 进行预处理。这意味着变量、宏和关键字会被展开,并在 tests/log/testNUMBER 中存储临时文件版本 - 然后,这个文件被所有测试服务器等使用。
这种处理允许测试格式提供像 %repeat 这样的功能,以创建非常大的测试文件,而不会相应地膨胀输入文件。
Base64 编码
在预处理阶段,可以使用特殊指令让 runtests.pl 对某个部分进行 base64 编码,并将其插入到生成的输出文件中。这对于测试用例特别有用,因为测试工具预期会传递 base64 编码的内容,这些内容可能使用了特定于此次测试调用的动态信息,例如服务器端口号。
要将 base64 编码的字符串插入到输出中,使用以下语法:
%b64[ data to encode ]b64%
要编码的数据可以使用以下提到的任何现有变量,甚至可以插入单独的字节。例如,插入 HTTP 服务器的端口号(ASCII 编码)后跟一个空格和十六进制字节 9a:
%b64[%HTTPPORT %9a]b64%
十六进制解码
在预处理阶段,可以使用特殊指令让 runtests.pl 生成一系列二进制字节。
要从十六进制编码的字符串中插入一系列字节,使用以下语法:
%hex[ %XX-encoded data to decode ]hex%
例如,要将二进制八位字节 0、1 和 255 插入到测试文件中:
%hex[ %00%01%FF ]hex%
重复内容
在预处理阶段,可以使用特殊指令让 runtests.pl 生成重复的字节序列。
要插入重复的字节序列,使用此语法使<string>重复<number>次。数字必须是 1 或更大,字符串可以包含%HH十六进制代码:
%repeat[<number> x <string>]%
例如,要插入单词“hello” 100 次:
%repeat[100 x hello]%
条件行
测试文件中的行可以根据特定特征(见下文“特征”部分)是否设置而条件性地显示。如果存在特定特征,则输出以下行,否则不输出任何内容,直到出现后续的else或endif子句。如下所示:
%if brotli
Accept-Encoding
%endif
它还可以检查逆条件,所以如果特征没有通过感叹号设置:
%if !brotli
Accept-Encoding: not-brotli
%endif
您还可以使用“else”子句来获取相反条件的输出,例如:
%if brotli
Accept-Encoding: brotli
%else
Accept-Encoding: nothing
%endif
注意,不能有嵌套条件。您一次只能执行一个条件,并且只能检查一个特征。
变量
在测试预处理时,测试文件中的一系列变量会被替换为当时的内容。
可用的替换变量包括:
-
%CLIENT6IP- 运行 curl 的客户端的 IPv6 地址 -
%CLIENTIP- 运行 curl 的客户端的 IPv4 地址 -
%CURL- curl 可执行文件的路径 -
%FILE_PWD- 当前目录,在 Windows 中前面带有斜杠 -
%FTP6PORT- FTP 服务器的 IPv6 端口号 -
%FTPPORT- FTP 服务器的端口号 -
%FTPSPORT- FTPS 服务器的端口号 -
%FTPTIME2- 应该刚好足够从测试 FTP 服务器接收响应的超时时间(秒) -
%FTPTIME3- 比%FTPTIME2更长 -
%GOPHER6PORT- Gopher 服务器的 IPv6 端口号 -
%GOPHERPORT- Gopher 服务器的端口号 -
%GOPHERSPORT- Gophers 服务器的端口号 -
%HOST6IP- 运行此测试的主机的 IPv6 地址 -
%HOSTIP- 运行此测试的主机的 IPv4 地址 -
%HTTP6PORT- HTTP 服务器的 IPv6 端口号 -
%HTTPPORT- HTTP 服务器的端口号 -
%HTTP2PORT- HTTP/2 服务器的端口号 -
%HTTPSPORT- HTTPS 服务器的端口号 -
%HTTPSPROXYPORT- HTTPS 代理的端口号 -
%HTTPTLS6PORT- HTTP TLS 服务器的 IPv6 端口号 -
%HTTPTLSPORT- HTTP TLS 服务器的端口号 -
%HTTPUNIXPATH- HTTP 服务器的 Unix 套接字路径 -
%SOCKSUNIXPATH- SOCKS 服务器的 Unix 套接字的绝对路径 -
%IMAP6PORT- IMAP 服务器的 IPv6 端口号 -
%IMAPPORT- IMAP 服务器的端口号 -
%MQTTPORT- MQTT 服务器的端口号 -
%TELNETPORT- telnet 服务器的端口号 -
%NOLISTENPORT- 没有服务监听的端口号 -
%POP36PORT- POP3 服务器的 IPv6 端口号 -
%POP3PORT- POP3 服务器的端口号 -
%POSIX_PWD- 对 mingw 友好的当前目录 -
%PROXYPORT- HTTP 代理的端口号 -
%PWD- 当前目录 -
%RTSP6PORT- RTSP 服务器的 IPv6 端口号 -
%RTSPPORT- RTSP 服务器的端口号 -
%SMBPORT- SMB 服务器的端口号 -
%SMBSPORT- SMBS 服务器的端口号 -
%SMTP6PORT- SMTP 服务器的 IPv6 端口号 -
%SMTPPORT- SMTP 服务器的端口号 -
%SOCKSPORT- SOCKS4/5 服务器的端口号 -
%SRCDIR- 源目录的完整路径 -
%SSHPORT- SCP/SFTP 服务器的端口号 -
%SSHSRVMD5- SSH 服务器的公钥的 MD5 -
%SSHSRVSHA256- SSH 服务器的公钥的 SHA256 -
%SSH_PWD- 对 SSH 服务器友好的当前目录 -
%TESTNUMBER- 测试用例的编号 -
%TFTP6PORT- TFTP 服务器的 IPv6 端口号 -
%TFTPPORT- TFTP 服务器的端口号 -
%USER- 运行测试的用户登录 ID -
%VERSION- 被测试 curl 的完整版本号
标签
每个测试始终完全在 <testcase> 标签内指定。每个测试用例进一步分为四个主要部分:info、reply、client 和 verify。
-
info 提供有关测试用例的信息
-
reply 用于让服务器知道在 curl 发送请求后应该发送什么作为回复
-
client 定义了客户端应该如何行为
-
verify 定义了如何验证在运行命令后存储的数据是否正确结束
每个主要部分支持一组可用的 子标签,可以指定,如果指定,则进行检查/使用。
<info>
<keywords>
一个以换行符分隔的关键字列表,描述了此测试用例使用和测试的内容。尽量使用已经使用过的关键字。这些关键字用于统计/信息目的,以及选择或跳过测试类别。“Keywords” 必须以字母字符、“-”、“[” 或 “{” 开头,并且可以由空格分隔的多个单词组成,这些单词被视为单个标识符。
当使用使用 Hyper 构建的 curl 时,关键字必须包括 HTTP 或 HTTPS 以启动“hyper 模式”并使行结束检查在测试中生效。
<reply>
<data [nocheck="yes"] [sendzero="yes"] [base64="yes"] [hex="yes"] [nonewline="yes"]>
在客户端请求时发送到客户端的数据,并在之后验证其是否安全到达。设置 nocheck="yes" 以防止测试脚本验证此数据的到达。
如果数据在起始和结束标签的任何位置包含 swsclose,并且这是一个 HTTP 测试,那么在发送此响应后,服务器将关闭连接。如果不是,则连接保持持续。
如果数据在起始和结束标签的任何位置包含 swsbounce,HTTP 服务器将检测这是否是使用相同测试和部分号的第二次请求,然后增加部分号一个。这对于身份验证测试和类似测试很有用。
sendzero=yes 表示即使数据大小为零字节,(FTP) 服务器也会“发送”数据。用于验证 curl 在零字节传输上的行为。
base64=yes 表示测试文件中提供的数据是一块使用 base64 编码的数据。这是测试用例包含二进制数据的唯一方式。(实际上,此属性可以用于任何部分,但对于“data”部分以外的部分来说没有太多意义)。
hex=yes 表示数据是一系列十六进制对。它被解码并用作“原始”数据。
nonewline=yes 表示在发送或比较数据之前应该从数据中截断最后一个字节(尾随换行符)。
对于 FTP 文件列表,只有当你确保首先将 CWD 做到名为 test-[number] 的目录中,其中 [number] 是测试用例号时,才使用 <data> 部分。否则,FTP 服务器无法知道从哪个测试文件加载列表内容。
<dataNUMBER>
发送此内容而不是 中的内容。数字 NUMBER 由以下设置确定:
-
请求行中的测试数字 >10000,这是 [测试用例编号]%10000 的余数。
-
请求是 HTTP 并包含摘要详情,这会将数字增加 1000
-
如果一个 HTTP 请求是 NTLM 类型-1,则将 1001 添加到数字
-
如果一个 HTTP 请求是 NTLM 类型-3,则将 1002 添加到数字
-
如果一个 HTTP 请求是 Basic 类型且数字已经 >=1000,则增加 1
-
如果一个 HTTP 请求是 Negotiate,则对于具有 Negotiate 授权头且在相同测试用例上的每个请求,数字增加 1
以这种方式动态更改测试数字允许测试工具用于测试认证协商,其中必须发送多个不同的请求才能完成传输。每个请求的响应都位于其自己的数据部分。可以通过指定 datacheck 部分来验证整个协商序列。
<connect>
连接部分用于所有 CONNECT 请求而不是“数据”。然后应用数据部分的其余规则,但带有连接前缀。
<socks>
记录的 SOCKS 代理的地址类型和地址详情。
<datacheck [mode="text"] [nonewline="yes"]>
如果数据已发送,但这是之后应该检查的内容。如果设置了 nonewline=yes,runtests 在与客户端实际接收到的数据进行比较之前,从数据中截断尾随换行符。
如果在具有文本/二进制差异的平台上的输出是文本模式,请使用 mode="text" 属性。
<datacheckNUM [nonewline="yes"] [mode="text"]>
编号 datacheck 部分的内文将附加到非编号部分。
<size>
对 ftp SIZE 命令返回的数字(设置为一 1 使此命令失败)
<mdtm>
如果客户端发送了设置为一 1 的 FTP MDTM 命令,则返回文件不存在
<postcmd>
特殊用途服务器命令,用于在发送回复后控制其行为。对于 HTTP/HTTPS,这些是支持的:
wait [secs] - 暂停给定时间
<servercmd>
服务器特殊命令。
此文件的第一个行总是由测试脚本设置为 Testnum [number],以便服务器读取以了解客户端即将发起的测试。
对于 FTP/SMTP/POP/IMAP
-
REPLY [command] [return value] [response string]- 改变服务器对 [command] 的响应方式。[response string] 被评估为 perl 字符串,因此它可以包含嵌入的\r\n,例如。有一个特殊的 [command] 名为“welcome”(不带引号),这是连接时立即发送的欢迎字符串。 -
REPLYLF(类似于上面,但只发送以 LF 结尾的响应,而不是 CRLF) -
COUNT [command] [number]- 仅对[command]进行REPLY更改[number]次,然后返回到内置方法 -
DELAY [command] [secs]- 延迟响应此命令给定时间 -
RETRWEIRDO- 启用在文件传输时一次性出现多个响应行时的“怪异”RETR 情况 -
RETRNOSIZE- 确保 RETR 响应不包含文件大小 -
NOSAVE- 不要保存接收到的数据 -
SLOWDOWN- 使用 0.01 秒的延迟发送 FTP 响应,每个字节之间延迟 -
PASVBADIP- 使 PASV 在其 227 响应中发送非法 IP -
CAPA [能力]- 启用对能力和指定用于 IMAPCAPABILITY、POP3CAPA和 SMTPEHLO命令的空格分隔的能力列表的支持 -
AUTH [机制]- 启用对 SASL 身份验证的支持并指定用于 IMAP、POP3 和 SMTP 的空格分隔的机制列表 -
STOR [消息]在STOR后用此消息作为默认响应
对于 HTTP/HTTPS
-
auth_required如果此选项已设置且未进行身份验证就发送了 POST/PUT 请求,则服务器不会等待发送完整的请求体 -
idle- 接收到请求后什么也不做,只是“空闲” -
stream- 连续向客户端发送数据,永不结束 -
writedelay: [毫秒]在回复包之间延迟指定的时间 -
skip: [数字]- 指示服务器忽略从 PUT 或 POST 请求中读取这么多字节 -
rtp: part [num] channel [num] size [num]- 在选择的通道上以指定的有效载荷大小流式传输给定部分的伪造 RTP 数据包 -
connection-monitor- 当使用时,在连接断开时将[DISCONNECT]记录到server.input日志中。 -
upgrade- 当找到 HTTP 升级标头时,服务器将升级到 http2 -
swsclose- 指示服务器在响应后关闭连接 -
no-expect- 如果存在 Expect:,则不读取请求体
对于 TFTP
writedelay: [秒] 在回复包之间延迟指定的时间(每个数据包为 512 字节有效载荷)
<client>
<server>
本测试用例需要/使用的服务器。可用服务器:
-
file -
ftp-ipv6 -
ftp -
ftps -
gopher -
gophers -
http-ipv6 -
http-proxy -
http-unix -
http/2 -
http -
https -
httptls+srp-ipv6 -
httptls+srp -
imap -
mqtt -
none -
pop3 -
rtsp-ipv6 -
rtsp -
scp -
sftp -
smtp -
socks4 -
socks5
每行只能输入一个服务器。本小节是必需的。
<features>
必须在客户端/库中存在的功能列表,以便本测试能够运行。如果缺少必需的功能,则测试将被跳过。
或者,可以通过在功能前加上感叹号来表示该功能不是必需的。如果该功能存在,则测试将被跳过。
在此处可测试的功能:
-
alt-svc -
bearssl -
c-ares -
cookies -
crypto -
debug -
DoH -
getrlimit -
GnuTLS -
GSS-API -
h2c -
HSTS -
HTTP-auth -
http/2 -
hyper -
idn -
ipv6 -
Kerberos -
large_file -
ld_preload -
libssh2 -
libssh -
oldlibssh(版本在 0.9.4 之前) -
libz -
manual -
Mime -
netrc -
NTLM -
OpenSSL -
parsedate -
proxy -
PSL -
rustls -
Schannel -
sectransp -
shuffle-dns -
socks -
SPNEGO -
SSL -
SSLpinning -
SSPI -
threaded-resolver -
TLS-SRP -
TrackMemory -
typecheck -
Unicode -
unittest -
unix-sockets -
verbose-strings -
wakeup -
win32 -
wolfssh -
wolfssl
除了 curl 支持的所有协议外。只有当协议与服务器不同时才需要指定协议(当服务器是none时很有用)。
<killserver>
使用与<server>中相同的语法,但在此处提到这些服务器在测试用例完成后会被明确杀死。只有在没有其他替代方案时才使用此选项。当然,使用此选项需要后续测试重新启动服务器。
<precheck>
在测试之前由测试脚本运行的命令行。如果命令显示输出或返回代码非零,则测试将被跳过,并且(单行)输出将显示为不运行测试的原因。
<postcheck>
在测试之后由测试脚本运行的命令行。如果命令以非零状态码退出,则测试被认为是失败的。
<tool>
调用而不是“curl”的工具名称。此工具必须在libtest/目录中构建并存在(如果工具名称以lib开头)或unit/目录中(如果工具名称以unit开头)。
<name>
简短的测试用例描述,在测试运行时显示。
<setenv>
variable1=contents1
variable2=contents2
在实际命令运行之前将指定的环境变量设置为指定的值。命令运行后,它们会被清除。
<command [option="no-output/no-include/force-output/binary-trace"] [timeout="secs"][delay="secs"][type="perl/shell"]>
要运行的命令行。
注意,传递给服务器的 URL 实际上控制着返回的数据。URL 中的最后一个斜杠后面必须跟一个数字。该数字(N)由测试服务器用于加载测试用例 N 并返回<reply><data></data></reply>部分中定义的数据。
如果上面没有找到测试编号,HTTP 测试服务器使用给定主机名中最后一个点的后面的数字(这样即使测试编号仍然可以通过 CONNECT 传递)来处理“foo.bar.123”作为测试用例 123。或者,如果提供了 IPv6 地址给 CONNECT,则地址中的最后一个十六进制组用作测试编号。例如,地址“[1234::ff]”将被处理为测试用例 255。
将type="perl"设置为将测试用例编写为 perl 脚本。这意味着没有内存调试,并且 valgrind 在此测试中会被关闭。
将type="shell"设置为将测试用例编写为 shell 脚本。这意味着没有内存调试,并且 valgrind 在此测试中会被关闭。
将option="no-output"设置为防止测试脚本在--output参数上附加,该参数将输出重定向到文件。如果使用 verify/stdout 部分,则也不会添加--output。
将option="force-output"设置为即使在测试其他情况下写入 verify stdout 时也要使用--output。
将option="no-include"设置为防止测试脚本在--include参数上附加。
将option="binary-trace"设置为使用--trace而不是--trace-ascii进行跟踪。适用于面向二进制的协议,如 MQTT。
将timeout="secs"设置为覆盖默认服务器日志顾问读取锁的超时时间。此超时由测试工具使用,一旦命令执行完成,将等待测试服务器写入服务器端日志文件并移除建议不要读取的锁。参数“secs”是超时的非负整数秒数。此timeout属性出于完整性考虑而记录,但这是深度测试工具的内容,并且仅适用于特定测试用例。请避免使用它。
将delay="secs"设置为在命令执行完成后和<postcheck>部分运行之前引入时间延迟。参数“secs”是延迟的非负整数秒数。此‘delay’属性旨在用于特定测试用例,通常不需要。
<file name="log/filename" [nonewline="yes"]>
在运行测试用例之前,使用此内容创建命名文件,这对于测试用例需要操作的文件很有用。
如果使用nonewline="yes",则创建的文件将移除最后的换行符。
<stdin [nonewline="yes"]>
将此给定数据通过 stdin 传递给工具。
如果设置nonewline,我们在将其与客户端实际接收到的数据进行比较之前,将此给定数据的尾部换行符截断。
<verify>
<errorcode>
cURL 应返回的数值错误代码。通过逗号分隔多个数字来指定接受的错误代码列表。请参阅测试 237 以获取示例。
<strip>
每行一个正则表达式,在比较之前从协议转储中移除。这对于移除对动态变化的协议数据(如端口号或用户代理字符串)的依赖很有用。
<strippart>
每行一个操作,该操作在协议转储上执行。这是相当高级的。例如:s/^EPRT .*/EPRT stripped/。
<protocol [nonewline="yes"]>
当设置nonewline时,如果 cURL 应传输协议转储,我们在将其与客户端实际发送的数据进行比较之前,将此给定数据的尾部换行符截断。在比较之前应用<strip>和<strippart>规则。
<proxy [nonewline="yes"]>
当使用 HTTP 代理服务器时,如果设置nonewline,我们在将其与客户端实际发送的数据进行比较之前,将此给定数据的尾部换行符截断。在比较之前应用<strip>和<strippart>规则。
<stderr [mode="text"] [nonewline="yes"]>
这验证了这些数据已传递到 stderr。
如果在具有文本/二进制差异的平台上的输出是文本模式,请使用mode="text"属性。
如果设置nonewline,我们在将其与客户端实际接收到的数据进行比较之前,将此给定数据的尾部换行符截断。
<stdout [mode="text"] [nonewline="yes"]>
这验证了这些数据已传递到 stdout。
如果在具有文本/二进制差异的平台上的输出是文本模式,请使用mode="text"属性。
如果设置了 nonewline,我们在与客户端实际收到的数据进行比较之前,将此给定数据的尾部换行符截断
<file name="log/filename" [mode="text"]>
测试完成后,文件内容必须与此完全相同。如果输出在具有文本/二进制差异的平台上的文本模式下,请使用 mode="text" 属性。
<file1>
可以将 1 到 4 添加到 'file' 以比较更多文件。
<file2>
<file3>
<file4>
<stripfile>
每行一个 Perl 操作符,该操作符在比较测试文件中存储的内容之前,对输出文件或 stdout 进行操作。这相当高级。例如:"s/^EPRT .*/EPRT stripped/"
<stripfile1>
可以将 1 到 4 添加到 stripfile 以剥离相应的
<stripfile2>
<stripfile3>
<stripfile4>
<upload>
上传数据 curl 应发送的内容
<valgrind>
disable - 禁用此测试的 valgrind 日志检查
构建测试
在你能够运行任何测试之前,你需要构建 curl,同时还需要构建测试套件及其相关的工具和服务器。
最方便的方法是,你只需在构建目录的根目录下运行 make test 就可以构建并运行所有内容,但如果你想更深入地工作于测试或者甚至调试其中一个,你可能需要跳转到 tests 目录并在其中工作。构建所有内容并按照以下方式运行测试 144:
cd tests
make
./runtests.pl 144
运行测试
运行测试的主要脚本被称为 tests/runtests.pl,其中一些更有用的特性包括:
运行一系列测试
运行测试 1 到 27:
./runtests.pl 1 to 27
运行所有标记为 SFTP 的测试:
./runtests.pl SFTP
运行所有未标记为 FTP 的测试:
./runtests.pl '!FTP'
使用 gdb 运行特定测试
./runtests.pl -g 144
它会启动 gdb,你可以设置断点等,然后输入 run 并开始运行,整个测试过程都会通过调试器执行。
不使用 valgrind 运行特定测试
如果测试套件找到了 valgrind,它默认会使用它,这是一个寻找问题的极好方法,但同时也使得测试运行速度大大减慢。有时你希望它运行得更快:
./runtests.pl -n 144
调试构建
当我们提到 调试构建 时,通常指的是带有调试代码和符号的 curl 构建。我们强烈建议您这样做,如果您想与 curl 开发工作,因为它使得测试和调试变得更加容易。
您可以使用如下方式使用 configure 来进行调试构建:
./configure --enable-debug
使用 runtests.pl,调试构建可以运行单个测试用例,并与 gdb 一起使用,这很方便——例如,如果您能让它崩溃在某处,那么 gdb 可以捕获它并显示它发生的确切位置等。
与常规 发布构建 相比,调试构建有一些不同的构建方式,其中包含一些使 curl 更容易测试的代码片段。例如,它允许测试套件覆盖随机数生成器,以便测试那些在其他情况下是随机的值实际上可以工作。此外,单元测试仅在调试构建上运行。
Memdebug
调试构建还启用了 memdebug 内部内存跟踪和调试系统。
当启用时,memdebug 系统会将大量与内存相关的选项的详细信息输出到日志文件中,以便事后进行分析和验证。验证所有内存都已释放,所有文件都已关闭等。
这是一个简化的 valgrind 版本,但它的功能远不如 valgrind。然而,它相当便携且影响较小。
在调试构建中,如果将 CURL_MEMDEBUG 环境变量设置为文件名,curl 会启用 memdebug 系统进行内存调试。测试套件会为我们设置此变量(参见 tests/log/memdump),并在每次测试运行后验证它(如果存在)。
测试服务器
大部分 curl 测试套件实际上运行的是 curl 命令行,这些命令行在测试期间仅与在本地机器上启动的服务器进行交互,用于测试目的,并在测试回合结束时再次关闭。
测试服务器是为此目的编写的自定义服务器,它们支持 HTTP、FTP、IMAP、POP3、SMTP、TFTP、MQTT、SOCKS 代理等。
所有测试服务器都通过测试文件进行控制:每个测试案例需要运行哪些服务器才能工作,它们应该返回什么,以及它们应该如何为每个测试行为。
测试服务器通常将它们的行为记录在tests/log目录中的专用文件中,如果您的测试没有按照预期执行,这些文件可能很有用。
curl 测试
套件中的标准测试是“curl 测试”。它们都调用一个 curl 命令行,并验证发送的、接收的以及返回的内容是否完全符合预期。任何不匹配的情况都会导致测试失败,并且脚本会显示关于错误的详细信息。
测试中在<client><command>部分所包含的特征就是命令行中使用的,直接引用。
在运行后查看tests/log/commands.log文件是很有用的,因为它包含了测试中运行的完整命令行。
如果你想要创建一个不调用 curl 命令行工具的测试,那么你应该考虑使用 libcurl 测试或单元测试。
libcurl 测试
libcurl 测试是一个独立的 C 程序,它使用公共的 libcurl API 来完成某些操作。
除了这些,其他所有内容都以与 curl 测试相同的方式进行测试、验证和检查。
由于这些 C 程序通常在不同的平台上构建和运行,可能需要考虑一些因素。
所有 libcurl 测试程序都保存在tests/libtest
单元测试
单元测试仅在调试构建上工作。
单元测试是使用 libcurl 内部函数进行的测试,因此它们不属于任何公共 API、头文件或外部文档的一部分。
如果您要测试的内部函数被定义为static,则应将其设置为UNITTEST - 这样调试构建将不会为它们使用static,从而使它们可以从单元测试中访问。
我们提供了一套方便的函数和宏,用于单元测试,以便快速轻松地编写它们。
所有单元测试程序都保存在tests/unit
Valgrind
Valgrind 是一个流行的强大工具,用于调试程序,尤其是它们对内存的使用和滥用。
runtests.pl 会自动检测你的系统上是否安装了 valgrind,并在默认情况下如果找到 valgrind 则使用它来运行测试。你可以传递 -n 参数给 runtests 以禁用 valgrind 的使用。
Valgrind 使得执行速度大大减慢,但它是一个用于查找内存泄漏和未初始化内存使用的优秀工具。
持续集成
对于提交到 GitHub 上 curl 项目的每个 pull request 以及对于推送到 git 仓库主分支的每个 commit,都会启动大量的虚拟机,从 git 检出代码,用不同的选项构建它,运行测试套件,并确保一切正常工作。
我们在包括 Linux、macOS、Windows、Solaris 和 FreeBSD 在内的多个不同的操作系统上运行 CI 作业。
我们运行构建和测试许多不同(组合)后端的作业。
我们有使用不同构建方式的作业:autotools、cmake、winbuild、Visual Studio 等。
我们验证分发 tar 包是否正常工作。
我们运行源代码分析器。
构建失败
不幸的是,由于涉及到的所有事情都很复杂,我们经常有一两个 CI 作业似乎陷入了“永久失败”的状态,这看起来像是永久性地使作业失败。
我们努力使它们不失败,但这是一项艰巨的任务,我们经常看到即使是应该全部为绿色的更改,也会出现红色构建。
自动构建
志愿者运行自动构建。这是一个自动运行的脚本,它:
-
从 git 仓库检出最新代码
-
构建一切
-
运行测试套件
-
将完整日志通过电子邮件发送到 curl 服务器
由于它们在不同的平台和不同的构建选项上运行,它们为 curl 构建健康提供了额外的反馈维度。
检查状态
所有日志都被解析、管理和显示在curl 网站上。
遗留
我们在 2003 年开始启动了自动构建系统,比 CI 作业开始成为严肃的替代品早了十年。
现在,随着我们越来越多地进入一个拥有 CI 和更直接、更早反馈的世界,自动构建更像是一个遗留系统。
疯狂
当 curl 构建时启用调试,它提供了一种特殊的测试类型。我们称之为疯狂测试。不用担心,它并不像听起来那么可怕。
它们验证 libcurl 和 curl 的退出路径在没有发生任何崩溃或内存泄漏的情况下工作,
疯狂测试是这样工作的:
-
首先以原样运行单个测试
-
计算调用次数的可失败函数数量
-
为每个下降函数调用重新运行一次测试
-
逐个使每个可失败函数调用返回错误
-
验证没有泄漏或崩溃
-
继续直到所有可失败函数都失败
这种测试方式可能需要非常长的时间。我建议你在尝试时关闭 Valgrind。
重新运行特定的失败测试
如果单个测试失败,runtests.pl会精确识别触发问题的“轮次”,并通过使用如所示-t选项,你可以运行一个命令行,当调用时只会失败那个特定的可失败函数。
浅层
为了使这种测试方式更加实用,测试套件还提供了一个--shallow选项。这允许用户为每个测试案例设置最大可失败函数的数量。如果失败调用多于这个值,脚本将随机选择哪些函数失败。
作为一项特殊功能,由于在测试中随机化事物可能会感到不舒服,脚本使用基于年份加月份的随机种子,因此对于每个月历月份都是相同的。方便得很,如果你用相同的--shallow值重新运行相同的测试,它将运行相同的随机测试。
你可以使用 runtests 的--seed选项强制使用不同的种子。
索引
A
-
–alt-svc: 启用
-
–anyauth: 认证
-
apt: Ubuntu 和 Debian
-
Arch Linux: Arch Linux
B
-
-b: Web 登录和会话
-
–basic: 认证
-
BearSSL: lib/vtls, TLS 库,
<features> -
绑定: 混淆和错误, 库, 在网站后端, 文档
-
BoringSSL: TLS 库, BoringSSL, 限制
-
brotli: HTTP 压缩, 版本, 哪个 libcurl 版本运行, 关于内容编码, 条件行
C
-
-c: Web 登录和会话
-
c-ares: c-ares, 第 4 行:功能, 使用 c-ares 的名称解析技巧, 名称解析后端,
CURLRES_ARES,<features> -
C89: 注释
-
CA: 可用退出代码, MITM 代理, 验证服务器证书, OCSP 粘贴, 缓存, 验证, 所有选项, 可用信息, CA 存储缓存
-
CA 证书缓存:CA 证书缓存
-
–ca-native:本地 CA 存储
-
Chrome:复制为 curl,SSLKEYLOGFILE
-
clone:在 MSYS2 上构建 libcurl,git,网站,构建 boringssl
-
行为准则:信任,行为准则
-
–compressed:压缩,Gzipped 传输
-
–compressed-ssh:压缩
-
configure:根目录,处理构建选项,平台相关代码,Autotools,
rpath,configure,设置构建树以便 curl 的 configure 检测,Ifdefs,内存调试,调试构建 -
–connect-timeout:连接超时,连接时永远不会超过这个时间
-
–connect-to:提供替代名称
-
连接缓存:持久连接,连接缓存,所有选项,连接重用,多句柄,连接缓存,connectdata
-
连接池: 连接重用, 持久连接, 连接缓存, 池大小, 连接重用, 连接缓存
-
连接重用: 连接重用, 连接缓存, 线程, 连接重用
-
content-encoding: 压缩, 传输编码, 关于内容编码
-
贡献: 行为准则, 贡献, 简介
-
贡献: 文档, 贡献
-
Cookie 引擎: Cookie 引擎
-
Cookies: 文档, libpsl, 第 4 行:功能, 不完美, 服务器差异, 更改 Host 头, 身份验证, Cookie 文件格式, Cookies, 所有选项, 可用信息, 身份验证, Cookies, 在 easy 句柄之间共享, 通过 HTTP 提交登录表单, Cookies, Curl_share, 《功能》
-
版权信息:许可证,版权
-
curl-announce:curl-announce,漏洞处理
-
curl-library:curl-users,为邮件列表制作补丁,漏洞处理
-
curl-users:curl-users,漏洞处理
-
<curl/curl.h>:include/curl,头文件,–libcurl,停止慢速传输,速率限制,进度条,包含文件,获取简单的 HTTP 页面,将响应存入内存,通过 HTTP 提交登录表单,获取 FTP 目录列表,非阻塞 HTTP 表单提交 -
CURLE_ABORTED_BY_CALLBACK:进度信息
-
CURLHSTS_ENABLE:为句柄启用 HSTS
-
CURLHSTS_READONLYFILE:为句柄启用 HSTS
-
CURLINFO_CERTINFO:可用信息
-
CURLINFO_CONN_ID:传输和连接标识符,可用信息
-
CURLINFO_CONTENT_TYPE:传输后信息
-
CURLINFO_EFFECTIVE_URL:可用信息
-
CURLINFO_FILETIME:可用信息
-
CURLINFO_TOTAL_TIME_T:可用信息
-
CURLINFO_XFER_ID: 传输和连接标识符, 可用信息
-
CURLMOPT_PIPELINING: 多路复用
-
CURLMOPT_SOCKETFUNCTION: socket_callback
-
CURLMOPT_TIMERFUNCTION: timer_callback, 仅向应用暴露单个超时
-
CURLOPT_ALTSVC: 所有选项, 启用
-
CURLOPT_ALTSVC_CTRL: 所有选项, 启用
-
CURLOPT_CA_CACHE_TIMEOUT: CA 证书缓存, 所有选项
-
CURLOPT_CLOSESOCKETFUNCTION: 所有选项, Socket 关闭回调
-
CURLOPT_CONNECTTIMEOUT: 所有选项, easy API
-
CURLOPT_COOKIE: 所有选项, 设置自定义 cookie
-
CURLOPT_COOKIEFILE: 所有选项, 通过读取启用 cookie 引擎, 通过 HTTP 提交登录表单
-
CURLOPT_COOKIEJAR: 所有选项, 通过写入启用 cookie 引擎
-
CURLOPT_COOKIELIST: 所有选项, 向 cookie 存储中添加 cookie
-
CURLOPT_CURLU: 所有选项, CURLOPT_CURLU
-
CURLOPT_CUSTOMREQUEST: 所有选项, 请求方法
-
CURLOPT_DEBUGDATA: 跟踪一切, 所有选项, 调试
-
CURLOPT_DEBUGFUNCTION: 跟踪一切, 所有选项, 调试
-
CURLOPT_DNS_CACHE_TIMEOUT: DNS 缓存, 所有选项, 缓存
-
CURLOPT_DNS_INTERFACE: 所有选项, 名称服务器选项
-
CURLOPT_DNS_LOCAL_IP4: 所有选项, 名称服务器选项
-
CURLOPT_DNS_LOCAL_IP6: 所有选项, 名称服务器选项
-
CURLOPT_DNS_SERVERS: 所有选项, 名称服务器选项
-
CURLOPT_DNS_USE_GLOBAL_CACHE: 所有选项, 无全局 DNS 缓存
-
CURLOPT_ERRORBUFFER: –libcurl, CURLcode 返回码, 所有选项
-
CURLOPT_FAILONERROR: 所有选项, 关于 HTTP 响应码“错误”
-
CURLOPT_HEADER: 所有选项, 写入数据, 引用者, 也下载头部数据
-
CURLOPT_HEADERDATA: –libcurl, 所有选项, 头部数据, 也下载头部数据
-
CURLOPT_HEADERFUNCTION: –libcurl, 所有选项, 头部数据
-
CURLOPT_HSTS: 所有选项, 设置 HSTS 缓存文件
-
CURLOPT_HSTS_CTRL: 所有选项, 为句柄启用 HSTS
-
CURLOPT_HTTPGET: 所有选项, 下载, 通过 HTTP 提交登录表单
-
CURLOPT_HTTPHEADER: 所有选项, 添加头信息, HTTP PUT, 非阻塞 HTTP 表单提交
-
CURLOPT_HTTPPOST: 所有选项
-
CURLOPT_IPRESOLVE: 所有选项, libcurl 如何连接, 名称解析
-
CURLOPT_LOCALPORT: 所有选项, 本地端口号
-
CURLOPT_LOCALPORTRANGE: 所有选项, 本地端口号
-
CURLOPT_LOW_SPEED_LIMIT: 所有选项, easy API, 停止缓慢传输
-
CURLOPT_LOW_SPEED_TIME: 所有选项, easy API, 停止缓慢传输
-
CURLOPT_MAXFILESIZE_LARGE: 设置数值选项, 所有选项
-
CURLOPT_MAXREDIRS: –libcurl, 所有选项
-
CURLOPT_MIMEPOST: 所有选项, HTTP 多部分表单, 非阻塞 HTTP 表单提交
-
CURLOPT_NOBODY: 所有选项, 请求方法
-
CURLOPT_NOPROGRESS: –libcurl, 所有选项, 进度信息, 进度条
-
CURLOPT_OPENSOCKETDATA: 所有选项, 提供文件描述符
-
CURLOPT_OPENSOCKETFUNCTION: 所有选项, 提供文件描述符
-
CURLOPT_PIPEWAIT: 所有选项, 多路复用
-
CURLOPT_POST: 所有选项, HTTP POST
-
CURLOPT_POSTFIELDS: 设置字符串选项, 所有选项, 请求方法, HTTP POST, 通过 HTTP 提交登录表单
-
CURLOPT_POSTFIELDSIZE:
CURLOPT_POSTFIELDS, 所有选项, HTTP POST -
CURLOPT_POSTREDIR: 决定重定向时使用的方法, 所有选项
-
CURLOPT_PROGRESSFUNCTION: 所有选项, 进度信息
-
CURLOPT_PROXY: 所有选项, 代理类型
-
CURLOPT_PROXYPORT: 所有选项, 代理类型
-
CURLOPT_PROXYTYPE: 所有选项, 代理类型
-
CURLOPT_READDATA: –libcurl, 所有选项, 读取数据
-
CURLOPT_READFUNCTION: –libcurl, 所有选项, 读取数据, HTTP POST
-
CURLOPT_RESOLVE: 所有选项, 为主机设置自定义地址
-
CURLOPT_SEEKDATA: –libcurl, 所有选项, 查找和 ioctl
-
CURLOPT_SEEKFUNCTION: –libcurl, 所有选项, 查找和 ioctl
-
CURLOPT_SOCKOPTDATA: 所有选项, sockopt
-
CURLOPT_SOCKOPTFUNCTION: 所有选项, sockopt
-
CURLOPT_SSH_KNOWNHOSTS: –libcurl, 所有选项, SSH 密钥
-
CURLOPT_SSLVERSION: 协议版本, 所有选项
-
CURLOPT_SSL_VERIFYHOST: 验证, 所有选项
-
CURLOPT_SSL_VERIFYPEER: 验证, 所有选项, HTTPS 代理
-
CURLOPT_STDERR: –libcurl, 详细操作, 所有选项
-
CURLOPT_TCP_KEEPALIVE: –libcurl, 所有选项
-
CURLOPT_TIMEOUT: 设置数值选项, 所有选项, easy API
-
CURLOPT_TLSAUTH_USERNAME: TLS 认证, 所有选项
-
CURLOPT_UPLOAD: 所有选项, 请求方法, HTTP PUT
-
CURLOPT_URL: –libcurl, 字符串是 C 字符串,不是 C++字符串对象, 简单句柄, 设置字符串选项, 所有选项, 停止缓慢传输, 速率限制, 进度条, 请求方法, Bearer, 下载, HTTP PUT, CURLOPT_CURLU, 获取简单的 HTTP 页面, 将响应存储到内存中, 通过 HTTP 提交登录表单, 获取 FTP 目录列表, 非阻塞 HTTP 表单提交
-
CURLOPT_USERAGENT: –libcurl, 所有选项, 将响应存储到内存中
-
CURLOPT_VERBOSE: 详细操作, 所有选项, 通过名称查找特定选项, 当连接没有被按预期重用时, 下载头部信息, 非阻塞 HTTP 表单提交
-
CURLOPT_WRITEDATA: –libcurl, 回调考虑, 所有选项, 写入数据, 将响应存入内存
-
CURLOPT_WRITEFUNCTION: –libcurl, 回调考虑, 所有选项, 写入数据, 将响应存入内存
-
CURLOPT_XFERINFODATA: 所有选项, 进度信息
-
CURLOPT_XFERINFOFUNCTION: 所有选项, 进度信息
-
CURLUPART_FRAGMENT: 获取 URL 部分, 设置 URL 部分
-
CURLUPART_HOST: CURLU_PUNYCODE, 获取 URL 部分, 设置 URL 部分
-
CURLUPART_PASSWORD: 获取 URL 部分, 设置 URL 部分
-
CURLUPART_PATH: 获取 URL 部分, 设置 URL 部分
-
CURLUPART_PORT: 获取 URL 部分, 设置 URL 部分
-
CURLUPART_QUERY: 获取 URL 部分, 设置 URL 部分, 追加到查询
-
CURLUPART_USER: 获取 URL 部分, 设置 URL 部分
-
curl_easy_cleanup: –libcurl, 停止慢速传输, 速率限制, 进度条, easy 句柄, Bearer, 启用带写入的 cookie 引擎, Header 结构, 获取简单的 HTTP 页面, 将响应存储到内存中, 通过 HTTP 提交登录表单, 获取 FTP 目录列表, 非阻塞 HTTP 表单提交
-
curl_easy_getinfo: docs/libcurl/opts, 传输和连接标识符, 传输后信息, 响应元数据, 从 cookie 存储中获取所有 cookie
-
curl_easy_init: –libcurl, Easy 句柄, 停止慢速传输, 速率限制, 进度条, Bearer, 下载, CURLOPT_CURLU, 获取简单的 HTTP 页面, 将响应存储到内存中, 通过 HTTP 提交登录表单, 获取 FTP 目录列表, 非阻塞 HTTP 表单提交
-
curl_easy_option_by_id: 通过 ID 查找特定选项
-
curl_easy_option_by_next: 遍历所有选项
-
curl_easy_perform: –libcurl, 驱动简单, 简单 API 池, 缓存, 简单 API, 停止缓慢传输, 速率限制, 进度计, 添加一个头, Bearer, 下载, 获取一个简单的 HTTP 页面, 将响应存入内存, 通过 HTTP 提交登录表单, 获取 FTP 目录列表, 一切都是多
-
curl_easy_reset: 重用
-
curl_easy_setopt: curl 库选项文档, –libcurl 选项, CURLcode 返回码, 详细操作, 字符串是 C 字符串,不是 C++字符串对象, Easy 句柄, 设置数值选项, 设置字符串选项, TLS 选项, 所有选项, 写入数据, 读取数据, 进度信息, 头部数据, 调试, sockopt 回调, 提供文件描述符, 名称解析, 停止慢速传输, 速率限制, 进度条, 请求方法, 范围, 用户名和密码, 启用带读取的 cookie 引擎, 下载, HTTP POST, 多路复用, 为句柄启用 HSTS, 启用, easy 句柄之间的共享, CURLOPT_CURLU, 获取简单的 HTTP 页面, 将响应存入内存, 通过 HTTP 提交登录表单, 获取 FTP 目录列表, 非阻塞 HTTP 表单提交
-
curl_global_cleanup: 全局初始化, 将响应存储到内存中, 获取 FTP 目录列表
-
curl_global_init: 全局初始化, 将响应存储到内存中, 获取 FTP 目录列表, 初始化调用
-
curl_global_trace: 跟踪更多
-
CURL_IPRESOLVE_V6: 名称解析
-
CURL_MAX_WRITE_SIZE: 写入数据
-
curl_mime_addpart: HTTP 多部分表单提交, 非阻塞 HTTP 表单提交
-
curl_mime_filedata: HTTP 多部分表单提交, 非阻塞 HTTP 表单提交
-
curl_mime_init: HTTP 多部分表单提交, 非阻塞 HTTP 表单提交
-
curl_mime_name: HTTP 多部分表单提交, 非阻塞 HTTP 表单提交
-
curl_multi_add_handle: 使用多线程驱动, 多个简单句柄, 非阻塞 HTTP 表单提交, Curl_multi
-
curl_multi_cleanup: 多线程 API, 非阻塞 HTTP 表单提交
-
curl_multi_fdset: 使用多线程驱动, Curl_easy
-
curl_multi_info_read: 单次传输何时完成?, 何时完成?, 多线程 API, Curl_multi
-
curl_multi_init: 使用多线程驱动, 非阻塞 HTTP 表单提交
-
curl_multi_remove_handle: 使用多线程驱动, 多个简单句柄, 多线程 API, 多线程 API
-
curl_multi_setopt: docs/libcurl/opts, 使用多线程驱动, socket_callback, 多路复用
-
curl_multi_socket_action: curl_multi_socket_action, socket_callback
-
curl_multi_timeout: 使用多线程驱动, 仅向应用程序暴露单个超时
-
curl_multi_wait: 使用多线程驱动
-
curl_off_t: 传输和连接标识符, 设置数值选项, 进度信息, 查找和 ioctl, 速率限制, 可用信息, 响应元数据, HTTP PUT, 元数据, curl_ws_send()
-
CURL_SOCKET_TIMEOUT: timer_callback
-
CURL_SSL_BACKEND: 第 1 行:TLS 版本, 多个 TLS 后端
-
curl_url: 包含文件, 创建、清理、复制, 解析 URL, 重定向到 URL, 更新部分, CURLOPT_CURLU
-
curl_url_cleanup: 创建、清理、复制
-
curl_url_dup: 创建、清理、复制
-
curl_url_get: CURLU_ALLOW_SPACE, 获取 URL, 获取 URL 部分
-
curl_url_set: 包含文件, 解析 URL, 重定向到 URL, 设置 URL 部分, 追加到查询, CURLOPT_CURLU
-
curl_version_info: 哪个 libcurl 版本在运行, 支持
D
-
-d: 选项的参数, 每个 URL 分开选项, POST, MQTT, 方法, 简单 POST, Content-Type, 上传二进制数据, 转换为 GET, 期望 100-continue, 分块编码的 POST, 隐藏表单字段, -d 与-F, HTTP PUT, Web 登录和会话
-
–data: 选项的参数, 每个 URL 分隔选项, POST, 简单 POST, JSON, URL 编码数据
-
–data-binary: 不完美, 简单 POST, 上传二进制数据, URL 编码数据
-
–data-urlencode: 查询, URL 编码数据, 转换为 GET
-
debian: Ubuntu 和 Debian, 版本
-
调试回调: 详细操作, 所有选项, 调试
-
开发: 项目沟通, curl-users, 报告错误, 商业支持, 开发, 开发团队, 未来, Ubuntu 和 Debian, 获取 macOS 的 libcurl, 谁决定包含什么?, 从 Safari, 弄清楚浏览器发送的内容, 转换网页表单, 哪个 libcurl 版本运行, 验证, 调试构建
-
DICT: curl 支持哪些协议?, DICT, 无方案, 版本, DICT, CURLU_GUESS_SCHEME
E
-
Edge: 复制为 curl, Ifdefs
-
环境变量: 环境变量, Windows, 代理环境变量, 代理环境变量, setenv
-
临时端口: 本地端口号
-
ETag: 条件
-
–etag-compare: 通过内容修改进行检查
-
–etag-save: 通过内容修改进行检查
-
/etc/hosts: 运行本地克隆, 主机, 编辑 hosts 文件
-
礼仪: 邮件列表礼仪
-
事件驱动: 使用多套接字驱动, 一切都是多
F
-
-F: 不完美, multipart formpost, 方法, 使用 curl 发送此类表单, -d 与 -F
-
–fail: 可用退出代码, HTTP 响应代码
-
–fail-with-body: HTTP 响应代码
-
Firefox: 复制为 curl, 发现你的代理, SSLKEYLOGFILE, 用户代理
-
Fragment: 查询, 片段, 可用的 –write-out 变量, 片段, 写回调, 元数据, curl_ws_send()
-
–ftp-method: 多级工作目录
-
–ftp-pasv: 被动连接
-
–ftp-port: 可用退出代码, 主动连接
-
–ftp-skip-pasv-ip: 被动连接
-
FTPS: curl 支持哪些协议?, FTPS, TLS 库, 支持的方案, 网络泄露, 版本, 跟踪选项, 允许上传的协议, 启用 TLS, FTPS, 变量
-
future: 项目沟通, 未来, 还有哪些其他协议?, 文档, curl-security@haxx.se, “未使用”, 更多数据, API 兼容性, 跟踪更多, 网络数据转换, HSTS, age, 设置超时
G
-
–get: trurl 示例命令行, 转换为 GET
-
git: 每日快照, 在 MSYS2 上构建 libcurl, root, git, 网站, 构建 boringssl, 持续集成, 自动构建
-
通配符: URL 通配符, 使用 FTP 上传
-
GnuTLS: lib/vtls, 选择 TLS 后端, TLS 库, 本地 CA 存储, OCSP stapling, 限制, 《
》 -
GOPHER: 它如何开始, curl 支持哪些协议?, GOPHER, 支持的方案, 版本, 变量
-
GOPHERS: curl 支持哪些协议?, GOPHERS, 支持的方案, 变量
H
-
Happy Eyeballs: 所有选项, Happy Eyeballs
-
haproxy: haproxy, 所有选项
-
–haproxy-clientip: curl 和 haproxy
-
–haproxy-protocol: curl 和 haproxy
-
–header: 服务器差异, 代理头, JSON, 自定义头
-
头部回调: 所有选项, 头部数据, 响应体, 也下载头部
-
homebrew: macOS
-
主机:: HTTP 基础, 跟踪选项, 更改 Host:头, 生成的 HTTP, 自定义头, 自定义 HTTP 请求头
-
–hsts: HSTS 缓存
-
HSTS: HSTS, 所有选项, HSTS, HSTS, HSTS, 《
》 -
HTTP 代理: 它的起源, 代理类型, HTTP 代理, 代理头, 身份验证, 所有选项, HTTP 代理, 可用信息, 《proxy [nonewline="yes"]》
-
HTTP 重定向: 简短选项, 可用退出代码, 告诉 curl 跟随重定向, 通过 HTTP 提交登录表单
-
HTTP 严格传输安全: HSTS, HSTS, HSTS
-
HTTP/1.1: HTTP, HTTP 基础知识, 跟踪选项, HTTP/2, 使用 TELNET 进行调试, HTTP/2, 注意事项, 生成的 HTTP, GET 或 POST?, 请求方法, 请求目标, 自定义 HTTP 请求头, 版本, 关于内容编码
-
HTTP/2: HTTP, 文档, nghttp2, 第 4 行:功能, 可用的退出代码, 更多数据, HTTP 头, HTTP/2, HTTP/2, HTTP/3, HTTP/2 及以后, GET 或 POST?, HTTP/3, 跟踪更多, 当连接没有被按预期重用时, HTTPS 上的 DNS, 版本, Expect:头, 多路复用, HTTP/3, 不同的后端, Curl_easy, 变量
-
HTTP/3: HTTP, 选择 HTTP/3 后端, QUIC 和 HTTP/3, TCP 与 UDP, 第 4 行:功能, 可用退出代码, 更多数据, HTTP 头, HTTP/3, HTTP/3, 哪个 libcurl 版本运行, 跟踪更多, HTTP/3, 当连接未按预期重用时, 版本, 期望:头, 多路复用, HTTP/3, 不同的后端
-
HTTP/3 后端: 选择 HTTP/3 后端
-
–http0.9: HTTP/0.9
-
–http2: HTTP/2
-
–http2-prior-knowledge: HTTP/2
-
–http3: 启用
-
–http3-only: 当 QUIC 被拒绝时
-
HttpGet: 它如何开始
-
HTTPS 代理: 第 4 行:功能, HTTPS 代理, 所有选项, 本地或代理名称查找
I
-
IDN: libidn2, 国际域名(IDN), 版本, CURLU_URLENCODE, 不同的后端, features
-
IETF:协议,TLS 版本
-
缩进:缩进
-
国际域名:libidn2,国际域名 (IDN),第 4 行:功能
-
IPFS:IPFS
-
–ipfs-gateway:网关
-
IPv4:主机,端口号,可用的 –write-out 变量,curl 和 haproxy,所有选项,libcurl 如何连接,名称解析,
host*.c源文件,变量 -
IPv6:主机,端口号,URL 调用,版本,可用的 –write-out 变量,SOCKS 代理,curl 和 haproxy,所有选项,libcurl 如何连接,名称解析,区域 ID,
CURLRES_IPV6,变量 -
IRC:如何开始,项目沟通
J
-
JavaScript:客户端差异,PAC,JavaScript 和表单,JavaScript 重定向,弄清楚浏览器做了什么
-
–json: trurl 示例命令行, JSON
-
json: 带空格的参数, 函数, 可用的 –write-out 变量, Content-Type, JSON, HTML 外部的 POST
K
-
-K: 命令行、引号和别名, 指定要使用的配置文件
-
keep-alive: 所有选项
-
–keepalive-time: 保持连接
L
-
-L: 短选项, 可用的 –write-out 变量, 告诉 curl 跟随重定向, 请求方法, 重定向
-
LD_LIBRARY_PATH: LD_LIBRARY_PATH
-
–libcurl: –libcurl
-
libcurl 版本: 行 1: curl, 可用的退出代码, 哪个 libcurl 版本, 网络数据转换
-
libidn2: libidn2
-
libpsl: libpsl
-
libressl: TLS 库, 限制
-
librtmp: librtmp
-
libssh: SSH 库, SCP 和 SFTP, 《功能》
-
libssh2: SSH 库, SCP 和 SFTP, 《功能》
-
许可证: 查找用户, 许可证, root, 许可证
-
–limit-rate: 速率限制
-
–location: 长选项, 每个 URL 分离选项, 语法, 告诉 curl 跟随重定向
M
-
–max-filesize: 最大文件大小
-
–max-time: 调整重试次数, 允许的最大时间
-
MIT: 许可证
-
MQTT: curl 支持哪些协议?, MQTT, 支持的方案, 第 3 行:协议, MQTT, 变量, 测试服务器
-
mTLS: 客户端证书
-
多线程: 多线程
N
-
名称解析: 主机名解析, 处理构建选项, 可用的 –write-out 变量, 使用 c-ares 的名称解析技巧, SOCKS 代理, 连接重用, 名称解析, 代理类型, 可用信息, 不同的后端
-
–negotiate: 网络泄露, 身份验证
-
.netrc: 命令行泄露, .netrc, 所有选项,
<features> -
–netrc-file: 启用.netrc
-
–netrc-optional: 启用.netrc
-
nghttp2: nghttp2, 哪个 libcurl 版本运行
-
nix: nix
-
–no-clobber: 覆盖, 使用服务器上的目标文件名
-
–no-eprt: 活跃连接
-
–no-epsv: 被动连接
-
NPN: 所有选项
-
–ntlm: 网络泄露, 认证
O
-
-O: 许多选项和 URL, 数字范围, 下载到由 URL 命名的文件, 使用服务器上的目标文件名, shell 重定向, 多文件下载, 断点续传和范围, 请求速率限制, 认证, 下载, 通过修改日期检查
-
openldap: openldap
-
OpenSSL: 在 MSYS2 上获取 curl 和 libcurl, lib/vtls, 选择 TLS 后端, TLS 库, 可用的退出代码, 本地 CA 存储, OCSP Stapling, 限制, CA 证书缓存, 所有选项, SSL 上下文, 可用的信息, 《
》
P
-
PAC: PAC, 哪个代理?
-
–parallel: 并行传输, 并行, 请求速率限制
-
–parallel-immediate: 在多路复用之前建立连接
-
–parallel-max: 并行传输
-
–path-as-is: –path-as-is
-
百分比编码: URL 编码数据
-
pop3: curl 支持哪些协议?, POP3, 无方案, 版本, 可用的退出代码, 启用 TLS, 读取电子邮件, 安全邮件传输, STARTTLS, CURLU_GUESS_SCHEME, 变量, 测试服务器
-
端口号:连接到端口号,URL 转换为请求,端口号,trurl 示例命令行,可用退出代码,可用 –write-out 变量,为名称提供自定义 IP 地址,本地端口号,HTTP 代理,历史 TELNET,启用,转换网页表单,隐式 FTPS,所有选项,预置,本地地址和端口号,连接重用,为主机提供自定义地址,代理,传输后信息,
CURLU_DEFAULT_PORT,设置 URL 部分,Alt-Svc,Base64 编码 -
–post301:决定重定向中使用的哪种方法
-
–post302:决定重定向中使用的哪种方法
-
–post303:决定重定向中使用的哪种方法
-
进度回调:所有选项,定时回调,进度信息,简易 API,进度回调
-
发音:发音
-
–proxy: HTTP 代理, 认证
-
proxy: 起始方式, 第 4 行:功能, 可用退出代码, 可用 –write-out 变量, 中间代理的篡改, 发现你的代理, PAC, 代理类型, HTTP 代理, SOCKS 代理, MITM 代理, 代理认证, HTTPS 代理, 代理环境变量, 代理头, haproxy, CONNECT 响应代码, 认证, 验证, 所有选项, 代理, 可用信息, 变量
-
–proxy-ca-native: 本地 CA 存储
-
–proxy-http2: HTTP/2
-
–proxy-user: 代理认证, 认证
-
–proxy1.0: HTTP 代理隧道
-
–proxytunnel: HTTP 代理隧道
Q
-
-Q: 引用
-
QUIC: 建立连接, HTTPS, QUIC 和 HTTP/3, 可用的退出代码, 连接时不要超过这个时间, QUIC, 哪个 libcurl 版本运行, HTTP/3, 版本 3 可能是强制性的
-
–quote: 引号
R
-
ranges: 数值范围, 恢复和范围, 范围, 提供文件描述符, HTTP 响应代码, 范围
-
–rate: 请求速率限制
-
读取回调: 尽可能快地制作回调, 所有选项, 读取数据, HTTP POST
-
redhat: Redhat 和 CentOS
-
重定向: 长选项, 每个 URL 单独选项, 语法, 可用的退出代码, 可用的 –write-out 变量, 下载到由 URL 命名的文件, Shell 重定向, 为名称提供自定义 IP 地址, 捕获门户, 重定向, 请求方法, 重定向, 所有选项, 为主机提供自定义地址, 可用的信息, 自动引用, 通过 HTTP 提交登录表单
-
RELEASE-NOTES: 脚本
-
发布: curl-announce, 发布, 脚本, 哪个 libcurl 版本
-
–remote-name-all: 为每个给定的 URL 输出一个, 使用 URL 的文件名部分为所有 URL
-
–remove-on-error: 错误时的残留
-
仓库: 发布, GitHub 上的源代码, Arch Linux, 在 MSYS2 上构建 libcurl, root, 要添加的内容, 网站, 持续集成, 自动构建, 内容
-
–resolve: 为名称提供自定义 IP 地址
-
–retry: 重试, 请求速率限制
-
–retry-all-errors: 在任何和所有错误上重试
-
–retry-connrefused: 连接被拒绝
-
–retry-delay: 调整你的重试次数
-
–retry-max-time: 调整你的重试次数
-
RFC 1436: GOPHER
-
RFC 1738: FILE, multicwd
-
RFC 1939: POP3
-
RFC 1945: 重定向
-
RFC 2229: DICT
-
RFC 2246: TLS 版本
-
RFC 2326: RTSP
-
RFC 2595: IMAP
-
RFC 2818: HTTPS
-
RFC 3207: SMTP
-
RFC 3501: IMAP
-
RFC 3986: 浏览器
-
RFC 4217: FTPS
-
RFC 4511: LDAP
-
RFC 5321: SMTP
-
RFC 7838: 替代服务
-
RFC 8314: IMAPS
-
RFC 8446: TLS 版本
-
RFC 854: TELNET
-
RFC 8999: HTTPS
-
RFC 9110: HTTP
-
RFC 9112: HTTP
-
RFC 9113: HTTP
-
RFC 9114: HTTP
-
RFC 959: FTP, Quote
-
roadmap: 未来
-
rpath:
rpath -
RTMP: curl 支持哪些协议?, RTMP, librtmp, 支持的方案, 版本
-
RTSP: curl 支持哪些协议?, RTSP, 支持的方案, 版本, 所有选项, RTSP 交织数据, 可用信息, 变量
-
rustls: TLS 库,
<功能> -
rustls-ffi: 选择 TLS 后端, Rustls
S
-
Safari: 复制为 curl
-
Schannel: TLS 库, 本地 CA 存储, CA 证书缓存,
<功能> -
Scheme: 连接到端口号, 文件, 命名, librtmp, Scheme, 用户名和密码, TCP 与 UDP, 浏览器, 可用的退出代码, 可用的 –write-out 变量, 代理类型, SOCKS 代理, 代理认证, TLS 用于电子邮件, 哪个 libcurl 版本, 代理类型, 可用的信息, 认证, CURLU_NON_SUPPORT_SCHEME, CURLU_DEFAULT_PORT, URLs, 将响应存储到内存中, 协议处理程序, Curl_handler
-
SCP: curl 支持哪些协议?, SCP, SSH 库, 支持的方案, 版本, 可用的退出代码, 压缩, 允许上传的协议, SCP 和 SFTP, 所有选项, Curl_handler, 《服务器》
-
安全性: curl-announce, 商业支持, 安全, 信任, 安全, 协议变化有多大?, FTPS, 文档, 报告漏洞, 仅小写
http_proxy, TLS, 加密算法, 启用 TLS, TLS 版本, HTTP/0.9, HSTS, 协议版本, 所有选项, HSTS, URLs, HSTS -
SFTP: curl 支持哪些协议?, SFTP, SSH 库, 支持的方案, 版本, 可用的退出代码, 跟踪选项, 压缩, 允许上传的协议, SCP 和 SFTP, 所有选项, Curl 处理程序, 《服务器》, 运行一系列测试
-
–silent: 进度条, 错误信息
-
SMTP: curl 支持哪些协议?, SMTP, 无方案, 版本, 可用的退出代码, 允许上传的协议, 启用 TLS, 发送电子邮件, STARTTLS, 所有选项, CURLU_GUESS_SCHEME, 变量, 测试服务器
-
SMTPS: curl 支持哪些协议?, SMTPS, TLS 库, 支持的方案, 版本, 允许上传的协议, 启用 TLS
-
快照: 每日快照, root
-
SNI: 更改主机头:header
-
–socks4: SOCKS 代理
-
–socks4a: SOCKS 代理
-
–socks5: SOCKS 代理
-
–socks5-hostname: SOCKS 代理
-
–speed-limit: 停止慢速传输
-
–speed-time: 停止慢速传输
-
SSH:SCP,选择 SSH 后端,SSH 库,可用的退出代码,SCP 和 SFTP,历史 TELNET,追踪一切,所有选项,SSH 密钥,不同的后端,Curl 处理程序,变量
-
SSH 后端:选择 SSH 后端
-
SSL 上下文回调:所有选项
-
SSLKEYLOGFILE:TLS,SSLKEYLOGFILE,弄清楚浏览器发送的内容
-
STARTTLS:IMAP,电子邮件的 TLS,STARTTLS
T
-
-T:PUT,上传,方法,HTTP PUT,使用 FTP 上传
-
TCP: 建立连接, 协议变化有多大?, DICT, TCP 与 UDP, 连接重用, 可用的退出代码, 可用的 –write-out 变量, 连接超时, 本地端口号, 保持活动状态, 超时, HTTP 代理隧道, MITM 代理, haproxy, TLS, 使用 TELNET 进行调试, TFTP, QUIC, HTTPS, 两个连接, 连接缓存, 所有选项, HTTP/3, connectdata
-
TELNET: 支持哪些协议?, TELNET, 支持的方案, 版本, 可用的退出代码, TELNET, 所有选项, 变量
-
测试: curl 做了什么?, 报告错误, 处理构建选项, 贡献, 运行本地克隆, 单独安装, 关于 HTTP 响应代码“错误”, 调试构建, 测试服务器, 折磨测试
-
TFTP: curl 支持哪些协议?, TFTP, 支持的方案, TCP 与 UDP, 版本, 可用的退出代码, 允许上传的协议, TFTP, 所有选项, 变量, 测试服务器
-
–tftp-blksize: TFTP 选项
-
–tftp-no-options: TFTP 选项
-
–time-cond: 通过修改日期检查
-
TLS: 安全, 协议变化有多大?, GOPHERS, URL 转换为请求, Ubuntu 和 Debian, lib/vtls, 处理构建选项, 选择 TLS 后端, TLS 库, TLS 库, 连接重用, 第 1 行:curl, 可用的退出代码, 更多数据, 可用的 –write-out 变量, 更改 Host:头, 连接时不要超过这个时间, MITM 代理, TLS, 加密套件, 启用 TLS, TLS 版本, 验证服务器证书, 证书固定, OCSP stapling, 客户端证书, TLS 认证, TLS 后端, SSLKEYLOGFILE, SCP 和 SFTP, TLS 用于电子邮件, 注意事项, 仅 HTTPS, HTTPS, 找出浏览器发送的内容, TLS 指纹识别, FTPS, 跟踪一切, 缓存, 重用句柄, TLS 选项, 所有选项, SSL 上下文, HTTP 代理, 可用信息, URLs, 不同的后端, 连接缓存, 变量
-
TLS 后端: Ubuntu 和 Debian, lib/vtls, 选择 TLS 后端, 第 1 行:curl, 可用退出代码, TLS, 本地 CA 存储, 证书固定, OCSP 粘贴, 客户端证书, TLS 后端, CA 证书缓存, SSL 上下文
-
TODO: 未来, 建议
-
–tr-encoding: 压缩, 传输编码
-
–trace: 跟踪选项,
<command [option="no-output/no-include/force-output/binary-trace"] [timeout="secs"][delay="secs"][type="perl/shell"]> -
–trace-ascii: 跟踪选项, 服务器差异,
<command [option="no-output/no-include/force-output/binary-trace"] [timeout="secs"][delay="secs"][type="perl/shell"]> -
–trace-config: 更多数据
-
–trace-ids: 识别传输和连接
-
–trace-time: 时间戳
-
transfer-encoding: 传递传输编码, 分块编码的 POST 请求
-
trurl: trurl
U
-
-U: 在 MSYS2 上构建 libcurl, 代理身份验证
-
-u: 在 MSYS2 上构建 libcurl, 密码, URLs, IMAP, 认证
-
Ubuntu: Ubuntu 和 Debian
-
URL Globbing: URL 通配符
-
URL parser: 浏览器, trurl, CURLU_ALLOW_SPACE
-
–url-query: 查询
V
-
–variable: 变量
-
变量: 条件中无赋值, 用于通配符的输出变量, 配置文件, 变量, 错误信息, 输出, 代理环境变量, 加密算法, 代理环境变量, 预处理的
-
–verbose: 长选项, 时间戳
-
–version: 版本, TLS 后端, 内存调试
-
漏洞: 漏洞处理
W
-
Wireshark: 可用退出代码, 跟踪选项, SSLKEYLOGFILE, 找出浏览器发送的内容
-
wolfSSH: SSH 库, SCP 和 SFTP, features
-
wolfSSL: 商业支持, lib/vtls, TLS 库, 本地 CA 存储, 限制, 所有选项, SSL 上下文, 《功能》
-
写回调: 尽可能使回调快速, 回调考虑, 所有选项, 写入数据, 响应体, 1. 回调方法, 原始模式, 写回调, 获取简单的 HTTP 页面, 将响应存入内存
-
–write-out: 错误信息, 输出, 覆盖, HTTP 响应代码
X
-
-X: 请求方法, 请求目标, HTTP PUT
-
-x: HTTP 代理, SOCKS 代理, 代理认证, 代理环境变量, 代理头, 代理环境变量
Y
- yum: Redhat 和 CentOS
Z
-
-Z: 并行传输, 并行
-
-z: 通过修改日期检查
-
zlib: HTTP 压缩, 关于内容编码
-
zstd: HTTP 压缩, 哪个 libcurl 版本运行, 支持的内容编码
