From d9c3963528238934ff0e2e0959cbf7848dd3c183 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Sat, 14 Jun 2025 17:18:29 +0100 Subject: [PATCH 1/2] Allow supplying a raw vector as ncurl() data --- NEWS.md | 1 + R/ncurl.R | 8 +++++--- man/ncurl.Rd | 8 +++++--- man/ncurl_aio.Rd | 8 +++++--- man/ncurl_session.Rd | 8 +++++--- src/ncurl.c | 25 ++++++++++++++++--------- tests/tests.R | 4 ++-- 7 files changed, 39 insertions(+), 23 deletions(-) diff --git a/NEWS.md b/NEWS.md index 07e85302c..acb5edfc0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ #### Updates +* `ncurl()` and variants now accept request data supplied as a raw vector (thanks @thomasp85, #158). * `cv_reset()` now correctly resets the flag in the case a flag event has already been registered with `pipe_notify()`. * The previous `listen()` and `dial()` argument `error`, removed in v1.6.0, is now defunct. * The previous `serial_config()` argument `vec`, unutilised since v1.6.0, is removed. diff --git a/R/ncurl.R b/R/ncurl.R index ff1f4d042..31dd08c34 100644 --- a/R/ncurl.R +++ b/R/ncurl.R @@ -17,9 +17,11 @@ #' request headers, for example: \cr #' `c(Authorization = "Bearer APIKEY", "Content-Type" = "text/plain")` \cr #' A non-character or non-named vector will be ignored. -#' @param data (optional) character string request data to be submitted. If a -#' vector, only the first element is taken, and non-character objects are -#' ignored. +#' @param data (optional) request data to be submitted. Must be a character +#' string or raw vector, and other objects are ignored. If a character vector, +#' only the first element is taken. When supplying binary data, the +#' appropriate 'Content-Type' header should be set to specify the binary +#' format. #' @param response (optional) a character vector specifying the response headers #' to return e.g. `c("date", "server")`. These are case-insensitive and #' will return NULL if not present. A non-character vector will be ignored. diff --git a/man/ncurl.Rd b/man/ncurl.Rd index e78dc08bc..f83fa1bdc 100644 --- a/man/ncurl.Rd +++ b/man/ncurl.Rd @@ -35,9 +35,11 @@ request headers, for example: \cr \code{c(Authorization = "Bearer APIKEY", "Content-Type" = "text/plain")} \cr A non-character or non-named vector will be ignored.} -\item{data}{(optional) character string request data to be submitted. If a -vector, only the first element is taken, and non-character objects are -ignored.} +\item{data}{(optional) request data to be submitted. Must be a character +string or raw vector, and other objects are ignored. If a character vector, +only the first element is taken. When supplying binary data, the +appropriate 'Content-Type' header should be set to specify the binary +format.} \item{response}{(optional) a character vector specifying the response headers to return e.g. \code{c("date", "server")}. These are case-insensitive and diff --git a/man/ncurl_aio.Rd b/man/ncurl_aio.Rd index 6eb8e267c..497d99726 100644 --- a/man/ncurl_aio.Rd +++ b/man/ncurl_aio.Rd @@ -30,9 +30,11 @@ request headers, for example: \cr \code{c(Authorization = "Bearer APIKEY", "Content-Type" = "text/plain")} \cr A non-character or non-named vector will be ignored.} -\item{data}{(optional) character string request data to be submitted. If a -vector, only the first element is taken, and non-character objects are -ignored.} +\item{data}{(optional) request data to be submitted. Must be a character +string or raw vector, and other objects are ignored. If a character vector, +only the first element is taken. When supplying binary data, the +appropriate 'Content-Type' header should be set to specify the binary +format.} \item{response}{(optional) a character vector specifying the response headers to return e.g. \code{c("date", "server")}. These are case-insensitive and diff --git a/man/ncurl_session.Rd b/man/ncurl_session.Rd index dda6dd37a..72bf1cc9e 100644 --- a/man/ncurl_session.Rd +++ b/man/ncurl_session.Rd @@ -33,9 +33,11 @@ request headers, for example: \cr \code{c(Authorization = "Bearer APIKEY", "Content-Type" = "text/plain")} \cr A non-character or non-named vector will be ignored.} -\item{data}{(optional) character string request data to be submitted. If a -vector, only the first element is taken, and non-character objects are -ignored.} +\item{data}{(optional) request data to be submitted. Must be a character +string or raw vector, and other objects are ignored. If a character vector, +only the first element is taken. When supplying binary data, the +appropriate 'Content-Type' header should be set to specify the binary +format.} \item{response}{(optional) a character vector specifying the response headers to return e.g. \code{c("date", "server")}. These are case-insensitive and diff --git a/src/ncurl.c b/src/ncurl.c index fe303b6eb..4b9eaaf85 100644 --- a/src/ncurl.c +++ b/src/ncurl.c @@ -52,9 +52,16 @@ static SEXP mk_error_ncurlaio(const int xc) { static nano_buf nano_char_buf(const SEXP data) { - nano_buf buf; - const char *s = NANO_STRING(data); - NANO_INIT(&buf, (unsigned char *) s, strlen(s)); + nano_buf buf = {0}; + switch (TYPEOF(data)) { + case STRSXP: ; + const char *s = NANO_STRING(data); + NANO_INIT(&buf, (unsigned char *) s, strlen(s)); + break; + case RAWSXP: + NANO_INIT(&buf, NANO_DATAPTR(data), XLENGTH(data)); + break; + } return buf; @@ -175,9 +182,9 @@ SEXP rnng_ncurl(SEXP http, SEXP convert, SEXP follow, SEXP method, SEXP headers, } } } - if (data != R_NilValue && TYPEOF(data) == STRSXP) { + if (data != R_NilValue) { nano_buf enc = nano_char_buf(data); - if ((xc = nng_http_req_set_data(req, enc.buf, enc.cur))) + if (enc.cur && (xc = nng_http_req_set_data(req, enc.buf, enc.cur))) goto fail; } @@ -345,9 +352,9 @@ SEXP rnng_ncurl_aio(SEXP http, SEXP convert, SEXP method, SEXP headers, SEXP dat } } } - if (data != R_NilValue && TYPEOF(data) == STRSXP) { + if (data != R_NilValue) { nano_buf enc = nano_char_buf(data); - if ((xc = nng_http_req_set_data(handle->req, enc.buf, enc.cur))) + if (enc.cur && (xc = nng_http_req_set_data(handle->req, enc.buf, enc.cur))) goto fail; } @@ -550,9 +557,9 @@ SEXP rnng_ncurl_session(SEXP http, SEXP convert, SEXP method, SEXP headers, SEXP } } } - if (data != R_NilValue && TYPEOF(data) == STRSXP) { + if (data != R_NilValue) { nano_buf enc = nano_char_buf(data); - if ((xc = nng_http_req_set_data(handle->req, enc.buf, enc.cur))) + if (enc.cur && (xc = nng_http_req_set_data(handle->req, enc.buf, enc.cur))) goto fail; } diff --git a/tests/tests.R b/tests/tests.R index 1ffb81884..29a6c4b01 100644 --- a/tests/tests.R +++ b/tests/tests.R @@ -430,7 +430,7 @@ test_error(socket(listen = "test"), "argument") test_type("list", ncurl("http://www.cam.ac.uk/")) test_type("list", ncurl("http://www.cam.ac.uk/", follow = FALSE, response = "date")) test_type("list", ncurl("http://www.cam.ac.uk/", follow = TRUE)) -test_type("list", ncurl("http://postman-echo.com/post", convert = FALSE, method = "POST", headers = c(`Content-Type` = "text/plain"), data = "test", response = c("Date", "Server"), timeout = 3000)) +test_type("list", ncurl("https://postman-echo.com/post", convert = FALSE, method = "POST", headers = c(`Content-Type` = "application/octet-stream"), data = as.raw(1L), response = c("Date", "Server"), timeout = 3000)) test_class("errorValue", ncurl("http")$data) test_class("recvAio", haio <- ncurl_aio("http://example.com/")) test_true(is_aio(haio)) @@ -438,7 +438,7 @@ test_type("integer", call_aio(haio)$status) test_class("ncurlAio", haio <- ncurl_aio("https://example.com/", convert = FALSE, response = "server")) test_notnull(haio$status) if (call_aio(haio)$status == 200L) test_notnull(haio$headers) -test_class("ncurlAio", put1 <- ncurl_aio("http://postman-echo.com/put", method = "PUT", headers = c(Authorization = "Bearer token"), data = "test", response = c("Date", "server"), timeout = 3000L)) +test_class("ncurlAio", put1 <- ncurl_aio("https://postman-echo.com/put", method = "PUT", headers = c(`Content-Type` = "text/plain", Authorization = "Bearer token"), data = "test", response = c("Date", "server"), timeout = 3000L)) test_print(put1) test_type("integer", call_aio_(put1)$status) if (put1$status == 200L) test_notnull(put1$headers) From 4efd86f723e7c540e9ad2d2c85ef99a15debf939 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Sat, 14 Jun 2025 17:27:36 +0100 Subject: [PATCH 2/2] Should copy the data for async ncurl cases --- src/ncurl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ncurl.c b/src/ncurl.c index 4b9eaaf85..a9b693567 100644 --- a/src/ncurl.c +++ b/src/ncurl.c @@ -354,7 +354,7 @@ SEXP rnng_ncurl_aio(SEXP http, SEXP convert, SEXP method, SEXP headers, SEXP dat } if (data != R_NilValue) { nano_buf enc = nano_char_buf(data); - if (enc.cur && (xc = nng_http_req_set_data(handle->req, enc.buf, enc.cur))) + if (enc.cur && (xc = nng_http_req_copy_data(handle->req, enc.buf, enc.cur))) goto fail; } @@ -559,7 +559,7 @@ SEXP rnng_ncurl_session(SEXP http, SEXP convert, SEXP method, SEXP headers, SEXP } if (data != R_NilValue) { nano_buf enc = nano_char_buf(data); - if (enc.cur && (xc = nng_http_req_set_data(handle->req, enc.buf, enc.cur))) + if (enc.cur && (xc = nng_http_req_copy_data(handle->req, enc.buf, enc.cur))) goto fail; }