HP LoadRunner Protocol SDK
Create a Class for each API Function

For each API function, implement a public class. To create the class in Visual Studio, right-click ReplayApi and select Add > Class.

Name the class with the same name as the function. The class must have a public constructor and destructor. Add other methods and data elements as required by each function.

The implementation and declartion are different depending on whether functions use  named arguments or positional arguments. See Argument Lists

To use text message resources, the header must include  <protocol ID>_res.h.

Required Class Methods

Each API Function class must have an implementation of methods: Init(), Invoke(), Terminate(), and Abort(). The implementation can be empty if no action is required.

See api_functions.h Reference

The total run time of these methods constitutes the duration of the step and any transactions that include the step. The transaction times should reflect only the process time of the application being tested. To ensure accurate transaction time, remove the processing time of the methods on the load generator from the transaction time, unless this local process time is negligible. Track the process time and call CApiFunction::WastedTime to remove it from transaction. See WastedTime in the HP Protocol SDK Test Run API Reference

Method Functionality
Init Set up for invocation by allocating resources as required, performing any required initialization, and so on, as required by the test step.
Invoke Implement the functionality of the API function.
Terminate Perform any clean-up or reporting required when the Invoke method has run to completion.
Abort Perform any clean-up or reporting required when the Invoke() method has terminated before completion because of a fatal error. The exception that causes termination can be thrown by the protocol's code or by LoadRunner

Example of declaration and implementation

For an example of implementation with positional arguments, see API Functions with Positional Arguments.

Example of implementation with named arguments:

#pragma once
#include "ApiFunctionWithNamedArgs.h"
#include "HandleDeleters.h"
#include <string>
#include "TWEB_res.h" // Enable use of text messages
/// HTTP GET method
class GetMethod : public CApiFunctionWithNamedArguments
{
public:
    /// initialize with requested URL
    GetMethod(const char* url);
    virtual ~GetMethod();
    
     virtual void Init();
     virtual void Invoke();
     virtual void Terminate();
     virtual void Abort();
private:
    /// URL of the request
    const char* m_url;
    // parsed URL elements
    /// host name e.g. www.xyz.com
    std::string m_hostName;
    /// host port number
    int m_port;
    /// URL path e.g. /abc/def
    std::string m_urlPath;
    /// URL parameters e.g. ?d=p&cx=555
    std::string m_urlParameters;
    /// user name for authorized access
    std::string m_userName;
    /// user password
    std::string m_password;
   
    /// split URL into components
    void CrackUrl();
    /// create session object
    UniqueHandle CreateSession();
    /// establish connection to server
    UniqueHandle EstablishConnection(UniqueHandle &session);
    /// create HTTP request
    UniqueHandle CreateRequest(UniqueHandle &connection);
    /// receive HTTP response
    std::string ReceiveResponse(UniqueHandle& request);
    /// emulate pause for reading web page
    void EmulateUserDelayAfterResponse(int delayAfterResponseSec);
    /// save HTTP body to a file
    void SavePage(const std::string& content);
};

 
#include "GetMethod.h"
#include "RequestHeaders.h"
#include "TWEB_res.h"
#include <ProtocolExtension.h>
#include <ProtocolException.h>
#include <vector>
#include <cstdlib>
#include <fstream>

