Skip to content

Instantly share code, notes, and snippets.

@ruxo
Last active February 9, 2023 23:13
Show Gist options
  • Select an option

  • Save ruxo/aaa630796ee3e60881cede10ff7024de to your computer and use it in GitHub Desktop.

Select an option

Save ruxo/aaa630796ee3e60881cede10ff7024de to your computer and use it in GitHub Desktop.
F# HTTP module
module TiraxTech.Http
open System
open System.Threading.Tasks
open System.Net
open System.Net.Http
open System.Net.Http.Json
open System.Text.Json
open System.IO
open System.Net.Http.Headers
type HttpStatusCode with
member me.IsInformational = let code = int(me) in code >= 100 && code < 200
member me.IsOk = let code = int(me) in code >= 200 && code < 300
member me.IsRedirection = let code = int(me) in code >= 300 && code < 400
member me.IsClientError = let code = int(me) in code >= 400 && code < 500
member me.IsServerError = let code = int(me) in code >= 500 && code < 600
type SameSiteCookie = Strict | Lax | Nothing
type SetCookieBody = {
Key: string
Value: string
Expires: DateTime option
MaxAge: int option
Domain: string option
Path: string option
Secure: bool
HttpOnly: bool
SameSite: SameSiteCookie option
}
type HttpHeaders =
| Cookie of string * string
let private http = HttpClient <| HttpClientHandler(UseCookies=false)
let private setHeader (req: HttpRequestMessage) = function
| Cookie (key, value) -> req.Headers.TryAddWithoutValidation("Cookie", $"{key}={value}") |> ignore
let inline private respondWith ([<InlineIfLambda>] getter: HttpContent -> Task<'Response>) (res: HttpResponseMessage) =
task {
use _ = res
let! text = res.Content |> getter
return struct (res.StatusCode, text)
}
let DefaultCamelSerializerOptions = JsonSerializerOptions()
exception HttpRequestUnhandled of struct (HttpStatusCode * string)
/// Read content from HttpResponseMessage as JSON and deserialize to 'Response.
// JsonSerializerOptions -> HttpResponseMessage -> Task<struct (HttpStatusCode * 'Response)>
let jsonResponseWithOptions<'Response> (opt: JsonSerializerOptions) (res: HttpResponseMessage) =
if res.IsSuccessStatusCode then
res |> respondWith (fun content -> content.ReadFromJsonAsync<'Response>(opt))
else
raise <| HttpRequestUnhandled (res.StatusCode, $"Status code is not OK! (%A{res.StatusCode})")
(* HttpResponseMessage -> Task<struct (HttpStatusCode * 'Response)> *)
let inline jsonResponse<'Response> = jsonResponseWithOptions<'Response> <| DefaultCamelSerializerOptions
(* HttpResponseMessage -> Task<struct (HttpStatusCode * string)> *)
let inline textResponse res = res |> respondWith (fun content -> content.ReadAsStringAsync())
[<NoComparison>]
type HTTP = {
Method: HttpMethod
Uri: Uri
Headers: HttpHeaders seq
Content: HttpContent option
}
with
static member Post uri = {
Method=HttpMethod.Post
Uri=uri
Headers=[]
Content=None
}
member inline me.WithHeaders([<ParamArray>] headers: HttpHeaders[]) = { me with Headers=headers }
member me.WithJson(body, ?serializer: JsonSerializerOptions) =
let text = JsonSerializer.Serialize(body, ?options = serializer)
let payload = MemoryStream(Text.Encoding.UTF8.GetBytes text)
let content = StreamContent(payload)
content.Headers.ContentType <- MediaTypeHeaderValue("application/json")
{ me with Content=Some content }
member my.Respond() =
let req = HttpRequestMessage(my.Method, my.Uri)
my.Headers |> Seq.iter (setHeader req)
if my.Content.IsSome then
req.Content <- my.Content |> Option.get
http.SendAsync(req)
member my.RespondAsJson<'Data>() =
task {
use! res= my.Respond()
return! res |> jsonResponse<'Data>
}
member my.RespondAsString() =
task {
use! res = my.Respond()
return! res.Content.ReadAsStringAsync()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment