1 of 78

Freya, Hopac, Kestrel:

Building a Concurrent, Functional

Web Service for .NET Core

Marcus Griep

@neoeinsteinhttps://goo.gl/L77RHj

2 of 78

Marcus Griep

Lead Software Engineer

Vistaprint

  • With Vistaprint for 10 years
  • Using F# since 2015
  • Contributor to Chiron & Hopac
  • Twitter: @neoeinstein

@neoeinsteinhttps://goo.gl/L77RHj

3 of 78

@neoeinsteinhttps://goo.gl/L77RHj

4 of 78

Hopac

@neoeinsteinhttps://goo.gl/L77RHj

5 of 78

Functional

Web Services

@neoeinsteinhttps://goo.gl/L77RHj

6 of 78

@neoeinsteinhttps://goo.gl/L77RHj

7 of 78

OS: Debian 8

GCE 4 vCPU, 4GB

Published self-contained

5.98

@neoeinsteinhttps://goo.gl/L77RHj

8 of 78

freya.io

@neoeinsteinhttps://goo.gl/L77RHj

9 of 78

NuGet

Freya

Freya.Hopac

@neoeinsteinhttps://goo.gl/L77RHj

10 of 78

https://www.myget.org

/F/neoeinstein/api/v3/index.json

@neoeinsteinhttps://goo.gl/L77RHj

11 of 78

@neoeinsteinhttps://goo.gl/L77RHj

12 of 78

Should I use Freya?

  • Like to write clean functional code
  • Using .NET Framework or .NET Core
  • Don't want to have to read the HTTP RFCs
  • Want a powerful framework that understands HTTP
  • Want to benefit from the power and speed of Kestrel
  • Want a framework that optimizes itself

@neoeinsteinhttps://goo.gl/L77RHj

13 of 78

Should I use Freya?

@neoeinsteinhttps://goo.gl/L77RHj

14 of 78

Hopac

Concurrent ML-style constructs for F#

@neoeinsteinhttps://goo.gl/L77RHj

15 of 78

Should I use Hopac?

  • Want more performance out of asynchronous code
  • Know how to use Async<'a>
  • Familiar with the async computation expression
  • Have many short threads of execution that you want to run concurrently
    • Like on a web server

@neoeinsteinhttps://goo.gl/L77RHj

16 of 78

CPU

CPU

CPU

CPU

Hopac

Thread Pool

Cooperative

Preemptive

@neoeinsteinhttps://goo.gl/L77RHj

17 of 78

Should I use Hopac?

@neoeinsteinhttps://goo.gl/L77RHj

18 of 78

job { … }

@neoeinsteinhttps://goo.gl/L77RHj

19 of 78

Alt<'a>

Promise<'a>

IVar<'a>

MVar<'a>

>>=

^=>

*<-

*<+->=

Stream<'a>

Mailbox<'a>

@neoeinsteinhttps://goo.gl/L77RHj

20 of 78

Hopac

Async

@neoeinsteinhttps://goo.gl/L77RHj

21 of 78

Kestrel

The ASP.NET Core web server

@neoeinsteinhttps://goo.gl/L77RHj

22 of 78

Should I use Kestrel?

@neoeinsteinhttps://goo.gl/L77RHj

23 of 78

Should I use Kestrel?

@neoeinsteinhttps://goo.gl/L77RHj

24 of 78

OWIN

Open Web Interface for .NET

@neoeinsteinhttps://goo.gl/L77RHj

25 of 78

Should I use Kestrel?

@neoeinsteinhttps://goo.gl/L77RHj

26 of 78

Suave

@neoeinsteinhttps://goo.gl/L77RHj

27 of 78

Functional

Web Services

@neoeinsteinhttps://goo.gl/L77RHj

28 of 78

@neoeinsteinhttps://goo.gl/L77RHj

29 of 78

@neoeinsteinhttps://goo.gl/L77RHj

30 of 78

Kestrel

Freya

Apache / nginx / IIS

OWIN

Suave

Suave

ASP.NET�MVC

@neoeinsteinhttps://goo.gl/L77RHj

31 of 78

ASP.NET

type HelloHandler = HelloHandler of (string option -> string)

module HelloHandler =

let run (HelloHandler f) x = f x

let impl = HelloHandler <| function

| Some name -> sprintf "Hello %s!" name

| None -> "Hello World!"

[<Route("hello")>]

type HelloController (handler:HelloHandler) =

inherit Controller()

[<HttpGet>]

member x.Get() =

base.Ok(HelloHandler.run handler None)

[<HttpGet("{name}")>]

member x.GetByName(name: string) =

base.Ok(HelloHandler.run handler (Some name))

