Skip to content

Commit d0bcc2c

Browse files
committed
ncurl - use weakrefs for async
1 parent fc37aa4 commit d0bcc2c

File tree

5 files changed

+80
-38
lines changed

5 files changed

+80
-38
lines changed

R/stream.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ stream <- function(dial = NULL, listen = NULL, textframes = FALSE) {
9999
#'
100100
#' In non-interactive sessions: redirects are never followed.
101101
#'
102+
#' For async requests, the redirect address will be returned as a character
103+
#' string at \code{$raw} and \code{$data} will be NULL.
104+
#'
102105
#' @section TLS Support:
103106
#'
104107
#' Connecting to secure https sites is supported if \code{\link{nng_version}}

README.Rmd

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Implemented transports:
5050
5. [RPC and Distributed Computing](#rpc-and-distributed-computing)
5151
6. [Publisher / Subscriber Model](#publisher-subscriber-model)
5252
7. [Surveyor / Repondent Model](#surveyor-respondent-model)
53-
8. [ncurl: Minimalist http Client](#ncurl-minimalist-http-client)
53+
8. [ncurl: (Async) HTTP Client](#ncurl-async-http-client)
5454
9. [stream: Websocket Client](#stream-websocket-client)
5555
10. [Building from source](#building-from-source)
5656
11. [Links](#links)
@@ -402,26 +402,33 @@ Above it can be seen that the final value resolves into a timeout, which is an i
402402

403403
[&laquo; Back to ToC](#table-of-contents)
404404

405-
### ncurl: Minimalist http Client
405+
### ncurl: Async HTTP Client
406406

407-
`ncurl()` is a minimalist http(s) client. In normal use, it takes only one argument, the URL. It can follow redirects.
407+
`ncurl()` is a minimalist http(s) client.
408+
409+
By setting `async = TRUE`, it performs requests asynchronously, returning immediately with a 'recvAio'.
410+
411+
For normal use, it takes just the URL. It can follow redirects.
408412

409413
```{r ncurl}
410414
411415
ncurl("http://httpbin.org/headers")
412416
413417
```
414418

415-
For advanced use, supports additional HTTP methods such as POST or PUT. In this respect, it may be used as a performant and lightweight method for making requests to REST APIs.
419+
For advanced use, supports additional HTTP methods such as POST or PUT.
416420

417421
```{r ncurladv}
418422
419-
res <- ncurl("http://httpbin.org/post", async = TRUE, "POST", "application/json", "Bearer APIKEY", '{"key": "value"}')
423+
res <- ncurl("http://httpbin.org/post", async = TRUE,
424+
"POST", "application/json", "Bearer APIKEY", '{"key": "value"}')
425+
res
426+
420427
call_aio(res)$data
421428
422429
```
423430

424-
There is also the option of performing requests asynchronously, in which case the function returns immediately with a 'recvAio'.
431+
In this respect, it may be used as a performant and lightweight method for making REST API requests.
425432

426433
[&laquo; Back to ToC](#table-of-contents)
427434

README.md

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Implemented transports:
5151
5. [RPC and Distributed Computing](#rpc-and-distributed-computing)
5252
6. [Publisher / Subscriber Model](#publisher-subscriber-model)
5353
7. [Surveyor / Repondent Model](#surveyor-respondent-model)
54-
8. [ncurl: Minimalist http Client](#ncurl-minimalist-http-client)
54+
8. [ncurl: (Async) HTTP Client](#ncurl-async-http-client)
5555
9. [stream: Websocket Client](#stream-websocket-client)
5656
10. [Building from source](#building-from-source)
5757
11. [Links](#links)
@@ -370,7 +370,7 @@ aio
370370
#> < recvAio >
371371
#> - $data for message data
372372
aio$data |> str()
373-
#> num [1:100000000] 1.079 0.855 -0.88 1.192 -0.777 ...
373+
#> num [1:100000000] 0.809 -1.518 1.473 0.943 1.033 ...
374374
```
375375

376376
As `call_aio()` is blocking and will wait for completion, an alternative
@@ -405,37 +405,37 @@ an environment variable `NANONEXT_LOG`.
405405

406406
``` r
407407
logging(level = "info")
408-
#> 2022-03-29 13:53:51 [ log level ] set to: info
408+
#> 2022-03-29 14:33:28 [ log level ] set to: info
409409

410410
pub <- socket("pub", listen = "inproc://nanobroadcast")
411-
#> 2022-03-29 13:53:51 [ sock open ] id: 9 | protocol: pub
412-
#> 2022-03-29 13:53:51 [ list start ] sock: 9 | url: inproc://nanobroadcast
411+
#> 2022-03-29 14:33:28 [ sock open ] id: 9 | protocol: pub
412+
#> 2022-03-29 14:33:28 [ list start ] sock: 9 | url: inproc://nanobroadcast
413413
sub <- socket("sub", dial = "inproc://nanobroadcast")
414-
#> 2022-03-29 13:53:51 [ sock open ] id: 10 | protocol: sub
415-
#> 2022-03-29 13:53:51 [ dial start ] sock: 10 | url: inproc://nanobroadcast
414+
#> 2022-03-29 14:33:28 [ sock open ] id: 10 | protocol: sub
415+
#> 2022-03-29 14:33:28 [ dial start ] sock: 10 | url: inproc://nanobroadcast
416416

417417
sub |> subscribe(topic = "examples")
418-
#> 2022-03-29 13:53:51 [ subscribe ] sock: 10 | topic: examples
418+
#> 2022-03-29 14:33:28 [ subscribe ] sock: 10 | topic: examples
419419
pub |> send(c("examples", "this is an example"), mode = "raw", echo = FALSE)
420420
sub |> recv(mode = "character", keep.raw = FALSE)
421421
#> [1] "examples" "this is an example"
422422

423423
pub |> send(c("other", "this other topic will not be received"), mode = "raw", echo = FALSE)
424424
sub |> recv(mode = "character", keep.raw = FALSE)
425-
#> 2022-03-29 13:53:51 [ 8 ] Try again
425+
#> 2022-03-29 14:33:28 [ 8 ] Try again
426426

427427
# specify NULL to subscribe to ALL topics
428428
sub |> subscribe(topic = NULL)
429-
#> 2022-03-29 13:53:51 [ subscribe ] sock: 10 | topic: ALL
429+
#> 2022-03-29 14:33:28 [ subscribe ] sock: 10 | topic: ALL
430430
pub |> send(c("newTopic", "this is a new topic"), mode = "raw", echo = FALSE)
431431
sub |> recv("character", keep.raw = FALSE)
432432
#> [1] "newTopic" "this is a new topic"
433433

434434
sub |> unsubscribe(topic = NULL)
435-
#> 2022-03-29 13:53:51 [ unsubscribe ] sock: 10 | topic: ALL
435+
#> 2022-03-29 14:33:28 [ unsubscribe ] sock: 10 | topic: ALL
436436
pub |> send(c("newTopic", "this topic will now not be received"), mode = "raw", echo = FALSE)
437437
sub |> recv("character", keep.raw = FALSE)
438-
#> 2022-03-29 13:53:51 [ 8 ] Try again
438+
#> 2022-03-29 14:33:28 [ 8 ] Try again
439439

440440
# however the topics explicitly subscribed to are still received
441441
pub |> send(c("examples", "this example will still be received"), mode = "raw", echo = FALSE)
@@ -444,7 +444,7 @@ sub |> recv(mode = "character", keep.raw = FALSE)
444444

445445
# set logging level back to the default of errors only
446446
logging(level = "error")
447-
#> 2022-03-29 13:53:51 [ log level ] set to: error
447+
#> 2022-03-29 14:33:28 [ log level ] set to: error
448448

449449
close(pub)
450450
close(sub)
@@ -495,7 +495,7 @@ aio2$data
495495
# after the survey expires, the second resolves into a timeout error
496496
Sys.sleep(0.5)
497497
aio2$data
498-
#> 2022-03-29 13:53:52 [ 5 ] Timed out
498+
#> 2022-03-29 14:33:28 [ 5 ] Timed out
499499
#> 'errorValue' int 5
500500

501501
close(sur)
@@ -510,36 +510,44 @@ integer message values.
510510

511511
[« Back to ToC](#table-of-contents)
512512

513-
### ncurl: Minimalist http Client
513+
### ncurl: Async HTTP Client
514514

515-
`ncurl()` is a minimalist http(s) client. In normal use, it takes only
516-
one argument, the URL. It can follow redirects.
515+
`ncurl()` is a minimalist http(s) client.
516+
517+
By setting `async = TRUE`, it performs requests asynchronously,
518+
returning immediately with a ‘recvAio’.
519+
520+
For normal use, it takes just the URL. It can follow redirects.
517521

518522
``` r
519523
ncurl("http://httpbin.org/headers")
520524
#> $raw
521525
#> [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
522526
#> [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
523527
#> [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
524-
#> [76] 2d 36 32 34 33 30 31 36 30 2d 31 30 36 36 64 32 64 35 32 35 33 34 62 34 37
525-
#> [101] 36 35 37 65 32 34 30 62 32 22 0a 20 20 7d 0a 7d 0a
528+
#> [76] 2d 36 32 34 33 30 61 61 38 2d 32 33 32 30 39 39 30 63 34 66 32 39 35 39 65
529+
#> [101] 66 33 38 32 63 63 34 38 62 22 0a 20 20 7d 0a 7d 0a
526530
#>
527531
#> $data
528-
#> [1] "{\n \"headers\": {\n \"Host\": \"httpbin.org\", \n \"X-Amzn-Trace-Id\": \"Root=1-62430160-1066d2d52534b47657e240b2\"\n }\n}\n"
532+
#> [1] "{\n \"headers\": {\n \"Host\": \"httpbin.org\", \n \"X-Amzn-Trace-Id\": \"Root=1-62430aa8-2320990c4f2959ef382cc48b\"\n }\n}\n"
529533
```
530534

531535
For advanced use, supports additional HTTP methods such as POST or PUT.
532-
In this respect, it may be used as a performant and lightweight method
533-
for making requests to REST APIs.
534536

535537
``` r
536-
res <- ncurl("http://httpbin.org/post", async = TRUE, "POST", "application/json", "Bearer APIKEY", '{"key": "value"}')
538+
res <- ncurl("http://httpbin.org/post", async = TRUE,
539+
"POST", "application/json", "Bearer APIKEY", '{"key": "value"}')
540+
res
541+
#> < recvAio >
542+
#> - $data for message data
543+
#> - $raw for raw message
544+
537545
call_aio(res)$data
538-
#> [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-62430160-4b8917cd2b569d8c0294f5ca\"\n }, \n \"json\": {\n \"key\": \"value\"\n }, \n \"origin\": \"79.173.189.204\", \n \"url\": \"http://httpbin.org/post\"\n}\n"
546+
#> [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-62430aa9-733452c942d2794e22818b4e\"\n }, \n \"json\": {\n \"key\": \"value\"\n }, \n \"origin\": \"79.173.189.204\", \n \"url\": \"http://httpbin.org/post\"\n}\n"
539547
```
540548

541-
There is also the option of performing requests asynchronously, in which
542-
case the function returns immediately with a ‘recvAio’.
549+
In this respect, it may be used as a performant and lightweight method
550+
for making REST API requests.
543551

544552
[« Back to ToC](#table-of-contents)
545553

man/ncurl.Rd

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

src/aio.c

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ static void url_finalizer(SEXP xptr) {
123123

124124
}
125125

126+
static void tls_finalizer(SEXP xptr) {
127+
128+
if (R_ExternalPtrAddr(xptr) == NULL)
129+
return;
130+
nng_tls_config *xp = (nng_tls_config *) R_ExternalPtrAddr(xptr);
131+
nng_tls_config_free(xp);
132+
R_ClearExternalPtr(xptr);
133+
134+
}
135+
126136
/* core aio functions ------------------------------------------------------- */
127137

128138
SEXP rnng_recv_aio(SEXP socket, SEXP timeout) {
@@ -1012,18 +1022,22 @@ SEXP rnng_ncurl_aio(SEXP http, SEXP method, SEXP ctype, SEXP auth, SEXP data) {
10121022
R_RegisterCFinalizerEx(mtx, mtx_finalizer, TRUE);
10131023
SEXP ares = PROTECT(R_MakeExternalPtr(res, R_NilValue, R_NilValue));
10141024
R_RegisterCFinalizerEx(ares, res_finalizer, TRUE);
1025+
Rf_setAttrib(mtx, nano_IovSymbol, ares);
10151026
SEXP areq = PROTECT(R_MakeExternalPtr(req, R_NilValue, R_NilValue));
10161027
R_RegisterCFinalizerEx(areq, req_finalizer, TRUE);
1028+
R_MakeWeakRef(mtx, areq, R_NilValue, TRUE);
10171029
SEXP acli = PROTECT(R_MakeExternalPtr(client, R_NilValue, R_NilValue));
10181030
R_RegisterCFinalizerEx(acli, client_finalizer, TRUE);
1031+
R_MakeWeakRef(mtx, acli, R_NilValue, TRUE);
10191032
SEXP aurl = PROTECT(R_MakeExternalPtr(url, R_NilValue, R_NilValue));
10201033
R_RegisterCFinalizerEx(aurl, url_finalizer, TRUE);
1021-
1022-
Rf_setAttrib(mtx, nano_IovSymbol, ares);
1023-
Rf_setAttrib(mtx, nano_DialerSymbol, areq);
1024-
Rf_setAttrib(mtx, nano_ContextSymbol, acli);
1025-
Rf_setAttrib(mtx, nano_UrlSymbol, aurl);
1026-
Rf_setAttrib(mtx, nano_IdSymbol, Rf_ScalarInteger(tls));
1034+
R_MakeWeakRef(mtx, aurl, R_NilValue, TRUE);
1035+
if (tls) {
1036+
SEXP acfg = PROTECT(R_MakeExternalPtr(cfg, R_NilValue, R_NilValue));
1037+
R_RegisterCFinalizerEx(acfg, tls_finalizer, TRUE);
1038+
R_MakeWeakRef(mtx, acfg, R_NilValue, TRUE);
1039+
UNPROTECT(1);
1040+
}
10271041
Rf_setAttrib(aio, nano_StateSymbol, mtx);
10281042

10291043
UNPROTECT(6);
@@ -1063,6 +1077,13 @@ SEXP rnng_aio_http(SEXP aio) {
10631077
code = nng_http_res_get_status(res);
10641078
if (code != 200)
10651079
REprintf("HTTP Server Response: %d %s\n", code, nng_http_res_get_reason(res));
1080+
if (code >= 300 && code < 400) {
1081+
const char *location = nng_http_res_get_header(res, "Location");
1082+
nng_aio_free(aiop);
1083+
R_ClearExternalPtr(aio);
1084+
Rf_setAttrib(aio, nano_StateSymbol, R_NilValue);
1085+
return Rf_mkString(location);
1086+
}
10661087

10671088
nng_http_res_get_data(res, &dat, &sz);
10681089
SEXP vec = PROTECT(Rf_allocVector(RAWSXP, sz));

0 commit comments

Comments
 (0)