Replace ${} parameters with values from a feeder file in an exec

6,148 views
Skip to first unread message

Steven Zaluk

unread,
Nov 12, 2014, 1:41:02 PM11/12/14
to gat...@googlegroups.com
The API's I am testing have a complex authorization scheme that involves generating an MD5 value based on the payload, requestType and uri of the service.  Values in that payload and uri will need to be parameterized so I have that defined in an external file and am feeding it in.  Simple enough.  The problem I am having is in the exec statement when I am generating the new session with the computed values, the ${} variables that are defined in the feeder document are not getting escaped.  Unfortunately the SignatureCalculator that generates the Authorization header requires the payload, requestType and uri to be passed in so I need to have those ${} values resolved in that exec.

Take this code for example.  This is a simple Get call that has no payload.  The problem I am having is resolving the ${userid}, which is defined in get_user_data_request.csv:

object GetUserDataRequest extends BaseRequest {

  val getUserDataRequestScenario = scenario("Get User Data Request")
                                   .feed(csv("get_user_data_request.csv").random)
                                   .exec(session => { val payload : String = ""
                                                                 val requestType : String = "GET"
                                                                 val uri : String = "/services/userids/${userid}"

                                                                 val newSession : Session = session.set("payload", payload)
                                                                                 .set("requestType", requestType)
                                                                                 .set("uri", uri)
                                                                                 .set("signatureCalculator", new CustomSignatureCalculator(payload, requestType, uri, MainController.environment))
                                                                                 .set("contentMd5", Authorization.getMD5MessageDigest(payload))

                                                                 newSession})
                                   .exec(http("get_user_data_request")
                                   .get("${uri}")
                                   .headers(baseHeader)
                                   .header(HttpHeaderNames.ContentMD5, "${contentMd5}")
                                   .signatureCalculator("${signatureCalculator}")
                                   .check(status.is(200), bodyString.transform(string => string).saveAs("responseBody")))
                                   .exec(session => { println(session("responseBody").as[String])
                                                      session})
}

I need to replace the value of ${userid} with a value coming in from an external CSV file from the feed statement.  The way it is written now the request that is being made is not replacing the ${userid} parameter with it's proper value and the request looks like this:


when it should look like this:


I need that replacement to happen in the exec where I am generating the new session as I need that uri and payload to have any ${} values replaced before I can generate the MD5 and Signature Calculator.  

How can I get the ${userid} value replaced in the exec statement?  Is this possible?

Sorry for the rambling.  This is difficult to explain in a short email.  :-)

Thanks,
Steve

Stéphane Landelle

unread,
Nov 12, 2014, 3:12:09 PM11/12/14
to gat...@googlegroups.com
EL in EL inception? No, that wouldn't work.

Then, you're trying to implement a signature calculator, so have you tried to search for "signature calculator" in our documentation (see textbox in banner)?

--
You received this message because you are subscribed to the Google Groups "Gatling User Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to gatling+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Steven Zaluk

unread,
Nov 12, 2014, 3:22:42 PM11/12/14
to gat...@googlegroups.com
Hi Stephane,

I got the signature calculator working and the scenario above works fine if the URI or Payload has no dynamic data.  All I would like to do it replace ${userid} in the following statement from the code above with the value from a feeder file:

val uri : String = "/services/userids/${userid}"

In the meantime I wrote my own EL processor that takes in a string and iterates over the session attributes and does the necessary replacements.  I am sure there is a better way than that.  I just can't figure it out.  :-)

Thanks Again,
Steve

John Arrowwood

unread,
Nov 12, 2014, 4:43:26 PM11/12/14
to gat...@googlegroups.com
Easy enough:

val uri : String = "/services/userids/" + session("userid").as[String]

It gets more complicated if you do not have the ability to change the code like that, like if the string is coming from an outside source.  But if that is the case, say so and we can help you cross that bridge.  :)

Stéphane Landelle

unread,
Nov 13, 2014, 4:44:48 AM11/13/14
to gat...@googlegroups.com
@Steven I think you misunderstood the SignatureCalculator logic. The goal is that you shouldn't be generating uri, payload and whatever upfront, but only on the spot.
Upfront generation has 2 drawbacks:
  • increase memory usage, if you forget to clean the sessions and they don't die immediately
  • increase data lifetime, so those data might survive young generation collection and end up in the old gen, causing long GC pauses
