Add curl_connect(), curl_send(), curl_recv(): raw socket access via CURLOPT_CONNECT_ONLY - #448
Add curl_connect(), curl_send(), curl_recv(): raw socket access via CURLOPT_CONNECT_ONLY#448allanvc wants to merge 1 commit into
Conversation
…URLOPT_CONNECT_ONLY
|
What would you need this for in the context of R? |
|
Hi Jeroen, thanks for taking a look. The use case is mRpostman, an IMAP client that runs on libcurl's native IMAP support. Most of the protocol maps fine to one request per command, but a few things don't fit that model. IDLE (RFC 2177) keeps the connection open while the server pushes notifications. MULTIAPPEND and CATENATE send multiple literals and the server paces the upload with continuation prompts. COMPRESS=DEFLATE switches the whole session to a deflate stream. Right now mRpostman ships a small C layer that does exactly what this PR does: open the connection with CURLOPT_CONNECT_ONLY and then use curl_easy_send()/curl_easy_recv(). libcurl still does the TCP connect, the TLS handshake and the certificate verification, and the package talks the protocol on the socket it gets back. If curl exposed curl_connect()/curl_send()/curl_recv(), I could drop that compiled code entirely and the package would be pure R again. It's not IMAP specific either. Any package that needs to speak a stateful text protocol (NNTP, custom TCP/TLS services, etc.) could use this instead of embedding its own socket code, and it gets libcurl's TLS, proxy and CA handling for free. This is basically the use case libcurl documents for connect-only mode, the PR just exposes it to R. Happy to adjust the API or trim the surface if you'd prefer something smaller. |
|
Could you try if the approach from #448 works for you, which uses the |
Opening a curl connection in "r+" or "r+b" mode now sets CURLOPT_CONNECT_ONLY: libcurl only establishes the connection (TCP, TLS handshake, certificate verification, proxy traversal) without speaking the protocol, and read/write on the connection map to curl_easy_recv()/curl_easy_send() on the socket. This gives an interface similar to base socketConnection(), but with TLS and proxy support, for speaking protocols or protocol extensions that libcurl does not implement, such as IMAP IDLE. Alternative to #448 without adding new API surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Hi Jeroen, thanks for #449. I tried it with mRpostman and it works for everything the package does over the raw socket (IDLE, literals with continuations, MULTIAPPEND, CATENATE, BINARY, NOTIFY, COMPRESS=DEFLATE). I replaced the compiled socket code with a few lines of R over Two things I noticed:
|
libcurl can establish a connection (TCP, TLS handshake, certificate
verification, proxies) without speaking the protocol, through
CURLOPT_CONNECT_ONLY, and then hand the socket to the application viacurl_easy_send()/curl_easy_recv(). The package exposes the option(
connect_only), but not the two functions, and a handle that went throughcurl_fetch_memory()cannot use them anyway: the fetch functions perform onthe shared multi handle and remove the easy handle afterwards
(
src/interrupt.c), which detaches the connection. So this PR adds themissing entry point as well:
curl_connect()setsCURLOPT_CONNECT_ONLYand callscurl_easy_perform()directly on the easy handle (the connect is quick and honors
connecttimeout); any handle option (timeouts, TLS, proxy) can be passedthrough its
handleargument.curl_send()loops overcurl_easy_send()handling
CURLE_AGAINby waiting onCURLINFO_ACTIVESOCKETwithselect();curl_recv()waits up totimeoutms for readability and returns what onecurl_easy_recv()yields.The concrete use case is IMAP in
mRpostman:IDLE(RFC 2177) parks aconnection while the server pushes notifications, and commands carrying
literals (
MULTIAPPEND, RFC 3502;REPLACE, RFC 8508) need a+continuation dialogue; neither fits libcurl's request/response model, but
both are simple once the (TLS) socket is available. The same applies to any
line-based protocol libcurl can connect to (SMTP, POP3, NNTP, or plain TCP
via
telnet://).mRpostman>= 2.0.0 ships this as its own compiled code(
src/imap_socket.c); with these functions incurl, that code goes awayand the package is back to pure R.
Notes:
CURLOPT_CONNECT_ONLYexists since libcurl 7.15.2 andcurl_easy_send/curl_easy_recvsince 7.18.2, so there is no new buildrequirement.
CONNECT_ONLY, libcurl consumes the initial exchange of pingpongprotocols (the IMAP greeting and capability banner, for instance) while
connecting; the application should not expect to read it (documented in
the man page).
documents it as one-shot); after the peer closes,
curl_recv()returns anempty raw vector with
attr(, "closed") = TRUE.literal upload with continuation handling, timeout, and peer-close
detection.
tests/testthat/test-socket.Rruns a round trip againstimap.gmail.com (skipped offline/on CRAN).
Validation performed with the patch applied (Linux, libcurl 8.14.1, R 4.4.1): the full package test suite passes (no new failures or skips), and the round trip in
tests/testthat/test-socket.Rwas exercised over TLS (imaps://) and against a plain imap:// server, including a literal upload with+continuation handling, the timeout path, and peer-close detection.roxygen2::roxygenize()regenerates NAMESPACE andman/curl_socket.Rdcleanly.