Tuesday, March 4, 2014

Load testing of a REST API, using HP LoadRunner’s web_custom_request

In recent years, Representational State Transfer (REST) has become a dominant model for software architecture, especially for Web-based applications. Today, most large websites and Web applications on the Internet have REST APIs. Some examples of websites using REST APIs include Twitter, Google, Flickr and so on.

The great thing about a REST API is that it is usually well-defined and therefore allows clear separation between the client logic and the server logic. This property in itself is very beneficial for both functional and load testing of the application server. In the following article we will demonstrate an easy way to test your application’s REST API using HP LoadRunner’sweb_custom_request method.

REST API Basics

Before we dive into load testing, let’s look at the basic terminology of a REST API. A REST API call consists of three major components:
The Uniform Resource Identifier (URI) – This is a string which consists of three parts. The first part contains the host (and optionally, the port) of the API call (e.g. http://www.hp.com). The second part contains the path which identifies the call functionality. This path can include some parameter values although they are distinguishable from the constants only by semantics (e.g. /users/megwhitman/type/ceo, where “megwhitman” and “ceo” are parameters while “users” and “type” are constants). The third part is the QueryString which is a list of key-value pairs (e.g. user= megwhitman & type=ceo). The pairs are separated by the “&” sign and the entire QueryString is separated from the path by the “?” sign. A full URI may look something like this: http://www.shopping.hp.com/en_US/home-office/-/products/Tablets/Tablets?SearchParameter=ElitePad
The verb – The verb can be any word describing the operation you want to perform on the URI. In HTTP, the five most commonly used verbs are: GET, POST, PUT, DELETE, and PATCH. Note that the operation for each verb is determined by the server although there are some common conventions in this area.
The payload (data) – This is the data sent to the server with the request. For example, if we want to save a text file to the server, we can send the content of the text file as the payload.

Test Case

The example application under test is a todoMVC application which manages a ‘todo’ list. This application has a REST API, as follows:
/todos, GET, No Payload – Retrieves all the todos currently in the list
/todos, POST, { isFinished :, todoText: } – Creates a new todo on the server with the text provided in the payload by the parameter.
/todos/, PUT, {id:, todoText: , isFinished:< isFinished>} – Updates the todo with id with the text and finished state < isFinished>

Note that all the data passed to and received from the server is in the JavaScript Object Notation (JSON) format which is gradually becoming the standard format for data transfer.

HP LoadRunner allows us to easily simulate HTTP calls through the web_custom_request function. We want to simulate a business process as follows:

1) Get the existing todos from the server

2) Add two new todos

3) Update the last todo added with a new text

Step 1

We use recording to ‘learn’ this step. In VuGen we create a new Web - HTTP/HTML script and record a browser opening the todoMVC application. When the application starts, it gets all the todos currently stored on the server through the GET API call. We can easily find the correct step in the recorded script by looking at the URL argument. In this case the argument is - "URL=http://localhost:3000/todos" (since the application is running locally on port 3000 on my machine). The relevant step is:

web_custom_request("todos",
"URL=http://localhost:3000/todos",
"Method=GET",
"Resource=1",
"RecContentType=application/json",
"Referer=http://localhost:3000/",
"Snapshot=t66.inf",
LAST);

The “Method” argument is the REST verb (GET in this case), and the “Snapshot” argument links to the correct snapshot file on the disk. The snapshot of this step can be viewed in the Snapshot Viewer:






We can see that the response from the server is a list of all the todos presented by our application in JSON format.

Step 2

Adding a new todo is very simple. We create our own web_custom_request step with the parameters required by thePOST REST API. The created step looks something like this:

web_custom_request("Add Todo",
"URL=http://localhost:3000/todos",
"Method=POST",
"Resource=0",
"EncType=application/json",
"Mode=HTTP",
"Body={\"isFinished\":false,\"todoText\":\"This is VuGen\"}",
LAST);

A few things to notice here:

1) The “Method” (verb) is POST as required by the REST API.

2) We specified the “EncType” parameter to inform the server that the payload is going to be in JSON format.

3) We added our payload to the “Body” parameter in JSON format.

