~glorifiedgluer/gluer.org

54879712a6af7b976c9ffece12f6d3bff51a72f6 — Victor Freire 6 months ago 8987c3b
article: dealing-with-httpclient-and-httprequestmessage-in-f
1 files changed, 97 insertions(+), 0 deletions(-)

M content-org/content.org
M content-org/content.org => content-org/content.org +97 -0
@@ 4686,6 4686,103 @@ Now testing our two properties is trivial:

#+include: "../static/blog/ironing-out-bugs-with-fscheck/main.fsx" src fsharp :lines "56-58"

** Dealing with HttpClient and HttpRequestMessage in F#      :fsharp:dotnet:
:PROPERTIES:
:EXPORT_DATE: 2023-06-07
:EXPORT_FILE_NAME: dealing-with-httpclient-and-httprequestmessage-in-f
:EXPORT_HUGO_SLUG: dealing-with-httpclient-and-httprequestmessage-in-f
:END:

#+begin_description
For God's sake, how many times do I need to look this up?
#+end_description

I simply cannot explain how many times I had to read the documentation
in order to remember how to do HTTP calls using [[https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.httpclient?view=net-7.0][HttpClient]] and
[[https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.httprequestmessage?view=net-8.0][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!

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

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

*** GET Request

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

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

*** 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!

#+begin_src fsharp
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>()
#+end_src

*** 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!

#+begin_src fsharp
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)
#+end_src


* Footnotes