Three easy ways to create simple Web Server with F#

I have tried to find easiest ways to create a simple web server with F#. There are three most simple ways to do it.

The goal is to create a simple web service that maps web request urls to the files in the site folder. If file with such name exists then return its content as html. Assume that all html files located in ‘D:\mySite\‘.


First and probably the most promising option was created by Julian Kay and described in his post “Creating a simple HTTP Server with F#“. I slightly modified source code to satisfy my initial goal. You can find detailed description of how it works in Julian’s post. (Works from FSI)

open System
open System.Net
open System.Text
open System.IO

let siteRoot = @"D:\mySite\"
let host = "http://localhost:8080/"

let listener (handler:(HttpListenerRequest->HttpListenerResponse->Async<unit>)) =
    let hl = new HttpListener()
    hl.Prefixes.Add host
    let task = Async.FromBeginEnd(hl.BeginGetContext, hl.EndGetContext)
    async {
        while true do
            let! context = task
            Async.Start(handler context.Request context.Response)
    } |> Async.Start

let output (req:HttpListenerRequest) =
    let file = Path.Combine(siteRoot,
    printfn "Requested : '%s'" file
    if (File.Exists file)
        then File.ReadAllText(file)
        else "File does not exist!"

listener (fun req resp ->
    async {
        let txt = Encoding.ASCII.GetBytes(output req)
        resp.ContentType <- "text/html"
        resp.OutputStream.Write(txt, 0, txt.Length)
// TODO: add your code here

Self-hosted WCF service

The second option is a tuned self-hosted WCF service. This approach was proposed by  Brian McNamara as an answer to the StackOverflow question “F# web server library“. (Works from FSI)

#r "System.ServiceModel.dll"
#r "System.ServiceModel.Web.dll"

open System
open System.IO

open System.ServiceModel
open System.ServiceModel.Web

let siteRoot = @"D:\mySite\"

type MyContract() =
    member this.Get(file:string) : Stream =
        printfn "Requested : '%s'" file
        WebOperationContext.Current.OutgoingResponse.ContentType <- "text/html"
        let bytes = File.ReadAllBytes(Path.Combine(siteRoot, file))
        upcast new MemoryStream(bytes)

let startAt address =
    let host = new WebServiceHost(typeof<MyContract>, new Uri(address))
    host.AddServiceEndpoint(typeof<MyContract>, new WebHttpBinding(), "")
      |> ignore

let server = startAt "http://localhost:8080/"
// TODO: add your code here


The third one is based on NancyFx. It is lightweight, low-ceremony, framework for building HTTP based services on .Net and Mono. Nancy is a popular framework in C# world, but does not have a natural support of F#. The F# code looks not so easy and simple as it could be. If you want to make it work, you need to create console application and install the Nancy and Nancy.Hosting.Self NuGet packages.

module WebServers

open System
open System.IO
open Nancy
open Nancy.Hosting.Self
open Nancy.Conventions

let (?) (this : obj) (prop : string) : obj =
    (this :?> DynamicDictionary).[prop]

let siteRoot = @"d:\mySite\"

type WebServerModule() as this =
    inherit NancyModule()
    do this.Get.["{file}"] <-
         fun parameters ->
              new Nancy.Responses.HtmlResponse(
                  (fun (s:Stream) ->
                      let file = (parameters?file).ToString()
                      printfn "Requested : '%s'" file
                      let bytes = File.ReadAllBytes(Path.Combine(siteRoot, file))
              )) |> box

let startAt host =
    let nancyHost = new NancyHost(new Uri(host))

let server = startAt "http://localhost:8080/"
printfn "Press [Enter] to exit."
Console.ReadKey() |> ignore

Further reading

5 thoughts on “Three easy ways to create simple Web Server with F#

  1. In the WCF method at least, you can return the file stream directly, no need to read to a (potentially) large block onto the heap and then return a memory stream to that. I’ve used WCF this way, works great and no other libraries needed. I hadn’t realised a separate interface definition for the contract was not needed though, I like that.

    1. Yes, you are right. You need to add cancellation token.

      open System
      open System.Net
      open System.Text
      open System.IO
      let siteRoot = @"D:\mySite\"
      let host = "http://localhost:8080/"
      let listener (handler😦HttpListenerRequest -> HttpListenerResponse -> Async<unit>)) cancellationToken =
      let hl = new HttpListener()
      hl.Prefixes.Add host
      let task = Async.FromBeginEnd(hl.BeginGetContext, hl.EndGetContext)
      let loop =
      async {
      while true do
      let! context = task
      Async.Start(handler context.Request context.Response)
      Async.Start(loop, cancellationToken)
      let output (req:HttpListenerRequest) =
      let file = Path.Combine(siteRoot,
      printfn "Requested : '%s'" file
      if (File.Exists file)
      then File.ReadAllText(file)
      else "File does not exist!"
      let cancellableWork = new System.Threading.CancellationTokenSource()
      listener (fun req resp ->
      async {
      let txt = Encoding.ASCII.GetBytes(output req)
      resp.ContentType <- "text/html"
      resp.OutputStream.Write(txt, 0, txt.Length)

      view raw


      hosted with ❤ by GitHub

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s