Groovy/Jenkins: how to refactor sh(script:"curl ...") to URL? - rest

My Jenkins pipeline currently successfully invokes Bitbucket REST API by invoking curl in a shell, as in the code below:
// Jenkinsfile
#Library('my-sandbox-libs#dev') my_lib
pipeline {
agent any
stages {
stage( "1" ) { steps { script { echo "hello" } } }
stage( "2" ) {
steps {
script {
log = new org.log.Log()
def cred_id = "bitbucket_cred_id"
def url_base = "https://bitbucket.company.com"
def commit = "76136485c45df256a62cbc2c3c5f1f3efcc86258"
def status =
//"INPROGRESS",
//"SUCCESSFUL",
"FAILED"
def viz_url = "https://path/to/nowhere"
try {
my_lib.notifyBitbucketBuildStatus(cred_id,
url_base,
commit,
status,
"foo",
42,
viz_url,
log)
}
}
}
}
stage( "3" ) { steps { script { echo "world" } } }
}
post { always { script { echo log.asJsonString() } } }
}
import groovy.json.JsonOutput
def notifyBitbucketBuildStatus(cred_id,
url_base,
commit,
build_state,
build_info_name,
build_info_number,
viz_url,
log) {
def rest_path = "rest/build-status/1.0/commits"
def dict = [:]
dict.state = build_state
dict.key = "${build_info_name}_${build_info_number}"
dict.url = viz_url
withCredentials([string(credentialsId: cred_id,
variable: 'auth_token')]) {
def cmd = "curl -f -L " +
"-H \"Authorization: Bearer ${auth_token}\" " +
"-H \"Content-Type:application/json\" " +
"-X POST ${url_base}/${rest_path}/${commit} " +
"-d \'${JsonOutput.toJson(dict)}\'"
if ( 0 != sh(script: cmd, returnStatus: true) ) {
log.warn("Failed updating build status with Bitbucket")
}
}
}
I would like to refactor function notifyBitbucketBuildStatus() to use a "native" Groovy-language solution, rather than invoking curl in a shell. I read the following on this topic:
https://www.baeldung.com/groovy-web-services
Groovy built-in REST/HTTP client?
...based on which I thought the refactored function would look like this:
def notifyBitbucketBuildStatus(cred_id,
url_base,
commit,
build_state,
build_info_name,
build_info_number,
viz_url,
log) {
def rest_path = "rest/build-status/1.0/commits"
def dict = [:]
dict.state = build_state
dict.key = "${build_info_name}_${build_info_number}"
dict.url = viz_url
def req = new URL("${url_base}/${rest_path}/${commit}").openConnection()
req.setRequestMethod("POST")
req.setDoOutput(true)
req.setRequestProperty("Content-Type", "application/json")
withCredentials([string(credentialsId: cred_id,
variable: 'auth_token')]) {
req.setRequestProperty("Authorization", "Bearer ${auth_token}")
}
def msg = JsonOutput.toJson(dict)
req.getOutputStream().write(msg.getBytes("UTF-8"));
if ( 200 != req.getResponseCode() ) {
log.warn("Failed updating build status with Bitbucket")
}
}
...but this generates the exception java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl
That "not serializable" made me think the error had something to do with a failure to transform something to a string, so I also tried this, but it did not change the error:
def msg = JsonOutput.toJson(dict).toString()
What is wrong with the refactored code that uses class URL, and what is the right way to use it to invoke the REST API?
For the life of me, I can't see what's different between the above and the linked Stack Overflow Q&A, and my inexperience with the language is such that I rely largely on adapting existing example.

