For those new to Fantom BedSheet, this article explains how to create a simple web application with just 3 small text files.

Every project has meager beginnings and web applications are no different. So our Bed Nap application will begin by serving up a single static web page.

Project Structure

To do this, we need 3 files / classes. A build script to build the project, an AppModule to configure BedSheet and a class to generate the HTML.

Create the files in the following directory structure:

bednap/
 |-- fan/
 |    |-- pages/
 |    |    `-- IndexPage.fan
 |    `-- AppModule.fan
 `-- build.fan

build.fan

Let build.fan contain only the bare essentials:

select all
using build

class Build : BuildPod {
    new make() {
        podName = "bednap"
        summary = "The Bed Nap Tutorial"
        version = Version("1.0.0")

        meta = [
            "proj.name"    : "Bed Nap Tutorial",
            "afIoc.module" : "bednap::AppModule"
        ]

        depends = [
            "sys 1.0",

            "afIoc      3.0.2 - 3.0",
            "afBedSheet 1.5.2 - 1.5"
        ]

        srcDirs = [`fan/`, `fan/pages/`]
        resDirs = [,]
    }
}

Our pod is called bednap (all lowercase) and it depends on IoC and BedSheet. We have also declared the location of our AppModule in the meta section.

build.fan does contain a lot of information, so see Inside a Fantom Pod Build for a more detailed explanation.

Note how for the tutorial we've locked down the versions of IoC and BedSheet by specifying full version numbers. The versions have been locked down to ensure this tutorial will always compile, even if future versions of the libraries aren't backwards compatible.

Make sure you have the correct versions of IoC and BedSheet installed in your current Fantom environment by running these commands:

C:\> fanr install -r http://eggbox.fantomfactory.org/fanr "afIoc 3.0.2 - 3.0"

C:\> fanr install -r http://eggbox.fantomfactory.org/fanr "afBedSheet 1.5.2 - 1.5"

AppModule

Every IoC application, and hence every BedSheet application, defines an AppModule. The AppModule is the central configuration point and effectively binds the app together.

select all
using afIoc
using afBedSheet

const class AppModule {

    @Contribute { serviceType=Routes# }
    Void contributeRoutes(Configuration config) {
        config.add(Route(`/`, IndexPage#render))
    }
}

Our AppModule adds a single Route to the BedSheet Routes service. The above code states that when a client requests the root URL / then it should return the result of the IndexPage.render() method.

IndexPage

Page classes conceptualise everything that concerns a particular URL, in this case IndexPage is concerned with the index url /. For now, it just returns HTML.

select all
using afBedSheet

class IndexPage {

    Text render() {
        html := """<html>
                   <head>
                   </head>
                   <body>
                       <h1>Bed Nap Tutorial</h1>
                   </body>
                   </html>
                   """
        return Text.fromHtml(html)
    }
}

Text is a special BedSheet object that, when returned from a request handler method such as IndexPage.render(), is converted into an appropriate HTTP response and sent to the browser / client.

As you can see, the render() method just returns a basic HTML string using Fantom's multiline syntax.

The class and method may be any visibility (private, internal, etc...). We'll leave them as default (public) to save on typing!

Build and Run

To build the pod, just run build.fan:

C:\Projects\bednap>fan build.fan
compile [bednap]
  Compile [bednap]
    FindSourceFiles [2 files]
    WritePod [file:/C:/Apps/fantom-1.0.66/lib/fan/bednap.pod]
BUILD SUCCESS [454ms]!

Lets start BedSheet and run the web application:

select all
C:\Projects\bednap>fan afBedSheet -env dev bednap -port 8069

[afBedSheet] Found pod 'bednap'
[afBedSheet] Found mod 'bednap::AppModule'
[afBedSheet] Starting Bed App 'bednap' on port 8069
[afIocEnv] Setting from cmd line argument '-env' : dev
 ...
   ___    __                 _____        _
  / _ |  / /_____  _____    / ___/__  ___/ /_________  __ __
 / _  | / // / -_|/ _  /===/ __// _ \/ _/ __/ _  / __|/ // /
/_/ |_|/_//_/\__|/_//_/   /_/   \_,_/__/\__/____/_/   \_, /
           Alien-Factory BedSheet v1.5.2, IoC v3.0.2 /___/

IoC Registry built in 287ms and started up in 709ms

Bed App 'bednap' v1.0.0 listening on http://localhost:8069/

Note how we started BedSheet with the option -env dev, this is short for specifying a development environment.

By default, BedSheet starts up in production mode. By specifying -env dev BedSheet starts up in development mode. The main, and currently only, difference between the two is the amount of detail given in the error and 404 pages. Production mode gives a single one-liner whilst development mode gives a bucket load of system debug info. When in development, you'll want development mode, trust me!

Anyway, visit http://localhost:8069/ and you should see:

My First BedApp Screenshot

Tidy Up

The web application works as is, but we're going to address a couple of loose ends.

Const is Cool

Wisp works in the following standard way, when a HTTP request comes in, it assigns an Actor (thread) to handle the processing of that request. Therefore all HTTP requests run in separate requests. That includes page requests, image requests and CSS requests - doesn't matter if they're from the same or different browsers, each request is handled by a different Actor / thread.

In the article From One Thread to Another... I redefine the keyword const as meaning thread-safe. This means that non-const classes are not thread-safe!

IndexPage currently is not const. That means it is not thread safe. Which means it can not be re-used by different threads / HTTP requests. So BedSheet is forced to create a new instance of IndexPage for each and every request for the URL /. While it is not a massive overhead, it is still a waste.

The answer? Make it const.

const class IndexPage {
   ...
}

Now it IS thread-safe and BedSheet will only ever create the ONE instance of IndexPage and will share it between all HTTP requests.

The same principle applies to all Route handlers and IoC services. Non-const classes are re-created for each request whilst const versions are cached and re-used.

Proxy On

After you've updated IndexPage you need to...

  • stop BedSheet,
  • build a new pod,
  • re-start BedSheet and
  • refresh your browser.

That can quickly get annoying in web development where you're constantly making tiny changes. To help you out BedSheet can be started in Proxy mode.

When that happens BedSheet starts its own tiny web app on the port you specify (8069), and the main web application on another port (8070). The tiny web app acts as a simple proxy, forwarding all http requests to the main web app and returning the response; with one important difference...

The proxy app also monitors the pod files in the your Fantom environment. Should any of them change / get updated then it kills the main web app and restarts it.

This means that once BedSheet is running in proxy mode, to see code changes you just need to re-build your pod and refresh your browser!

To use BedSheet's development proxy, just add -proxy to the BedSheet command:

select all
C:\Projects\bednap>fan afBedSheet -proxy -env dev bednap 8069

[afBedSheet] Starting BedSheet Proxy on port 8069
[web] WispService started on port 8069
[afBedSheet] Cached the timestamps of 42 pods
[afBedSheet] Launching BedSheet WebApp 'bednap' on port 8070
[afBedSheet] Executing external process:

java -cp ... -Dfan.home=... fanx.tools.Fan afBedSheet::MainProxied -pingProxy -pingProxyPort 8069 -env dev bednap 8070

[web] WispService started on port 8070
[afBedSheet] Starting Bed App 'bednap' on port 8070
[afBedSheet] Found pod 'bednap'
[afBedSheet] Found mod 'bednap::AppModule'
[afIoc] Adding module definitions from pod 'bednap'
[afIoc] Adding module bednap::AppModule
[afIoc] Adding module afBedSheet::BedSheetModule
[afIoc] Adding module afIocConfig::IocConfigModule
[afIoc] Adding module afIocEnv::IocEnvModule
[afIocEnv] Setting from cmd line argument '-env' : dev
[afIoc]
   ___    __                 _____        _
  / _ |  / /_____  _____    / ___/__  ___/ /_________  __ __
 / _  | / // / -_|/ _  /===/ __// _ \/ _/ __/ _  / __|/ // /
/_/ |_|/_//_/\__|/_//_/   /_/   \_,_/__/\__/____/_/   \_, /
           Alien-Factory BedSheet v1.5.2, IoC v3.0.2 /___/

IoC Registry built in 387ms and started up in 1,709ms

[afBedSheet] Starting AppDestroyer. Pinging proxy every 1sec...
[afBedSheet]

Bed App 'Bed Nap Tutorial' listening on http://localhost:8069/

In the logs above you can see the proxy starting up first followed by the main bednap web app.

When you rebuild the pod and refresh you will log output like this:

[afBedSheet] Pod 'bednap' pod was updated 8min 20sec ago
[afBedSheet] Killing BedSheet WebApp 'bednap'
[afBedSheet] Launching BedSheet WebApp 'bednap' on port 8070
[afBedSheet] Executing ext...
...

Source Code

All the source code for this tutorial is available on the Bed Nap Tutorial Bitbucket Repository.

Code for this particular article is available on the 01-My-First-BedSheet-Web-Application branch.

Use the following commands to check the code out locally:

C:\> hg clone https://bitbucket.org/fantomfactory/bed-nap-tutorial
C:\> cd bed-nap-tutorial
C:\> hg update 01-My-First-BedSheet-Web-Application

Don't forget, you can trial the finished tutorial application at Bed Nap.

Have fun!

Edits

  • 1 Aug 2016 - Updated tutorial to use BedSheet 1.5 & IoC 3.0.
  • 1 Aug 2015 - Updated tutorial to use BedSheet 1.4.
  • 26 Aug 2014 - Original article.


Discuss