Advanced Load Testing with Gatling

Gatling is an incredibly useful tool for stress testing web applications based on real-world user interaction and automatically outputs beautiful dynamic HTML reports to allow you to analyze the results of the tests.  To start testing with Gatling download the latest version from the Gatling project page and unzip the contents. In the ‘bin’ directory of the extracted folder are two sets of executables: gatling and recorder.  The Recorder application stands of a proxy server which will record all user interaction with the target site, and the Gatling application is used to replay the recorded session.  If you’re not already familiar with recording and replaying scenarios the Gatling Quickstart guide is an excellent resource to get you up to speed.

For most websites the basic ‘Record’ and ‘Replay’ functionality works well, but if you need to POST data to a site with CSRF protection you’ll need to update the auto-generated code to account for it.  I was recently tasked with load testing a customer application prior to release to the public with just this type of protection.  The site was built using Java Server Faces and each POST request is protected by a javax.faces.ViewState parameter which acts as a CSRF token and is updated every page load.  If the correct viewstate is not submitted the POST will fail.

Handling the ViewState

To get around this issue three new functions are required to capture the current viewstate from the previous page request and submit it on subsequent requests:

val jsfViewStateCheck = regex("""id="j_id1:javax.faces.ViewState:1" value="([^"]*)"""")
    .saveAs("viewState")
def jsfGet(name: String, url: String) = http(name).get(url)
    .check(jsfViewStateCheck)
def jsfPost(name: String, url: String) = http(name).post(url)
    .formParam("javax.faces.ViewState", "${viewState}")
    .check(jsfViewStateCheck)

The first item is a check function which does a regex match on the page looking for the ViewState (Note: If there is no viewstate present the check will report as an Error and show up as a KO in the report).

The second function is an override of the Gatling GET method which checks the response data using the check method and sets the “${viewState}” variable.

The third function is an override of the Gatling POST method and is similar to the GET method in that it will set the “${viewState}” variable, but also adds the previous “${viewState}” as a form parameter.

To implement these functions in your load test you will need to alter all of the recorded GET and POST requests to use the new functions as shown below:

GET

The GET request code from the recorded session

exec(http("request_identifier")
    .get("http://jsfsite.com/page"))

would be updated to

exec(jsfGet("request_identifier","http://jsfsite.com/page"))

POST

The POST request code from the recorded session

exec(http("request_identifier")
    .post("http://jsfsite.com/page"))

would be updated to

exec(jsfPost("request_identifier","http://jfssite.com/page")
    .headers(headers_0)
    .formParam("form_field", "payload"))

The jsfPost method will automatically append the viewstate form parameter to the request and the request will succeed.

Handling Uploads

File upload requests need to be handled a little bit differently.  HTML file upload forms set the enctype=”multipart/form-data” attribute which changes how browsers submit the data to the web server.  Instead of sending the data as the POST payload the file contents an form field data are sent delimited by a random numerical string called a boundary (Here’s the RFC for those curious).  In order to send the file to the web server we need to construct this type of request.  To do so requires different methods than the formParam method we used previously as shown below.

exec(http("upload_request")
    .post("http://jsfsite.com/upload")
    .headers(headers_0)
    .bodyPart(StringBodyPart("form_field_1","payload"))
    .bodyPart(StringBodyPart("form_field_2","payload"))
    .bodyPart(StringBodyPart("javax.faces.ViewState","${viewState}"))
    .bodyPart(RawFileBodyPart("file_upload_file","some_file.txt"))
 )
.check(jsfViewStateCheck)

Notice that instead of using the formParam method to set the fields we use the bodyPart method instead.  This tells Gatling to submit the payload as a multipart form request instead of a standard POST. To set the form fields new BodyParts must be created depending on the type of data.  String fields use the StringBodyPart and files use the RawRileBodyPart.  The file that you wish to upload should be located in the $GATLING_HOME\user-files\bodies\ directory.

A final call to the jsfViewStateCheck function is used to update the “${viewState}” variable once the request completes.