nanonext - a web toolkit

R

async https and secure websocket client and cryptographic hashing

shikokuchuo
2022-09-08


The previous two articles have centered on the main uses which led to the creation of Nanonext - the desire to bridge code written in different languages, as well as the ability to perform actions concurrently.

This article aims to highlight the additional features that have been built around the core capabilities in the NNG library that actually make it a very good tool for interacting with the web.

This is especially relevant as version 0.5.5 just released to CRAN integrates the ‘mbedtls’ library providing TLS support for secure websites and websocket connections across all platforms.

The package has also made it into the ‘Web Technologies’ CRAN Task View under ‘Core Tools For HTTP Requests’: https://cran.r-project.org/web/views/WebTechnologies.html

ncurl - a minimalist (async) http(s) client

For normal use, it takes just the URL.

It can follow redirects.

ncurl("https://httpbin.org/headers")
$status
[1] 200

$headers
NULL

$raw
  [1] 7b 0a 20 20 22 68 65 61 64 65 72 73 22 3a 20 7b 0a 20 20 20 20
 [22] 22 48 6f 73 74 22 3a 20 22 68 74 74 70 62 69 6e 2e 6f 72 67 22
 [43] 2c 20 0a 20 20 20 20 22 58 2d 41 6d 7a 6e 2d 54 72 61 63 65 2d
 [64] 49 64 22 3a 20 22 52 6f 6f 74 3d 31 2d 36 33 31 61 34 39 64 36
 [85] 2d 31 63 63 30 36 63 37 61 33 36 65 65 64 39 30 36 32 34 31 32
[106] 39 62 62 61 22 0a 20 20 7d 0a 7d 0a

$data
[1] "{\n  \"headers\": {\n    \"Host\": \"httpbin.org\", \n    \"X-Amzn-Trace-Id\": \"Root=1-631a49d6-1cc06c7a36eed90624129bba\"\n  }\n}\n"

Whilst it is designed to be minimalist and easy to use, the real power however lies in its ability to use other methods such as POST or PUT, and the ability of the arguments ‘headers’ and ‘data’ to take arbitrary values that are sent in the HTTP request.

This makes it perfect as a client for making REST API calls, and is indeed a rather performant solution.

res <- ncurl("http://httpbin.org/post",
             async = TRUE,
             convert = FALSE,
             method = "POST",
             headers = c(`Content-Type` = "application/json", Authorization = "Bearer APIKEY"),
             data = '{"key": "value"}',
             request = c("Date", "Server"))

Above:

res
< ncurlAio >
 - $status for response status code
 - $headers for requested response headers
 - $raw for raw message
 - $data for message data
call_aio(res)$status
[1] 200
res$headers
$Date
[1] "Thu, 08 Sep 2022 20:00:23 GMT"

$Server
[1] "gunicorn/19.9.0"
res$raw
  [1] 7b 0a 20 20 22 61 72 67 73 22 3a 20 7b 7d 2c 20 0a 20 20 22 64
 [22] 61 74 61 22 3a 20 22 7b 5c 22 6b 65 79 5c 22 3a 20 5c 22 76 61
 [43] 6c 75 65 5c 22 7d 22 2c 20 0a 20 20 22 66 69 6c 65 73 22 3a 20
 [64] 7b 7d 2c 20 0a 20 20 22 66 6f 72 6d 22 3a 20 7b 7d 2c 20 0a 20
 [85] 20 22 68 65 61 64 65 72 73 22 3a 20 7b 0a 20 20 20 20 22 41 75
[106] 74 68 6f 72 69 7a 61 74 69 6f 6e 22 3a 20 22 42 65 61 72 65 72
[127] 20 41 50 49 4b 45 59 22 2c 20 0a 20 20 20 20 22 43 6f 6e 74 65
[148] 6e 74 2d 4c 65 6e 67 74 68 22 3a 20 22 31 36 22 2c 20 0a 20 20
[169] 20 20 22 43 6f 6e 74 65 6e 74 2d 54 79 70 65 22 3a 20 22 61 70
[190] 70 6c 69 63 61 74 69 6f 6e 2f 6a 73 6f 6e 22 2c 20 0a 20 20 20
[211] 20 22 48 6f 73 74 22 3a 20 22 68 74 74 70 62 69 6e 2e 6f 72 67
[232] 22 2c 20 0a 20 20 20 20 22 58 2d 41 6d 7a 6e 2d 54 72 61 63 65
[253] 2d 49 64 22 3a 20 22 52 6f 6f 74 3d 31 2d 36 33 31 61 34 39 64
[274] 37 2d 36 64 34 62 32 38 65 34 33 65 37 36 39 36 30 34 33 66 63
[295] 66 66 38 37 31 22 0a 20 20 7d 2c 20 0a 20 20 22 6a 73 6f 6e 22
[316] 3a 20 7b 0a 20 20 20 20 22 6b 65 79 22 3a 20 22 76 61 6c 75 65
[337] 22 0a 20 20 7d 2c 20 0a 20 20 22 6f 72 69 67 69 6e 22 3a 20 22
[358] 31 38 35 2e 32 32 35 2e 34 35 2e 34 39 22 2c 20 0a 20 20 22 75
[379] 72 6c 22 3a 20 22 68 74 74 70 3a 2f 2f 68 74 74 70 62 69 6e 2e
[400] 6f 72 67 2f 70 6f 73 74 22 0a 7d 0a

