AF-BedSheetUser Guide


BedSheet is a Fantom framework for delivering web applications. Built on top of afIoc and Wisp, BedSheet aims to be: Powerful, Flexible and Simple.

Quick Start

  1. Create an AppModule, this is where all your service configuration will go.
  2. Contribute to Router and other services
  3. Create some page / request handlers
  4. Start the app...
using afBedSheet
using afIoc

class AppModule {
  @Contribute { serviceType=RouteSource# }
  static Void contributeRoutes(OrderedConfig config) {
    config.addUnordered(Route(`/hello`, HelloPage#hello))

class HelloPage {
  TextResult hello(Str name, Int iq := 666) {
    return TextResult.fromPlain("Hello! I'm $name and I have an IQ of $iq!")

From the command line:

$ fan afBedSheet <mypod>::AppModule 8080
IoC started up in 323ms

$ curl http://localhost:8080/hello/Traci/69
Hello! I'm Traci and I have an IQ of 69!

$ curl http://localhost:8080/hello/Luci
Hello! I'm Luci and I have an IQ of 666!

Wow! That's awesome! But what just happened!?


Every application has an AppModule that is used to contribute to afIoc services. There we told the Router service to route all requests to /hello to our HelloPage class.

The #hello handler now owns all uris that start with /hello.


Deeper path segments are converted into method parameters. Hence our handler method takes a Str and an Int. Parameters of type Uri or Str[] are capture all parameters and match the whole uri.

TIP: Contribute your own ValueEncoders to convert uris into Entities. That way BedSheet can call your handlers with real Entities, and not just str IDs.

As per our example, you can use default parameter values to declare optional uri segments. Any urls that don't match the handler parameters are reported as 404s.

Result Processing

Handlers should perform the logic processing of your request and not attempt to write to the HTTP OutStream. That way, if there's an Err, it can be gracefully handled and a suitable response sent to the user.

Instead handlers return objects that ResultProcessors deal with. Current default handlers include TextResult and JsonResult.

If the response is over a given size, and is deemed compressible then it is gzipped before being sent to the user.


By default, BedSheet compresses the HTTP response where it can.(1) But it doesn't do this willy nilly, oh no! There are many hurdles to overcome...

Disable All

Gzip, although enabled by default, can be disabled for the entire web app by setting the following config property:

config.addOverride(ConfigIds.gzipDisabled, "my.gzip.disabled", true)

Disable per Response

Although enabled by default, gzip can be disabled on a per request / response basis by calling the following:


Gzip'able Mime Types

Not everything should be gzipped. For example, text files gzip very well and yield high compression rates. JPG images on the other hand, because they're already compressed, don't gzip well and can end up bigger than the original! For this reason you must contribute to the GzipCompressible service to enable gzip for specified Mime Types:

config.addMapped(MimeType("text/funky"), true)

By default BedSheet will compress plain text, css, html, javascript, xml and json responses.

Gzip only when asked

It's guaranteed that someone, somewhere is still using Internet Explorer 3.0 and they can't handle gzipped content. As such, and as per RFC 2616 HTTP1.1 Sec14.3, we only gzip the response if the client actually asked for it!

Minimum content threshold

Gzip is great when compressing large files, but if you've only got a few bytes to squash... then the compressed version is going to be bigger! Which kinda defeats the point of using gzip in the first place! For that reason the response data must reach a minimum size / threshold before it gets gzipped. Set the threshold config with the following:

config.addOverride(ConfigIds.gzipThreshold, "my.gzip.threshold", 768)

See GzipOutStream and ConfigIds.gzipThreshold for more details.

Phew! Made it!

If (and only if!) your data passed all the tests above, then it will be lovingly gzipped and sent to the client.

Buffered Response


All URI handlers and processors are built by afIoc so feel free to @Inject DAOs and other services. BedSheet itself is built with afIoc so look at the BedSheet source for afIoc examples.

TIP: const handler classes are cached by BedSheet and reused on every request.

There's also some Err reporting, HTTP status handling and probably more besides. It's early days still...

Push To Live!

In a hurry to go live?

Check out Heroku and see how ridiculously easy it is to deploy your app to a live server with the heroku-fantom-buildpack.

Release Notes


  • New: Proxy mode to restart web app on pod change.
  • Chg: Gzip handling overhauled.
  • Chg: afIoc upgraded to 1.3.2.
  • Bug: Resolved issue injecting Request & Response into handler classes.


  • Preview release.