Jenkins中withCredentials搭配HttpURLConnection触发NotSerializableException原因
问题本质原因
- Jenkins Pipeline在执行任意内置步骤(包括
withCredentials、sh等)时,都会触发一次上下文持久化操作:会把当前作用域内的所有对象序列化后存储到磁盘,用于异常中断后的恢复执行。 - 你遇到的
java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl异常,不是withCredentials引入了不可序列化对象,而是你在调用withCredentials之前就创建了HttpURLConnection实例,这个类本身设计上就不可序列化。当withCredentials执行触发序列化检查时,扫描到上下文中的这个不可序列化对象,就抛出了异常。 - curl版本可以正常运行的原因是:该实现全程没有创建
HttpURLConnection这类不可序列化对象,所有上下文变量都是可序列化的字符串、Map类型,序列化检查可以正常通过。
规避方案
核心逻辑非常简单:把所有需要调用Jenkins Pipeline步骤的操作,都放在创建不可序列化的HttpURLConnection对象之前完成。这样调用Pipeline步骤触发序列化时,上下文中没有不可序列化对象;创建HttpURLConnection之后也不会再触发序列化检查。
修改后的可运行代码如下:
// vars/my_lib.groovy import groovy.json.JsonOutput def v4() { // 先调用withCredentials获取凭证,此时上下文无不可序列化对象 String authToken = null withCredentials([string(credentialsId: 'bitbucket_cred_id', variable: 'auth_token')]) { authToken = auth_token } // 所有Pipeline步骤执行完成后,再创建HttpURLConnection对象完成请求 def post = new URL("https://bitbucket.company.com/rest/build-status/1.0/commits/86c36485c0cbf956a62cbc1c370f1f3eecc8665d").openConnection() def dict = [:] dict.state = "INPROGRESS" dict.key = "foo_42" dict.url = "http://url/to/nowhere" def message = JsonOutput.toJson(dict).toString() post.setRequestMethod("POST") post.setDoOutput(true) post.setRequestProperty("Content-Type", "application/json") post.setRequestProperty("Authorization", "Bearer " + authToken) post.getOutputStream().write(message.getBytes("UTF-8")) def postRC = post.getResponseCode() println(postRC) if (postRC.equals(200)) { println(post.getInputStream().getText()) } }
方案说明
该方案完全基于原生URL、HttpURLConnection实现,不需要额外安装插件,兼容你使用的Jenkins 2.190.3、Groovy 2.4.12版本环境。
如果你的逻辑中确实需要在创建HttpURLConnection之后调用Pipeline步骤,可以将HttpURLConnection相关的操作封装到添加了@NonCPS注解的方法中,注意@NonCPS方法不能调用任何Pipeline步骤,且返回值必须是可序列化类型。
内容的提问来源于stack exchange,提问作者StoneThrow
相关产品推荐
相关产品推荐

