HTTP
To work with HTTP, NBomber provides NBomber.Http plugin that includes:
- API to create, send request, receive response with tracking of data transfer and status codes.
- HttpMetricsPlugin to get real-time metrics about the current Http connections.
To install NBomber.Http package you should execute the following dotnet command:
dotnet add package NBomber.Http
HTTP API
HTTP plugin provides helper methods that works with native HttpClient. These methods help reduce the boilerplate needed to build HttpRequestMessage
, send or receive JSON objects, calculate data transfer, etc.
Basic Example:
using var httpClient = new HttpClient();
var scenario = Scenario.Create("http_scenario", async context =>
{
var request =
Http.CreateRequest("GET", "https://nbomber.com")
.WithHeader("Content-Type", "application/json")
.WithBody(new StringContent("{ some JSON }", Encoding.UTF8, "application/json"));
var response = await Http.Send(httpClient, request);
return response;
});
Advanced Example:
using var httpClient = new HttpClient();
var scenario = Scenario.Create("http_scenario", async context =>
{
var step1 = await Step.Run("step_1", context, async () =>
{
var request =
Http.CreateRequest("GET", "https://nbomber.com")
.WithHeader("Content-Type", "application/json")
.WithBody(new StringContent("{ some JSON }", Encoding.UTF8, "application/json"));
var response = await Http.Send(httpClient, request);
return response;
});
// example of sending JSON as a body
// Http.WithJsonBody<T>() will automatically serialize object to JSON
// the header "Content-Type": "application/json" will be added automatically
var step2 = await Step.Run("step_2", context, async () =>
{
var user = new UserData { UserId = 1, Title = "test user" };
var request =
Http.CreateRequest("POST", "https://nbomber.com")
.WithJsonBody(user);
var response = await Http.Send(httpClient, request);
return response;
});
// example of using Http.Send<TResponse>
// it sends HTTP request
// and deserialize JSON response to specific type
var step3 = await Step.Run("step_3", context, async () =>
{
var request =
Http.CreateRequest("GET", "https://jsonplaceholder.typicode.com/todos/1")
.WithHeader("Content-Type", "application/json");
var response = await Http.Send<UserData>(httpClient, request);
// user: UserData
var user = response.Payload.Value.Data;
var userId = user.UserId;
return response;
});
// example of using CancellationToken for timeout operation
var step4 = await Step.Run("step_4", context, async () =>
{
using var timeout = new CancellationTokenSource();
timeout.CancelAfter(50); // the operation will be canceled after 50 ms
var clientArgs = HttpClientArgs.Create(timeout.Token);
var request =
Http.CreateRequest("GET", "https://jsonplaceholder.typicode.com/todos/1")
.WithHeader("Content-Type", "application/json");
var response = await Http.Send(httpClient, clientArgs, request);
return response;
});
return Response.Ok();
});
CreateRequest
This method should be used to create HTTP request.
public static HttpRequestMessage CreateRequest(string method, string url)
Example:
var scenario = Scenario.Create("http_scenario", async context =>
{
var request = Http.CreateRequest("GET", "https://nbomber.com")
.WithHeader("Content-Type", "text/html");
// .WithHeader("Content-Type", "application/json")
// .WithBody(new StringContent("{ id: 1 }", Encoding.UTF8, "application/json");
// .WithBody(new ByteArrayContent(new [] {1,2,3}))
...
});
Send
This method should be used to send HTTP request.
public static Task<Response<HttpResponseMesage>> Send(HttpClient client, HttpRequestMessage request);
public static Task<Response<HttpResponseMesage>> Send(HttpClient client, HttpClientArgs clientArgs, HttpRequestMessage request);
Example 1:
using var httpClient = new HttpClient();
var scenario = Scenario.Create("http_scenario", async context =>
{
var request = Http.CreateRequest("GET", "https://nbomber.com")
.WithHeader("Content-Type", "text/html");
// .WithHeader("Content-Type", "application/json")
// .WithBody(new StringContent("{ id: 1 }", Encoding.UTF8, "application/json");
// .WithBody(new ByteArrayContent(new [] {1,2,3}))
var response = await Http.Send(httpClient, request);
return response;
});
Example 2: in this example we use HttpClientArgs.
using var httpClient = new HttpClient();
var scenario = Scenario.Create("http_scenario", async context =>
{
var request = Http.CreateRequest("GET", "https://nbomber.com")
.WithHeader("Content-Type", "application/json");
// .WithHeader("Content-Type", "application/json")
// .WithBody(new StringContent("{ id: 1 }", Encoding.UTF8, "application/json");
// .WithBody(new ByteArrayContent(new [] {1,2,3}))
var clientArgs = HttpClientArgs.Create(
CancellationToken.None,
httpCompletion: HttpCompletionOption.ResponseContentRead,
jsonOptions: JsonSerializerOptions.Default
);
var response = await Http.Send(httpClient, clientArgs, request);
return response;
});
You can find the complete example by this link.
JSON support
HTTP plugin provides helper methods that simplify working with JSON format.
Http.WithJsonBody<T>(data)
- Populates request body by serializing data record to JSON format. Also, it adds HTTP header: "Content-Type": "application/json".
using var httpClient = new HttpClient();
Http.GlobalJsonSerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var scenario = Scenario.Create("http_scenario", async context =>
{
var user = new UserData { UserId = 1, Title = "anton" };
var request =
Http.CreateRequest("GET", "https://nbomber.com")
.WithJsonBody(user);
var response = await Http.Send(httpClient, request);
return response;
});
You can find the complete example by this link.
Http.Send<TResponse>
- Send request and deserialize HTTP response body to JSON format.
using var httpClient = new HttpClient();
Http.GlobalJsonSerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var scenario = Scenario.Create("http_scenario", async context =>
{
var request =
Http.CreateRequest("GET", "https://jsonplaceholder.typicode.com/todos/1")
.WithHeader("Content-Type", "application/json");
var response = await Http.Send<UserData>(httpClient, request);
var userData = response.Payload.Value.Data;
var title = userData.Title;
var userId = userData.UserId;
return response;
});
You can find the complete example by this link.
For JSON serialization you can set global serializer options:
Http.GlobalJsonSerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
If the global serializer options doesn't fit for your use case and you need more granular control, you can pass different serializer options for each request via HttpClientArgs.
var clientArgs = HttpClientArgs.Create(
CancellationToken.None,
httpCompletion: HttpCompletionOption.ResponseContentRead,
jsonOptions: JsonSerializerOptions.Default // you set custom options
);
var response = await Http.Send(httpClient, clientArgs, request);
Timeout operation
var scenario = Scenario.Create("http_scenario", async context =>
{
using var timeout = new CancellationTokenSource();
timeout.CancelAfter(50); // the operation will be canceled after 50 ms
var clientArgs = HttpClientArgs.Create(timeout.Token);
var request =
Http.CreateRequest("GET", "https://jsonplaceholder.typicode.com/todos/1")
.WithHeader("Content-Type", "application/json");
var response = await Http.Send(httpClient, clientArgs, request);
return response;
});
You can find the complete example by this link.
HttpClientArgs
HttpClientArgs represents a structure that can configure HTTP clients per request. You can use it to set request timeout, or JsonSerializerOptions
.
Example:
var scenario = Scenario.Create("http_scenario", async context =>
{
var request =
Http.CreateRequest("GET", "https://nbomber.com")
.WithHeader("Content-Type", "application/json");
.WithBody(new StringContent("{ some JSON }", Encoding.UTF8, "application/json"));
var clientArgs = HttpClientArgs.Create(
CancellationToken.None,
httpCompletion: HttpCompletionOption.ResponseHeadersRead, // or ResponseContentRead
jsonOptions: new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}
);
var response = await Http.Send(httpClient, clientArgs, request);
return response;
});
You can find the complete example by this link.
Tracing HTTP requests
The HTTP plugin supports tracing requests and corresponding responses. To do this, you need to pass Logger
into HttpClientArgs
.
using var httpClient = new HttpClient();
var scenario = Scenario.Create("http_scenario", async context =>
{
var request =
Http.CreateRequest("GET", "https://jsonplaceholder.typicode.com/todos/1")
.WithHeader("Content-Type", "application/json");
var clientArgs = HttpClientArgs.Create(logger: context.Logger);
var response = await Http.Send(httpClient, clientArgs, request);
return response;
})
After running this example, we will have a log file populated with HTTP tracing. Each trace message contains correspondent TraceId
.
2024-04-06 10:56:11.090 +03:00 [DBG] [ThreadId:7] HTTP Request:
TraceId: 30774c79337a4d3e9c6fa897bdb87ad1
Method: GET
RequestUri: "https://jsonplaceholder.typicode.com/todos/1"
HttpVersion: 1.1
Headers: Content-Type: application/json
Content:
2024-04-06 10:56:11.994 +03:00 [DBG] [ThreadId:9] HTTP Response:
TraceId: 30774c79337a4d3e9c6fa897bdb87ad1
HttpVersion: 1.1
StatusCode: "OK"
ReasonPhrase: OK
Headers: Date: Sat, 06 Apr 2024 07:56:10 GMT, Connection: keep-alive, Report-To: {"group":"heroku-nel","max_age":3600,"endpoints":[{"url":"https://nel.heroku.com/reports?ts=1712102103&sid=e11707d5-02a7-43ef-b45e-2cf4d2036f7d&s=NEDTWuReF%2Ftx6jFV3Ve%2FTGNlbenGGlra6iQD4Wu%2BL1k%3D"}]}, Reporting-Endpoints: heroku-nel=https://nel.heroku.com/reports?ts=1712102103&sid=e11707d5-02a7-43ef-b45e-2cf4d2036f7d&s=NEDTWuReF%2Ftx6jFV3Ve%2FTGNlbenGGlra6iQD4Wu%2BL1k%3D, Nel: {"report_to":"heroku-nel","max_age":3600,"success_fraction":0.005,"failure_fraction":0.05,"response_headers":["Via"]}, X-Powered-By: Express, X-Ratelimit-Limit: 1000, X-Ratelimit-Remaining: 999, X-Ratelimit-Reset: 1712102115, Vary: Origin, Accept-Encoding, Access-Control-Allow-Credentials: true, Cache-Control: max-age=43200, Pragma: no-cache, X-Content-Type-Options: nosniff, ETag: W/"53-hfEnumeNh6YirfjyjaujcOPPT+s", Via: 1.1 vegur, CF-Cache-Status: HIT, Age: 23727, Accept-Ranges: bytes, Server: cloudflare, CF-RAY: 8700384848945b45-VIE, Alt-Svc: h3=":443"
Content: {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
HttpMetricsPlugin
HttpMetricsPlugin - provides a monitoring layer for HTTP connections.
Example:
NBomberRunner
.RegisterScenarios(scenario)
.WithWorkerPlugins(new HttpMetricsPlugin(new [] { HttpVersion.Version1 }))
// .WithWorkerPlugins(new HttpMetricsPlugin(new [] { HttpVersion.Version1, HttpVersion.Version2 }))
.Run();
After running with HttpMetricsPlugin, you will get real-time HTTP connections metrics for the Console output:
Also, after running with HttpMetricsPlugin, you will get HTTP connections history metrics for the HTML report:
Connections limit
You may need to limit the sockets connections count.
var socketsHandler = new SocketsHttpHandler
{
MaxConnectionsPerServer = 3
};
using var httpClient = new HttpClient(socketsHandler);
Best practices
Blog posts
Load simulation
HTTP services should be considered as Open system. Open systems - it's where you control the arrival rate of users. For Open systems NBomber provides the following load simulations: Inject, RampingInject and InjectRandom.
HttpClient
HttpClient should be used carefully since the wrong use of it can cause socket exhaustion problems
. You can read more about this problem in this article: You are using HttpClient wrong. The basic recommendations are:
- Use a singleton HttpClient (shared instance) per Scenario.
- Do not create many HttpClient instances. Instead just reuse a single instance per Scenario.
- Disposing HttpClient is not a cheap operation. It can cause
socket exhaustion problems
.
// this usage is WRONG
// since HttpClient will be created and disposed for each Scenario iteration
var scenario = Scenario.Create("my scenario", async context =>
{
using var httpClient = new HttpClient();
var request = Http.CreateRequest("GET", "https://nbomber.com")
var response = await Http.Send(httpClient, request);
...
});
// this usage is OK
// since HttpClient will be created once and then reused for each Scenario iteration
using var httpClient = new HttpClient();
var scenario = Scenario.Create("my scenario", async context =>
{
var request = Http.CreateRequest("GET", "https://nbomber.com")
var response = await Http.Send(httpClient, request);
...
});