Skip to content

Commit 8fd8d95

Browse files
committed
ncurl() gains 'request' argument for specifying arbitrary headers to return
1 parent cebb2bd commit 8fd8d95

File tree

11 files changed

+146
-56
lines changed

11 files changed

+146
-56
lines changed

NEWS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
* Implements `sha224()`, `sha256()`, `sha384()` and `sha512()` series of fast, optimised cryptographic hash and HMAC generation functions using the 'Mbed TLS' library.
66
* `ncurl()` and `stream()` gain the argmument 'pem' for optionally specifying a certificate authority certificate chain PEM file for authenticating secure sites.
7-
* `ncurl()` now returns additional `$status` (response status code) and `$time` (server date header) fields.
7+
* `ncurl()` gains the argument 'request' for specifying response headers to return.
8+
* `ncurl()` now returns additional `$status` (response status code) and `$headers` (response headers) fields.
89
* `messenger()` gains the argument 'auth' for authenticating communications based on a pre-shared key.
910
* `random()` gains the argument 'n' for generating a vector of random numbers.
1011

R/nano.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ print.sendAio <- function(x, ...) {
273273
#'
274274
print.ncurlAio <- function(x, ...) {
275275

276-
cat("< ncurlAio >\n - $status for response status code\n - $time for server date header\n - $raw for raw message\n - $data for message data\n", file = stdout())
276+
cat("< ncurlAio >\n - $status for response status code\n - $headers for requested response headers\n - $raw for raw message\n - $data for message data\n", file = stdout())
277277
invisible(x)
278278

279279
}
@@ -341,5 +341,5 @@ print.errorValue <- function(x, ...) {
341341

342342
#' @export
343343
#'
344-
.DollarNames.ncurlAio <- function(x, pattern = "") grep(pattern, c("status", "time", "raw", "data"),
344+
.DollarNames.ncurlAio <- function(x, pattern = "") grep(pattern, c("status", "headers", "raw", "data"),
345345
value = TRUE, fixed = TRUE)

R/ncurl.R

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
#' HTTP request headers e.g. \code{list(`Content-Type` = "text/plain")} or
3030
#' \code{c(Authorization = "Bearer APIKEY")}.
3131
#' @param data (optional) the request data to be submitted.
32+
#' @param request (optional) a character vector or list specifying the response
33+
#' headers to return e.g. \code{c("Date", "Server")} or
34+
#' \code{list("date", "server")}. These are case-insensitive and will return
35+
#' NULL if not present.
3236
#' @param pem (optional) applicable to secure HTTPS sites only. The path to a
3337
#' file containing X.509 certificate(s) in PEM format, comprising the
3438
#' certificate authority certificate chain (and revocation list if present).
@@ -37,7 +41,7 @@
3741
#' @return Named list of 4 elements:
3842
#' \itemize{
3943
#' \item{\code{$status}} {- integer HTTP repsonse status code (200 - OK).}
40-
#' \item{\code{$time}} {- character 'Date' server response header.}
44+
#' \item{\code{$headers}} {- list of requested response headers.}
4145
#' \item{\code{$raw}} {- raw vector of the received resource (use
4246
#' \code{\link{writeBin}} to save to a file).}
4347
#' \item{\code{$data}} {- converted character string (if \code{'convert' = TRUE}
@@ -59,7 +63,7 @@
5963
#' string at \code{$raw} and \code{$data} will be NULL.
6064
#'
6165
#' @examples
62-
#' ncurl("https://httpbin.org/get")
66+
#' ncurl("https://httpbin.org/get", request = c("Date", "Server"))
6367
#' ncurl("http://httpbin.org/put",,,"PUT", list(Authorization = "Bearer APIKEY"), "hello world")
6468
#' ncurl("http://httpbin.org/post",,,"POST", c(`Content-Type` = "application/json"),'{"k":"v"}')
6569
#'
@@ -71,6 +75,7 @@ ncurl <- function(url,
7175
method = NULL,
7276
headers = NULL,
7377
data = NULL,
78+
request = NULL,
7479
pem = NULL) {
7580

7681
data <- if (!missing(data)) writeBin(object = data, con = raw())
@@ -80,55 +85,56 @@ ncurl <- function(url,
8085
aio <- .Call(rnng_ncurl_aio, url, method, headers, data, pem)
8186
is.integer(aio) && return(aio)
8287

83-
convert <- missing(convert) || isTRUE(convert)
84-
status <- time <- raw <- data <- NULL
88+
force(convert)
89+
force(request)
90+
status <- headers <- raw <- data <- NULL
8591
unresolv <- TRUE
8692
env <- new.env(hash = FALSE)
8793
makeActiveBinding(sym = "status", fun = function(x) {
8894
if (unresolv) {
89-
res <- .Call(rnng_aio_http, aio)
95+
res <- .Call(rnng_aio_http, aio, convert, request)
9096
missing(res) && return(.Call(rnng_aio_unresolv))
9197
if (is.integer(res)) {
9298
data <<- raw <<- res
9399
} else {
94100
status <<- res[[1L]]
95-
time <<- res[[2L]]
101+
headers <<- res[[2L]]
96102
raw <<- res[[3L]]
97-
data <<- if (convert) tryCatch(rawToChar(raw), error = function(e) NULL)
103+
data <<- res[[4L]]
98104
}
99105
aio <<- env[["aio"]] <<- NULL
100106
unresolv <<- FALSE
101107
}
102108
status
103109
}, env = env)
104-
makeActiveBinding(sym = "time", fun = function(x) {
110+
makeActiveBinding(sym = "headers", fun = function(x) {
105111
if (unresolv) {
106-
res <- .Call(rnng_aio_http, aio)
112+
res <- .Call(rnng_aio_http, aio, convert, request)
107113
missing(res) && return(.Call(rnng_aio_unresolv))
108114
if (is.integer(res)) {
109115
data <<- raw <<- res
110116
} else {
111117
status <<- res[[1L]]
112-
time <<- res[[2L]]
118+
headers <<- res[[2L]]
113119
raw <<- res[[3L]]
114-
data <<- if (convert) tryCatch(rawToChar(raw), error = function(e) NULL)
120+
data <<- res[[4L]]
115121
}
116122
aio <<- env[["aio"]] <<- NULL
117123
unresolv <<- FALSE
118124
}
119-
time
125+
headers
120126
}, env = env)
121127
makeActiveBinding(sym = "raw", fun = function(x) {
122128
if (unresolv) {
123-
res <- .Call(rnng_aio_http, aio)
129+
res <- .Call(rnng_aio_http, aio, convert, request)
124130
missing(res) && return(.Call(rnng_aio_unresolv))
125131
if (is.integer(res)) {
126132
data <<- raw <<- res
127133
} else {
128134
status <<- res[[1L]]
129-
time <<- res[[2L]]
135+
headers <<- res[[2L]]
130136
raw <<- res[[3L]]
131-
data <<- if (convert) tryCatch(rawToChar(raw), error = function(e) NULL)
137+
data <<- res[[4L]]
132138
}
133139
aio <<- env[["aio"]] <<- NULL
134140
unresolv <<- FALSE
@@ -137,15 +143,15 @@ ncurl <- function(url,
137143
}, env = env)
138144
makeActiveBinding(sym = "data", fun = function(x) {
139145
if (unresolv) {
140-
res <- .Call(rnng_aio_http, aio)
146+
res <- .Call(rnng_aio_http, aio, convert, request)
141147
missing(res) && return(.Call(rnng_aio_unresolv))
142148
if (is.integer(res)) {
143149
data <<- raw <<- res
144150
} else {
145151
status <<- res[[1L]]
146-
time <<- res[[2L]]
152+
headers <<- res[[2L]]
147153
raw <<- res[[3L]]
148-
data <<- if (convert) tryCatch(rawToChar(raw), error = function(e) NULL)
154+
data <<- res[[4L]]
149155
}
150156
aio <<- env[["aio"]] <<- NULL
151157
unresolv <<- FALSE
@@ -157,7 +163,7 @@ ncurl <- function(url,
157163

158164
} else {
159165

160-
res <- .Call(rnng_ncurl, url, convert, method, headers, data, pem)
166+
res <- .Call(rnng_ncurl, url, convert, method, headers, data, request, pem)
161167

162168
is.character(res) && {
163169
continue <- if (interactive()) readline(sprintf("Follow redirect to <%s>? [Y/n] ", res)) else "n"

README.Rmd

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,10 +425,13 @@ For advanced use, supports additional HTTP methods such as POST or PUT.
425425
```{r ncurladv}
426426
res <- ncurl("http://httpbin.org/post", async = TRUE, method = "POST",
427427
headers = c(`Content-Type` = "application/json", Authorization = "Bearer APIKEY"),
428-
data = '{"key": "value"}')
428+
data = '{"key": "value"}',
429+
request = c("Date", "Server"))
429430
res
430431
431-
call_aio(res)$data
432+
call_aio(res)$headers
433+
434+
res$data
432435
433436
```
434437

README.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ aio
384384
#> < recvAio >
385385
#> - $data for message data
386386
aio$data |> str()
387-
#> num [1:100000000] -0.503 0.475 1.854 0.741 0.834 ...
387+
#> num [1:100000000] 1.783 0.185 1.083 0.849 0.801 ...
388388
```
389389

390390
As `call_aio()` is blocking and will wait for completion, an alternative
@@ -536,35 +536,43 @@ ncurl("https://httpbin.org/headers")
536536
#> $status
537537
#> [1] 200
538538
#>
539-
#> $time
540-
#> [1] "Thu, 01 Sep 2022 18:21:48 GMT"
539+
#> $headers
540+
#> NULL
541541
#>
542542
#> $raw
543543
#> [1] 7b 0a 20 20 22 68 65 61 64 65 72 73 22 3a 20 7b 0a 20 20 20 20 22 48 6f 73
544544
#> [26] 74 22 3a 20 22 68 74 74 70 62 69 6e 2e 6f 72 67 22 2c 20 0a 20 20 20 20 22
545545
#> [51] 58 2d 41 6d 7a 6e 2d 54 72 61 63 65 2d 49 64 22 3a 20 22 52 6f 6f 74 3d 31
546-
#> [76] 2d 36 33 31 30 66 38 33 63 2d 34 37 34 63 35 32 63 32 30 39 35 36 63 34 64
547-
#> [101] 66 37 32 61 66 35 61 66 36 22 0a 20 20 7d 0a 7d 0a
546+
#> [76] 2d 36 33 31 31 32 62 32 30 2d 33 31 39 35 33 31 38 35 35 66 61 36 33 36 62
547+
#> [101] 30 32 62 34 37 62 37 63 38 22 0a 20 20 7d 0a 7d 0a
548548
#>
549549
#> $data
550-
#> [1] "{\n \"headers\": {\n \"Host\": \"httpbin.org\", \n \"X-Amzn-Trace-Id\": \"Root=1-6310f83c-474c52c20956c4df72af5af6\"\n }\n}\n"
550+
#> [1] "{\n \"headers\": {\n \"Host\": \"httpbin.org\", \n \"X-Amzn-Trace-Id\": \"Root=1-63112b20-319531855fa636b02b47b7c8\"\n }\n}\n"
551551
```
552552

553553
For advanced use, supports additional HTTP methods such as POST or PUT.
554554

555555
``` r
556556
res <- ncurl("http://httpbin.org/post", async = TRUE, method = "POST",
557557
headers = c(`Content-Type` = "application/json", Authorization = "Bearer APIKEY"),
558-
data = '{"key": "value"}')
558+
data = '{"key": "value"}',
559+
request = c("Date", "Server"))
559560
res
560561
#> < ncurlAio >
561562
#> - $status for response status code
562-
#> - $time for server date header
563+
#> - $headers for requested response headers
563564
#> - $raw for raw message
564565
#> - $data for message data
565566

566-
call_aio(res)$data
567-
#> [1] "{\n \"args\": {}, \n \"data\": \"{\\\"key\\\": \\\"value\\\"}\", \n \"files\": {}, \n \"form\": {}, \n \"headers\": {\n \"Authorization\": \"Bearer APIKEY\", \n \"Content-Length\": \"16\", \n \"Content-Type\": \"application/json\", \n \"Host\": \"httpbin.org\", \n \"X-Amzn-Trace-Id\": \"Root=1-6310f83c-48c315072c11b954353e13f4\"\n }, \n \"json\": {\n \"key\": \"value\"\n }, \n \"origin\": \"185.225.45.49\", \n \"url\": \"http://httpbin.org/post\"\n}\n"
567+
call_aio(res)$headers
568+
#> $Date
569+
#> [1] "Thu, 01 Sep 2022 21:58:57 GMT"
570+
#>
571+
#> $Server
572+
#> [1] "gunicorn/19.9.0"
573+
574+
res$data
575+
#> [1] "{\n \"args\": {}, \n \"data\": \"{\\\"key\\\": \\\"value\\\"}\", \n \"files\": {}, \n \"form\": {}, \n \"headers\": {\n \"Authorization\": \"Bearer APIKEY\", \n \"Content-Length\": \"16\", \n \"Content-Type\": \"application/json\", \n \"Host\": \"httpbin.org\", \n \"X-Amzn-Trace-Id\": \"Root=1-63112b21-1018c937316c24cb11110b8a\"\n }, \n \"json\": {\n \"key\": \"value\"\n }, \n \"origin\": \"185.225.45.49\", \n \"url\": \"http://httpbin.org/post\"\n}\n"
568576
```
569577

570578
In this respect, it may be used as a performant and lightweight method
@@ -607,10 +615,10 @@ s |> send('{"action": "subscribe", "symbols": "EURUSD"}')
607615
#> [26] 73 79 6d 62 6f 6c 73 22 3a 20 22 45 55 52 55 53 44 22 7d 00
608616

609617
s |> recv(keep.raw = FALSE)
610-
#> [1] "{\"s\":\"EURUSD\",\"a\":0.99582,\"b\":0.9958,\"dc\":\"-0.8003\",\"dd\":\"-0.0080\",\"ppms\":false,\"t\":1662056512000}"
618+
#> [1] "{\"s\":\"EURUSD\",\"a\":0.99499,\"b\":0.99426,\"dc\":\"-0.8844\",\"dd\":\"-0.0088\",\"ppms\":false,\"t\":1662069549000}"
611619

612620
s |> recv(keep.raw = FALSE)
613-
#> [1] "{\"s\":\"EURUSD\",\"a\":0.99583,\"b\":0.99581,\"dc\":\"-0.7993\",\"dd\":\"-0.0080\",\"ppms\":false,\"t\":1662056513000}"
621+
#> [1] "{\"s\":\"EURUSD\",\"a\":0.99474,\"b\":0.99454,\"dc\":\"-0.9098\",\"dd\":\"-0.0091\",\"ppms\":false,\"t\":1662069549000}"
614622

615623
close(s)
616624
```

man/ncurl.Rd

Lines changed: 8 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/aio.c

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ SEXP rnng_ncurl_aio(SEXP http, SEXP method, SEXP headers, SEXP data, SEXP pem) {
650650

651651
}
652652

653-
SEXP rnng_aio_http(SEXP aio) {
653+
SEXP rnng_aio_http(SEXP aio, SEXP convert, SEXP request) {
654654

655655
if (R_ExternalPtrTag(aio) != nano_AioSymbol)
656656
error_return("object is not a valid Aio");
@@ -667,33 +667,71 @@ SEXP rnng_aio_http(SEXP aio) {
667667

668668
nano_handle *handle = (nano_handle *) haio->data;
669669
uint16_t code = nng_http_res_get_status(handle->res);
670-
const char *date = nng_http_res_get_header(handle->res, "Date");
671670

672671
if (code != 200) {
673672
REprintf("HTTP Server Response: %d %s\n", code, nng_http_res_get_reason(handle->res));
674673
if (code >= 300 && code < 400) {
675674
SEXP out;
676-
PROTECT(out = Rf_allocVector(VECSXP, 3));
675+
PROTECT(out = Rf_allocVector(VECSXP, 4));
677676
SET_VECTOR_ELT(out, 0, Rf_ScalarInteger(code));
678-
SET_VECTOR_ELT(out, 1, Rf_mkString(date == NULL ? "" : date));
677+
SET_VECTOR_ELT(out, 1, R_NilValue);
679678
SET_VECTOR_ELT(out, 2, Rf_mkString(nng_http_res_get_header(handle->res, "Location")));
679+
SET_VECTOR_ELT(out, 3, R_NilValue);
680680
UNPROTECT(1);
681681
return out;
682682
}
683683
}
684684

685685
void *dat;
686686
size_t sz;
687-
SEXP out, vec;
687+
SEXP out, vec, cvec = R_NilValue, rvec = R_NilValue;
688688

689-
PROTECT(out = Rf_allocVector(VECSXP, 3));
689+
PROTECT(out = Rf_allocVector(VECSXP, 4));
690690
SET_VECTOR_ELT(out, 0, Rf_ScalarInteger(code));
691-
SET_VECTOR_ELT(out, 1, Rf_mkString(date == NULL ? "" : date));
691+
692+
if (request != R_NilValue) {
693+
const R_xlen_t rlen = Rf_xlength(request);
694+
PROTECT(rvec = Rf_allocVector(VECSXP, rlen));
695+
SEXP rnames;
696+
697+
switch (TYPEOF(request)) {
698+
case STRSXP:
699+
for (R_xlen_t i = 0; i < rlen; i++) {
700+
const char *r = nng_http_res_get_header(handle->res, CHAR(STRING_ELT(request, i)));
701+
SET_VECTOR_ELT(rvec, i, r == NULL ? R_NilValue : Rf_mkString(r));
702+
}
703+
Rf_namesgets(rvec, request);
704+
break;
705+
case VECSXP:
706+
PROTECT(rnames = Rf_allocVector(STRSXP, rlen));
707+
for (R_xlen_t i = 0; i < rlen; i++) {
708+
SEXP rname = STRING_ELT(VECTOR_ELT(request, i), 0);
709+
SET_STRING_ELT(rnames, i, rname);
710+
const char *r = nng_http_res_get_header(handle->res, CHAR(rname));
711+
SET_VECTOR_ELT(rvec, i, r == NULL ? R_NilValue : Rf_mkString(r));
712+
}
713+
Rf_namesgets(rvec, rnames);
714+
UNPROTECT(1);
715+
break;
716+
}
717+
UNPROTECT(1);
718+
}
719+
SET_VECTOR_ELT(out, 1, rvec);
720+
692721
nng_http_res_get_data(handle->res, &dat, &sz);
693722
vec = Rf_allocVector(RAWSXP, sz);
694723
memcpy(RAW(vec), dat, sz);
695724
SET_VECTOR_ELT(out, 2, vec);
696725

726+
if (Rf_asLogical(convert)) {
727+
SEXP expr;
728+
int xc;
729+
PROTECT(expr = Rf_lang2(nano_RtcSymbol, vec));
730+
cvec = R_tryEvalSilent(expr, R_BaseEnv, &xc);
731+
UNPROTECT(1);
732+
}
733+
SET_VECTOR_ELT(out, 3, cvec);
734+
697735
UNPROTECT(1);
698736
return out;
699737

src/init.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ static void RegisterSymbols(void) {
5959
static const R_CallMethodDef CallEntries[] = {
6060
{"rnng_aio_call", (DL_FUNC) &rnng_aio_call, 1},
6161
{"rnng_aio_get_msg", (DL_FUNC) &rnng_aio_get_msg, 3},
62-
{"rnng_aio_http", (DL_FUNC) &rnng_aio_http, 1},
62+
{"rnng_aio_http", (DL_FUNC) &rnng_aio_http, 3},
6363
{"rnng_aio_result", (DL_FUNC) &rnng_aio_result, 1},
6464
{"rnng_aio_stop", (DL_FUNC) &rnng_aio_stop, 1},
6565
{"rnng_aio_stream_in", (DL_FUNC) &rnng_aio_stream_in, 3},
@@ -89,7 +89,7 @@ static const R_CallMethodDef CallEntries[] = {
8989
{"rnng_matchargs", (DL_FUNC) &rnng_matchargs, 1},
9090
{"rnng_matchwarn", (DL_FUNC) &rnng_matchwarn, 1},
9191
{"rnng_messenger", (DL_FUNC) &rnng_messenger, 1},
92-
{"rnng_ncurl", (DL_FUNC) &rnng_ncurl, 6},
92+
{"rnng_ncurl", (DL_FUNC) &rnng_ncurl, 7},
9393
{"rnng_ncurl_aio", (DL_FUNC) &rnng_ncurl_aio, 5},
9494
{"rnng_protocol_open", (DL_FUNC) &rnng_protocol_open, 2},
9595
{"rnng_random", (DL_FUNC) &rnng_random, 1},

0 commit comments

Comments
 (0)