如何为Plumber API指定带JSON类型的非默认响应状态码?
问题
我基于注解风格构建Plumber API,想指定除默认200“OK”和500“内部服务器错误”之外的响应状态码,且这些错误响应的Content-Type为application/json(而非text/html)。参考结构化错误处理逻辑实现后,示例路由原本运行正常,但添加@response注解后,生成的OpenAPI规范中403、404响应并未设置为期望的application/json类型(默认500错误的Content-Type却是正确的)。
原正常路由代码:
# Define the Plumber API using annotations #* @get /predict/<file_id> #* @param file_id:string ID or name of the RDS file #* @serializer html function(res, req, file_id) { future::future({ loaded_model <- list() loaded_model <- find_or_download_model(file_id, env_var$model_local_dir, env_var$model_s3_bucket, env_var$model_s3_endpoint, env_var$s3_accesskey, env_var$s3_secretkey) return("Success, File Found.") }) }
添加@response注解后的路由:
# Define the Plumber API using annotations #* @get /predict/<file_id> #* @param file_id:string ID or name of the RDS file #* @serializer html #* @response 403 Forbidden <== HERE #* @response 404 Not Found <== AND HERE
修复方案
1. 明确指定@response的内容类型
Plumber的@response注解支持直接嵌入OpenAPI响应结构,以此明确指定application/json类型和响应体schema。修改注解如下:
#* @response 403 Forbidden {"description": "Permission denied", "content": {"application/json": {"schema": {"type": "object", "properties": {"error": {"type": "string"}, "message": {"type": "string"}}}}}} #* @response 404 Not Found {"description": "Model file not found", "content": {"application/json": {"schema": {"type": "object", "properties": {"error": {"type": "string"}, "message": {"type": "string"}}}}}}
这样生成的OpenAPI规范会直接识别403、404响应的application/json类型。
2. 全局配置错误响应序列化器
在API入口文件添加全局错误序列化器,统一覆盖所有错误状态码的响应格式,避免逐个路由重复配置:
pr <- plumber::plumber$new() # 全局设置错误响应为JSON格式 pr$setErrorSerializer(function(err, req, res) { res$setHeader("Content-Type", "application/json") jsonlite::toJSON(list( error = err$message, status = err$status ), auto_unbox = TRUE) })
该配置会让所有错误(包括403、404)自动返回application/json类型的结构化响应。
3. 路由内手动控制错误响应
在业务逻辑触发错误场景时,手动设置响应头、状态码并返回JSON格式内容:
function(res, req, file_id) { future::future({ # 模拟权限校验逻辑 if (!check_user_permission(req)) { res$status <- 403 res$setHeader("Content-Type", "application/json") return(jsonlite::toJSON( list(error = "Forbidden", message = "无资源访问权限"), auto_unbox = TRUE )) } loaded_model <- find_or_download_model(file_id, env_var$model_local_dir, env_var$model_s3_bucket, env_var$model_s3_endpoint, env_var$s3_accesskey, env_var$s3_secretkey) if (is.null(loaded_model)) { res$status <- 404 res$setHeader("Content-Type", "application/json") return(jsonlite::toJSON( list(error = "Not Found", message = paste("模型文件", file_id, "不存在")), auto_unbox = TRUE )) } return("Success, File Found.") }) }
这种方式可针对特定业务场景精准控制错误响应内容,同时确保OpenAPI规范能识别响应类型。
4. 路由级错误序列化器注解
如果仅需当前路由的错误响应使用JSON格式,可添加@errorSerializer注解:
#* @get /predict/<file_id> #* @param file_id:string ID or name of the RDS file #* @serializer html #* @errorSerializer json #* @response 403 Forbidden #* @response 404 Not Found
该注解会让当前路由的错误响应自动使用JSON序列化器,设置Content-Type为application/json。
内容的提问来源于stack exchange,提问作者LongStreak

