1. @bagder
/* set the options (I left out a few, you will get the point anyway) */
curl_easy_setopt(handles[HTTP_HANDLE], CURLOPT_URL, "https://example.com");
curl_easy_setopt(handles[FTP_HANDLE], CURLOPT_URL, "ftp://example.com");
curl_easy_setopt(handles[FTP_HANDLE], CURLOPT_UPLOAD, 1L);
/* init a multi stack */
multi_handle = curl_multi_init();
/* add the individual transfers */
for(i = 0; i<HANDLECOUNT; i++)
curl_multi_add_handle(multi_handle, handles[i]);
while(still_running) {
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;
}
/* See how the transfers went */
while((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
if(msg->msg == CURLMSG_DONE) {
int idx;
/* Find out which handle this message is about */
for(idx = 0; idx<HANDLECOUNT; idx++) {
int found = (msg->easy_handle == handles[idx]);
if(found)
break;
}
switch(idx) {
case HTTP_HANDLE:
printf("HTTP transfer completed with status %dn", msg->data.result);
break;
case FTP_HANDLE:
printf("FTP transfer completed with status %dn", msg->data.result);
break;
}
}
}
/* remove the transfers and cleanup the handles */
for(i = 0; i<HANDLECOUNT; i++) {
curl_multi_remove_handle(multi_handle, handles[i]);
curl_easy_cleanup(handles[i]);
}
#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, "https://example.com");
/* example.com is redirected, so we tell libcurl to follow redirection */
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %sn",
curl_easy_strerror(res));
/* always cleanup */
curl_easy_cleanup(curl);
}
return 0;
}
mastering libcurl
November 16, 2023 Daniel Stenberg
more libcurl source code and details in a single video than you ever saw before
part one
4. Setup - November 16 + 20, 2023
Live-streamed
Expected to last multiple hours
Recorded
Lots of material never previously presented
There will be LOTS of source code on display
https://github.com/bagder/mastering-libcurl
@bagder
6. mastering libcurl
The project
Getting it
API and ABI
Architecture
API fundamentals
Setting up
@bagder
Transfers
Share API
TLS
Proxies
HTTP
Header API
URL API
WebSocket
Future
Part 1 Part 2
November 16, 2023 November 20, 2023
10. Main products
curl - command line tool for client-side internet transfers with URLs
libcurl - library for client-side internet transfers with URLs
★ Always and only client-side
★ An internet transfer: upload or download or both
★ Endpoint described with a URL
@bagder
11. Open Source
Everything in the curl project is open source
Every discussion and decision are held and done in the open
Open source means everyone can reshare and change
curl is (essentially) MIT licensed
@bagder
Free
Open
Gratis
13. Development
curl is developed by “everyone”
only Daniel works on curl full-time
everyone can provide their proposed changes as “pull requests”
No paperwork required
Changes are reviewed and tested thoroughly
A small team of maintainers can accept and merge changes
@bagder
14. Releases
We do releases every 8 weeks (or sooner when necessary)
At 252 releases
We release what is in the master branch at the time
@bagder
19. Asking for help
Mailing lists
library/development/debugging questions: curl-library
https://lists.haxx.se/mailman/listinfo/curl-library
Web “forum”
https://github.com/curl/curl/discussions
@bagder
23. 101 operating systems
@bagder
Syllable OS TPF
Tizen
Symbian Tru64
SunOS tvOS
ucLinux
Genode Hurd
iOS
Integrity
Illumos
HP-UX
HardenedBSD
Haiku
z/OS
Nintendo
Switch
NonStop OS
NetWare
MorphOS MPE/iX MS-DOS
NCR MP-RAS NetBSD
RISC OS
Redox
ReactOS
Sailfish OS SCO Unix Serenity SINIX-Z
Qubes OS
UnixWare WebOS
vxWorks
VMS
Windows
UNICOS
Windows CE
Wii System
Software
AmigaOS Blackberry 10
BeOS
Android
Blackberry
Tablet OS
AIX
Cell OS
Aros
IRIX
RTEMS
Mbed Micrium
macOS
Mac OS 9
Linux Lua RTOS
eCOS
FreeRTOS
FreeBSD
FreeDOS
Fuchsia
DragonFly
BSD
ROS
Cisco IOS
OpenBSD
OS/2 OS/400
Ultrix
ipadOS
NuttX
Solaris
Xbox
System
Chrome OS
MINIX
Garmin OS
QNX
PlayStation
Portable
Plan 9
OS21
OpenStep
Orbis OS
z/TPF
z/VM z/VSE Operating systems known to have run curl
Atari FreeMiNT
DR DOS
Sortix
Zephyr
watchOS
Xenix
DG/UX
ArcaOS
Wii U
SkyOS
Wear OS
Meego
Maemo Moblin
NextStep
CheriBSD
32. Build it yourself
configure
cmake
MSVC
needs third party development packages installed
@bagder
autotools
./configure with-openssl …
make -sj
make install
cmake
mkdir build
cd build
cmake .
make -sj
MSVC
cd winbuild
name /f Makefile.vc mode=dll WITH_SSL=dll
Visual C++ project files
<projects/Windows/VC*/curl-all.sln>
33. Building custom versions
enable/disable features, protocols and 3rd parties
libcurl API functions remain, but may return different values
Common when you build devices / control the environment
@bagder
34. Third party dependencies
Many features require 3rd party libraries
TLS
SSH
LDAP
RTMP
HTTP compression (gzip, brotli, zstd)
HTTP/2
HTTP/3
Asynchronous name resolving without threads
Auth - kerberos, SASL
@bagder
35. Target requirements
a minimal libcurl build with smallest TLS lands on around 100kB
minimum run-time memory requirements for libcurl: 20kB
add memory requirements for the TLS library
the API remains the same
@bagder
37. compatibility
The same API on all platforms
You can always upgrade to the next version without breakage
You may not be able to downgrade
Options and functions are clearly documented when they were added
@bagder
38. versions
libcurl 7.1 released August 7, 2000
libcurl 8.4.0 released October 11, 2023
Differ in features, protocols, performance
Later versions have more functions
@bagder
39. The libcurl API is for C
libcurl provides a C interface
works for C++ as well
We will only speak C today
Users of other languages get to use bindings
@bagder
41. compiling libcurl programs
gcc mycode.c -lcurl
clang mycode.c -lcurl
Your other C compilers work too:
1. Specify where the curl header file is
2. Tell the linker to link with libcurl
@bagder
42. Documentation
We take documentation seriously
Every function has a man page
Every option has a man page
120 stand-alone examples
https://curl.se/ has them all (updated)
https://everything.curl.dev/ provides more resources
Tip: just google the function/option name
@bagder
44. @bagder
When curl started there
was no choice
C89
Only now alternatives
appear for libraries
C keeps curl extremely
portable
C code will remain a build
option
@bagder
49. basic transfers by default
Transfers only do what is asked; basic by default
The API is largely protocol agnostic
Change behavior by setting options
@bagder
50. global init
An application should do a global libcurl init
And a global cleanup
@bagder
global.c
#include <stdio.h>
#include <curl/curl.h>
int main(void)
{
curl_global_init(CURL_GLOBAL_DEFAULT);
/* use libcurl, then before exiting... */
curl_global_cleanup();
return 0;
}
51. easy handle
CURL *handle - often referred to as an easy handle
A CURL * is an opaque handle
Compare to FILE *
A handle identifies a single transfer
Can and should be reused
Create one with curl_easy_init()
Free it with curl_easy_cleanup()
@bagder
easy-init.c
#include <stdio.h>
#include <curl/curl.h>
int main(void)
{
CURL *easy = curl_easy_init();
curl_easy_setopt(easy, CURLOPT_URL,
“https://curl.se/”);
curl_easy_perform(easy);
curl_easy_cleanup(easy);
return 0;
}
52. easy options
An easy handle needs options set to know what to do
sticky
independent
order-independent
Options set copy data
CURLOPT_URL is the only mandatory option
Download is the default action
An application typically sets many options
Options control timeouts, name resolve alternatives, connectivity, protocol version selection,
TLS version selection, authentication, proxies, how to receive or receive data - and much much
more.
@bagder
54. curl_easy_setopt
CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parameter);
The parameter type depends on the option.
Beware: varargs makes the type checks lax
Might want extra typecasts
When setting strings, libcurl copies the data
Returns an error code
@bagder
easy-setopt.c
#include <stdio.h>
#include <curl/curl.h>
int main(void)
{
CURL *easy = curl_easy_init();
CURLcode result;
curl_easy_setopt(easy, CURLOPT_URL,
“https://curl.se/”);
curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L);
result = curl_easy_perform(easy);
curl_easy_cleanup(easy);
return (int)result;
}
55. curl_easy_perform
For synchronous transfers
Performs to the end, successful or failure
Done as fast as possible
Might take a long - or short - time
You can limit how long time to allow
Returns success as an CURLcode
Stores cached info in the easy handle
@bagder
easy-perform.c
#include <stdio.h>
#include <curl/curl.h>
int main(void)
{
CURL *easy = curl_easy_init();
CURLcode result;
curl_easy_setopt(easy, CURLOPT_URL,
“https://curl.se/”);
result = curl_easy_perform(easy);
curl_easy_cleanup(easy);
return (int)result;
}
56. curl_easy_cleanup
Kills and frees all resources associated with an easy handle
You might want to call curl_easy_reset() and use it again
@bagder
57. callbacks
provide function pointer to curl (*FUNCTION)
provide a custom pointer to pass to the function (*DATA)
libcurl calls it when it needs things done
do not call libcurl functions from within callbacks
(unless you meet the exceptions)
@bagder
58. write callback
Data is provided to the application using the write callback: CURLOPT_WRITEFUNCTION
libcurl wants to write incoming data somewhere
size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata);
ptr - points to the data that arrives
size - is always 1
nmemb - the size of the data in number of bytes
userdata - a custom pointer you set with CURLOPT_WRITEDATA
(prototype looks like fwrite)
@bagder
60. multi handle
A “super handle” made for holding one or more easy handles.
Remember: each easy handle is a single transfer
With multi, all transfers are performed in parallel, in a non-blocking manner
Easy handles can be added and removed from the multi handle at any time
Create a multi handle with CURLM *handle = curl_multi_init()
Add an easy handle to a multi handle with curl_multi_add_handle(multi, easy)
Remove an easy handle again with curl_multi_remove_handle(multi, easy)
@bagder
61. curl_multi_perform
Do drive all transfers in the multi handle, call curl_multi_perform()
Does as much work it can without blocking, then returns
Your application waits until there is more work to do
Easiest done with curl_multi_poll()
Check if any of the transfers are done with curl_multi_info_read()
While there are unfinished transfers, continue performing them
@bagder
62. multi internals
Internally, the easy interface is using the multi interface
This ensures everything always work for either interface
All internals are done non-blocking (almost)
@bagder
63. curl_multi_info_read
The multi interface drives N parallel transfers
curl_multi_perform() does not return status about individual transfers
Returns messages when one or more of the transfers has ended
The message contains individual easy handle and CURLcode for the specific transfer
Call it again to get the next message
@bagder
struct CURLMsg {
CURLMSG msg; /* what this message means */
CURL *easy_handle; /* the handle it concerns */
union {
void *whatever; /* message-specific data */
CURLcode result; /* return code for transfer */
} data;
};
man curl_multi_info_read
65. multi.c
@bagder
CURL *easy = curl_easy_init();
CURL *easy2 = curl_easy_init();
curl_easy_setopt(easy, CURLOPT_URL, "https://example.com/");
curl_easy_setopt(easy2, CURLOPT_URL, "https://curl.se/");
CURLM *multi = curl_multi_init();
curl_multi_add_handle(multi, easy);
curl_multi_add_handle(multi, easy2);
int still = 1;
while(still) {
CURLMsg *msg;
CURLMcode mc = curl_multi_perform(multi, &still);
if(still) {
mc = curl_multi_poll(multi, NULL, 0, 1000, NULL);
if(mc)
break;
}
do {
int queued;
msg = curl_multi_info_read(multi, &queued);
if(msg) {
if(msg->msg == CURLMSG_DONE) {
printf("Completed: %dn", (int)msg->data.result);
}
}
} while(msg);
} /* loop while still is non-zero */
curl_multi_remove_handle(multi, easy);
curl_multi_remove_handle(multi, easy2);
curl_multi_cleanup(multi);
curl_easy_cleanup(easy);
curl_easy_cleanup(easy2);
setup
tear down
transfer
66. caches
curl_easy_perform - caches in the easy handle
curl_multi_perform - caches in the multi handle, shared by all added easy
handles
@bagder
DNS
cache
connection
pool
TLS
session-ID
cache
CA store
cache
cookies
Alt-svc
data
HSTS data
per single handle even in multi handles
67. curl_multi_socket_action
The event-based API flavor
Scales better when going above hundreds of transfers
Applications call libcurl and pass in the socket that has activity
And what specific activity it is (read, write etc)
libcurl tells application what sockets to monitor
libcurl tells application about when its timer expires
@bagder
77. connection pool
connections stored if still alive
only stores up to N connections: CURLOPT_MAXCONNECTS
kept up to a maximum age: CURLOPT_MAXAGE_CONN
reuse is done per name, not IP address
works for all supported protocols
numerous extra conditions and requirements
opt-out reuse: CURLOPT_FRESH_CONNECT
make next transfer not reusable: CURLOPT_FORBID_REUSE
TCP keepalive: CURLOPT_TCP_KEEPALIVE
@bagder
connection
pool
78. Connections: name tricks
Pre-populate the DNS cache: CURLOPT_RESOLVE
@bagder
int main(void)
{
CURL *curl;
CURLcode res = CURLE_OK;
struct curl_slist *host;
host = curl_slist_append(NULL,
"example.com:443:127.0.0.1");
host = curl_slist_append(host,
" .example:443:127.0.0.1");
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_RESOLVE, host);
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
curl_slist_free_all(host);
return (int)res;
}
resolve.c
79. Connections: name tricks
Host + port “redirect”: CURLOPT_CONNECT_TO
@bagder
connect-to.c
int main(void)
{
CURL *curl;
CURLcode res = CURLE_OK;
struct curl_slist *host;
host = curl_slist_append(NULL,
"curl.se:443:example.com:443");
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_CONNECT_TO, host);
curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se/");
/* unless we disable the check libcurl
returns CURLE_PEER_FAILED_VERIFICATION */
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
curl_slist_free_all(host);
return (int)res;
}
80. Connections: when you use c-ares
c-ares is a third party name resolver
CURLOPT_DNS_SERVERS
@bagder
dns-servers.c
int main(void)
{
CURL *curl;
CURLcode res = CURLE_OK;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se/");
curl_easy_setopt(curl, CURLOPT_DNS_SERVERS,
“192.168.1.100:53,192.168.1.101”);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
return (int)res;
}
81. Connections: network interface
Bind the socket to an interface or address: CURLOPT_INTERFACE
@bagder
interface.c
int main(void)
{
CURL *curl;
CURLcode res = CURLE_OK;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_INTERFACE, "enp3s0");
curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se/");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
return (int)res;
}
82. Connections: local port number
Restrict local TCP port number: CURLOPT_LOCALPORT and
CURLOPT_LOCALPORTRANGE
@bagder
localport.c
int main(void)
{
CURL *curl;
CURLcode res = CURLE_OK;
curl = curl_easy_init();
if(curl) {
/* Try to use a local port number between 20000-20009 */
curl_easy_setopt(curl, CURLOPT_LOCALPORT, 20000L);
curl_easy_setopt(curl, CURLOPT_LOCALPORTRANGE, 10L);
curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se/");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
return (int)res;
}
83. Connections: TCP KEEPALIVE
Prevent TCP connections from being
idle
By default, a connection can be
completely silent over long periods
or disconnected without it getting
noticed
@bagder
keepalive.c
int main(void)
{
CURL *curl;
CURLcode res = CURLE_OK;
curl = curl_easy_init();
if(curl) {
/* enable TCP keep-alive for this transfer */
curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
/* keep-alive idle time to 120 seconds */
curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 120L);
/* interval time between keep-alive probes: 60 seconds */
curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 60L);
curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se/");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
return (int)res;
}
84. Connections: IP family
libcurl does IPv6 + IPv4 by default
Using happy eyeballs
You can change that
@bagder
ipv6only.c
int main(void)
{
CURL *curl;
CURLcode res = CURLE_OK;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se/");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
return (int)res;
}
85. Authentication
Most protocols offer authentication
Many more than one type
Set CURLOPT_USERNAME +
CURLOPT_PASSWORD
And optionally the type
For proxy, it has its own set of
credentials and auth type options:
CURLOPT_PROXYUSERNAME,
CURLOPT_PROXYPASSWORD etc
For HTTP, cookies is often used
@bagder
auth.c
int main(void)
{
CURL *curl;
CURLcode res = CURLE_OK;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_USERNAME, "clark");
curl_easy_setopt(curl, CURLOPT_PASSWORD, "kent");
curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se/");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
return (int)res;
}
86. .netrc
old config file for ftp authentication
libcurl supports it for generic credentials per host
defaults to $HOME/.netrc
@bagder
netrc.c
int main(void)
{
CURL *curl;
CURLcode res = CURLE_OK;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
curl_easy_setopt(curl, CURLOPT_NETRC_FILE,
“/home/daniel/s3cr3ts.txt”);
curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se/");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
return (int)res;
}
87. return codes
The easy interface returns CURLcode
The multi interface returns CURLMcode
The share interface returns CURLSHcode
The URL interface returns CURLUcode
0 is always success
non-zero is an error code
libcurl-errors.3 has all the details
@bagder