第 1 章 httpuv
在httpuv的官网中,有这么一段描述:
Allows R code to listen for and interact with HTTP and WebSocket clients, so you can serve web traffic directly out of your R process. Implementation is based on libuv and http-parser.
This is a low-level library that provides little more than network I/O and implementations of the HTTP and WebSocket protocols. For an easy way to create web applications, try Shiny instead.
我们可以通过httpuv搭建一个访问R模型的web API,但可能这不是最好的。
本部分我们首先介绍官方提供的一些方法,然后解析官方提供的演示Demo,从而达到熟练使用httpuv的目的。
1.1 方法介绍
下面我们解析一下httpuv官方提供的一些调用方法,并演示一些调用方法对应的实例
1.使用URI编码/解码以与Web浏览器相同的方式对字符串进行编码/解码。
encodeURI(value)
encodeURIComponent(value)
decodeURI(value)
decodeURIComponent(value)
参数列表
- value 用于编码和解码的字符向量,UTF-8字符编码
library(httpuv)
value <- "https://baidu.com/中国;?/"
encodeURI(value)
## [1] "https://baidu.com/%D6%D0%B9%FA;?/"
encodeURIComponent(value)
## [1] "https%3A%2F%2Fbaidu.com%2F%D6%D0%B9%FA%3B%3F%2F"
decodeURI(value)
## [1] "https://baidu.com/中国;?/"
decodeURIComponent(value)
## [1] "https://baidu.com/中国;?/"
注意: encodeURI 与 encodeURIComponent是不一样的因为前者不对特殊字符: ;,/?:@&=+$等进行encode
2.中断httpuv运行的环路
interrupt()
- 检查ip地址的类型是ipv4还是ipv6
ipFamily(ip)
参数列表
ip 一个代表IP地址的字符串
返回值的意义:如果是IPv4返回4,如果是IPv6返回6,如果不是IP地址返回-1
ipFamily("127.0.0.1") # 4
## [1] 4
ipFamily("500.0.0.500") # -1
## [1] -1
ipFamily("500.0.0.500") # -1
## [1] -1
ipFamily("::") # 6
## [1] 6
ipFamily("::1") # 6
## [1] 6
ipFamily("fe80::1ff:fe23:4567:890a") # 6
## [1] 6
3.将原始向量转换为BASE64编码字符串
rawToBase64(x)
参数列表
- x 原始向量
set.seed(100)
result <- rawToBase64(as.raw(runif(19, min=0, max=256)))
#stopifnot(identical(result, "TkGNDnd7z16LK5/hR2bDqzRbXA=="))
result
## [1] "TkGNDnd7z16LK5/hR2bDqzRbXA=="
4.运行一个server
runServer(host, port, app, interruptIntervalMs = NULL)
参数列表
host IPv4地址, 或是“0.0.0.0”监听所有的IP
port 端口号
app 一个定义应用的函数集合
interruptIntervalMs 该参数不提倡使用,1.3.5版本后废除
app <- list(call = function(req){
list(status=200L,
headers = list(
'Content-Type' = 'text/html'
),
body = "HelloWorld!")
})
runServer("0.0.0.0", 5000,app)
5.过程请求
处理HTTP请求和WebSocket消息。 如果R的调用堆栈上没有任何东西,如果R是 在命令提示符下闲置,不必调用此函数,因为请求将 自动处理。但是,如果R正在执行代码,则请求将不被处理。 要么调用栈是空的,要么调用这个函数(或者,调用run_now())。
service(timeoutMs = ifelse(interactive(), 100, 1000))
参数列表
- timeoutMs 返回之前运行的毫秒数。
6.创建HTTP/WebSocket后台服务器(弃用)
startDaemonizedServer(host, port, app)
7.创建HTTP/WebSocket服务器
startServer(host, port, app)
startPipeServer(name, mask, app)
参数列表
host ip地址
port 端口号
app 一个定义应用的函数集
app <- list(
call = function(req) {
list(
status = 200L,
headers = list(
'Content-Type' = 'text/html'
),
body = "Hello world!"
)
}
)
handle <- startServer("0.0.0.0", 5000,app)
# 此服务器的句柄,可以传递给StestServer以关闭服务器。
stopServer(handle)
8.停止所有应用
stopAllServers()
9.在UNIX环境中停止运行的后台服务器(弃用)
stopDaemonizedServer(handle)
10.停止一个服务
stopServer(handle)
1.2 例子演示
- json-server
# Connect to this using websockets on port 9454
# Client sends to server in the format of {"data":[1,2,3]}
# The websocket server returns the standard deviation of the sent array
library(jsonlite)
library(httpuv)
# Server
app <- list(
onWSOpen = function(ws) {
ws$onMessage(function(binary, message) {
# Decodes message from client
message <- fromJSON(message)
# Sends message to client
ws$send(
# JSON encode the message
toJSON(
# Returns standard deviation for message
sd(message$data)
)
)
})
}
)
runServer("0.0.0.0", 9454, app, 250)
2.echo
library(httpuv)
app <- list(
call = function(req) {
wsUrl = paste(sep='',
'"',
"ws://",
ifelse(is.null(req$HTTP_HOST), req$SERVER_NAME, req$HTTP_HOST),
'"')
list(
status = 200L,
headers = list(
'Content-Type' = 'text/html'
),
body = paste(
sep = "\r\n",
"<!DOCTYPE html>",
"<html>",
"<head>",
'<style type="text/css">',
'body { font-family: Helvetica; }',
'pre { margin: 0 }',
'</style>',
"<script>",
sprintf("var ws = new WebSocket(%s);", wsUrl),
"ws.onmessage = function(msg) {",
' var msgDiv = document.createElement("pre");',
' msgDiv.innerHTML = msg.data.replace(/&/g, "&").replace(/\\</g, "<");',
' document.getElementById("output").appendChild(msgDiv);',
"}",
"function sendInput() {",
" var input = document.getElementById('input');",
" ws.send(input.value);",
" input.value = '';",
"}",
"</script>",
"</head>",
"<body>",
'<h3>Send Message</h3>',
'<form action="" onsubmit="sendInput(); return false">',
'<input type="text" id="input"/>',
'<h3>Received</h3>',
'<div id="output"/>',
'</form>',
"</body>",
"</html>"
)
)
},
onWSOpen = function(ws) {
ws$onMessage(function(binary, message) {
ws$send(message)
})
}
)
browseURL("http://localhost:9454/")
runServer("0.0.0.0", 9454, app, 250)
3.deamon-echo
library(httpuv)
.lastMessage <- NULL
app <- list(
call = function(req) {
wsUrl = paste(sep='',
'"',
"ws://",
ifelse(is.null(req$HTTP_HOST), req$SERVER_NAME, req$HTTP_HOST),
'"')
list(
status = 200L,
headers = list(
'Content-Type' = 'text/html'
),
body = paste(
sep = "\r\n",
"<!DOCTYPE html>",
"<html>",
"<head>",
'<style type="text/css">',
'body { font-family: Helvetica; }',
'pre { margin: 0 }',
'</style>',
"<script>",
sprintf("var ws = new WebSocket(%s);", wsUrl),
"ws.onmessage = function(msg) {",
' var msgDiv = document.createElement("pre");',
' msgDiv.innerHTML = msg.data.replace(/&/g, "&").replace(/\\</g, "<");',
' document.getElementById("output").appendChild(msgDiv);',
"}",
"function sendInput() {",
" var input = document.getElementById('input');",
" ws.send(input.value);",
" input.value = '';",
"}",
"</script>",
"</head>",
"<body>",
'<h3>Send Message</h3>',
'<form action="" onsubmit="sendInput(); return false">',
'<input type="text" id="input"/>',
'<h3>Received</h3>',
'<div id="output"/>',
'</form>',
"</body>",
"</html>"
)
)
},
onWSOpen = function(ws) {
ws$onMessage(function(binary, message) {
.lastMessage <<- message
ws$send(message)
})
}
)
server <- startDaemonizedServer("0.0.0.0", 9454, app)
# check the value of .lastMessage after echoing to check it is being updated
# call this after done
#stopDaemonizedServer(server)
library(httpuv)
app = list(call = function(req){
# 获取POST的参数
postdata = req$rook.input$read_lines()
qs = httr:::parse_query(gsub("^\\?", "", postdata))
dat = jsonlite::fromJSON(qs$jsonDat)
print(dat)
# 计算返回结果
r = 0.3 + 0.1 * dat$v1 - 0.2 * dat$v2 + 0.1 * dat$v3
output = jsonlite::toJSON(list(message = 'suceess', result = r), auto_unbox = T)
res = list(status = 200L, headers = list('Content-Type' = 'application/json'), body = output)
return(res)
})
# 启动服务
server = startServer("0.0.0.0", 1124L, app = app)
while(TRUE) {
service()
Sys.sleep(0.001)
}
# stopServer(server)
RCurl::postForm('127.0.0.1:1124',
style = 'post',
.params = list(jsonDat = '{"v1":1,"v2":2,"v3":3}')
)
httpuv是相对比较底层的包,熟练使用需要掌握前端知识,并且需要用到RCurl,httr相关爬虫包的一些知识去处理。本人不推荐这种方式进行模型的部署。