The function is named ‘ncurl’ after the ubiquitous ‘curl’, but it uses a completely different technology stack, leveraging the ‘NNG’ and ‘MbedTLS’ libraries instead.

stream - websocket client

stream() exposes NNG’s low-level byte stream interface for communicating with raw sockets. This may be used for connecting to arbitrary non-NNG endpoints.

Perhaps its most important use (in connection with the web at least), is for communicating with (secure) websocket servers. The argument textframes = TRUE can be specified where the websocket server uses text rather than binary frames, which is often the case.

# official demo API key used below
s <- stream(dial = "wss://ws.eodhistoricaldata.com/ws/forex?api_token=OeAFFmMliFG5orCUuwAKQ8l4WWFQ67YX",
            textframes = TRUE)
s
< nanoStream >
 - type: dialer 
 - url: wss://ws.eodhistoricaldata.com/ws/forex?api_token=OeAFFmMliFG5orCUuwAKQ8l4WWFQ67YX 
 - textframes: TRUE 

send() and recv(), as well as their asynchronous counterparts send_aio() and recv_aio() can be used on Streams in the same way as Sockets.

This affords a great deal of flexibility in ingesting, manipulating and processing streaming data.

s |> recv(keep.raw = FALSE)
[1] "{\"status_code\":200,\"message\":\"Authorized\"}"
s |> send('{"action": "subscribe", "symbols": "EURUSD"}')
 [1] 7b 22 61 63 74 69 6f 6e 22 3a 20 22 73 75 62 73 63 72 69 62 65 22
[23] 2c 20 22 73 79 6d 62 6f 6c 73 22 3a 20 22 45 55 52 55 53 44 22 7d
[45] 00
s |> recv(keep.raw = FALSE)
[1] "{\"s\":\"EURUSD\",\"a\":0.99982,\"b\":0.99978,\"dc\":\"0.0060\",\"dd\":\"0.0001\",\"ppms\":false,\"t\":1662667227000}"
s |> recv(keep.raw = FALSE)
[1] "{\"s\":\"EURUSD\",\"a\":0.99983,\"b\":0.99979,\"dc\":\"0.0070\",\"dd\":\"0.0001\",\"ppms\":false,\"t\":1662667229000}"
close(s)

sha[224|256|384|512] - cryptographic hash and HMAC algorithms

As ‘nanonext’ now links to the ‘mbedtls’ library as well as ‘NNG’, the series of SHA-2 crypographic hash functions have been added to the package: sha224(), sha256(), sha384() and sha512().

These call the secure, optimized implementations from the ‘MbedTLS’ library and return a hash as a raw vector. These can be compared directly for authentication. Alternatively, as.character() may be used to return a character string of the hash value.

To generate an HMAC (hash-based message authentication code), simply supply the value ‘key’ to use as the secret key. Many REST APIs require the request strings to be signed, and now the ‘nanonext’ package provides a fast and reliable method of generating a SHA-256 HMAC for this purpose.

sha256("hello world!")
75 09 e5 bd a0 c7 62 d2 ba c7 f9 0d 75 8b 5b 22 63 fa 01 cc bc 54 2a b5 e3 df 16 3b e0 8e 6c a9
as.character(sha256("hello world!"))
[1] "7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9"
sha256("hello world!", key = "MY_SECRET")
d8 f0 e2 d3 68 ff 63 26 82 d5 5e 2c 1c cd 49 c1 5f 8a 6a 38 62 d8 eb 68 f1 90 6b 6e e6 58 89 0a

messenger - console-based instant messaging

There is also messenger() which is not so easy to demonstrate here as it is by nature interactive, but it is in effect a 2-way walkie talkie which can be connected to a TCP/IP or other socket address. This is a rather fun demonstration of how a multi-threaded application can be built using the NNG framework.

Whilst this function has been around for quite a few versions of ‘nanonext’, the recent addition of authentication based on a pre-shared key makes it a somewhat viable solution rather than just something for fun. We encourage you to give it a try and play around with it.

?messenger

Package website: https://shikokuchuo.net/nanonext/ On CRAN: https://cran.r-project.org/package=nanonext

Citation

For attribution, please cite this work as

shikokuchuo (2022, Sept. 8). shikokuchuo{net}: nanonext - a web toolkit. Retrieved from https://shikokuchuo.net/posts/19-nanonext-webtools/

BibTeX citation

@misc{shikokuchuo2022nanonext,
  author = {shikokuchuo, },
  title = {shikokuchuo{net}: nanonext - a web toolkit},
  url = {https://shikokuchuo.net/posts/19-nanonext-webtools/},
  year = {2022}
}