Since writing the JSON in the middle of the script may be a bit cumbersome, we can add it as an extra file to the script. To do this simply right-click the “Extra Files” node in the Solution Explorer and select “Add Files to Script…”, and choose the data.json file we prepared earlier. On a fresh installation of LoadRunner you may need to specify the .json extension as a valid extension for extra files. You can also do this after adding the file to your script. In VuGen, select Tools > Options > Scripting > Script Management and add the .json extension to the Allowed Extensions list. Once the file is added, the project looks something like this:




Now we can add the file as our payload by specifying the BodyFilePath parameter:

web_custom_request("Add Todo from file",
"URL=http://localhost:3000/todos",
"Method=POST",
"Resource=0",
"EncType=application/json",
"Mode=HTTP",
"BodyFilePath=data.json",
LAST);

Step 3

We want to update the last todo with some new values. To do this, we use another web_custom_request with the PUTverb as specified by the REST API. This is very similar to the step that adds a new todo:

web_custom_request("Update Todo",
"URL=http://localhost:3000/todos/3",
"Method=PUT",
"Resource=0",
"EncType=application/json",
"Mode=HTTP",
"Body={\"id\":3, \"isFinished\":false,\"todoText\":\"Updated the name\"}",
LAST);

This command updates the todo with id = 3 with the string “Updated the name”.

As with the previous step, you can manage the JSON text as a file, rather than writing it directly into the script.

Conclusion

We’ve seen how easy it is to create a simple load test with a REST API using HP LoadRunner. The web_custom_requestfunction is versatile enough to cover both simple and complex cases, thus simplifying REST API testing.

How to use JavaScript in your HP LoadRunner scripts

JavaScript is rapidly becoming the driving force behind the Internet. Any website, small or large, has some parts written with JavaScript to enhance the user experience. Browser vendors constantly improve the speed in which JavaScript is parsed and executed in their browsers. This increase in speed is now to the point that it’s become one of the key benchmarks for every browser version. When we performance test a website, a web application, or even a REST service we can be sure that JavaScript plays some role in the business process we are about to test.

HP LoadRunner uses C as the native language for scripting HTTP/HTML scripts, but it allows integration and use of JavaScript code with relative ease. To learn more about this integration, consult Using the LoadRunner JavaScript Engine chapter in the HP LoadRunner User Guide.

Why would we want to use JavaScript in our script?


There are four main reasons:
JavaScript often offers a more intuitive experience which is easier to implement than in C.
The JavaScript regular expression library simplifies the challenge of working with regular expressions.

Numerous JavaScript libraries that assist with any coding task (e.g. string manipulation) are available.

Client-side logic is often implemented in JavaScript. Inserting snippets of the original JavaScript code means you don’t have to translate the JavaScript client logic into C code.

This article explores the following scenario: We are testing a web server application which is used for authentication. Our goal is to receive an authentication token from the server and use the token in all future communication with the server. The authentication process is as follows:

Send an HTTP GET request to the “/challenge” REST API to get a challenge string.
Perform some calculation on that string and generate a unique password using a proprietary encryption algorithm.
Send a GET request to the “/token” REST API passing the password generated in step 2 as a query parameter.
For this scenario we run a simple web server which serves static values (using Node.js). The server code is available as an attachment to this article (web_js_example_server.zip).

Getting the challenge string

Our first goal is to obtain the challenge string from the server. We type the following step into our script to make the required REST API call:

web_custom_request("challenge",
"URL=http://localhost:3000/challenge",
"Method=GET",
"RecContentType=application/json",
LAST);

We want to capture the server response in a parameter. To this end, we add a web_reg_save_param step before our request and save the response body into a parameter named “challenge”:

web_reg_save_param("challenge","LB=","RB=","Search=Body",LAST);

Calculating the password

Now that we have the challenge string we can use some JavaScript manipulations to generate the password.

First we need to enable JavaScript for our script. To do this, open the Run-Time Settings (F4) and go to Internet Protocol > Preferences > Set advanced options. Click on Options…, and in the dialog that opens (scroll all the way down), set Enable running JavaScript code to Yes (as in the screenshot below) and then click OK on both dialogs.



Next we need to create a new JavaScript file that contains our code. Right-click the Extra Files node in the Solution Explorer and select Create new file… Name the new file calcPassword.js and press OK.

