asynchronousf#httpclient

F# async expressions using HttpClient


This example is taken from dotnet/fsharp documentation and it's outdated for this reason - "This construct is deprecated. WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead." https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/async-expressions

How would this example look using HttpClient?

Just to mention I'm new to web development but I do understand what async expressions are, just having trouble converting the example to be using HttpClient.

open System.Net
open Microsoft.FSharp.Control.WebExtensions

let urlList = [ "Microsoft.com", "http://www.microsoft.com/"
                "MSDN", "http://msdn.microsoft.com/"
                "Bing", "http://www.bing.com"
            ]

let fetchAsync(name, url:string) =
    async {
        try
            let uri = new System.Uri(url)
            let webClient = new WebClient()
            let! html = webClient.AsyncDownloadString(uri)
            printfn "Read %d characters for %s" html.Length name
        with
            | ex -> printfn "%s" (ex.Message);
    }

let runAll() =
    urlList
    |> Seq.map fetchAsync
    |> Async.Parallel
    |> Async.RunSynchronously
    |> ignore

runAll()

Solution

  • Here is some code from one of my projects. You may want to deal with errors differently, but I guess you can extract what you need.

    EDIT: I also added some simple error handling and made the example complete.

    open System.Net
    open System.Net.Http
    
    [<RequireQualifiedAccess>]
    module Web =
    
        let private http = new HttpClient(Timeout = System.TimeSpan.FromSeconds 40.)
    
        type WebError =
            | HttpErrorCode of HttpStatusCode * Body: string
            | HttpNetworkIssue of string
            | HttpTimeout
    
        let getStringAsync (url: string) =
            async {
                try
                    let! response = http.GetAsync url |> Async.AwaitTask
                    let! body = response.Content.ReadAsStringAsync() |> Async.AwaitTask
                    if response.IsSuccessStatusCode then return Ok body
                    else return Error (HttpErrorCode (response.StatusCode, body))
                with
                    | :? System.Threading.Tasks.TaskCanceledException -> return Error HttpTimeout
                    | ex -> return Error (HttpNetworkIssue ex.Message)
            }
    
    module Test =
    
        let urlList = [ "Microsoft.com", "http://www.microsoft.com/"
                        "MSDN", "http://msdn.microsoft.com/"
                        "Bing", "http://www.bing.com"
                    ]
    
        let checkSite (name, url:string) =
            async {
                let! res = Web.getStringAsync url
                match res with
                | Ok html ->
                    printfn "Read %d characters for %s" html.Length name
                | Error (Web.HttpErrorCode(code, body)) ->
                    printfn $"ERROR: http error code {code} with body {body}"
                | Error (Web.HttpNetworkIssue(s)) ->
                    printfn $"ERROR: network issue: {s}"
                | Error (Web.HttpTimeout) ->
                    printfn $"ERROR: timeout"
    
    
            }
    
        let runAll() =
            urlList
            |> Seq.map checkSite
            |> Async.Parallel
            |> Async.RunSynchronously
            |> ignore
    
        runAll()