Protocol Implementation > Implementing Test Run > Code the API Functions > 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
CApiFunctionWithNamedArguments
. See CApiFunctionWithNamedArguments Class Reference. If the function uses positional arguments, the class type is CApiFunction
. See CApiFunction Class Reference.ApiFunctionWithNamedArgs.h
. If the protocol functions use positional arguments, the header file includes ApiFunctions.h
. If there are both functions with named arguments and positional arguments in the protocol, include ApiFunctionWithNamedArgs.h
.To use text message resources, the header must include <protocol ID>_res.h
.
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.
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 |
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; }