Solution
I would highly suggest you use the HTTP Request and the Pipeline Steps Utility plugin for this. You can then use those steps in a Groovy script as follows
node('master') {
withCredentials([string(credentialsId: cred_id, variable: 'auth_token')]) {
def response = httpRequest url: "https://jsonplaceholder.typicode.com/todos", customHeaders: [[name: 'Authorization', value: "Bearer ${auth_token}"]]
}
if( response.status != 200 ) {
error("Service returned a ${response.status}")
}
def json = readJSON text: response.content
println "The User ID is ${json[0]['userId']}"
println "The follow json obj is ${json}"
}
Obviously you can modify the code if you want to build a method, and you will need to update with the appropriate URL.

I found a sucky and unsatisfying answer - but an answer nonetheless - that I posted here: https://stackoverflow.com/a/69486890/5437543
I hate that solution because it would appear to demonstrate that the Jenkins/Groovy language itself imposes an artificial contrivance on how I can organize my code. Effectively, I am prevented from doing
// Jenkinsfile
#Library('my-sandbox-libs#dev') my_lib
pipeline {
agent any
stages {
stage( "1" ) { steps { script { my_lib.func() } } }
}
}
// vars/my_lib.groovy
def func() {
def post = new URL("https://whatever").openConnection();
...
withCredentials([string(credentialsId: cred_id,
variable: 'auth_token')]) {
req.setRequestProperty("Authorization", "Bearer ${auth_token}")
}
...
}
...and I am forced to do
// Jenkinsfile
#Library('my-sandbox-libs#dev') my_lib
pipeline {
agent any
stages {
stage( "1" ) { steps { script { my_lib.func(my_lib.getCred()) } } }
}
}
// vars/my_lib.groovy
def getCred() {
withCredentials([string(credentialsId: cred_id,
variable: 'auth_token')]) {
return auth_token
}
}
def func(auth_token) {
def post = new URL("https://whatever").openConnection();
...
req.setRequestProperty("Authorization", "Bearer ${auth_token}")
...
}
Extremely dissatisfying conclusion. I hope another answerer can point out a solution that doesn't rely on this contrived code organization.

Related

Akka Http route test with formFields causing UnsupportedRequestContentTypeRejection

I have a GET request with parameters and a formField.
It works when I use a client like Insomnia/Postman to send the req.
But the route test below fails with the error:
UnsupportedRequestContentTypeRejection(Set(application/x-www-form-urlencoded, multipart/form-data))
(Rejection created by unmarshallers. Signals that the request was rejected because the requests content-type is unsupported.)
I have tried everything I can think of to fix it but it still returns the same error.
It is the formField that causes the problem, for some reason when called by the test it doesnt like the headers.
Is it something to do with withEntity ?
Code:
path("myurl" ) {
get {
extractRequest { request =>
parameters('var1.as[String], 'var2.as[String], 'var3.as[String], 'var4.as[String]) { (var1, var2, var3, var4) =>
formField('sessionid.as[String]) { (sessionid) =>
complete {
request.headers.foreach(a => println("h=" + a))
}
}
}
}
}
}
Test:
// TESTED WITH THIS - Fails with UnsupportedRequestContentTypeRejection(Set(application/x-www-form-urlencoded, multipart/form-data))
class GETTest extends FreeSpec with Matchers with ScalatestRouteTest {
val get = HttpRequest(HttpMethods.GET, uri = "/myurl?var1=456&var2=123&var3=789&var4=987")
.withEntity("sessionid:1234567890")
.withHeaders(
scala.collection.immutable.Seq(
RawHeader("Content-Type", "application/x-www-form-urlencoded"), // same problem if I comment out these 2 Content-Type lines
RawHeader("Content-Type", "multipart/form-data"),
RawHeader("Accept", "Application/JSON")
)
)
get ~> route ~> check {
status should equal(StatusCodes.OK)
}
The exception is thrown before the formField line.
Full exception:
ScalaTestFailureLocation: akka.http.scaladsl.testkit.RouteTest$$anonfun$check$1 at (RouteTest.scala:57)
org.scalatest.exceptions.TestFailedException: Request was rejected with rejection UnsupportedRequestContentTypeRejection(Set(application/x-www-form-urlencoded, multipart/form-data))
at akka.http.scaladsl.testkit.TestFrameworkInterface$Scalatest$class.failTest(TestFrameworkInterface.scala:24)
}
You could either use:
val get = HttpRequest(HttpMethods.GET, uri = "/myurl?var1=456&var2=123&var3=789&var4=987", entity = FormData("sessionid" -> "1234567.890").toEntity)
or
val get = Get("/myurl?var1=456&var2=123&var3=789&var4=987", FormData("sessionid" -> "1234567.890"))