@neoeinsteinhttps://goo.gl/L77RHj

32 of 78

Suave

let helloWorld : WebPart =

choose

[ GET >=> path "/hello" >=> (Successful.OK "Hello World!")

GET >=> pathScan "/hello/%s" (sprintf "Hello %s!" >> Successful.OK) ]

@neoeinsteinhttps://goo.gl/L77RHj

33 of 78

Freya

let sayHello = freya {

let! uO = Freya.Optic.get (Route.atom_ "name")

let helloStr =

uO

|> Option.map (sprintf "Hello, %s!")

|> Option.defaultValue "Hello, World!"

return Represent.text helloStr

}

let helloMachine = freyaMachine {

handleOk sayHello

}

let router = freyaRouter {

resource "/hello{/name}" helloMachine

}

let root = UriTemplateRouter.Freya router

@neoeinsteinhttps://goo.gl/L77RHj

34 of 78

ASP.NET

[<Route("hello")>]

type HelloController () =

[<HttpGet>]

member x.Get() =

[<HttpGet("{name}")>]

member x.GetByName(name: string) =

@neoeinsteinhttps://goo.gl/L77RHj

35 of 78

Suave

let helloWorld =

choose

[ GET >=> path "/hello"

GET >=> pathScan "/hello/%s" ]

@neoeinsteinhttps://goo.gl/L77RHj

36 of 78

Freya

let name_ = Route.atom_ "name"

let router = freyaRouter {

resource "/hello{/name}" helloMachine

}

@neoeinsteinhttps://goo.gl/L77RHj

37 of 78

ASP.NET

[<Authorize>]

type HelloController () =

type MyRequirement () =

interface IAuthorizationRequirement

type MyAuthHandler () =

inherit AuthorizationHandler<MyRequirement>()

member __.HandleRequirementAsync(ctx:AuthorizationHandlerContext, req:MyRequirement) =

ctx.Succeed(req)

Task.CompletedTask

type Startup () =

member __.ConfigureServices(svc:IServiceCollection) =

svc.AddSingleton<IAuthorizationHandler,MyAuthHandler>()

svc.AddIdentity<IdentityType, IdentityRole>()

.WithSomeBackingStore()

@neoeinsteinhttps://goo.gl/L77RHj

38 of 78

Suave

let checkAllowed (user,pass) = true

Authentication.authenticateBasic checkAllowed (Successful.OK "You're in!")

@neoeinsteinhttps://goo.gl/L77RHj

39 of 78

Freya

let checkValidAuth = freya { return true }

let checkAllowed = freya { return true }

let helloMachine = freyaMachine {

authorized checkValidAuth

allowed checkAllowed

handleOk (Represent.text "You're in!")

}

@neoeinsteinhttps://goo.gl/L77RHj

40 of 78

Freya

Suave

path "/hello" >=> choose

[ GET >=> Successful.OK "hello"

Writers.setHeader "Allow" "GET" >=> RequestErrors.METHOD_NOT_ALLOWED "" ]

let helloMachine = freyaMachine {

methods [GET; HEAD; OPTIONS]

handleOk (Represent.text "You're in!")

}

@neoeinsteinhttps://goo.gl/L77RHj

41 of 78

Freya

let helloMachine = freyaMachine {

availableMediaTypes [MediaType.Json; MediaType.Text]

availableContentCodings ContentCoding.GZip

availableCharsets Charset.Utf8

handleOk (fun (acpt: Acceptable) -> freya {})

}

@neoeinsteinhttps://goo.gl/L77RHj

42 of 78

Freya

let helloMachine = freyaMachine {

methods [GET; HEAD; OPTIONS; POST]

availableMediaTypes [MediaType.Json; MediaType.Text]

availableContentCodings ContentCoding.GZip

availableCharsets Charset.Utf8

acceptableMediaTypes MediaType.Json

handlePost doThePost

handleOk (fun (acpt: Acceptable) -> freya {})

}

@neoeinsteinhttps://goo.gl/L77RHj

43 of 78

Freya

let helloMachine = freyaMachine {

cors

corsMaxAge (60*60*24)

corsOrigins (SerializedOrigin.parse "https://example.com")

methods [GET; HEAD; OPTIONS; POST]

availableMediaTypes [MediaType.Json; MediaType.Text]

availableContentCodings ContentCoding.GZip

availableCharsets Charset.Utf8

acceptableMediaTypes MediaType.Json

handlePost doThePost

handleOk (fun (acpt: Acceptable) -> freya {})

}

@neoeinsteinhttps://goo.gl/L77RHj

44 of 78

Freya

