【问题标题】:How to send curl variables to plumber function dynamically?如何动态地将 curl 变量发送到管道工功能?
【发布时间】:2026-02-01 16:15:01
【问题描述】:

我想根据任意数量的输入变量动态调用管道工 API。我需要将 curl 输入映射到函数名的输入。例如,如果函数有一个输入hi,那么curl -s --data 'hi=2' 意味着hi=2 应该作为输入参数传递给函数。这可以直接在 R 中使用 match.call() 完成,但在通过管道工 API 调用它时会失败。

取函数

#' @post /API
#' @serializer unboxedJSON
tmp <- function(hi) {

  out <- list(hi=hi)

  out <- toJSON(out, pretty = TRUE, auto_unbox = TRUE)

  return(out)

}

tmp(hi=2)
out: {hi:2}

然后

curl -s --data 'hi=10' http://127.0.0.1/8081/API
out: {\n  \"hi\": \"2\"\n}

一切看起来都不错。但是,取函数

#' @post /API
#' @serializer unboxedJSON
tmp <- function(...) {

  out <- match.call() %>%
         as.list() %>%
         .[2:length(.)] # %>%

  out <- toJSON(out, pretty = TRUE, auto_unbox = TRUE)

  return(out)

}
tmp(hi=2)
out: {hi:2}

然后

curl -s --data 'hi=10' http://127.0.0.1/8081/API
out: {"error":"500 - Internal server error","message":"Error: No method asJSON S3 class: R6\n"}

在实践中,我真正想做的是加载我的 ML 模型以使用管道工 API 预测分数。例如

model <- readRDS('model.rds') # Load model as a global variable

predict_score <- function(...) {

    df_in <- match.call() %>%
        as.list() %>%
        .[2:length(.)] %>%
        as.data.frame()

    json_out <- list(
        score_out = predict(model, df_in) %>%
        toJSON(., pretty = T, auto_unbox = T)

    return(json_out)
}

此函数在本地运行时按预期工作,但通过 API 通过curl -s --data 'var1=1&amp;var2=2...etc' http://listen_address 运行

我收到以下错误:{"error":"500 - Internal server error","message":"Error in as.data.frame.default(x[[i]], optional = TRUE): cannot coerce class \"c(\"PlumberResponse\", \"R6\")\" to a data.frame\n"}

【问题讨论】:

    标签: r api machine-learning deployment plumber


    【解决方案1】:

    内部管道工将请求中的参数与函数中的参数名称相匹配。您可以使用special 参数来探索请求中的所有args。如果您有一个名为req 的参数,它将为您提供一个包含整个请求元数据的环境,其中之一是req$args。然后你可以解析它。前两个参数是对特殊参数reqres 的自引用。它们是环境,不应序列化。我不建议在任何生产代码中执行此处显示的操作,因为它会使 api 被滥用。

    model <- readRDS('model.rds') # Load model as a global variable
    
    #' @post /API
    #' @serializer unboxedJSON
    predict_score <- function(req) {
    
        df_in <- as.data.frame(req$args[-(1:2)])
    
        json_out <- list(
            score_out = predict(model, df_in)
    
        return(json_out)
    }
    

    但是对于您的用例,我实际上建议使用一个名为 df_in 的参数。以下是您的设置方法。

    model <- readRDS('model.rds') # Load model as a global variable
    
    #' @post /API
    #' @param df_in
    #' @serializer unboxedJSON
    predict_score <- function(df_in) {
    
        json_out <- list(
            score_out = predict(model, df_in)
    
        return(json_out)
    }
    
    

    然后用 curl

    curl --header "Content-Type: application/json" \
      --request POST \
      --data '{"df_in":{"hi":2, "othercrap":4}}' \
      http://listen_address
    

    当请求正文以“{”开头时,管道工将解析带有jsonlite:fromJSON 的正文内容,并使用解析对象的名称映射到函数中的参数。

    目前 github 上的 CRAN 和 master 分支都不能通过 swagger api 正确处理这个问题,但它可以通过 curl 或其他直接调用方法正常工作。我相信下一个管道工版本将处理所有这些以及更多。

    在此处查看与此问题类似的答案:https://github.com/rstudio/plumber/issues/512#issuecomment-605735332

    【讨论】:

    • 有没有办法我可以做到这一点而不必更改 curl 请求?理想情况下,我会更改代码以处理 curl -s --data 'myvar1=2&myvar2=3' listen_address
    • 是的,使用“req”参数并从 req$args 中检索所有参数。
    • 另外,您不需要执行 toJSON,因为序列化程序会为您执行此操作。
    • 感谢您的帮助。第二个有效,但第一个功能无效。这是我的函数#' @post /API #' @serializer unboxedJSON predict_score &lt;- function(req) { return(req$args)} 当它正在监听时,我在终端中输入 curl -s --data 'hi=2' listen_address 并得到an exception occurred 我也尝试只返回req。
    • return(req$args) 行之前打一个browser() 电话。运行它并用 curl 调用它。它应该进入调试模式,让您探索在执行时可用的内容。就像我说的,req$args 包含环境,因此您将无法序列化它们。我仍然不建议这样做,因为您将 API 暴露给注入和不受保护的行为。