The SignatureCalculator provides a hook to generate a new request definition, based on the one that was resolved.

You have to access the resolved uri and body from the Request, and add the new header to the RequestBuilder.

Steven Zaluk

unread,
Nov 13, 2014, 9:02:36 AM11/13/14
to gat...@googlegroups.com
Thanks for the great comments, John and Stephane!

I was able to simplify this somewhat and updated my SignatureCalculator.  I have one more stumbling block I need to get over.  Take this snippet of code for example:

  val getUserDataRequestScenario = scenario("Get User Data Request")
                                   .feed(csv("get_user_data_request.csv").random)
                                   .exec(http("get_user_data_request")
                                   .get("/services/userids/${userid}")
                                   .headers(baseHeader)
                                   .signatureCalculator(new CISignatureCalculator("", "GET", "/services/userids/${userid}", MainController.environment))
                                   .check(status.is(200), bodyString.transform(string => string).saveAs("responseBody")))
                                   .exec(session => { println(session("responseBody").as[String])
                                                      session})

How do I save the string that was generated in the get call after El strings have been replaced?  I need to use that when I generate the SignatureCalculator.

Also, in the folllowing code:

  val createAccountRequestScenario = scenario("Create Account Request")
                                     .exec(http("create_account_request")
                                     .post("/services/accounts")
                                     .headers(baseHeader)
                                     .body(ELFileBody("create_account_request_template.json")).asJSON
                                     .signatureCalculator(new CISignatureCalculator("<same content as was generated from the template int the body>", "GET", "/services/accounts", MainController.environment))
                                     .check(status.is(200), bodyString.transform(string => string).saveAs("responseBody")))
                                     .exec(session => { println(session("responseBody").as[String])
                                                        session})

How do I save the string generated in the body call (create_account_request_template.json is template I am using with EL strings) so I can use that when generating the signature calculator?

Here is what create_account_request_template.json looks like:
{
    "type": "USER",
    "status": "PERMANENT",
    "userId": "${username}",
    "email": "${username}@domain.com",
    "realm": " ",
    "realmAccountId": "12345",
    "enabled": true,
    "phones": {},
    "label": "${username}"
}

If I can do that easily then I can remove all that processing I was doing completely.

Thanks!

--Steve


Stéphane Landelle

unread,
Nov 13, 2014, 9:18:55 AM11/13/14
to gat...@googlegroups.com
  val getUserDataRequestScenario = scenario("Get User Data Request")
                                   .feed(csv("get_user_data_request.csv").random)
                                   .exec(http("get_user_data_request")
                                   .get("/services/userids/${userid}")
                                   .headers(baseHeader)
                                   .signatureCalculator(new CISignatureCalculator("", "GET", "/services/userids/${userid}", MainController.environment))
                                   .check(status.is(200), bodyString.transform(string => string).saveAs("responseBody")))
                                   .exec(session => { println(session("responseBody").as[String])
                                                      session})


But why do you insist on passing "GET" and "/services/userids/${userid}" to the CISignatureCalculator?!?!
You'll get the resolved values in the request passed to the SignatureCalculator method.

CISignatureCalculator {
  def calculateAndAddSignature(request: Request, requestBuilder: RequestBuilderBase): Unit = {
     val method = request.getMethod // GET
     val path = request.getUri.getPath // /services/userids/userIdResolvedValue
     val body = request.getStringData // computed body
     val token = generateToken(method, path, body)
     requestBuilder.addHeader("X-Token", token)
  }
}

Steven Zaluk

unread,
Nov 13, 2014, 10:41:55 AM11/13/14
to gat...@googlegroups.com
Hey Stephane,

That worked perfectly!  I knew I had to be doing this wrong and making it more complicated than it needed to be.  I can't thank you enough for the help!

Thanks,
Steve

--

Stéphane Landelle

unread,
Nov 13, 2014, 10:51:26 AM11/13/14
to gat...@googlegroups.com
Great!
Glad I helped!
Reply all
Reply to author
Forward
0 new messages