Jenkins/Groovy: Why does withCredentials() introduce a NotSerializableException exception?

This question is a follow-up to Groovy/Jenkins: how to refactor sh(script:"curl ...") to URL?.
Though I intend to try invoking REST API with HTTP Request - as the one answerer has suggested - I'd like to also specifically learn about/understand the original problem RE: non-serializability.
I've reduced the code in the linked Q&A to more minimally demonstrate the problem.
This code:
#Library('my-sandbox-libs#dev') sandbox_lib
pipeline {
agent any
stages {
stage( "1" ) { steps { script { echo "hello" } } }
stage( "2" ) {
steps {
script {
try {
my_lib.v4()
}
catch(Exception e) {
echo "Jenkinsfile: ${e.toString()}"
throw e
}
}
}
}
stage( "3" ) { steps { script { echo "world" } } }
}
}
// vars/my_lib.groovy
import groovy.json.JsonOutput
def v4() {
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.getOutputStream().write(message.getBytes("UTF-8"));
def postRC = post.getResponseCode();
println(postRC);
if (postRC.equals(200)) {
println(post.getInputStream().getText());
}
}
...generates HTTP error 401. This is expected, because it invokes a Bitbucket REST API without necessary authentication.
What's missing is the "Bearer: xyz" secret-text. In the past, I've gotten this secret-text using Jenkins'/Groovy's withCredentials function, as in the modified function v4() below:
// vars/my_lib.groovy
import groovy.json.JsonOutput
def v4() {
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")
withCredentials([string(credentialsId: 'bitbucket_cred_id',
variable: 'auth_token')]) {
post.setRequestProperty("Authorization", "Bearer " + auth_token)
}
post.getOutputStream().write(message.getBytes("UTF-8"));
def postRC = post.getResponseCode();
println(postRC);
if (postRC.equals(200)) {
println(post.getInputStream().getText());
}
}
...but I've discovered that the addition of that withCredentials-block, specifically, introduces a java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl runtime exception.
At the linked Stack Overflow Q&A, one commenter has informed me that the problem has to do with serializable vs. non-serializable objects in this function. But I'm having a hard time understanding what non-serializable object is introduced by use of the withCredentials-block.
As a point of reference, use of the same withCredentials-block works just fine when I invoke the same REST API using curl instead of "native" Jenkins/Groovy functions. I.e. the following code works fine:
def v1() {
def dict = [:]
dict.state = "INPROGRESS"
dict.key = "foo_42"
dict.url = "http://url/to/nowhere"
withCredentials([string(credentialsId: 'bitbucket_cred_id',
variable: 'auth_token')]) {
def cmd = "curl -f -L " +
"-H \"Authorization: Bearer ${auth_token}\" " +
"-H \"Content-Type:application/json\" " +
"-X POST https://bitbucket.company.com/rest/build-status/1.0/commits/86c36485c0cbf956a62cbc1c370f1f3eecc8665d " +
"-d \'${JsonOutput.toJson(dict)}\'"
sh(script: cmd, returnStatus: true)
}
}
So, in summary, this question is why does withCredentials() introduce non-serializable objects (and what is the non-serializable object), which causes NonSerializableException exceptions with use of URL and/or HttpURLConnection, and does one work around this?
I'm not unwilling to use a different solution, such as httpRequest objects, but this question is about learning the nature of this specific problem, and how to work around it with the existing objects, i.e. URL and HttpURLConnection objects.
Update: As it turns out, I'm unable to use the suggested alternate solution using httpRequest objects, because the HTTP Request plugin is only available to Jenkins versions 2.222.4 or newer, which our Jenkins does not meet. It's outside my privilege to update our Jenkins version, and basically I'll need to assume an inability to upgrade Jenkins.
Our Jenkins version is 2.190.3, which has Groovy version 2.4.12.
When having such issues I usually try to put the "low level" code that calls down to Groovy/Java API, into #NonCPS functions. Objects in such functions don't need to be serializable, so we can freely use any Groovy/Java API.
Background reading: Pipeline CPS Method Mismatches
Make sure you don't call any Jenkins pipeline steps from #NonCPS functions (nor any other function that is not marked #NonCPS) - such code could silently fail or do unexpected stuff! (There are some "safe" functions like echo though.)
As withCredentials is a pipeline step, it has to be called from a "regular" function (not marked as #NonCPS), one level up the call chain.
Note that I'm passing auth_token as an argument to v4_internal. If you need other Jenkins variables in the code, these should also be passed as arguments.
// vars/my_lib.groovy
import groovy.json.JsonOutput
def v4() {
withCredentials([string(credentialsId: 'bitbucket_cred_id',
variable: 'auth_token')]) {
v4_internal(auth_token)
}
}
#NonCPS
def v4_internal( def auth_token ) {
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 " + auth_token)
post.getOutputStream().write(message.getBytes("UTF-8"));
def postRC = post.getResponseCode();
println(postRC);
if (postRC.equals(200)) {
println(post.getInputStream().getText());
}
}
This sucks - and I hope someone comes along with a better answer - but it looks like the only way I can pull this off is as follows:
#Library('my-sandbox-libs#dev') sandbox_lib
pipeline {
agent any
stages {
stage( "1" ) { steps { script { echo "hello" } } }
stage( "2" ) {
steps {
script {
try {
my_lib.v5(my_lib.getBitbucketCred())
}
catch(Exception e) {
echo "Jenkinsfile: ${e.toString()}"
throw e
}
}
}
}
stage( "3" ) { steps { script { echo "world" } } }
}
}
// vars/my_lib.groovy
import groovy.json.JsonOutput
def getBitbucketCred() {
withCredentials([string(credentialsId: 'bitbucket_cred_id',
variable: 'auth_token')]) {
return auth_token
}
}
def v5(auth_token) {
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")
req.setRequestProperty("Authorization", "Bearer " + auth_token)
post.getOutputStream().write(message.getBytes("UTF-8"));
def postRC = post.getResponseCode();
println(postRC);
if (postRC.equals(200)) {
println(post.getInputStream().getText());
}
}
Specifically, I must invoke withCredentials() completely separate from the scope of the function that uses URL and/or HttpURLConnection.
I don't know if this is considered acceptable in Jenkins/Groovy, but I'm dissatisfied by the inability to call withCredentials() from the v5() function itself. I'm also unable to call a withCredentials()-wrapper function from v5().
When I was trying to call withCredentials() either directly in v5() or from a wrapper function called by v5(), I tried every combination of #NonCPS between v5() and the wrapper function, and that didn't work. I also tried explicitly setting the URL and HttpURLConnection objects to null before the end of the function (as suggested at Jenkins/Groovy: Why does withCredentials() introduce a NotSerializableException exception?), and neither did that work.
I'd be disappointed in Jenkins/Groovy if this is the only solution. This feels like an artificial limit on how one can choose to organize his code.
Updating with more detail in response to #daggett:
RE: calling withCredentials() directly from my_lib.v5() or calling it from a wrapper function, let's start with mylib.groovy set up as follows (let me also take the opportunity to give the functions better names):
def withCredWrapper() {
withCredentials([string(credentialsId: 'bitbucket_cred_id',
variable: 'auth_token')]) {
return auth_token
}
}
def callRestFunc() {
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")
// version 1:
withCredentials([string(credentialsId: 'bb_auth_bearer_token_cred_id',
variable: 'auth_token')]) {
post.setRequestProperty("Authorization", "Bearer " + auth_token)
}
// version 2:
//post.setRequestProperty("Authorization", "Bearer " + withCredWrapper())
post.getOutputStream().write(message.getBytes("UTF-8"));
def postRC = post.getResponseCode();
println(postRC);
if (postRC.equals(200)) {
println(post.getInputStream().getText());
}
}
With the above code, function callRestFunc() can either call withCredentials() directly, as above, or indirectly by the wrapper function withCredWrapper(), i.e.:
...
// version 1:
//withCredentials([string(credentialsId: 'bb_auth_bearer_token_cred_id',
// variable: 'auth_token')]) {
// post.setRequestProperty("Authorization", "Bearer " + auth_token)
//}
// version 2:
post.setRequestProperty("Authorization", "Bearer " + withCredWrapper())
...
Further, #NonCPS can be applied to one of withCredWrapper() or callRestFunc(), both, or neither.
Below are the specific failures with all 8 combinations thereof:
1.
def withCredWrapper() {
...
}
def callRestFunc() {
...
// version 1:
withCredentials(...)
...
}
Failure: Jenkinsfile: java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl
2.
def withCredWrapper() {
...
}
#NonCPS
def callRestFunc() {
...
// version 1:
withCredentials(...)
...
}
Failure: expected to call my_lib.callRestFunc but wound up catching withCredentials; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/ Masking supported pattern matches of $auth_token, Jenkinsfile: java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl
3.
#NonCPS
def withCredWrapper() {
...
}
def callRestFunc() {
...
// version 1:
withCredentials(...)
...
}
Failure: Jenkinsfile: java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl
4.
#NonCPS
def withCredWrapper() {
...
}
#NonCPS
def callRestFunc() {
...
// version 1:
withCredentials(...)
...
}
Failure: expected to call my_lib.callRestFunc but wound up catching withCredentials; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/ Masking supported pattern matches of $auth_token, Jenkinsfile: java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl
5.
def withCredWrapper() {
...
}
def callRestFunc() {
...
// version 2:
post.setRequestProperty("Authorization", "Bearer " + withCredWrapper())
...
}
Failure: Jenkinsfile: java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl
6.
def withCredWrapper() {
...
}
#NonCPS
def callRestFunc() {
...
// version 2:
post.setRequestProperty("Authorization", "Bearer " + withCredWrapper())
...
}
Failure: expected to call my_lib.callRestFunc but wound up catching my_lib.withCredWrapper; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/
7.
#NonCPS
def withCredWrapper() {
...
}
def callRestFunc() {
...
// version 2:
post.setRequestProperty("Authorization", "Bearer " + withCredWrapper())
...
}
Failure: expected to call my_lib.withCredWrapper but wound up catching withCredentials; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/ Masking supported pattern matches of $auth_token, Jenkinsfile: java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl
8.
#NonCPS
def withCredWrapper() {
...
}
#NonCPS
def callRestFunc() {
...
// version 2:
post.setRequestProperty("Authorization", "Bearer " + withCredWrapper())
...
}
Failure: expected to call my_lib.callRestFunc but wound up catching withCredentials; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/

akka-http HttpEntity.toStrict timed out while still waiting for outstanding data

I encounter a problem randomly without SSL(sometimes it works without issue), and each time with SSL, and I don't understand why.
It's a time out during HttpEntity.toStrict.
extractRequest{payload =>
val futureRequestEntity = payload.entity.toStrict(5.seconds).map(_.data.utf8String)
val requestEntity = Await.result(futureRequestEntity, 10.seconds)
... }
I tried to set longer waiting times, but doesn't solve the issue.
java.util.concurrent.TimeoutException: HttpEntity.toStrict timed out after 5 seconds while still waiting for outstanding data
at akka.http.impl.util.ToStrict$$anon$1.onTimer(package.scala:138)
I need to use extractRequest for my path because I extract the entity and the header further in the code.
Route code :
options {
corsHandler.corsHandler(complete(StatusCodes.OK))
} ~ post {
path("recommandation" / Segment / Segment / "suggestion") {(docType,docId) =>
extractRequest{payload =>
writeLog("info",s"/recommandation : Post request for $docType suggestion : $docId ")
val coBranding = payload.headers.filter(x => x.is("cobrandingcontext")).map(x => x.value()).head
val futureRequestEntity = payload.entity.toStrict(5.seconds).map(_.data.utf8String)
val requestEntity = Await.result(futureRequestEntity, 10.seconds)
val parsedPayload = suggestionEngine.payloadParser.suggestionEnginePayloadParser(requestEntity,
coBranding,docType, docId)
if (parsedPayload.isDefined){
val suggestionResult = suggestionEngine.suggestionWorker.suggestionPipeline(parsedPayload.get)
val suggestionResponse = suggestionEngine.responseHandler.responseBuilder(suggestionResult).get
complete(200,List(`Content-Type`(`application/json`)),suggestionResponse)
} else {
writeLog("error","/recommandation : undefined payload")
complete(StatusCodes.BadRequest,List(`Content-Type`(`application/json`)),"Undefined payload")
}
}
}
}
Request example :
curl -i https://mydns.com:443/recommandation/products/59ad73be20a35d3fa47c80c8/suggestion -H
'cobrandingcontext: branding' -H 'Content-Type: application/json;charset=UTF-8' -X POST -d '{"collection":"products",
"query":{"category_parent":"category-ex","category_child":"category-child-ex","dimensions.width":{"$lt":70,"$gt":0},
"dimensions":{"$lt":40,"$gt":-40},"status":true,"structured":true,"visibilities":"architects-3d-btoc"},"configuration":{"type":"nearest-neighbors","metric":"cosine","features":["styles"]}}'
Thank's in advance
I think the explanation is that the use of Await will block a dispatcher thread which can lead to thread starvation.
The solution I found is to nest directives :
options {
corsHandler.corsHandler(complete(StatusCodes.OK))
} ~ post {
path("/"){
entity(as[String]){payload =>
headerValueByName("context"){ contextValue => {
...// work with payload and contextValue
}
}
}
}
}

Scala/Spray/Akka unable to leverage mapRequest

I am new to Scala/Spray/AKKA so please excuse this dumb requests.
I have the following Directive and it is being called as the first
logger line ("inside") is showing up in logs.
However, anything inside mapRequest{} is skipped over. The logging line ("headers:") isn't showing up
private def directiveToGetHeaders(input: String) : Directive0 = {
logger.info("inside")
mapRequest { request =>
val headList: List[HttpHeader] = request.headers
logger.info("headers: " + headList.size)
request
}
}
I am not sure what I did wrong. My goal is to pull out all the HTTP headers. Any tip/pointer much appreciated. Thanks
-v
You can use extractRequest directive for getting headers.
private def directiveToGetHeaders(input: String) : Directive0 = {
logger.info("inside")
extractRequest { request =>
val headList: Seq[HttpHeader] = request.headers
logger.info("headers: " + headList.size)
complete(HttpResponse())
}
}

Attaching file in SoapUI with groovy

I'm creating some tests in SoapUI. SOAP request, that i want to test has attachment. When I'm setting it manualy, everything is ok:
But in my case, i need to set attachment dynamically. I'm trying to made it by properties to hold file path, and groovy script to set attachment. but it's not work at all:
// get request
def request = testRunner.testCase.getTestStepByName( "UploadRoutingCodes" ).testRequest
// clear existing attachments
for( a in request.attachments ) {
request.removeAttachment( a )
}
// get file to attach
//def fileName = context.expand( '${Source of data#PathToXRC File data name }' )
def fileName = context.expand( '${#TestCase#XRC_file_name}' )
def filePath = context.expand( '${#Project#XRC_files_path}' )
log.info "file: " + filePath + fileName
def file = new File(filePath + fileName )
if ( file == null) {
log.error "bad filename"
}
else
{
// attach and set properties
def attachment = request.attachFile( file, true )
attachment.contentType = "application/octet-stream"
def list = fileName.tokenize("\\");
attachment.setPart(list.last())
}
After run this script, request look like this:
Documentation to SoapUI is not helpful at all.
So, my question is: what i'm doing wrong?
I found the answer:
def holder2 = groovyUtils.getXmlHolder( "UploadRoutingCodes#Request" ) // Get Request body
def startDate2 = holder2.setNodeValue( "//blac:FileByteStream","cid:"+list.last()); //Set "link" to attachment in request body
holder2.updateProperty() //and update
attachment.setPart(list.last()); //set attachment
Thaven, thank you for your answer. It helped. I will attach my full groovy script as I spent some time to fully assembled your parts, but anyhow all tributes goes to you.
Please note that:
//FileNamePath
def fileNamePath = testCase.getTestStepAt(testRunner.testCase.getTestStepIndexByName("FileNameProperties")).getProperty("FileNamePath")
//FileName
def fileName = testCase.getTestStepAt(testRunner.testCase.getTestStepIndexByName("FileNameProperties")).getProperty("FileName")
are the test step properties defined inside the test case. Filename: my_sample_filename.xml and FileNamePath: C:\samples\my_sample_filename.xml accordingly.
import groovy.xml.MarkupBuilder
import org.custommonkey.xmlunit.*
import java.util.Random
import java.security.MessageDigest
import java.nio.file.*
def groovyUtils = new com.eviware.soapui.support.GroovyUtils(context)
def projectPath = groovyUtils.projectPath
log.info projectPath
def project = testRunner.testCase.testSuite.project
log.info "Project: " + project.name
def myTestSuite = testRunner.testCase.testSuite;
log.info "TestSuite: " + myTestSuite.name
def testCase = testRunner.testCase
log.info "TestCase: " + testCase.name
def testStepUploadDataAfterCheck = testCase.getTestStepByName("UploadDataAfterCheck")
def request= testStepUploadDataAfterCheck.testRequest
log.info "TestStep: " + testStepUploadDataAfterCheck.name
// clear existing attachments
for( a in request.attachments ) {
request.removeAttachment( a )
}
//FileNamePath
def fileNamePath = testCase.getTestStepAt(testRunner.testCase.getTestStepIndexByName("FileNameProperties")).getProperty("FileNamePath")
//FileName
def fileName = testCase.getTestStepAt(testRunner.testCase.getTestStepIndexByName("FileNameProperties")).getProperty("FileName")
// get file to attach
log.info "file to attach: " + fileNamePath.getValue()
def file = new File(fileNamePath.getValue() )
if ( file == null) {
log.error "bad filename"
}
else
{
// attach and set properties
def attachment = request.attachFile( file, true )
attachment.contentType = "application/octet-stream"
attachment.setPart(fileName.getValue())
def holder2 = groovyUtils.getXmlHolder( "UploadDataAfterCheck#Request" ) // Get Request body
holder2.setNodeValue( "//upl:UploadDataAfterCheckRequest/uploadedData","cid:"+fileName.getValue()); //Set "link" to attachment in request body
holder2.updateProperty() //and update
log.info "file attached succesfully"
}
And here is my soap request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:upl="http://www.acer.europa.eu/aris/upload">
<soapenv:Header/>
<soapenv:Body>
<upl:UploadDataAfterCheckRequest>
<uploadedData>cid:my_sample_filename.xml</uploadedData>
</upl:UploadDataAfterCheckRequest>
</soapenv:Body>
</soapenv:Envelope>