用 R 开发一个 RESTful 服务器
2023年10月6日
编程
近日偶然发现 R 语言有一个 **plumber** 包,可以用来开发类似于 express.js 的服务器。目前来看功能还比较简单,应该和 Django 或者 Egg.js 等企业级框架是没法比的。但是可堪一用,在发布算法服务方面又多了一种选择。 这里就以发布一个简单的 RESTful 的 Basic GWR 算法服务为例进行演示。 首先我们编写一个 `app.R` 用来编写应用的启动命令 ```R library(plumber) app <- pr("routes.R") pr_run(app, port = 6000) ``` 其中的 `pr()` 函数通过读取 R 源码生成路由,当然也可以编写函数,但是 R 由于暂不支持像 Python 或 JavaScript 那样从一个脚本文件中导入符号(除非用 `source()` 命令执行),所以如果想分开编写的话,还是要传入文件名。 然后编写 `routes.R` 实现路由 ```R library(GWmodel3) library(geojsonsf) #' Basic GWR #' @serializer unboxedJSON #' @post /gwr function(req) { body = req$body data = body$data depen = body$depen indeps = body$indeps bw = body$bw adaptive = body$adaptive if (any(is.null(c(data, depen, indeps, bw, adaptive)))) { res$status <- 500 return(list(msg = "No sufficient arguments")) } model_formula <- formula(paste(depen, paste(indeps, collapse = "+"), sep = "~")) model_data <- geojson_sf(data) model <- gwr_basic(model_formula, model_data, bw = bw, adaptive = adaptive) list( sdf = sf_geojson(model$SDF), diagnostic = model$diagnostic ) } ``` 文件中通过类似于 **roxygen2** 的注释实现路由的设置,支持 GET POST PUT DELETE 等常用方法,也支持 Query String 设置参数。但是由于我们这个场景下参数量比较大(要通过 GeoJSON 的方式传入整个数据文件),所以使用 POST 方法并通过 HTTP 请求的请求体(Body)以 JSON 格式传输数据。 在 **plumber** 中,如果路由有名为 `req` 的参数,则这个参数将自动捕获请求对象。同样,如果有名为 `res` 的参数,则这个参数代表响应对象。在请求对象中,我们可以直接获取 `body` 作为解析后的请求体。经过一系列参数校验和处理,就可以执行我们相应的算法了。 **plumber** 默认使用 **jsonlite** 包的 `toJSON()` 函数将返回值序列化为 JSON 格式字符串。但是由于 R 底层以向量格式存储所有数据,直接解析 `toJSON()` 的返回值得到的结果往往使用起来比较麻烦。这时可以在注释中为路由通过如下方式指定序列化器 ```R #' @serializer unboxedJSON ``` 这样得到的响应数据在解析后就非常方便了。 使用下面的命令运行服务器 ```bash Rscript app.R ``` 我们可以写一个脚本,用来测试服务器输出 ```R library(sf) library(GWmodel3) library(jsonlite) library(request) library(geojsonsf) data("LondonHP") data_json <- sf_geojson(LondonHP) body <- list( data = data_json, depen = "PURCHASE", indeps = c("FLOORSZ", "UNEMPLOY", "PROF"), bw = 64, adaptive = TRUE ) res <- api("http://127.0.0.1:6000/gwr") %>% api_body(body_value = jsonlite::toJSON(body)) %>% http(method="POST") res_sdf <- geojson_sf(res$sdf) res_sdf ``` API成功执行,得到如下结果 ```plaintext Simple feature collection with 316 features and 14 fields Geometry type: POINT Dimension: XY Bounding box: xmin: 507400 ymin: 159400 xmax: 552300 ymax: 194900 Geodetic CRS: WGS 84 First 10 features: Intercept residual PROF.TV Intercept.SE PROF.SE PROF FLOORSZ geometry FLOORSZ.SE yhat UNEMPLOY.SE Intercept.TV UNEMPLOY FLOORSZ.TV UNEMPLOY.TV 1 -174627.7 17000.075 0.04807418 6085658 7630457 366827.9 1474.336 POINT (533200 159400) 376758.8 139999.9 16619161 -0.02869497 715033.1 0.003913209 0.04302462 2 -188388.5 -25462.517 0.04667130 6199661 7750510 361726.4 1726.832 POINT (533300 159700) 403665.0 138962.5 17228214 -0.03038690 691956.9 0.004277883 0.04016417 3 -182290.4 -41494.196 0.04482533 6331307 7903758 354288.5 1789.489 POINT (532000 159800) 423045.9 123244.2 17552711 -0.02879190 600252.3 0.004230012 0.03419713 4 -186506.4 -19445.584 0.04921103 6033409 7538740 370989.2 1597.651 POINT (531900 160100) 382846.4 169445.6 16564237 -0.03091228 745295.2 0.004173086 0.04499424 5 -149628.0 9652.861 0.04558238 6147209 7627975 347701.3 1307.517 POINT (532800 160300) 370839.3 180347.1 16687476 -0.02434081 662406.8 0.003525832 0.03969485 6 -161355.4 -7070.584 0.04603614 5977369 7425576 341844.9 1489.324 POINT (535700 161700) 369344.8 167020.6 16373526 -0.02699438 674865.2 0.004032342 0.04121685 7 -147853.3 13809.725 0.04373267 6343223 7891175 345102.2 1276.782 POINT (535600 161800) 376042.9 136185.3 17193489 -0.02330886 659122.3 0.003395309 0.03833557 8 -167828.1 2121.905 0.04706348 6177821 7734898 364031.2 1417.696 POINT (533400 161900) 377135.1 237828.1 16809147 -0.02716623 687233.4 0.003759120 0.04088449 9 -170397.9 -8811.337 0.04380477 6239154 7875400 344980.0 1537.825 POINT (535500 162200) 401824.0 161811.3 17524101 -0.02731106 698204.3 0.003827112 0.03984252 10 -142768.0 -108816.091 0.04193421 6589694 8164301 342363.5 1239.989 POINT (534200 162500) 387976.5 358766.1 17805151 -0.02166535 651863.5 0.003196041 0.03661095 ``` 在云计算时代,我们可以通过云计算运营商发布函数计算服务,当有大量计算任务时,或对计算有特殊需求时(比如需要极大的内存、需要GPU等),就可以编写这样一个 RESTful 服务器,挂在函数计算上执行了。函数计算与购买服务器相比,成本会低很多。再结合 Shiny ,甚至可以开发一套前后端分离的网页端应用,前端挂在 GitHub Pages 上,后端挂在函数计算上,就实现了 serverless 应用的开发。
感谢您的阅读。本网站 MyZone 对本文保留所有权利。