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.

Creating a .NET Plugin

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

The IWebSurgeExtensibility Interface

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.

Implementing the IWebSurgeExtensibility Interface

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.

Make it Thread Safe!

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.

Make it Fast!

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.

Modifying Request Content

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.

⚠️ **GitHub.com Fallback** ⚠️