Last active
May 14, 2023 15:42
-
-
Save jhewlett/865c11442ab383a5ada5d0c6cad174d1 to your computer and use it in GitHub Desktop.
Functional wrapper around System.Net.Http.HttpClient. Inspired in part by Http.fs (https://github.com/haf/Http.fs) and FSharp.Data (https://fsharp.github.io/FSharp.Data/library/Http.html)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace HttpClient.FSharp | |
open System | |
open System.Net.Http | |
type HttpMethod = | |
| Post | |
| Put | |
| Delete | |
| Get | |
module HttpMethod = | |
let value method = | |
match method with | |
| Post -> System.Net.Http.HttpMethod.Post | |
| Put -> System.Net.Http.HttpMethod.Put | |
| Delete -> System.Net.Http.HttpMethod.Delete | |
| Get -> System.Net.Http.HttpMethod.Get | |
[<RequireQualifiedAccess>] | |
type RequestBody = | |
| Json of string | |
module RequestBody = | |
let value body = | |
match body with | |
| RequestBody.Json json -> new StringContent(json, System.Text.Encoding.UTF8, "application/json") | |
type Request = { | |
Url : string | |
Method : HttpMethod | |
Timeout : TimeSpan option | |
Headers : (string * string) list | |
Body : RequestBody option | |
Query : (string * string) list | |
} | |
[<AutoOpen>] | |
module Request = | |
let withTimeout timeout request = | |
{ request with Timeout = Some timeout } | |
let withBody body request = | |
{ request with Body = Some body } | |
let withHeader header request = | |
{ request with Headers = header :: request.Headers } | |
let withQueryParam param request = | |
{ request with Query = param :: request.Query } | |
type Response = { | |
StatusCode : int | |
Body : string | |
Headers : (string * string) list | |
} | |
module Http = | |
let createRequest url method = | |
{ | |
Url = url | |
Method = method | |
Timeout = None | |
Headers = [] | |
Body = None | |
Query = [] | |
} | |
module Url = | |
let private encodeUrlParam param = | |
System.Uri.EscapeDataString param | |
let appendQueryToUrl (url : string) query = | |
match query with | |
| [] -> url | |
| query -> | |
url | |
+ if url.Contains "?" then "&" else "?" | |
+ String.concat "&" [ for k, v in List.rev query -> encodeUrlParam k + "=" + encodeUrlParam v ] | |
let execute (httpClientFactory : IHttpClientFactory) (request : Request) : Async<Response> = | |
async { | |
use httpClient = httpClientFactory.CreateClient() | |
request.Timeout | |
|> Option.iter (fun t -> httpClient.Timeout <- t) | |
let fullUrl = Url.appendQueryToUrl request.Url request.Query | |
use requestMessage = new HttpRequestMessage(request.Method |> HttpMethod.value, fullUrl) | |
request.Headers | |
|> List.iter requestMessage.Headers.Add | |
request.Body | |
|> Option.iter (fun b -> | |
let body = RequestBody.value b | |
requestMessage.Content <- body) | |
use! response = httpClient.SendAsync(requestMessage) |> Async.AwaitTask | |
let! body = response.Content.ReadAsStringAsync() |> Async.AwaitTask | |
return | |
{ | |
StatusCode = int response.StatusCode | |
Body = body | |
Headers = [for (KeyValue (k, v)) in response.Headers -> (k, String.concat "," v)] | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Main | |
open System | |
open Microsoft.Extensions.DependencyInjection | |
open System.Net.Http | |
open HttpClient.FSharp | |
let createHttpClientFactory () = | |
let services = ServiceCollection() | |
services.AddHttpClient() |> ignore | |
let serviceProvider = services.BuildServiceProvider() | |
serviceProvider.GetRequiredService<IHttpClientFactory>() | |
[<EntryPoint>] | |
let main argv = | |
let httpClientFactory = createHttpClientFactory () | |
let request = | |
Http.createRequest "https://hacker-news.firebaseio.com/v0/item/8863.json" Get | |
|> withTimeout (TimeSpan.FromSeconds 2.) | |
|> withHeader ("Accept", "application/json") | |
|> withQueryParam ("print", "pretty") | |
let response = | |
request | |
|> Http.execute httpClientFactory | |
|> Async.RunSynchronously | |
printfn "Status: %d; Body: %s" response.StatusCode response.Body | |
0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Project Sdk="Microsoft.NET.Sdk"> | |
<PropertyGroup> | |
<OutputType>Exe</OutputType> | |
<TargetFramework>netcoreapp3.1</TargetFramework> | |
</PropertyGroup> | |
<ItemGroup> | |
<Compile Include="Http.fs" /> | |
<Compile Include="Program.fs" /> | |
</ItemGroup> | |
<ItemGroup> | |
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" /> | |
</ItemGroup> | |
</Project> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment