Here’s a new, geeky technical article for all my HP LoadRunner buddies. Recently, I had to work with a web application built on JSON. The page would display a different number of records/rows for a each distinct user id. The user I recorded with had three records, but a different id could have a different amount.

The VuGen script contained web_custom_request() functions. The BODY argument contained a JSON response, and I would need to correlate/parameterize a portion of it that was unique and dynamic.  Here is what a typical BODY looked like:

"Body={\"cache\":true,\"intraday\":true,\"base\":{\"entities\":[{\"entity\":\"99999\",\"faNumber\":\"1111\"}],\"s\":[\"12345678\",\"22222222\",\"33333333\"],\"session\":\"999999999999\",\"id\":888888888888,\"it\":0,\"iv\":\"\"}}",

The part I need to extract are the accounts that are the numbers between the brackets [ ]: 12345678, 22222222, and 33333333.

I need to capture all of the account numbers and figure out how many there are (since it will be different each time). It’s easy enough to find the accounts with standard correlation in LoadRunner. A previous request passed that information and I snagged them all using the ORD=ALL argument of web_reg_save_param_ex() to story them all in an array:

First, we capture the array (just know that my boundaries are correct and assume I know what I am doing here):

web_reg_save_param_ex(
"ParamName=pAcctNum",
"LB=acctNum\":\"",
"RB=\",\"hasFI",
"Ordinal=ALL",
SEARCH_FILTERS,
"Scope=BODY",
LAST);
web_custom_request("AccountList",
"URL=https://urlthatreallydoesntmatter.atall.ok",
"Method=POST",
"TargetFrame=",
"Resource=0",
"RecContentType=application/json",
"Referer=https://urlthatreallydoesntmatter.atall.ok",
"Snapshot=t25.inf",
"Mode=HTML",
"EncType=application/json;charset=utf-8",
"Body={\"account\":\"\",\"acctNm\":\"\",\"iac\":false,\"rid\":\"\",\"rnm\":\"\",\"gid\":\"\",\"cat_id\":\"\",\"selAll\":true,\"exClosed\":true,\"prev\":false,\"isRefresh\":false,\"action\":1,\"base\":{\"id\":999999999999,\"it\":0,\"iv\":\"\",\"entities\":[{\"entity\":\"99999\",\"faNumber\":\"8888\"}],\"s\":[],\"session\":\"888888888888\"}}",
LAST);
// Send a message to the output log that tells us how many we found.
lr_output_message("Number of Accounts found is: %s", lr_eval_string("{pAcctNum_count}"));

This is what the log shows:

Action.c(305): Notify: Saving Parameter "pAcctNum_count = 3".
Action.c(305): web_custom_request("AccountList") was successful, 2940 body bytes, 477 header bytes [MsgId: MMSG-26386]
Action.c(318): Notify: Parameter Substitution: parameter "pAcctNum_count" = "3"
Action.c(318): Number of Accounts is: 3

Now the interesting part. We need to create a string of accounts with brackets on each end, double quotes and commas, and it looks like some backslashes too. This is what the next request has that I want to replace with a parameter.