We know that the string returned by the server is a JSON object which contains an array with some strings. The last member of that array is a number which tells us the index of the string we want to extract from the array and encrypt using the proprietary algorithm. We add a new JavaScript function that extracts the value from the array.

function getPassword(stringData){
var data = JSON.parse(stringData);
var index = data[data.length - 1];
var value = data[index];
}

The required value is stored in the “value” variable, and now we have to apply the encryption function. Luckily for us, the server code is written in JavaScript since it runs on Node.js and it already has a module called crypto.js which implements the proprietary cryptographic algorithm. Import the crypto.js file into the script by right-clicking the Extra Files node in theSolution Explorer and selecting the Add files to script… option. Select the file crypto.js from our server folder (you would typically ask for this file from the R&D team). Edit the file so that it contains only the function we need (this step is not mandatory and is performed for clarity, we could use the original file):

function encrypt(data) {
return data.toString().split("").reverse().join("");
}

(I don’t recommend using this function as a real life encryption method as it only reverses the input string J)

Now we can update our getPassword function to call the encrypt function:

function getPassword(stringData){
var data = JSON.parse(stringData);
var index = data[data.length - 1];
var value = data[index];
return encrypt(value);
}

Now we just add the web_js_run step to run our JavaScript code:

web_js_run(
"Code=getPassword(LR.getParam('challenge'));",
"ResultParam=password",
SOURCES,
"File=crypto.js", ENDITEM,
"File=calcPassword.js", ENDITEM,
LAST);

The parameters are straightforward but you can always consult the product documentation if you are not sure. In this case we call the getPassword function defined in the JavaScript file. We want to pass it the parameter we got from the REST API call. We use the LR.getParam utility function (implemented in JavaScript) to pass that parameter as string to thegetPassword function. The result of the function is stored in the parameter defined by the ResultParam argument (“password” in our case). Finally we have the source files that contain our JavaScript code added as “File=…” arguments separated by the ENDITEM constant.

Obtaining the token

Our final step is obtaining the token from the server. We could just use the password parameter to create the URI, but that would be a mistake because we need to encode the URI first. We can do it easily using another web_js_run call (since JavaScript natively supports URI encoding). This time the call is quite simple:
web_js_run(
"Code='http:/'+'/localhost:3000/token?password=' + encodeURI(LR.getParam('password'));",
"ResultParam=uri",
LAST);

(Note that we split up the ‘//’ string in the ‘Code’ parameters into two separate sub-strings. If we were to pass it in as a single string “//”, LoadRunner would report error -26312, “Using "//" line comments in argument of type 'code' is illegal/unsupported”)

Now we add the step that makes the final call with the calculated URI to the appropriate 

REST API:
web_custom_request("get token",
"URL={uri}",
"Method=GET",
"RecContentType=application/json"
LAST);

The result from the server is as expected:

Action.c(25): t=1987ms: 38-byte response body for "http://localhost:3000/token?password=damhA" (RelFrameId=1, Internal ID=4)

Action.c(25): "4f6dfbce-412f-4c6c-8bac-7d7d66a6b822"

Conclusion:

Enhancing your HTTP/HTML script with JavaScript code is easy and useful. There are numerous scenarios where this capability can be employed to shorten scripting time and create a much more robust script. The HP LoadRunner User Guide is key to understanding the full potential of this feature as it includes a built in function library that links the JavaScript code to your test script’s code.

Error: Communication error: The Client failed to send packet. The socket has been shut down. [MsgId: MERR-10343]​ in loadrunner

Load Runner Agent Process/service starts at port 50500 and 54345 default port for monitoring/running vuser over firewall, it is port 443.apply netstat -an on the machine and check to see if 54345 and 50500 are occupied or free. If these ports are in used when you start the agent, you will get the above error.

You will need to shut down the application that is using those ports, so that the ports are freed before restarting Load Runner agent.

You may also get this error during replay if Load Runner agent of the host machine is connecting back to the controller using a port that is already bound. By default, this is a dynamic port and Load Runner agent will automatically try a different port.

Make sure the Load Runner Agent service is running on the Generator machine.

Verify the connectivity performing ping operation. Ping the LG from controller using name and IP and visa versa from controller to LG. Make a entry to host files mapping IP and name both in controller and LG .