Async. Communications: The WebSocket protocol made easy in LoadRunner 12.00

(This post was written by Peng-Ji Yin (Jerry), with contributions from Avishai Moshka, both from the LoadRunner R&D Team)

 

HTML5 introduced several features for web developers. One of the major features is WebSocket, which is a full-duplex asynchronous communication channel over a single TCP connection. The channel can be used for continuous communication between the client and the server. See my colleague Wilson Mar's post for a brief introduction to the concept.

 

Before LoadRunner 12.00, it was almost impossible to record/replay WebSocket traffic. I say ’almost’, because some of our customers did manage to use the Windows Sockets protocol to handle WebSocket connections. But it took them enormous effort, maybe several months of work, to prepare a workable script. The good news is that LoadRunner 12.00 makes their life much easier! This post will take a look at the new support for WebsSockets, which is actually an enhancement to the Web-HTTP/HTML protocol for supporting HTML5 features, rather than a new protocol.

 

Recording

Recording doesn’t require any special treatment. Just record a Web-HTTP/HTML script as usual, and if there are WebSocket connections, they will be recorded. Here’s a screenshot of a project and script after it’s been recorded: 

p1.png

 

Notice that two new files, “WebSocketBuffer.h” and “WebSocketCB.c”, have been added to the project, and there are some new API functions whose names start with “web_websocket”.

 

All buffers received by the client will be stored in WebSocketBuffer.h.  Buffers larger than 1024 characters sent by the client are stored in WebSocketBuffer.h, while smaller buffers are contained in the web_websocket_send ‘Buffer’ argument: 

p2.png

 

The callback functions -  OnOpen, OnMessage, OnClose and OnError -will be generated automatically in WebSocketCB.c, along with the commented out sample code:

p3.jpg

 

API

Before replay, let’s look at the three new APIs that were introduced for WebSockets.

 

Connection API

web_websocket_connect("ID=0", "URI=ws://your_server:port/",
                          "Origin=http://your_server",
                          "OnOpenCB=OnOpenCB0",
                          "OnMessageCB=OnMessageCB0",
                          "OnErrorCB=OnErrorCB0",
                          "OnCloseCB=OnCloseCB0", LAST);

The important arguments are ID, URI and the four callbacks. You can find the meaning of other arguments from the online help.

  • ID: A unique number indicating the connection index. This value is generated automatically during the code generation.
  • URI: The endpoint WebSocket with which to connect.  Note that bothws (WebSocket) and wss (WebSocket Secure) are supported.
  • OnXxxCB: The callbacks will be registered for the connection. Different WebSocket connections may share the same callbacks. For example, if you open a new connection whose ID is “1”, you can re-use the OnOpenCB0 for this connection, even though it may have already been registered for the connection “0”. 

 

In simple cases where there is no client logic, the callbacks can be used for logging or for setting user datapoints. If the application contains a lot of client logic, the callbacks are the place where you implement that logic in the script. For example, if the client needs to give feedback when it gets a message from the server, it can be done in the OnMessage callbacks. APIs such as web_url and web_websocket_send will work in the callbacks.

 

Here is a sample OnMessage callback. Since the application uses WebSocket.IO, which uses its own heartbeat messages, the callback will send back a heartbeat when it receives one from the server. Besides the heartbeat, the function also gets the required information and saves it to a parameter:

  

void OnMessageCB0 (
const char* connectionID,
		int isbinary,
		const char * data,
		int length)
{
	int isactivesession;
	int isheartbeat;
	int isupdate;

	lr_output_message("WebSocket ID = %s got message=%s length = %d, ", connectionID, data, length);

	isactivesession = strncmp(data,"5:::{\"name\":\"message\"",21);
	isupdate = strncmp(data,"5:::{\"name\":\"update\"",20);
	isheartbeat = strncmp(data,"2::",3);

	if(isheartbeat==0) {
		lr_user_data_point("Heartbeat message",1);
		web_websocket_send("ID=0","Buffer=2::","IsBinary=false",LAST);
		lr_output_message("WebSocket ID = %s heartbeat message sent back", connectionID);
	}

	if(isactivesession==0) {
		lr_save_param_regexp (
			data,
			length,
			"RegExp=5:::{\"name\":\"message\",\"args\":.(.+).}",        		
			"ResultParam=myActiveSessions",
			LAST );

lr_user_data_point_ex("Active Sessions", atoi(lr_eval_string("{myActiveSessions}"))-1, DP_FLAGS_EXTENDED_LOG);
		lr_output_message(">>>> got visits counter!!!");
	}

	if(isupdate==0) {
		lr_save_param_regexp (
			data,
			length,
			"RegExp=LoadRunner\\\\\",\\\\\"Price\\\\\":([0-9]*\.?[0-9]+)},{",
			"ResultParam=myLRPrice",
			LAST );

		lr_user_data_point_ex("LoadRunner price", atof(lr_eval_string("{myLRPrice}")), DP_FLAGS_EXTENDED_LOG);
		lr_output_message(">>>> got price update!!!");
	}

	if (counter++ > 10) lr_save_string("OK","ready");

}

 

