Skip to main content

HttpClient

To work with HTTP, NBomber provides NBomber.Http plugin for the native .NET HttpClient. This plugin offers useful extensions that simplify creating and sending requests, receiving responses, and tracking data transfer and status codes.

info

You can find the source code here.

To install NBomber.Http package you should execute the following dotnet command:

build NuGet

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:

var httpClient = Http.CreateDefaultClient();

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:

var httpClient = Http.CreateDefaultClient();

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();
});

CreateDefaultClient

This method creates a default preconfigured instance of HttpClient.

HttpClient CreateDefaultClient(int maxConnectionsPerServer = 5000)
info

We highly recommend using CreateDefaultClient instead of using the default constructor new HttpClient(). This is because CreateDefaultClient applies important preconfigured settings to the underlying SocketsHttpHandler, including:

  • MaxConnectionsPerServer: 5000
  • Timeout: 1 minute
  • PooledConnectionLifetime: 10 minutes
  • PooledConnectionIdleTimeout: 5 minutes

These settings enhance connection management and ensure more efficient use of network resources.

Example:

var httpClient = Http.CreateDefaultClient();

CreateRequest

This method should be used to create HTTP request.

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.

static Task<Response<HttpResponseMesage>> Send(HttpClient client, HttpRequestMessage request);

static Task<Response<HttpResponseMesage>> Send(HttpClient client, HttpClientArgs clientArgs, HttpRequestMessage request);

Example 1:

var httpClient = Http.CreateDefaultClient();

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.

var httpClient = Http.CreateDefaultClient();

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".
var httpClient = Http.CreateDefaultClient();

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.
var httpClient = Http.CreateDefaultClient();

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.

info

Configuring JSON serialization. You can set global serializer options for JSON serialization as follow:

// By default GlobalJsonSerializer is configured with JsonSerializerOptions.Web
Http.GlobalJsonSerializerOptions = JsonSerializerOptions.Web;

// You may also customize the configuration to suit your specific needs
Http.GlobalJsonSerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

If the default GlobalJsonSerializerOptions 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.Web // you set custom options
);

var response = await Http.Send(httpClient, clientArgs, request);

Timeout

If you create an HttpClient using Http.CreateDefaultClient(), the default timeout is 1 minute. To change the default timeout, you can set it manually:

var httpClient = Http.CreateDefaultClient();
httpClient.Timeout = TimeSpan.FromMinutes(5);

Alternatively, if you want more granular control over timeouts, you can use CancellationTokenSource. Just make sure that your custom timeout is not longer than the default HttpClient.Timeout; you may need to adjust the default timeout accordingly.

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.

var httpClient = Http.CreateDefaultClient();

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.

nbomber-log-20240406.txt
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
}

You can find the complete example by this link.

Connections limit

You may need to restrict the maximum number of socket connections.

var httpClient = Http.CreateDefaultClient(maxConnectionsPerServer: 3);