let core = freyaMachine {

cors

corsMaxAge (60*60*24)

corsOrigins (SerializedOrigin.parse "https://example.com")

methods [GET; HEAD; OPTIONS]

availableContentCodings ContentCoding.GZip

availableCharsets Charset.Utf8

}

let helloMachine = freyaMachine {

including core

methods [GET; HEAD; OPTIONS; POST]

availableMediaTypes [MediaType.Json; MediaType.Text]

acceptableMediaTypes MediaType.Json

handlePost doThePost

handleOk (fun (acpt: Acceptable) -> freya {})

}

@neoeinsteinhttps://goo.gl/L77RHj

45 of 78

Freya Machine

@neoeinsteinhttps://goo.gl/L77RHj

46 of 78

Freya Machines

Decision Trees

Is the server in a state to respond?� true: continue� false: return "503 Service Unavailable"��Is the version of HTTP being requested supported?� true: continue� false: return "505 HTTP Version Not Supported"

@neoeinsteinhttps://goo.gl/L77RHj

47 of 78

@neoeinsteinhttps://goo.gl/L77RHj

48 of 78

Freya Machine

@neoeinsteinhttps://goo.gl/L77RHj

49 of 78

Freya Machine

@neoeinsteinhttps://goo.gl/L77RHj

50 of 78

Freya Machine

Optimization

@neoeinsteinhttps://goo.gl/L77RHj

51 of 78

@neoeinsteinhttps://goo.gl/L77RHj

52 of 78

@neoeinsteinhttps://goo.gl/L77RHj

53 of 78

@neoeinsteinhttps://goo.gl/L77RHj

54 of 78

@neoeinsteinhttps://goo.gl/L77RHj

55 of 78

@neoeinsteinhttps://goo.gl/L77RHj

56 of 78

@neoeinsteinhttps://goo.gl/L77RHj

57 of 78

@neoeinsteinhttps://goo.gl/L77RHj

58 of 78

Freya Machine

Prototype

@neoeinsteinhttps://goo.gl/L77RHj

59 of 78

Freya Machine

Configuration

@neoeinsteinhttps://goo.gl/L77RHj

60 of 78

Freya Machine

Literal elimination

@neoeinsteinhttps://goo.gl/L77RHj

61 of 78

Freya Machine

Unary elimination

@neoeinsteinhttps://goo.gl/L77RHj

62 of 78

Freya Machine

Subgraph elimination

@neoeinsteinhttps://goo.gl/L77RHj

63 of 78

Freya Machine

Minimal

@neoeinsteinhttps://goo.gl/L77RHj

64 of 78

@neoeinsteinhttps://goo.gl/L77RHj

65 of 78

Demo

@neoeinsteinhttps://goo.gl/L77RHj

66 of 78

Benchmarks

@neoeinsteinhttps://goo.gl/L77RHj

67 of 78

/hello{/name}

@neoeinsteinhttps://goo.gl/L77RHj

68 of 78

256 connections

10 sec

Google Compute Engine Instance

4 vCPUs, 4GiB RAM

Google Compute Engine Instance

2 vCPUs, 2GiB RAM

wrk

dotnet helloWorld.dll

/hello{/name}

@neoeinsteinhttps://goo.gl/L77RHj

69 of 78

wrk -c 256 -t 32 -d 10 --latency http://104.155.85.181:8080/hello

OS: Debian 8

GCE 4 vCPU, 4GB

Published self-contained

@neoeinsteinhttps://goo.gl/L77RHj

70 of 78

wrk -c 256 -t 32 -d 10 --latency http://104.155.85.181:8080/hello/fred

OS: Debian 8

GCE 4 vCPU, 4GB

Published self-contained

@neoeinsteinhttps://goo.gl/L77RHj

71 of 78

OS: Debian 8

GCE 4 vCPU, 4GB

Published self-contained

5.98

@neoeinsteinhttps://goo.gl/L77RHj

72 of 78

OS: Debian 8

GCE 4 vCPU, 4GB

Published self-contained

@neoeinsteinhttps://goo.gl/L77RHj

73 of 78

Functional

Web Service

@neoeinsteinhttps://goo.gl/L77RHj

74 of 78

.NET Core

@neoeinsteinhttps://goo.gl/L77RHj

75 of 78

@neoeinsteinhttps://goo.gl/L77RHj

76 of 78

Kestrel

OWIN

Hopac

Async

@neoeinsteinhttps://goo.gl/L77RHj

77 of 78

@neoeinsteinhttps://goo.gl/L77RHj

78 of 78

freya.io

https://goo.gl/L77RHj

@neoeinsteinhttps://goo.gl/L77RHj