GetMethod::GetMethod(const char* url) :
    m_url(url)
{
}
GetMethod::~GetMethod()
{
}
void GetMethod::Init()
{
    // abort if the URL is null
    if (!m_url)
    {
        // See CProtocolException class in Test Run API Reference
        throw ExceptionObject(ABORT_NO_MATTER_WHAT, TWEB_NO_URL);
    }
   
    // define optional parameters
    // don't save page by default
    DefineBooleanArgument("SavePage", true, false);
    DefineNumericArgument("DelayAfterResponse", true);
    // use TinyWeb user agent by default
    DefineStringArgument("UserAgent", true, "TinyWeb");
    DefineStringArgument("SaveToParam", true);
    CrackUrl();
}
void GetMethod::Invoke()
{
    using std::string;
    try
    {
        // perform HTTP GET request
        auto session = CreateSession();
        auto connection = EstablishConnection(session);
        auto request = CreateRequest(connection);
        auto result = ReceiveResponse(request);
        // emulate user delay if needed
        int delayAfterResponseSec = static_cast<int>(GetNumericArgumentValue("DelayAfterResponse"));
        if (delayAfterResponseSec != 0)
        {
            EmulateUserDelayAfterResponse(delayAfterResponseSec);
        }
        // save page if enabled
        if (GetBooleanArgumentValue("SavePage"))
        {
            SavePage(result);
        }
        // save to parameter
        string paramName = GetStringArgumentValue("SaveToParam");
        if (!paramName.empty())
        {
            CProtocolExtension::Instance()->SaveStringToParameter(result.c_str(), paramName.c_str());
        }
    }
    catch (const CProtocolException&)
    {
        // rethrow protocol exceptions
        throw;
    }
    catch (const std::exception& ex)
    {
        // handle other exception
        throw ExceptionObject(ABORT_NO_MATTER_WHAT, TWEB_GET_FAILED, ex.what());
    }
}
void GetMethod::Terminate()
{
}
void GetMethod::Abort()
{
}
void GetMethod::CrackUrl()
{
    using std::string;
    // evaluate parameterized URL e.g. http://{subdomain}.aaa.com -> http://br.aaa.com
    string url = CProtocolExtension::Instance()->EvaluateString(m_url);
    URL_COMPONENTS components = { sizeof(URL_COMPONENTS) };
    // mark the components for extraction
    components.dwHostNameLength = 1;
    components.dwUrlPathLength = 1;
    components.dwExtraInfoLength = 1;
    components.dwUserNameLength = 1;
    components.dwPasswordLength = 1;
    if (!::InternetCrackUrl(url.c_str(), 0, 0, &components))
    {
        throw ExceptionObject(ABORT_NO_MATTER_WHAT, TWEB_INVALID_URL, GetLastError());
    }
    // abort if the scheme is not HTTP
    if (components.nScheme != INTERNET_SCHEME_HTTP)
    {
        throw ExceptionObject(ABORT_NO_MATTER_WHAT, TWEB_ONLY_HTTP_SUPPORTED);
    }
    // host name should be specified
    if (components.dwHostNameLength == 0)
    {
        throw ExceptionObject(ABORT_NO_MATTER_WHAT, TWEB_NO_HOSTNAME);
    }
    // save host name and port
    m_hostName = string(components.lpszHostName, components.dwHostNameLength);
    m_port = components.nPort;
    // save URL path
    if (components.dwUrlPathLength != 0)
    {
        m_urlPath = string(components.lpszUrlPath, components.dwUrlPathLength);
    }
    // save URL parameters
    if (components.dwExtraInfoLength != 0)
    {
        m_urlParameters = string(components.lpszExtraInfo, components.dwExtraInfoLength);
    }
    // save user name
    if (components.dwUserNameLength != 0)
    {
        m_userName = string(components.lpszUserName, components.dwUserNameLength);
    }
    // save user password
    if (components.dwPasswordLength != 0)
    {
        m_password = string(components.lpszPassword, components.dwPasswordLength);
    }
}
void GetMethod::EmulateUserDelayAfterResponse(int delayAfterResponseSec)
{
    auto start = CProtocolExtension::Instance()->GetNumberOfMillisecondsFromStartOfRun();
    // sleep for specified number of seconds
    ::Sleep(delayAfterResponseSec * 1000);
    // add the delay to the Think Time counter
    ThinkTime(delayAfterResponseSec);
    // log real delay duration in milliseconds
    int passed = CProtocolExtension::Instance()->GetNumberOfMillisecondsFromStartOfRun() - start;
    CProtocolExtension::Instance()->LogNotifyMessage(EXTENDED_ADVANCED_TRACE_LOG_LEVEL, TWEB_DELAY_DURATION, passed);
}
namespace
{
    // simple page file name generator
    std::string GenerateFileName()
    {
        // create GUID
        GUID guid = {};
        ::CoCreateGuid(&guid);
        // convert GUID to string
        OLECHAR* guidString = nullptr;
        ::StringFromCLSID(guid, &guidString);
        std::wstring name = guidString;
        // free GUID
        ::CoTaskMemFree(guidString);
        // convert to ASCII and add file extenstion
        return std::string(begin(name), end(name)) + ".html";
    }
}

