AF-BedSheetUser Guide

Fandoc ERRORS:
Line 521 - Invalid annotation []

Overview [#overview]
BedSheet is a [Fantom]`` framework for delivering web applications.

Built on top of [afIoc]`` and [Wisp]``,
BedSheet's main concern is proving a rich mechanism for the routing and delivery of content over HTTP.

BedSheet is inspired by Java's [Tapestry5]``, Ruby's [Sinatra]`` and
Fantom's [Draft]``.

Quick Start [#quickStart]

1. Create an 'AppModule'
2. Contribute to 'Routes' and other services
3. Create some page / request handlers
4. Start your web app...

using afBedSheet
using afIoc

class AppModule {
  static Void contributeRoutes(OrderedConfig conf) {
    conf.add(Route(`/hello/**`, HelloPage#hello))

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

From the command line:

$ fan afBedSheet <mypod>::AppModule 8080
BedSheet v1.0 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 configures [afIoc]``
services. Here we told the `Routes` service to send all request URIs that start with '/hello' to the 'HelloPage#hello' method.
`Route` converts extra URI path segments into method arguments, or in our case, to 'Str name' and an
optional 'Int iq'.

Request handlers are typically what we, the app developers, write. They perform logic processing and render responses. Our
'HelloPage' handler simply renders a plain `Text` response.

A default `ResponseProcessor` then sends the `Text` response to the client.

Starting BedSheet [#startingBedSheet]
BedSheet may be started from the command line with:

$ fan afBedSheet <fully-qualified-app-module-name> <port-number>

For example:

$ fan afBedSheet myWebApp::AppModule 80

Every BedSheet application has an 'AppModule' class that defines and configures your
[afIoc]`` services. It is an
[afIoc]`` concept that allows you centralise your application configuration in
one place.

> TIP: Should your AppModule grow too big, break logical chunks out into their own classes using the afIoc @SubModule facet.

You may find it convenient to create your own BedSheet Main wrapper.

using util

class Main : AbstractMain {

  @Arg { help="The HTTP port to run the app on" }
  private Int port

  override Int run() {
    return afBedSheet::Main().main("<fully-qualified-app-module-name> $port".split)

'<fully-qualified-app-module-name>' may be replaced with '<pod-name>' as long as your pod's '' defines the following

meta = [ ...
         "afIoc.module" : "<fully-qualified-app-module-name>",

Note that 'AppModule' is named so out of convention but may be called anything you like.

HTTP Pipeline [#httpPipeline]
When a http request is received it is passed through a pipeline of filters. The terminating filter performs the standard
BedSheet routing (see [Request Routing]`#requestRouting`). Filters can be used to address cross cutting concerns such as

Standard BedSheet filters are:
 - 'HttpCleanupFilter': Ensures the 'HttpOutStream' is closed and cleans up all request / thread scoped data.
 - 'HttpErrFilter': Catches and processes Errs, see [Error Processing]`#errorProcessing`.
 - `IeAjaxCacheBustingFilter`: Adds cache headers to the response to prevent IE from caching ajax responses.
 - `HttpRequestLogFilter`: Generates request logs in the standard [W3C Extended Log File Format]``.

It is important that the first two filters are always first in the pipeline. As such, when contributing your own filters you are
encouraged to add the ordering constraint of '"after: BedSheetFilters"', example:

@Contribute { serviceType=HttpPipeline# }
static Void contributeHttpPipeline(OrderedConfig conf, AuthFilter myAuthFilter) {

  conf.addOrdered("AuthFilter", myAuthFilter, ["after: BedSheetFilters"])


Request Routing [#requestRouting]
When BedSheet receives a web request, it is matched to a handler for processing. This is configured through the `Routes` service.

@Contribute { serviceType=Routes# }
static Void contributeRoutes(OrderedConfig conf) {

  conf.add(Route(`/index`, IndexPage#service))


BedSheet is bundled with the following Route objs:
 - `Route`: Matches against the request URI, converting deeper path segments into method arguments.

Routes are not limited to matching against uris, you can create your own routes that match against anything, such as http
headers or time of day! Just create a `RouteMatcher` and contribute it to `RouteMatchers`.

Using Draft Routes [#usingDraftRoutes]
If you prefer the [draft]`` style of routing, that's no problem, you can use draft Routes
in BedSheet!

Just add [afBedSheetDraft]`` and [draft]``
as dependencies in your '' (no config required!). Now you can contribute draft routes to BedSheet:

using draft::Route as DraftRoute

@Contribute { serviceType=Routes# }
static Void contributeRoutes(OrderedConfig conf) {

  conf.add(DraftRoute("/", "GET", PageHandler#index))
  conf.add(DraftRoute("/echo/{name}/{age}", "GET", PageHandler#print))


Routing lesson over.

( Aussies may stop giggling now.)

Request Handling [#requestHandling]
Request handlers is where logic is processed and responses are rendered. Handlers generally shouldn't pipe anything to
http response out stream, instead they should return a response object. Example, the above 'HelloPage' handler returns a
`Text` obj.

You usually write the handlers yourself, but BedSheet also bundles with the following request handlers:
 - `FileHandler`: Maps request URIs to files on file system.
 - `PodHandler` : Maps request URIs to pod file resources.
 - `CorsHandler` : A pass through filter for handling Cross Origin Resource Sharing requests.

Request handlers should always return an object to the processed and sent to the client.

Response Processing [#responseProcessing]
[ResponseProcessors]`ResponseProcessor` process the return values from request handlers (`sys::File`, `Text`, etc...) and send
data to the client. 'ResponseProcessors' should return 'true' if no further processing should performed on the http request. Or they
may return another response object for further processing, such as a `Text` obj.

By default, BedSheet handles the following response objects:
 - 'Void' / 'null' / 'false' : Processing should fall through to the next Route match.
 - 'true' : No further processing is required.
 - `Text` : The text (be it plain, json, xml, etc...) is sent to the client with the given `sys::MimeType`.
 - `sys::File` : The file is sent to the client.
 - `HttpStatus` : Sets the http response status and renders a mini html page with the status msg. (See [Error Processing]`#errorProcessing`.)
 - `Redirect` : Sends a 3xx redirect response to the client.
 - `sys::InStream` : The 'InStream' is piped to the client. The 'InStream' is guarenteed to be closed.

For handling the above, BedSheet bundles with the following response processors:
 - 'TextResponseProcessor' : Sends text to the client.
 - 'FileResponseProcessor' : Sends files to the client.
 - `HttpStatusProcessors` : Routes `HttpStatus` to contributed 'HttpStatusProcessors'.
 - 'RedirectResponseProcessor' : Sends a 3xx redirect responses to the client.
 - 'InStreamResponseProcessor' : Pipes an 'InStream' to the client and closes the stream.

Error Processing [#errorProcessing]
When BedSheet catches an Err it scans through its list of contributed [ErrProcessors]`ErrProcessor` to find the closest match.
ErrProcessor's takes an Err and returns a response for further processing (example, `Text`). Or it may return 'true'
if the error has been completely handled and no further processing is required.

BedSheet bundles with Err processors for the following Errs:
 - 'HttpStatusErr' : Returns the wrapped `HttpStatus`.
 - 'Err' : A general catch all processor that wraps (and returns) the Err in a `HttpStatus` 500.

'HttpStatus' responses are handled by `HttpStatusProcessors` which selects a contributed processor dependent on the http status
code. If none are found, a default catch all 'HttpStatusProcessor' sets the http status code and sends a mini html page to the

By default, it is this page you see when you receive a '404 Not Found' error.

404 Not Found [#notFound]
To set your own '404 Not Found' page, contribute to the 'HttpStatusProcessors' service:

  @Contribute { serviceType=HttpStatusProcessors# }
  static Void contributeHttpStatusProcessors(MappedConfig conf) {

    conf[404] = conf.autobuild(Page404#)


500 Server Error [#serverError]
BedSheet bundles with a error handler for '500 Internal Server Error' status codes. This handler renders a very verbose error
page with shed loads of debugging information. Great for development! Not so great for production.

To set your own '500 Internal Server Error' page, override the BedSheet default:

  @Contribute { serviceType=HttpStatusProcessors# }
  static Void contributeHttpStatusProcessors(MappedConfig conf) {

    // override because BedSheet supplies one by default
    conf.setOverride(500, "MyErrPage", conf.autobuild(Page500#))


Inject Config [#injectConfig]
BedSheet extends [afIoc]`` to give injectable '@Config' values. '@Config' values
are essesntially a map of Str to immutable / constant values that may be set and overriden at application start up. (Consider
config values to be immutable once the app has started).

BedSheet sets the initial config values by contributing to the `FactoryDefaults` service. An application may then override
these values by contibuting to the `ApplicationDefaults` service.

  @Contribute { serviceType=ApplicationDefaults# }
  static Void contributeApplicationDefaults(MappedConfig conf) {
    conf["afBedSheet.errPrinter.noOfStackFrames"] = 100

All BedSheet config keys are listed in `ConfigIds` meaning the above can be more safely rewriten as:

  conf[ConfigIds.noOfStackFrames] = 100

To inject config values in your services, use the '@Config' facet with conjunction with
[afIoc]``'s '@Inject':

  @Inject	@Config { id="afBedSheet.errPrinter.noOfStackFrames" }
  Int noOfStackFrames

The config mechanism is not just for BedSheet, you can use it too when creating add-on libraries! Contributing to
`FactoryDefaults` gives users of your library an easy way to override your values.

Development Proxy [#DevelopmentProxy]
Never (manually) restart your app again!

Use the '-proxy' option to create a Development Proxy to auto re-start your app when any of your pods are updated:

$ fan afBedSheet -proxy <mypod> <port>

The proxy sits on '<port>', starts your real app on '<port>+1' and forwards all requests to it.

  Client <--> Proxy (port) <--> Web App (port+1)

A problem other (Fantom) web development proxies suffer from, is that when the proxy dies your real web app is left hanging
around; requiring you to manually kill it.

  Client <-->   ????????   <--> Web App (port+1)

BedSheet goes a step further and, should it be started in proxy mode, it pings the proxy every second to stay alive. Should
the proxy not respond, the web app kills itself.

See `ConfigIds.proxyPingInterval` for more details.

Gzip [#gzip]
By default, BedSheet compresses HTTP responses with gzip where it can.(1) But it doesn't do this willy nilly, oh no! There are
many hurdles to overcome...

Disable All [#disablePerWebApp]
Gzip, although enabled by default, can be disabled for the entire web app by setting the following config property:

    config[ConfigIds.gzipDisabled] = true

Disable per Response [#disablePerResponse]
Gzip can be disabled on a per request / response basis by calling the following:


Gzip'able Mime Types [#mimeTypes]
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]`sys::MimeType`:

    config["text/funky"] = true

(Note: The GzipCompressible contrib type is actually `sys::MimeType` - [afIoc]``
kindly coerces the 'Str' to 'MimeType' for us.)

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

Gzip only when asked [#askNicely]
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!

Min content threshold [#minThreshold]
Gzip is great when compressing large files, but if you've only got a few bytes to squash... 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.

See 'GzipOutStream' and `ConfigIds.gzipThreshold` for more details.

Phew! Made it! [#madeIt]
If (and only if!) your request passed all the tests above, will it then be lovingly gzipped and sent to the client.

 - (1) ``

Buffered Response [#bufferedResponse]
By default, BedSheet attempts to set the 'Content-Length' http response header.(2) It does this by buffering
'HttpResponse.out'. When the stream is closed, it writes the 'Content-Length' and pipes the buffer to the real http response.

Response buffering can be disabled on a per http response basis.

A threshold can be set, whereby if the buffer exeeds that value, all content is streamed directly to the client.

See 'BufferedOutStream' and `ConfigIds.responseBufferThreshold` for more details.

 - (2) ``

Testing [#testing]
BedSheet ships with a means of testing your web app without starting the wisp server. This forgoes the overhead of starting a
real web server, opening real ports and making real http requests. Essentially you're testing the entire app, just cutting
out the middle man.

Use `BedServer` to start an instance of BedSheet, and then the re-usable `BedClient` to serve (fake) http requests. The
benifits (besides the obvious performance ones) include:
 - override real services with test servies / configuration
 - inject your [afIoc]`` services direct into your test class
 - inspect your client's `web::WebSession`
 - cookies are auto sent on each http request

See below for a test example:

using afIoc
using afBedSheet

class TestWebApp : Test {
  BedServer? server

  @Inject UserDAO? userDAO            // inject your services

  override Void setup() {
    server = BedServer(AppModule#)
    server.addModule(TestOverrides#)  // override services and config with test values
    server.injectIntoFields(this)     // inject your services

  override Void teardown() {

  Void testIndexPage() {
    // given
    client := server.makeClient

    // when
    res := client.get(`/index`)

    // then

Tips [#tips]
All request 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.

Even if your request handlers aren't services, if they're 'const' classes, they're cached by BedSheet and reused on every request.

Go Live with Heroku [#goLive]
In a hurry to go live? Use [Heroku]``!

[Heroku]`` and the [heroku-fantom-buildpack]``
makes it ridiculously to deploy your web app to a live server. Just check in your code and Heroku will build your web app from
source and deploy it to a live environment!

To have Heroku run your BedSheet web app you have 2 options:

1) Create a Heroku text file called 'Procfile' at the same level as your '' with the following line:

web: fan afBedSheet <fully-qualified-app-module-name> $PORT

substituting '<fully-qualified-app-module-name>' with, err, your fully qualified app module name! Example, 'MyPod::AppModule'.
Type '$PORT' verbatim, as it is.

2) Create a Main class in your app:

using util

class Main : AbstractMain {

  @Arg { help="The HTTP port to run the app on" }
  private Int port

  override Int run() {
    return afBedSheet::Main().main("<fully-qualified-app-module-name> $port".split)

Main classes have the advantage of being easy to run from an IDE or cmd line.

See [heroku-fantom-buildpack]`` for more details.

Release Notes [#releaseNotes]

v1.0.8 [#v1.0.8]
 - Chg: Updated to use 'afIoc-1.4.0'
 - Chg: Overhauled `Route` to match 'null' values. Thanks go to [LightDye]''.
 - Chg: Warnings on startup if an AppModule could not be found - see [Issue #1]``. Thanks go to Jorge Ortiz.
 - Chg: Better Err handling when a dir is not mapped to 'FileHandler'
 - Chg: Transferred VCS ownership to [AlienFactory]``
 - Chg: Test code is no longer distributed with the afBedSheet.pod.

v1.0.6 [#v1.0.6]
 - Chg: `HttpResponse.statusCode` is now a field.
 - Chg: `HttpResponse.disableGzip` is now a field.
 - Chg: `HttpResponse.disableBuffering` is now a field.

v1.0.4 [#v1.0.4]
 - New: Added `BedServer` and `BedClient` to test BedSheet apps without using a real web server.

v1.0.2 [#v1.0.2]
 - New: Initial release