InvokeHttp POST请求接口-application/json

增加APIKey请求头 + 完善全量注释 Groovy脚本(NiFi InvokeScriptedProcessor)

改动说明:

  1. 请求头新增 apikey从FlowFile属性 api.key 获取(方便上游动态传入密钥,不硬编码)
  2. 补充脚本头部说明注释
  3. 逐段增加清晰中文注释
  4. 兼容原有逻辑:空Body POST、解决411报错、规范NiFi数据流API、异常处理不变
groovy 复制代码
/**
 * Apache NiFi InvokeScriptedProcessor Groovy脚本
 * 功能:发起【无请求体POST请求】调用外部HTTP接口
 * 特性:
 * 1. POST不带Body,主动设置Content-Length=0,规避服务端411 Length Required错误
 * 2. 请求头携带 apikey,API地址、密钥均从FlowFile属性读取,支持动态配置
 * 3. 接口返回内容覆盖写入当前FlowFile
 * 4. 成功流向REL_SUCCESS,失败流向REL_FAILURE,自动写入各类状态属性便于排查
 * 属性依赖(上游需要预先给FlowFile设置属性):
 *    api.url  :目标接口地址
 *    api.key  :接口鉴权apikey
 * 输出属性:
 *    http.response.code  HTTP响应码
 *    http.request.url    请求地址
 *    request.status      success / failure
 *    request.error       简短错误信息(失败时)
 *    error.message       全局异常信息
 *    error.stack         异常堆栈信息
 * 超时配置:连接超时60s,读取超时60s
 * 编码:UTF-8,交互格式JSON
 */
import org.apache.nifi.processor.io.StreamCallback
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets
import org.apache.commons.io.IOUtils
import org.apache.commons.lang3.exception.ExceptionUtils
import java.io.InputStream
import java.io.OutputStream

// 从NiFi会话获取流入的数据流文件FlowFile
def flowFile = session.get()

// ========== 配置区域 ==========
// 目标接口地址,从FlowFile属性api.url获取
def targetUrl = flowFile.getAttribute("api.url")
// 接口鉴权apikey,从FlowFile属性api.key获取
def apiKey = flowFile.getAttribute("api.key")
// HTTP连接超时 60000毫秒=60秒
def connectTimeout = 60000
// HTTP响应读取超时 60000毫秒=60秒
def readTimeout = 60000
// 统一字符编码 UTF-8
def charset = StandardCharsets.UTF_8

// 临时存放待写入FlowFile的属性
// 【重要约束】session.write回调内部禁止直接调用putAttribute,防止线程异常
def attrMap = [:]
// HTTP响应码初始值-1,代表未建立连接
def responseCode = -1