void GetMethod::SavePage(const std::string& content)
{
    using namespace std;
    // remember time of page saving start
    auto start = CProtocolExtension::Instance()->GetNumberOfMillisecondsFromStartOfRun();
    // get the script directory
    string scriptPath = CProtocolExtension::Instance()->GetConfigurationAttribute(SCRIPT_DIRECTORY_ATTRIBUTE);
    auto filePath = scriptPath + "\\" + GenerateFileName();
    // open page file
    ofstream pageFile(filePath.c_str());
    if (!pageFile)
    {
        // abort current method execution but not the script
        throw ExceptionObject(ABORT_BY_CONTINUE_ON_ERROR, TWEB_FILE_OPEN_FAILED, filePath.c_str(), GetLastError());
    }
    // save page content to the file
    pageFile << content;
    pageFile.close();
    // measure time wasted for saving page to disk
    int timeWastedOnSavingPage = CProtocolExtension::Instance()->GetNumberOfMillisecondsFromStartOfRun() - start;
    // add wasted time to the Wasted Time counter
    WastedTime(timeWastedOnSavingPage);
}
std::string GetMethod::ReceiveResponse(UniqueHandle &request)
{
    // allocate buffer
    std::vector<char> buffer(1025);
    DWORD bytesRead = 0;
    std::string result;
    while (::InternetReadFile(request.get(), &buffer[0], sizeof(buffer) - 1, &bytesRead) && bytesRead)
    {
        buffer[bytesRead] = 0;
        // concatenate buffer to the resulting string
        result += buffer.data();
        bytesRead = 0;
    }
    // output non-localized text
    CProtocolExtension::Instance()->LogTextMessage(EXTENDED_DATA_RETURNED_BY_SERVER_LOG_LEVEL, "Response:\n\n");
    // output possibly binary buffer (non-printable characters are escaped)
    CProtocolExtension::Instance()->LogBuffer(EXTENDED_DATA_RETURNED_BY_SERVER_LOG_LEVEL, result.c_str(), result.length());
    // create data point "Bytes Received" fro the Analysis
    CProtocolExtension::Instance()->CreateDataPoint("BytesReceived", result.length());
    // set the message on the Vugen Result View
    SetResultMessage(TWEB_GET_URL, m_url);
    return result;
}
UniqueHandle GetMethod::CreateRequest(UniqueHandle &connection)
{
    // prepare request object (path + GET parameters)
    std::string object = m_urlPath + m_urlParameters;
    // open HTTP GET request
    UniqueHandle request(HttpOpenRequest(connection.get(), "GET", object.c_str(), 0, 0, 0, INTERNET_FLAG_RELOAD, 0), InternetHandleDeleter());
    if (!request.get())
    {
        throw ExceptionObject(ABORT_BY_CONTINUE_ON_ERROR, TWEB_REQUEST_CREATION_FAILED, GetLastError());
    }
    // set mandatory headers
    std::string headers = "Content-Type: text/html\n";
    // get custom headers from VUser data
    RequestHeaders* customHeaders = reinterpret_cast<RequestHeaders*>(CProtocolExtension::Instance()->GetVirtualUserData());
    // concatenate headers
    headers += customHeaders->GlueTogether();
    // send request to server
    if (!HttpSendRequest(request.get(), headers.c_str(), headers.length(), nullptr, 0))
    {
        throw ExceptionObject(ABORT_BY_CONTINUE_ON_ERROR, TWEB_REQUEST_SENDING_FAILED, GetLastError());
    }
    return request;
}
UniqueHandle GetMethod::CreateSession()
{
    // get user agent optional parameter
    // if not set, the default value is used
    std::string agent = GetStringArgumentValue("UserAgent");
    // open session
    UniqueHandle session(InternetOpen(agent.c_str(), INTERNET_OPEN_TYPE_DIRECT, nullptr, nullptr, 0), InternetHandleDeleter());
    if (!session.get())
    {
        throw ExceptionObject(ABORT_BY_CONTINUE_ON_ERROR, TWEB_OPEN_SESSION_FAILED, GetLastError());
    }
    return session;
}
UniqueHandle GetMethod::EstablishConnection(UniqueHandle &session)
{
    // connect to the web server
    UniqueHandle connection(InternetConnect(
        session.get(),
        m_hostName.c_str(),
        m_port,
        m_userName.length() ? m_userName.c_str() : nullptr,
        m_password.length() ? m_password.c_str() : nullptr,
        INTERNET_SERVICE_HTTP, 0, 0),
        InternetHandleDeleter());
    if (!connection.get())
    {
        throw ExceptionObject(ABORT_BY_CONTINUE_ON_ERROR, TWEB_CONNECTION_FAILED, m_hostName.c_str(), GetLastError());
    }
    return connection;
}

See Also