Using F# and Flurl

Read Time: 5 minutes

Today’s post is really light, and nothing too involved. It is composed mostly of notes and examples using the fluent http client Flurl with F#.

If you’re looking for an alternative to the HttpClient, Flurl offers some nice functionality and ergonomics. For reference I’m using version 3.2.4 of the client.

1
dotnet add package flurl.http --version 3.2.4

Before getting into examples, there is some small setup. I’ve defined a User type to send and receive data. Based on the Flurl response objects (which you’ll see in a moment) I have some response types specifically for HTTP GET and POST. Note here, I’m only including the args, since that is all I care about, but this could also include things like data or headers as well. For simplicity, I’m using httpbin.org, which echos what I send.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
open System
open System.IO
open Flurl
open Flurl.Http

type User =
{ Id: int
FirstName: string
LastName: string
Notes: string }

type GetResult =
{ args: User }

type PostResult =
{ json: User }

let targetUrl = "https://httpbin.org/anything"

A simple HTTP GET is straight-forward. Here I am returning the raw result, so you can see the general structure of the data returned. Query parameters are sent using the SetQueryParams method. GetStringAsync returns the raw string result.

1
2
3
4
5
6
7
8
9
10
11
12
13
task {
let! result =
targetUrl
.SetQueryParams(
{| Id = 123
FirstName = "James"
LastName = "Cole"
Notes = "" |})
.GetStringAsync()

printfn "%A" result }
|> Async.AwaitTask
|> Async.RunSynchronously

Results:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"{
"args": {
"FirstName": "James",
"Id": "123",
"LastName": "Cole",
"Notes": ""
},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip, deflate",
"Host": "httpbin.org",
"X-Amzn-Trace-Id": "Root=1-712ae14b-12b5a0fd70cfafb2213dd1b2"
},
"json": null,
"method": "GET",
"origin": "###.###.###.###",
"url": "https://httpbin.org/anything?FirstName=James&Id=123&LastName=Cole&Notes="
}
"

It is nice to see the raw result, but it is much more practical to deserialize the response into the desired object. To do this, change the Get call to GetJsonAsync<GetResult>.

1
2
3
4
5
6
7
8
9
10
11
12
13
task {
let! result =
targetUrl
.SetQueryParams(
{| Id = 456
FirstName = "Jeffrey"
LastName = "Goines"
Notes = "" |})
.GetJsonAsync<GetResult>()

printfn "%A" result }
|> Async.AwaitTask
|> Async.RunSynchronously

Results:

1
2
3
4
{ args = { Id = 456
FirstName = "Jeffrey"
LastName = "Goines"
Notes = "" } }

Often you want to send additional http headers with your request. To do this, use the WithHeaders method. It takes an array of key/value pairs.

1
2
3
4
5
6
7
8
9
10
11
task {
let! getResult =
targetUrl
.WithHeaders(
{| X_CUSTOM_HEADER = "12 Monkeys" |},
true)
.GetStringAsync()

printfn "%A" getResult }
|> Async.AwaitTask
|> Async.RunSynchronously

Results:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip, deflate",
"Host": "httpbin.org",
"X-Amzn-Trace-Id": "Root=1-712ae14b-887a539f672c4c3360afd11e",
"X-Custom-Header": "12 Monkeys"
},
"json": null,
"method": "GET",
"origin": "###.###.###.###",
"url": "https://httpbin.org/anything"
}
"

Performing an HTTP POST is similar to a GET. In this case use the PostJsonAsync method along with the object to serialize. Like the previous example, ReceiveJson<PostResult> will deserialize the response into the desired object.

1
2
3
4
5
6
7
8
9
10
11
12
13
task {
let! postResult =
targetUrl
.PostJsonAsync(
{| Id = 789
FirstName = "Kathryn"
LastName = "Railly"
Notes = "" |})
.ReceiveJson<PostResult>()

printfn "%A" postResult }
|> Async.AwaitTask
|> Async.RunSynchronously

Results:

1
2
3
4
{ json = { Id = 789
FirstName = "Kathryn"
LastName = "Railly"
Notes = "" } }

To put these components into a larger call, here is an HTTP POST that also sends headers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
task {
let! postResult =
targetUrl
.WithHeaders(
{| Accept = "application/json"
ContentType = "application/json"
X_CUSTOM_HEADER = "12 Monkeys" |},
true // replace _ with -
)
.PostJsonAsync(
{| Id = 101112
FirstName = "Lelan"
LastName = "Goines"
Notes = "" |})
.ReceiveJson<PostResult>()

printfn "%A" postResult }
|> Async.AwaitTask
|> Async.RunSynchronously

Results:

1
2
3
4
{ json = { Id = 101112
FirstName = "Lelan"
LastName = "Goines"
Notes = "" } }

Up until now, it has been send/receiving json payloads. But Flurl can be used to download files as well. Here I how a pdf can be downloaded and saved using DownloadFileAsync.

1
2
3
4
5
6
7
task {
let! result =
"https://people.math.harvard.edu/~ctm/home/text/others/shannon/entropy/entropy.pdf"
.DownloadFileAsync("./", "entropy.pdf")
printfn $"{result}" }
|> Async.AwaitTask
|> Async.RunSynchronously

Results:

1
./entropy.pdf

Hopefully these short notes have been helpful if you want to use Flurl with F#. Until next time.