try {
    // session.write:标准NiFi API,用于读取原FlowFile、生成新FlowFile内容
    flowFile = session.write(flowFile, { InputStream inputStream, OutputStream outputStream ->
        HttpURLConnection connection = null
        try {
            // 构建请求URL对象
            URL url = new URL(targetUrl)
            connection = (HttpURLConnection) url.openConnection()

            // 请求方式POST
            connection.setRequestMethod("POST")
            // POST请求必须开启doOutput,即使没有请求体
            connection.setDoOutput(true)
            // 设置超时时间
            connection.setConnectTimeout(connectTimeout)
            connection.setReadTimeout(readTimeout)

            // ========== HTTP 请求头配置 ==========
            connection.setRequestProperty("Content-Type", "application/json;charset=" + charset.name())
            connection.setRequestProperty("Accept", "application/json")
            // 空POST关键配置:设置内容长度0,解决411报错
            connection.setRequestProperty("Content-Length", "0")
            // 新增鉴权头 apikey
            connection.setRequestProperty("apikey", apiKey)

            log.info("发起无请求体POST调用,URL:${targetUrl},已携带apikey请求头")

            // 发起请求,获取响应状态码
            responseCode = connection.getResponseCode()
            String responseBody = ""

            if (responseCode >= 200 && responseCode < 300) {
                // 2xx 成功状态码,读取正常响应流
                responseBody = IOUtils.toString(connection.getInputStream(), charset)
                // 将成功相关属性存入临时map
                attrMap["http.response.code"] = String.valueOf(responseCode)
                attrMap["http.request.url"] = targetUrl
                attrMap["request.status"] = "success"
            } else {
                // 4xx/5xx 错误状态码,读取错误流
                responseBody = IOUtils.toString(connection.getErrorStream(), charset)
                throw new RuntimeException("接口返回非2xx状态码:${responseCode},响应内容:${responseBody}")
            }

            // 将接口返回结果写入FlowFile,覆盖原有内容
            outputStream.write(responseBody.getBytes(charset))

        } catch (Exception e) {
            log.error("HTTP接口调用异常:${e.getMessage()}", e)
            attrMap["request.error"] = e.getMessage()
            attrMap["request.status"] = "failure"
            // 抛出异常,向上传递进入外层异常分支
            throw e
        } finally {
            // 强制关闭连接,避免HTTP连接长期占用导致资源泄漏
            if (connection != null) {
                connection.disconnect()
            }
        }
    } as StreamCallback)

    // 回调执行完毕,统一批量写入FlowFile属性
    attrMap.each { key, value ->
        flowFile = session.putAttribute(flowFile, key, value)
    }

    // 正常流程:路由至成功端口,提交会话
    session.transfer(flowFile, REL_SUCCESS)
    session.commit()
    log.info("POST接口调用成功,响应码:${responseCode},请求地址:${targetUrl}")

} catch (Exception e) {
    // 全局捕获所有异常(网络错误、参数缺失、接口报错等)
    log.error("FlowFile整体处理失败:${e.getMessage()}", e)
    // 写入失败相关属性
    flowFile = session.putAttribute(flowFile, "error.message", e.getMessage())
    flowFile = session.putAttribute(flowFile, "error.stack", ExceptionUtils.getStackTrace(e))
    flowFile = session.putAttribute(flowFile, "request.status", "failure")
    flowFile = session.putAttribute(flowFile, "http.response.code", String.valueOf(responseCode))

    // 路由至失败端口,会话回滚
    session.transfer(flowFile, REL_FAILURE)
    session.rollback()
}

配套使用说明

  1. 上游处理器必须设置两个属性
  • api.url:完整接口地址,例如 https://xxx.xxx.com/api/test
  • api.key:接口所需的密钥字符串
  1. 如果你的 apikey header名称大小写不一样(例如ApiKeyAPIKEY),直接修改这一行:
groovy 复制代码
connection.setRequestProperty("apikey", apiKey)
  1. 风险提示
  • 如果 api.url / api.key 属性为空,执行 new URL(targetUrl) 或header赋值时会直接抛出异常,进入失败端口;
  • 如需增加空值校验,我可以再帮你补充前置判断。

可选增强(如果你需要)

增加前置校验,防止属性缺失直接报错,在def apiKey = ...下方添加:

groovy 复制代码
// 参数合法性校验
if (!targetUrl) {
    throw new RuntimeException("FlowFile属性【api.url】不能为空,请检查上游流程")
}
if (!apiKey) {
    throw new RuntimeException("FlowFile属性【api.key】apikey不能为空,请检查上游流程")
}
相关推荐
qq_163135756 天前
InvokeHttp 单文件上传文件-multipart/form-data
groovy
唐青枫12 天前
Java Grails 实战详解:用 Groovy 和 GORM 快速开发 Web 应用
java·groovy
蜡台23 天前
通过Gradle脚本声明更改Java变量
android·java·开发语言·python·kotlin·gradle·groovy
七夜zippoe5 个月前
Java项目CI/CD实战:Jenkins与GitLab CI深度解析
java·ci/cd·gitlab·jenkins·groovy·pipline
摇滚侠7 个月前
三天学通 Groovy—Groovy 程序设计,Groovy 中的数据类型,笔记 1-13
笔记·groovy
trayvontang8 个月前
快速上手Groovy
jmeter·groovy
带刺的坐椅8 个月前
Liquor(Java 脚本)替代 Groovy 作脚本引擎的可行性分析
java·groovy·liquor
掉鱼的猫8 个月前
Java 低代码平台的“动态引擎”:Liquor
java·低代码·groovy
带刺的坐椅8 个月前
Java 低代码平台的“动态引擎”:Liquor
java·javascript·低代码·groovy·liquor