Skip to main content

HTTP plugin

This document will help you learn about NBomber.HTTP plugin in more detail.

Overview#

The NBomber.Http provides a simple API to define HTTP steps, handle responses in an efficient way (without waiting on full response body but rather first bytes), calculate response size, apply assertions.

Add HTTP package

dotnet add package NBomber.Http

Create HTTP load test

Program.fs
open System
open System.Net.Http
open FSharp.Control.Tasks.V2.ContextInsensitive
open NBomber.Contracts
open NBomber.FSharp
open NBomber.Plugins.Network.Ping
open NBomber.Plugins.Http.FSharp
[<EntryPoint>]
let main argv =
// it's optional Ping plugin that brings additional reporting data
let pingConfig = PingPluginConfig.CreateDefault ["nbomber.com"]
use pingPlugin = new PingPlugin(pingConfig)
let step = HttpStep.create("fetch_html_page", fun context ->
Http.createRequest "GET" "https://nbomber.com"
)
Scenario.create "nbomber_web_site" [step]
|> Scenario.withLoadSimulations [InjectPerSec(rate = 100, during = seconds 30)]
|> NBomberRunner.registerScenario
|> NBomberRunner.withWorkerPlugins [pingPlugin]
|> NBomberRunner.run
|> ignore
0 // return an integer exit code

API#

Http step#

The main thing that this plugin adds is a convenient way to define the NBomber step that creates a Http request and then sends it.

HttpStep.create (name: string,
feed: IFeed<'TFeedItem>,
createRequest: IStepContext<unit,'TFeedItem> -> Task<HttpRequest>,
completionOption: HttpCompletionOption) =

By default NBomber.Http is set completionOption = HttpCompletionOption.ResponseHeadersRead for performance optimizations. You can read about this optimization here.

// creates simple HTTP step
let step =
HttpStep.create("step", fun context ->
Http.createRequest "GET" "https://gitter.im"
|> Http.withHeader "Accept" "text/html"
)
// creates two steps that run sequentially
let step1 =
HttpStep.create("step 1", fun context ->
Http.createRequest "GET" "https://gitter.im"
|> Http.withHeader "Accept" "text/html"
)
let step2 =
HttpStep.create("step 2", fun context -> task {
let step1Response = context.GetPreviousStepResponse<HttpResponseMessage>()
let headers = step1Response.Headers
let! body = step1Response.Content.ReadAsStringAsync()
return Http.createRequest "POST" "asdsad"
|> Http.withHeader "Accept" "text/html"
})

Http headers#

By default NBomber sets no headers.

Http.withHeader "Accept" "application/json"

Http body#

Http.withBody(new StringContent(json))
Http.withBody(new ByteArrayContent(bytes))

Http check response#

By default, NBomber is using this check for every response but you can override it for every step.

Http.withCheck(fun response -> task {
return if response.IsSuccessStatusCode then Response.Ok()
else Response.Fail("status code: " + response.StatusCode.ToString())
})

Advanced example#

Here you can find a full example using all API functions.

let userFeed = ["1"; "2"; "3"; "4"; "5"]
|> FeedData.fromSeq
|> Feed.createRandom "userFeed"
let getUser = HttpStep.create("get_user", userFeed, fun context ->
let userId = context.FeedItem
let url = "https://jsonplaceholder.typicode.com/users?id=" + userId
Http.createRequest "GET" url
|> Http.withCheck(fun response -> task {
let! json = response.Content.ReadAsStringAsync()
// parse JSON
let users = json
|> JsonConvert.DeserializeObject<UserResponse[]>
|> ValueOption.ofObj
match users with
| ValueSome usr when usr.Length = 1 ->
return Response.Ok(usr.[0]) // we pass user object response to the next step
| _ -> return Response.Fail("not found user: " + userId)
})
)
// this 'getPosts' will be executed only if 'getUser' finished OK.
let getPosts = HttpStep.create("get_posts", fun context ->
let user = context.GetPreviousStepResponse<UserResponse>()
let url = "https://jsonplaceholder.typicode.com/posts?userId=" + user.Id
Http.createRequest "GET" url
|> Http.withCheck(fun response -> task {
let! json = response.Content.ReadAsStringAsync()
// parse JSON
let posts = json
|> JsonConvert.DeserializeObject<PostResponse[]>
|> ValueOption.ofObj
match posts with
| ValueSome ps when ps.Length > 0 ->
return Response.Ok()
| _ -> return Response.Fail()
})
)
// it's optional Ping plugin that brings additional reporting data
let pingPluginConfig = PingPluginConfig.CreateDefault ["jsonplaceholder.typicode.com"]
let pingPlugin = new PingPlugin(pingPluginConfig)
Scenario.create "rest_api" [getUser; getPosts]
|> Scenario.withWarmUpDuration(seconds 5)
|> Scenario.withLoadSimulations [InjectPerSec(rate = 100, during = seconds 30)]
|> NBomberRunner.registerScenario
|> NBomberRunner.withWorkerPlugins [pingPlugin]
|> NBomberRunner.withTestSuite "http"
|> NBomberRunner.withTestName "advanced_test"
|> NBomberRunner.run
|> ignore

Advanced logging (tracing)#

There may be situations when you need to trace your requests and responses. The NBomber.Http has built-in functionality for tracing every request/response. In order to start tracing you need to set minimum logging level to verbose.

important

Make sure that you always return a new instance (not from a variable) of LoggerConfiguration. This limitation is mandatory since Serilog logger does not allow to create multiple instances from the same instance of LoggerConfiguration.

NBomberRunner.withLoggerConfig(fun () ->
LoggerConfiguration().MinimumLevel.Verbose()
)

JSON parsing#

To work with the JSON format, you can use any library you prefer. Here is a list of popular libraries:

Best practices#

  • To test HTTP use LoadSimulation.InjectPerSec since usually web server is an open system. You can read more here.
  • For debugging or tracing you can use LoadSimulation.KeepConstant with copies = 1 since for this simulation NBomber will use a single task which is easier to debug.
  • Use Ping plugin to get more info about networking.