Dealing with HttpClient and HttpRequestMessage in F#

I simply cannot explain how many times I had to read the documentation in order to remember how to do HTTP calls using HttpClient and HttpRequestMessage. Let's put an end to this madness now!

Defining a Base Address

This is useful if you intend to use the same HttpClient to make multiple calls to the same API. If that's the case, do not remove the trailing-slash as it will cause problems when concatenating endpoint paths!

let client = new HttpClient()
client.BaseAddress <- "https://example.com/api/"

// joining our endpoint path
let uri = Uri(client.BaseAddress, "login")

GET Request

In order to make GET requests, you need to setup an HttpRequestMessage instance with the desired method and your endpoint Uri.

let message = new HttpRequestMessage(HttpMethod.Get, uri)
// send request
use! response = client.SendAsync(message)
// read json
let! body = response.Content.ReadFromJsonAsync<T>()

Sending JSON on a POST request

This process is a bit more involved as you need to serialize the JSON into a string before sending the message. Fortunately, we have everything we need without relying on external libraries!

let credentials =
    { Username = "gluer"
      Password = "secret" }

// serialize the credentials object into a string
let json =
    let serialized =
        credentials |> JsonSerializer.Serialize

    new StringContent(serialized, Encoding.UTF8, "application/json")

// prepare the request with a POST method and our Uri
let message = new HttpRequestMessage(HttpMethod.Post, uri)
message.Content <- json

// send request
use! response = state.Client.SendAsync(message)

// read json response
let! body = response.Content.ReadFromJsonAsync<T>()

Error handling

Unfortunately, by default, there is no F#-friendly way of handling errors on HTTP calls. However, the good news is that dealing with them is not difficult either!

type ResponseError =
    | NetworkError of Exception
    | TimeoutError of Exception
    | Unknown of Exception
//...
try
    use! responseMessage = httpClient.SendAsync(message)
    let! body = responseMessage.Content.ReadFromJsonAsync<T>()
    return Ok body
with
| :? HttpRequestException as ex ->
    return Error(NetworkError ex)
| :? TaskCanceledException as ex ->
    return Error(TimeoutError ex)
| ex ->
    return  Error(Unknown ex)