Bidirectional (connect-only) socket connections via curl() - #449
Conversation
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>
5146ed3 to
2f58ffb
Compare
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Like base socketConnection(), blocking reads on a connect-only
connection now wait at most getOption("timeout") seconds (default
60) and then return the available data, possibly zero bytes, with
isIncomplete() still TRUE. This prevents e.g. readLines(con) from
hanging indefinitely when the peer sends no more data. Sends that
cannot make progress within the timeout raise an error.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Added in b0b0ecc: blocking reads/writes on socket connections now honor 🤖 Generated with Claude Code |
…nnections" This reverts commit b0b0ecc.
Requesting a socket connection with curl(url, "r+") now returns the connection without opening it, so the user can choose how to open it: a regular blocking open(con) for a request/response dialogue where reads wait for data, or open(con, blocking = FALSE) to poll for data without waiting, consistent with regular non-blocking curl connections. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Design update: the con <- curl("imaps://imap.gmail.com", "r+")
open(con, blocking = FALSE)
writeLines("A1 CAPABILITY", con, sep = "\r\n")
while(isIncomplete(con)){
line <- readLines(con, n = 1)
if(length(line)){
cat(line, "\n")
if(grepl("^A1 ", line)) break
}
}
writeLines("A2 LOGOUT", con, sep = "\r\n")
close(con)🤖 Generated with Claude Code |
Alternative proposal for #448: instead of adding new entry points (
curl_connect()/curl_send()/curl_recv()), exposeCURLOPT_CONNECT_ONLYraw socket access through the existing connection interface.Opening a curl connection in read-write mode now creates a bidirectional socket connection, analogous to base
socketConnection()but with libcurl doing TCP, TLS, certificate verification, and proxy traversal:How it works
The constraint behind #448 is that
curl_easy_send()/curl_easy_recv()only work while the easy handle remains attached to its connection, which is why handles that went through the fetch functions cannot be used. Butcurl()connections already satisfy this: the handle stays in the connection's private multi handle untilclose(). So this slots intosrc/curl.cwith no new C entry points, no new exports, and no changes toinit.c/NAMESPACE:rcurl_open()setsCURLOPT_CONNECT_ONLYwhen the open mode contains+. The existing multi open loop needs no changes: the perform completes right after the connect, and connect errors surface through the samecurl_multi_info_read()path (honoring all handle options: timeouts, TLS, proxy).con->readbranches tocurl_easy_recv()and the newcon->writeusescurl_easy_send(), each waiting onCURLINFO_ACTIVESOCKETwithselect(), in interruptible 500ms slices.writeLines()/writeBin()to send,readLines()/readBin()to receive,open(con, "r+b", blocking = FALSE)for polling reads (returns available bytes immediately;isIncomplete()tells you the peer has not closed), and zero-byte read withisIncomplete() == FALSEat peer disconnect. The non-blocking mode covers the IMAPIDLEuse case from Add curl_connect(), curl_send(), curl_recv(): raw socket access via CURLOPT_CONNECT_ONLY #448 without atimeoutparameter.Notes
imaps://,smtps://, ...) libcurl consumes the server greeting (and runs its own capability exchange) during the connect phase, so the first read returns the response to your own first command. Documented in?curl.https://URLs where ALPN negotiates HTTP/2, libcurl 8.x routescurl_easy_send()through the connection filter chain, so hand-written HTTP/1.1 does not go out verbatim. Sethttp_version = 2(HTTP/1.1) on the handle for that use case. This equally affects Add curl_connect(), curl_send(), curl_recv(): raw socket access via CURLOPT_CONNECT_ONLY #448 as proposed. Plain TCP viatelnet://and pingpong protocols are unaffected.close()resets the option on the handle.Testing
tests/testthat/test-socket.R(allskip_on_cran()):serverSocket()(offline)serverSocket()(offline)skip_if_offline())Verified additionally with raw HTTP/1.1 over TLS against hb.cran.dev, and that existing connection tests (
test-connection.R,test-blockopen.R,test-seek.R,test-nonblocking.R) still pass.🤖 Generated with Claude Code