Creating a WebSurge Plugin - RickStrahl/WestWindWebSurge GitHub Wiki
You can hook into the request processing pipeline of WebSurge using a .NET plugin created specifically for WebSurge. Plugins allow you to examine and modify every request before and after the URL has been processed, so you can manipulate requests based on complex business rules or logic, and post process requests for logging or other actions you might need to perform on requests.
To create a plugin is very easy:
- Create a .NET Class Library Project
- Add a Reference to WebSurge.Core
- Implement the
IWebSurgeExtensibility
interface - Compile the plug-in into a DLL
- Copy the DLL into the
Plugins
folder below the WebSurge folder - You can implement multiple plugins in the same DLL
This interface is very simple and has only four event hook methods that fire before and after a request starts and completes and when a load test startes and completes.
Here's the implementation:
/// <summary>
/// WebSurge extensibility class that can be used to intercept each request before it
/// is sent to the server. Allows for modification of the request data and custom
/// logging or update oprations.
///
/// Please note that these events are asynchronous and multi-threaded so take care
/// that you write thread safe code. You should also keep these methods as quick as
/// possible as they are fired on each request and can result in decreased throughput
/// as requests are fired.
/// </summary>
public interface IWebSurgeExtensibility
{
/// <summary>
/// Pre-request processing of the request that is
/// fired against the server.
/// </summary>
/// <remarks>
/// These events are fired asynchronously and have to be threadsafe. The data
/// instance passed is thread-safe, but any other resources you use may not be.
/// For example, if you write output to a file make sure you put a Sync lock on
/// the file to avoid simultaneous access by multiple threads.
/// </remarks>
/// <param name="data"></param>
/// <returns>return true to process the request, false to abort processing and skip this request</returns>
bool OnBeforeRequestSent(HttpRequestData data);
/// <summary>
/// Post-request processing of the request that has
/// been fired agains the server. The passed request data
/// contains the full completed request data.
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
void OnAfterRequestSent(HttpRequestData data);
/// <summary>
/// This method is fired when a test run is started. You are passed a list
/// of the requests that are to be fired. These are raw requests as entered
/// in the file without any rule processing applied (you can look at transformed
/// requests in OnBeforeRequestSent).
/// </summary>
/// <param name="requests">The list of requests that will be processed</param>
/// <returns>return true to run the test, false to stop processing</returns>
bool OnLoadTestStarted(IList<HttpRequestData> requests);
/// <summary>
/// This method is fired when a test has completed running or was cancelled. You
/// are passed a large list of every request that was fired during the test. Note
/// this list can be potentially VERY large.
/// </summary>
/// <param name="results">List of every request run in the test - can be very large!</param>
/// <param name="timeTakenForTestMs">Milliseconds take for test</param>
void OnLoadTestCompleted(IList<HttpRequestData> results, int timeTakenForTestMs);
}
There's is also a WebSurgeExtensibilityBase
class that implements this interface with empty methods. You can either implement the full IWebSurgeExtensibility
interface, or inherit from the base class and only override the method(s) you are interested in.
To implement this interface you need to add a reference to WebSurge.Core.dll
which houses this interface and add it to your .NET Class Library project that creates a DLL.
The following is a simple sample implementation that demonstrates how to log each inbound and outbound request to a text log file.
public class SamplePlugIn : IWebSurgeExtensibility
{
public bool OnBeforeRequestSent(HttpRequestData data)
{
logRequests(data, 0);
return true;
}
public void OnAfterRequestSent(HttpRequestData data)
{
logRequests(data, 1);
}
public bool OnLoadTestStarted(IList<HttpRequestData> requests)
{
LogString("Starting test with " + requests.Count + " in Session.");
return true;
}
public void OnLoadTestCompleted(IList<HttpRequestData> results, int timeTakenForTestMs)
{
LogString("Completed test with " + results.Count + " requests processed.");
}
private void logRequests(HttpRequestData data, int mode = 0)
{
string output;
// Check for a non-success response/errors
bool isErorr = data.IsError;
if (mode != 0)
output = data.StatusCode + " " + data.HttpVerb + " " + data.Url + " " + data.TimeTakenMs;
else
output = data.HttpVerb + " " + data.Url;
LogString(output);
}
public static object syncLock = new object();
private void LogString(string message)
{
lock (syncLock)
{
StreamWriter streamWriter = new StreamWriter(Environment.CurrentDirectory + "\\requestlog.txt", true);
streamWriter.WriteLine(DateTime.Now.ToString() + " - " + message);
streamWriter.Close();
}
}
}
If you prefer to inherit from WebSurgeExtensibilityBase
instead, you can implement like this instead:
public class SamplePlugIn : WebSurgeExtensibilityBase
{
public void override OnAfterRequestSent(HttpRequestData data)
{
logRequests(data, 1);
}
private void logRequests(HttpRequestData data, int mode = 0) { ... }
private void LogString(string message) { ... }
A few notes:
OnBeforeRequestSent()
receives a HttpRequest parameter which is what is used to run the current request. This is a copy of a shared request object so this object is thread safe and can be safely modified by your code. The method can return true
to specify that the request should be processed, and false
to specify that it shouldn't be processed. This can be useful if you need to filter out certain requests from processing.
OnAfterRequestSent()
contains the original request data, plus any response data returned from the request. You can check the IsError
property to check for HTTP or operational errors which is triggered by 400 or 500 level HTTP status codes or any hard errors during processing.
Note both of these methods are called asynchronously from multi-threaded code so any code you write inside of these methods has to be thread safe. You can safely access the data object as it's a private copy reserved for the calling thread. But if you access external resources, make sure your code takes into account multiple simultaneous thread accesses. For example when writing to a file make sure you use a sync lock to ensure no two threads try to write to the file simultaneously. The example above demonstrates the sync lock around the log operation.
If you are running high volume tests make sure that the any plug-in you create is fast and efficient. You don't want to have blocking code that will slow down the calling thread while making requests.
One of the most common things you'll want to do in a plug-in is to modify request content most likely and you can do that by overriding the OnBeforeRequestSent
method. For example the following code demonstrates how to add a custom header or modify the content of the Request Content buffer with a dynamic value:
public bool OnBeforeRequestSent(HttpRequestData data)
{
var header = new HttpRequestHeader
{
Name = "x-request-time",
Value = DateTime.UtcNow.ToString("0")
};
data.Headers.Add(header);
if (!string.IsNullOrEmpty(data.RequestContent))
{
// do something here to get your unique value
var userId = new Random().Next().ToString();
// then embed it into the content
data.RequestContent = data.RequestContent.Replace("##UserId##", "USER_" + userId);
}
return true;
}
If you now create any test requests that include ##USERID## in the Content body of the request, that content is replaced when the test runs.
You can use this mechanism to automate ids in your tests, and it allows you a lot of control about the rules you use to determine how requests are served. It is however your responsibility to figure out how to map specific ids to individual requests.