Send API

 

web_websocket_send("ID=0", "Buffer=abcde", "IsBinary=0", LAST);
web_websocket_send("ID=0", "Buffer=abcde", "IsBinary=1", LAST) ;
web_websocket_send("ID=0", "Buffer/Bin=\x61\x62\x63\x64\x65", "IsBinary=0", LAST)

 

The arguments are as follows:

  • Buffer: The text that will be sent.
  • Buffer/Bin: Use this to send a message in binary mode.  The buffer may contain zeros.
  • IsBinary: Indicates whether the data will be sent in text or binary mode. In non-binary mode, ie. when IsBinary is set to 0, the message will be truncated at the first zero. In binary mode, all content in the buffer will be sent.

If you need to send long messages or big buffers/files, it’s better to store it in a parameter, which makes the script code cleaner and more maintainable.  For example, this code will save a big string to a parameter WebSocketSend0 which will be sent later:

char* BigString = “….”;
….
lr_save_string(BigString, "WebSocketSend0");
web_websocket_send("ID=0", "Buffer={WebSocketSend0}", "IsBinary=0", LAST); 

 

Close API

web_websocket_close("ID=0", "Code=1000", "Reason=Any Reason...", LAST);

 

The Code and Reason arguments can be omitted for this API. If the Code argument is supplied, the value must be an integer between 1000 and 1011, with the default value being 1000 [RFC6455 Section-7.4]. If Reason is not specified, the default value will be empty.

 

Replay

After adding the necessary client-side logic to the script, just click the replay button or press F5 to replay the script. Don’t forget to turn on the extended log in the Run-Time Settings dialog when debugging the script. Since there are no snapshots for the WebSocket steps, logs are vital. All of the handshake information, as well as sent/received messages, will be shown in the extended log.

 

During replay, some WebSocket-related datapoints will be generated. The WebSocket Statistics graph in the Controller shows the number of new or closed connections, the number of bytes sent, and the number of bytes received:

 

p4.png

 

 

Tips

Here are some tips for creating a script.

  • Add think time in the script to allow callbacks to be invoked
  • You can assign the same callback to different connections
  • Callbacks are allowed to invoke Web actions
  • Use WireShark with the “WebSocket” filter to verify or monitor WebSocket traffic
  • For large binary buffers (file uploads), load the file to a parameter

Troubleshooting

Is there an option to run the WebSocket script synchronously, so that it waits for the previous step to be completed (including reply from the server) before performing the next one?

The web_websocket_connect, web_websocket_send and web_websocket_close are all synchronized APIs, and will be executed one-by-one. While the WebSocket can receive the data from the server at any time, this is done asynchronously by design.

You can also use the web_sync API.

 

I don’t see WebSocket steps in my script!

  1. Don’t panic!
  2. Verify with WireShark that the AUT actually has WebSocket traffic. When recording in a browser, you can  verify it using the developer tools (by pressing F12 for IE, Chrome and Firefox).
  3. Verify that the full handshake appears in the code generation log
    1. Search for the 101 response from the server and verify that Upgrade: WebSocket and Connection: Upgrade headers are valid
    2. Validate that the handshake headers are compliant with RFC6455
  4. Verify in the recorded events log that WebSocket events exist by searching for websocket_snd and websocket_rcv events
  5. Still nothing? Check that the WebSocket Analyzer is loaded during recording, by looking for the following information in the recording log:

[Network Analyzer ( 6b8: 508)]     Analyzer Module: QTWeb (value=GetWebSocketProtocolAnalyzer:WebSocketAnalyzer.dll)

[Network Analyzer ( 6b8: 508)]     + Network Analyzer: WebSocketAnalyzer.dll @ GetWebSocketProtocolAnalyzer Loaded!

 

 

Leave a comment in the comment box below to let us know if you found this article useful.

 

To download HP LoadRunner, click here

 

Thanks to Jerry and Avishai for providing this article!

Labels: LoadRunner
Comments
boba kim(anon) | ‎06-24-2014 10:31 PM

is websocket support the mobile protocols as well?

Leave a Comment

We encourage you to share your comments on this post. Comments are moderated and will be reviewed
and posted as promptly as possible during regular business hours

To ensure your comment is published, be sure to follow the Community Guidelines.

Be sure to enter a unique name. You can't reuse a name that's already in use.
Be sure to enter a unique email address. You can't reuse an email address that's already in use.
Type the characters you see in the picture above.Type the words you hear.
Search
About the Author
Malcolm is a functional architect, focusing on best practices and methodologies across the software development lifecycle.


Follow Us
The opinions expressed above are the personal opinions of the authors, not of HP. By using this site, you accept the Terms of Use and Rules of Participation