[\"12345678\",\"22222222\",\"33333333\"]

I created the dynamic array the way I thought the server wanted to see it in the BODY:

/ Put the first element in the array. It has brackets that begin the string.
// This is unique to the first one.
lr_param_sprintf("c_buffer","[\\\"%s",lr_paramarr_idx("pAcctNum",1));
// Loop through the rest of the array items and put in all the characters it needs // in between the numbers
for (i=2;i<=atoi(lr_eval_string("{pAcctNum_count}"));i++) {
lr_param_sprintf("c_buffer", "%s\\\",\\\"%s", lr_eval_string("
{c_buffer}"), lr_paramarr_idx("pAcctNum", i));
}
// lr_param_sprintf() lets you keep adding stuff to it by referencing the same
// variable name and it just keeps concatenating values
// lr_paramarr_idx allows us to easily reference a single element in the array
// for the first iteration, by number, and the rest by the value of the loop (i).
// Now let's get to the final value we want to end up with. This saves the string // as a LR parameter and we add those brackets on the right side since we need // to close it that way
lr_save_string(lr_eval_string("{c_buffer}\\\"]"), "c_finalvalue");
lr_output_message("The new file name is %s", lr_eval_string("{c_finalvalue}"));

The log showed the following:

Action.c(348): Notify: Parameter Substitution: parameter "pAcctNum_1" = "10080360"
Action.c(348): Notify: Saving Parameter "c_buffer = [\"12345678".
Action.c(352): Notify: Parameter Substitution: parameter "pAcctNum_count" = "3"
Action.c(353): Notify: Parameter Substitution: parameter "c_buffer" = "[\"12345678"
Action.c(353): Notify: Parameter Substitution: parameter "pAcctNum_2" = "22222222"
Action.c(353): Notify: Saving Parameter "c_buffer = [\"12345678\",\"22222222".
Action.c(352): Notify: Parameter Substitution: parameter "pAcctNum_count" = "3"
Action.c(353): Notify: Parameter Substitution: parameter "c_buffer" = "[\"12345678\",\"22222222"
Action.c(353): Notify: Parameter Substitution: parameter "pAcctNum_3" = "33333333"
Action.c(353): Notify: Saving Parameter "c_buffer = [\"12345678\",\"22222222\",\"33333333".
Action.c(352): Notify: Parameter Substitution: parameter "pAcctNum_count" = "3"
Action.c(358): Notify: Parameter Substitution: parameter "c_buffer" = "[\"12345678\",\"22222222\",\"33333333"
Action.c(358): Notify: Saving Parameter "c_finalvalue = [\"12345678\",\"22222222\",\"33333333\"]".
Action.c(360): Notify: Parameter Substitution: parameter "c_finalvalue" = "[\"12345678\",\"22222222\",\"33333333\"]"
Action.c(360): The new file name is [\"12345678\",\"22222222\",\"33333333\"]
Action.c(362): web_custom_request("Holdings") started [MsgId: MMSG-26355]
Action.c(362): Notify: Parameter Substitution: parameter "c_finalvalue" = "[\"12345678\",\"22222222\",\"33333333\"]"

So this looks correct. We think we are sending this to the web server

[\"12345678\",\"22222222\",\"33333333\"]

However, we received a rejection of the page (400 BAD REQUEST) from the web server when we tried to play back the script using the parameter we created in the BODY:

web_custom_request("Holdings",
"URL=https://urlthatreallydoesntmatter.atall.ok",
"Method=POST",
"TargetFrame=",
"Resource=0",
"RecContentType=application/json", "Referer=https://urlthatreallydoesntmatter.atall.ok",
"Snapshot=t28.inf",
"Mode=HTML",
"EncType=application/json;charset=utf-8",
"Body={\"cache\":true,\"intraday\":true,\"base\":{\"entities\":[{\"entity\":\"99999\",\"faNumber\":\"8888\"}],\"s\":{c_finalvalue}\"],\"session\":\"999999999999\",\"id\":888888888888,\"it\":0,\"iv\":\"\"}}",
LAST);

Why? In order to see the problem, you need to have ADVANCED TRACE logging enabled in the run-time settings. I usually make a habit of leaving this unchecked so at first I wasn’t able to see what I was sending. So, I enabled it:

Without enabling the option for Advanced trace, you will not see the actual string submitted. You will just see the rejection of the request by the web server (a 4XX range HTTP status code). You would not know there are extra characters seen by the web server – the extra back slashes. That is why it was refused.  Here is the log with the Advance Trace turned on:

Action.c(362): t=87398ms: 196-byte request body for "https://urlthatreallydoesntmatter.atall.ok" (RelFrameId=1, Internal ID=93)
Action.c(362): {"cache":true,"intraday":true,"base":{"entities":[{"entity":"9999","faNumber":"8888"}],"s
Action.c(362): ":[\\"12345678\\",\\"22222222\\",\\"33333333\\"],"session":"999999999999","id":888888888888
Action.c(362): 99999,"it":0,"iv":""}}

You can see we are actually sending TWO back slashes, not one:

[\\"12345678\\",\\"22222222\\",\\"33333333\\"]

We’re “overcompensating” for the backslashes. Vugen is able to handle the escape characters in the BODY automatically and we don’t need to make them literal. Let’s modify our code to take the backslashes out and see what we get:

//put the first element in the array
lr_param_sprintf("c_buffer","[\"%s",lr_paramarr_idx("pAcctNum",1));
//loop through the rest of the array items
for (i=2;i<=atoi(lr_eval_string("{pAcctNum_count}"));i++) {
lr_param_sprintf("c_buffer", "%s\",\"%s", lr_eval_string("
{c_buffer}"), lr_paramarr_idx("pAcctNum", i));
}
//Final Value we want to end up with
lr_save_string(lr_eval_string("{c_buffer}\"]"), "c_finalvalue");
lr_output_message("The new file name is %s", lr_eval_string("{c_finalvalue}"));

Now the log shows:

Action.c(362): t=49375ms: 188-byte request body for "https://urlthatreallydoesntmatter.atall.ok" (RelFrameId=1, Internal ID=93)
Action.c(362): {"cache":true,"intraday":true,"base":{"entities":[{"entity":"99999","faNumber":"9999"}],"s
Action.c(362): ":["12345678","22222222","33333333"],"session":"999999999999","id":888888888888,"it":0,"
Action.c(362): iv":""}}

So now we are actually sending:

["12345678","22222222","33333333"]

Now the script plays back successfully. This indicates that when a BODY argument in a web_custom_request has a bunch of escape characters, we should not have to worry about sending them literally when we create a parameter.

LESSON LEARNED: To see everything being sent to the web server as it was sent, ensure Advanced Tracing is on in the Log Run-Time Settings in VuGen.

As always, your feedback is welcome.

About the Author

Scott Moore

Scott Moore specializes in application performance engineering and testing. This includes education, hands-on implementation, and application performance monitoring.

1 Comment

  1. Dalip

    Strtok would an efficient to achieve this