Relax and let the HTTP Machine do the Work

Andrew Cherry and Ryan Riley

Tachyus logo

@panesofglass

https://github.com/panesofglass

OWIN

F# logo

Contents

  • Problems
  • Solution
  • Freya
  • Examples
  • Questions

HTTP is Hard

HTTP State Machine

What's wrong with existing solutions?

Weak support for most of HTTP

Little to no enforcement of the HTTP RFCs

Manual protocol manipulation

Team disagreements ...

... and bloodshed

There Will Be Blood

Prior Attempts (in F#)

A Better Solution

Machine-style Frameworks

Modeled as a graph

Freya visual debugging

Declarative config via overrides

Resource = defaults + overrides

After all, if you're working against an enigma, you don't stand a chance without a machine.

Webmachine

Liberator

Freya

A functional web stack in F#

Freya logo

Architectural Principles

Stack rather than monolithic framework

Freya stack

Building blocks for higher-level abstractions

Compat with external libraries

"Ethical" Principles

Work with existing abstractions

(where possible)

Enforce the pit of success

Leverage F#; eliminate the wrong thing

Static File Server

Todo Backend

Next for Freya

Questions?

Freya Stack

A Tour

Freya.Core

  • Basic abstractions over OWIN
  • freya computation expression (the wrapping abstraction for our mutable state -- we will never speak of this in polite conversation)
  • Set of operators to use freya computation expressions in a more concise way
  • Some basic functions and lenses to give access to the state in a controllable way
1: 
2: 
type Freya<'T> =
    FreyaState -> Async<'T * FreyaState>

Roughly equivalent to Erlang's webmachine signature:

1: 
2: 
f(ReqData, State) ->
    {RetV, ReqData, State}.
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
type FreyaState =
    { Environment: FreyaEnvironment
      Meta: FreyaMetaState }

    static member internal EnvironmentLens =
        (fun x -> x.Environment), 
        (fun e x -> { x with Environment = e })

    static member internal MetaLens =
        (fun x -> x.Meta), 
        (fun m x -> { x with Meta = m })

and FreyaMetaState =
    { Memos: Map<Guid, obj> }

    static member internal MemosLens =
        (fun x -> x.Memos),
        (fun m x -> { x with Memos = m })

Lenses?

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
let ``getLM, setLM, modLM behave correctly`` () =
    let m =
        freya {
            do! Freya.setLens answer_ 42
            let! v1 = Freya.getLens answer_

            do! Freya.mapLens answer_ ((*) 2)
            let! v2 = Freya.getLens answer_

            return v1, v2 }

    let result = run m

    fst result =! (42, 84)

OWIN Integration

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
/// Type alias of <see cref="FreyaEnvironment" /> in terms of OWIN.
type OwinEnvironment =
    FreyaEnvironment

/// Type alias for the F# equivalent of the OWIN AppFunc signature.
type OwinApp = 
    OwinEnvironment -> Async<unit>

/// Type alias for the OWIN AppFunc signature.
type OwinAppFunc = 
    Func<OwinEnvironment, Task>

Use OWIN in Freya

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
let ``freya computation can compose with an OwinAppFunc`` () =
    let app =
        OwinAppFunc(fun (env: OwinEnvironment) ->
            env.["Answer"] <- 42
            Task.FromResult<obj>(null) :> Task)

    let converted = OwinAppFunc.toFreya app

    let m =
        freya {
            do! converted
            let! v1 = Freya.getLens answer_
            return v1 }
    
    let result = run m
    fst result =! 42

Convert Freya to OWIN

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
let ``freya computation can roundtrip to and from OwinAppFunc`` () =
    let app = Freya.setLens answer_ 42

    let converted =
        app
        |> OwinAppFunc.ofFreya
        |> OwinAppFunc.toFreya

    let m =
        freya {
            do! converted
            let! v1 = Freya.getLens answer_
            return v1 }
    
    let result = run m
    fst result =! 42

Freya.Pipeline

  • Very small and simple -- all about composing freya computations in a way that represents a continue/halt processing pipeline
  • A pipeline is simply a freya computation that returns Next or Halt (FreyaPipelineChoice cases)
  • Single, simple operator: >?=
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
let ``pipeline executes both monads if first returns next`` () =
    let o1 = Freya.mapState (fun x -> x.Environment.["o1"] <- true; x) *> Freya.next
    let o2 = Freya.mapState (fun x -> x.Environment.["o2"] <- true; x) *> Freya.next

    let choice, env = run (o1 >?= o2)

    choice =! Next
    unbox env.Environment.["o1"] =! true
    unbox env.Environment.["o2"] =! true

let ``pipeline executes only the first monad if first halts`` () =
    let o1 = Freya.mapState (fun x -> x.Environment.["o1"] <- true; x) *> Freya.halt
    let o2 = Freya.mapState (fun x -> x.Environment.["o2"] <- true; x) *> Freya.next

    let choice, env = run (o1 >?= o2)

    choice =! Halt
    unbox env.Environment.["o1"] =! true
    unbox env.Environment.["o2"] =! false

Freya.Recorder

  • Build introspection into the framework at a low level
  • Provide some infrastructure for recording metadata about processing that more specific implemenations can use
  • For example, Freya.Machine records the execution process so it can be examined later

Freya.Types.*

  • Set of libraries providing F# types which map (very closely) to various specifications, such as HTTP, URI, LanguageTag, etc.
  • These are used throughout the higher level stack projects
  • Always favor strongly-typed representations of data over strings
  • Provides parsers, formatters (statically on the types) and lenses from state to that type (either total or partial)

Really?

  • Why not use System.Net.Whatever?
  • Well ...

Ask the UriKind. One. More. Time.

Freya.Types.*

  • Types and parsers for when you don't already know everything about the string you've been given
  • Types which map closely to HTTP specifications
  • Types which can distinguish between different kinds of URIs being valid in different places
  • Types which can actually express languages that aren't "en-US"
  • ("hy-Latn-IT-arevela"? Of course we support Aremenian with a Latin script as spoken in Northern Italy why do you ask?)

Integration with existing standards

OWIN

1: 
Func<IDictionary<string, obj>, Task>
  • Standard contract between servers and apps/frameworks
  • Several server implementations, including IIS
  • Reasonably well followed standard

Freya.Router

  • A simple, trie-based router, does pretty much what you'd expect
  • Doesn't try and do anything but route requests to pipelines
  • (and is itself a pipeline -- everything's composable / nestable!)

Freya.Machine

  • A "machine" style resource definition / processing library
  • Inspired by projects like webmachine (Erlang) and Liberator (Clojure)
  • Adds types

Freya.Inspector

  • Built-in introspection
  • Has an extensibility model (WIP)
  • Right now provides an API; UI in-progress

Freya.*.Inspector

  • Component-specific extensions to the inspector, currently providing component-specific JSON for the inspection API
  • Will provide UI extensions, too, but haven't decided on the best approach to that (suggestions welcome, of course)