BedSheetUser Guide
Overview
BedSheet
is a platform for delivering web applications written in Fantom.
Built on top of IoC and Wisp, BedSheet provides a rich middleware mechanism for the routing and delivery of content over HTTP.
BedSheet is inspired by Java's Tapestry5, Ruby's Sinatra and Fantom's Draft.
Install
Install BedSheet
with the Fantom Repository Manager ( fanr ):
C:\> fanr install -r http://repo.status302.com/fanr/ afBedSheet
To use in a Fantom project, add a dependency to build.fan
:
depends = ["sys 1.0", ..., "afBedSheet 1.4"]
Documentation
Full API & fandocs are available on the Status302 repository.
Quick Start
- Create a text file called
Example.fan
:using afIoc using afBedSheet class HelloPage { Text hello(Str name, Int iq := 666) { return Text.fromPlain("Hello! I'm $name and I have an IQ of $iq!") } } class AppModule { @Contribute { serviceType=Routes# } static Void contributeRoutes(Configuration conf) { conf.add(Route(`/index`, Text.fromHtml("<html><body>Welcome to BedSheet!</body></html>"))) conf.add(Route(`/hello/**`, HelloPage#hello)) } } class Example { Int main() { BedSheetBuilder(AppModule#.qname).startWisp(8080) } }
- Run
Example.fan
as a Fantom script from the command line:C:\> fan Example.fan -env development [info] [afBedSheet] Found mod 'Example_0::AppModule' [info] [afIoc] Adding module definitions from pod 'Example_0' [info] [afIoc] Adding module definition for Example_0::AppModule [info] [afIoc] Adding module definition for afBedSheet::BedSheetModule [info] [afIoc] Adding module definition for afIocConfig::ConfigModule [info] [afIoc] Adding module definition for afIocEnv::IocEnvModule [info] [afBedSheet] Starting Bed App 'Example_0::AppModule' on port 8080 [info] [web] WispService started on port 8080 40 IoC Services: 10 Builtin 26 Defined 0 Proxied 4 Created 65.00% of services are unrealised (26/40) ___ __ _____ _ / _ | / /_____ _____ / ___/__ ___/ /_________ __ __ / _ | / // / -_|/ _ /===/ __// _ \/ _/ __/ _ / __|/ // / /_/ |_|/_//_/\__|/_//_/ /_/ \_,_/__/\__/____/_/ \_, / Alien-Factory BedSheet v1.4.8, IoC v2.0.6 /___/ IoC Registry built in 210ms and started up in 20ms Bed App 'Example_0' listening on http://localhost:8080/
- Visit
localhost
to hit the web application:C:\> curl http://localhost:8080/index <html><body>Welcome to BedSheet!</body></html> C:\> curl http://localhost:8080/hello/Traci/69 Hello! I'm Traci and I have an IQ of 69! C:\> 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 BedSheet
application has an AppModule
that configures IoC services. Here we told the Routes service to return some plain text in response to /index
and to call the HelloPage#hello
method for all requests that start with /hello
. Route converts URL path segments into method arguments, or in our case, to Str name
and to an optional Int iq
.
Route handlers are typically what we, the application developers, write. They perform logic processing and render responses. Our HelloPage
route handler simply returns a plain Text response, which BedSheet
sends to the client via an appropriate ResponseProcessor.
Starting BedSheet
Every Bed App (BedSheet Application) has an AppModule
class that defines and configures your IoC services. It is an IoC concept that allows you centralise your application's configuration in one place. It is the AppModule
that defines your Bed App and is central everything it does.
To start BedSheet
from the command line, you need to tell it where to find the AppModule
and which port to run on:
C:\> fan afBedSheet -env development <fully-qualified-app-module-name> <port-number>
For example:
C:\> fan afBedSheet -env development myWebApp::AppModule 8069
TIP: Should your AppModule grow too big, break logical chunks out into their own classes using the @SubModule facet.
<fully-qualified-app-module-name>
may be replaced with just <pod-name>
as long as your pod's build.fan
defines the following meta:
meta = [ ... ... "afIoc.module" : "<fully-qualified-app-module-name>" ]
This allows BedSheet
to look up your AppModule
from the pod. Example:
C:\> fan afBedSheet -env development myWebApp 8069
Note that AppModule
is named so out of convention but the class may be called anything you like.
Request Routing
The Routes
service maps HTTP request URLs to response objects and handler methods. It is where you would typically define how requests are handled. You configure the Routes
service by contributing instances of Route. Example:
using afIoc using afBedSheet class AppModule { @Contribute { serviceType=Routes# } static Void contributeRoutes(Configuration conf) { conf.add(Route(`/home`, Redirect.movedTemporarily(`/index`))) conf.add(Route(`/index`, IndexPage#service)) conf.add(Route(`/work`, WorkPage#service, "POST")) } }
Route objects take a matching glob
and a response object. A response object is any object that BedSheet
knows how to process or a Method
to be called. If a method is given, then request URL path segments are matched to the method parameters. See Route for more details.
Routing lesson over.
(...you Aussies may stop giggling now.)
Route Handling
Route Handler is the name given to a class or method that is processed by a Route
. They process logic and generally don't pipe anything to the HTTP response stream. Instead they return a Response Object for further processing. For example, the Quick Start HelloPage
route handler returns a Text response object.
Route handlers are usually written by the application developer, but a couple of common use-cases are bundled with BedSheet
:
- FileHandler: Maps request URLs to files on the file system.
- PodHandler : Maps request URLs to pod file resources.
Response Objects
Response Objects are returned from Route Handlers. It is then the job of Response Processors to process these objects, converting them into data to be sent to the client. Response Processors may themselves return Response Objects, which will be handled by another Response Processor.
You can define Response Processors and process Response Objects yourself; but by default, BedSheet
handles the following:
Void
/null
/false
: Processing should fall through to the next Route match.true
: No further processing is required.- Err : An appropriate response object is selected from contributed Err responses. (See Error Processing.)
- File : The file is streamed to the client.
- FileAsset : The file is streamed to the client.
- Func : The function is called, using IoC to inject the parameters. The return value is treated as reposonse object for further processing.
- HttpStatus : An appropriate response object is selected from contributed HTTP status responses. (See HTTP Status Processing.)
- InStream : The
InStream
is piped to the client. TheInStream
is guaranteed to be closed. - MethodCall : The method is called and the return value used for further processing.
- Redirect : Sends a 3xx redirect response to the client.
- Text : The text (be it plain, json, xml, etc...) is sent to the client with a corresponding
Content-Type
.
Because of the nature of response object processing it is possible, nay normal, to chain multiple response objects together. Example:
- If a Route returns or throws an
Err
, ErrProcessor
looks up its responses and returns aFunc
,FuncProcessor
calls a handler method which returns aText
,TextProcessor
serves content to the client and returnstrue
.
Note that response object processing is extensible, just contribute your own Response Processor.
Template Rendering
Templating, or formatting text (HTML or otherwise) is left for other 3rd party libraries and is not a conern of BedSheet
. That said, there a couple templating libraries out there and integrating them into BedSheet
is relatively simple. For instance, Alien-Factory provides the following libraries:
- efan for basic templating,
- Slim for concise HTML templating, and
- Pillow for integrating efanXtra components (may be used with Slim!)
Taking Slim as an example, simply inject the Slim service into your Route Handler and use it to return a Text
response object:
using afIoc::Inject using afBedSheet::Text using afSlim::Slim class IndexPage { @Inject Slim? slim Text render() { html := slim.renderFromFile(`xmas.html.slim`.toFile) return Text.fromHtml(html) } }
BedSheet Middleware
When a HTTP request is received, it is passed through a pipeline of BedSheet Middleware; this is a similar to Java Servlet Filters. If the request reaches the end of the pipeline without being processed, a 404 is returned.
Middleware bundled with BedSheet
include:
RequestLog
: Generates request logs in the standard W3C Extended Log File Format.Routes
: Performs the standard request routing
You can define your own middleware to address cross cutting concerns such as authentication and authorisation. See the FantomFactory article Basic HTTP Authentication With BedSheet for working examples.
Error Processing
When BedSheet
catches an Err it scans through a list of contributed response objects to find one that can handle the Err. If no matching response object is found then the default err response object is used. This default response object displays BedSheet's extremely verbose Error 500 page. It displays (a shed load of) debugging information and is highly customisable:
The BedSheet Err page is great for development, but not so great for production - stack traces tend to scare Joe Public! So note that in a production environment (see IocEnv) a simple HTTP status page is displayed instead.
ALIEN-AID: BedSheet defaults to production mode, so to see the verbose error page you must switch to development mode. The easiest way to do this is to set an environment variable called
ENV
with the valuedevelopment
. See IocEnv details.
To handle a specific Err, contribute a response object to ErrResponses
:
@Contribute { serviceType=ErrResponses# } static Void contributeErrResponses(Configuration config) { config[ArgErr#] = MethodCall(MyErrHandler#process).toImmutableFunc }
Note that in the above example, ArgErr
and all subclasses of ArgErr
will be processed by MyErrHandler.process()
. A contribute for just Err
will act as a capture all and be used should a more precise match not be found. You could also replace the default err response object:
@Contribute { serviceType=ApplicationDefaults# } static Void contributeApplicationDefaults(Configuration config) { config[BedSheetConfigIds.defaultErrResponse] = Text.fromHtml("<html><b>Oops!</b></html>") }
Err
objects are stored in the HttpRequest.stash
map and may be retrieved by handlers with the following:
err := (Err) httpRequest.stash["afBedSheet.err"]
HTTP Status Processing
HttpStatus
objects are handled by a ResponseProcessor that selects a contributed response object that corresponds to the HTTP status code. If no specific response object is found then the default http status response object is used. This default response object displays BedSheet's HTTP Status Code page. This is what you see when you receive a 404 Not Found
error.
To set your own 404 Not Found
page contribute a response object to HttpStatusResponses service with the status code 404
:
@Contribute { serviceType=HttpStatusResponses# } static Void contribute404Response(Configuration config) { conf[404] = MethodCall(Error404Page#process).toImmutableFunc }
In the above example, all 404 status codes will be processed by Error404Page.process()
.
To replace all status code responses, replace the default HTTP status response object:
@Contribute { serviceType=ApplicationDefaults# } static Void contributeApplicationDefaults(Configuration config) { config[BedSheetConfigIds.defaultHttpStatusResponse] = Text.fromHtml("<html>Error</html>") }
HttpStatus
objects are stored in the HttpRequest.stash
map and may be retrieved by handlers with the following:
httpStatus := (HttpStatus) httpRequest.stash["afBedSheet.httpStatus"]
Config Injection
BedSheet uses IoC Config to give injectable @Config
values. @Config
values are essentially 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 contributing to the ApplicationDefaults
service.
@Contribute { serviceType=ApplicationDefaults# } static Void contributeApplicationDefaults(Configuration conf) { ... conf["afBedSheet.errPrinter.noOfStackFrames"] = 100 ... }
All BedSheet config keys are listed in BedSheetConfigIds meaning the above can be more safely rewriten as:
conf[BedSheetConfigIds.noOfStackFrames] = 100
To inject config values in your services, use the @Config
facet with conjunction with IoC'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 3rd Party libraries! Contributing initial values to FactoryDefaults
gives users of your library an easy way to override your values.
RESTful Services
BedSheet can be used to create RESTful applications. The general approach is to use Routes
to define the URLs and HTTP methods that your app responds to.
For example, for a POST
method:
class RestAppModule { @Contribute { serviceType=Routes# } static Void contributeRoutes(Configuration conf) { conf.add(Route(`/restAPI/*`, RestService#post, "POST")) } }
The routes then delegate to methods on a RouteHandler
service:
using afIoc using afBedSheet class RestService { @Inject HttpRequest httpRequest @Inject HttpResponse httpResponse new make(|This| in) { in(this) } Text post(Int id) { // use the request body to get submitted data as... // a [Str:Str] form map or form := httpRequest.body.form // as JSON objects json := httpRequest.body.jsonObj // return a different status code, e.g. 201 - Created httpResponse.statusCode = 201 // return plain text or JSON objects to the client return Text.fromPlainText("OK") } }
File Uploading
File uploading can be pretty horrendous in other languages, but here in Fantom land it's pretty easy.
First create your HTML. Here's a form snippet:
<form action="/uploadFile" method="POST" enctype="multipart/form-data"> <input name="theFile" type="file" /> <input type="submit" value="Upload File" /> </form>
A Route
should then service the /uploadFile
URL:
class RestAppModule { @Contribute { serviceType=Routes# } static Void contributeRoutes(Configuration conf) { conf.add(Route(`/uploadFile`, UploadService#uploadFile, "POST")) } }
The UploadService
uses HttpRequest.parseMultiPartForm()
to gain access to the uploaded data and save it as a file:
using afIoc using afBedSheet class UploadService { @Inject HttpRequest? httpRequest Text uploadFile() { httpRequest.parseMultiPartForm |Str formName, InStream in, Str:Str headers| { // this closure is called for each file in the form quoted := headers["Content-Disposition"].split(';').find { it.startsWith("filename") }.split('=')[1] filename := WebUtil.fromQuotedStr(quoted) // save file to temp dir file := Env.cur.tempDir.createFile(filename) in.pipe(file.out) file.out.close } return Text.fromPlain("OK") } }
Request Logging
BedSheet can generate standard HTTP request logs in the W3C Extended Log File Format.
To enable, just configure the directory where the logs should be written and (optionally) set the log filename, or filename pattern for log rotation:
@Contribute { serviceType=ApplicationDefaults# } static Void contributeApplicationDefaults(Configuration conf) { conf[BedSheetConfigIds.requestLogDir] = `/my/log/dir/` conf[BedSheetConfigIds.requestLogFilenamePattern] = "bedSheet-{YYYY-MM}.log" }
Ensure the log dir ends in a trailing /slash/.
The fields writen to the logs may be set by configuring BedSheetConfigIds.requestLogFields
, but default to looking like:
2013-02-22 13:13:13 127.0.0.1 - GET /doc - 200 222 "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) etc" "http://localhost/index"
Gzip
By default, BedSheet compresses HTTP responses with gzip where it can, for optimisation. 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[BedSheetConfigIds.gzipDisabled] = true
Gzip'able Mime Types
Text files gzip very well and yield high compression rates, but not everything should be gzipped. For example, JPG images are already compressed when gzip'ed often end up larger than the original! For this reason only Mime Types contributed to the GzipCompressible service will be gzipped:
config["text/funky"] = true
(Note: The GzipCompressible
contrib type is actually sys::MimeType - IoC kindly coerces the Str
to MimeType
for us.)
By default BedSheet will compress html, css, javascript, json, xml and other text responses.
Disable per Response
Gzip can be disabled on a per request / response basis by calling the following:
httpResponse.disableGzip()
Gzip only when asked
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, the response is only gzipped if the appropriate HTTP request header was set.
Min content threshold
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 compression! For that reason the response data must reach a minimum size / threshold before it gets gzipped.
See GzipOutStream
and gzipThreshold for more details.
Phew! Made it!
If (and only if!) the request passed all the tests above, will it then be lovingly gzipped and sent to the client.
Buffered Response
By default, BedSheet attempts to set the Content-Length
HTTP response header. 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 responseBufferThreshold for more details.
Development Proxy
Never (manually) restart your app again!
Use the -proxy
option when starting BedSheet to create a development Proxy and your app will auto re-start when a pod is updated:
C:\> fan afBedSheet -proxy <mypod> <port>
The proxy sits on <port>
and starts your real app on <port>+1
, forwarding 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 applications go 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 proxyPingInterval for more details.
Wisp Integration
To some, BedSheet may look like a behemoth web framework, but it is in fact just a standard Fantom WebMod. This means it can be plugged into a Wisp application along side other all the other standard webmods. Just create an instance of BedSheetWebMod and pass it to Wisp like any other.
For example, the following Wisp application places BedSheet under the path poo/
.
using concurrent using wisp using webmod using afIoc using afBedSheet class Example { Void main() { reg := BedSheetBuilder(AppModule#.qname).buildRegistry mod := RouteMod { it.routes = [ "poo" : BedSheetWebMod(reg) ]} WispService { it.port=8069; it.root=mod }.install.start Actor.sleep(Duration.maxVal) } } ** A tiny BedSheet app that returns 'Hello Mum!' for every request. class TinyBedAppModule { @Contribute { serviceType=Routes# } static Void contributeRoutes(Configuration conf) { conf.add(Route(`/***`, Text.fromPlain("Hello Mum!"))) } }
When run, a request to http://localhost:8069/
will return a Wisp 404 and any request to http://localhost:8069/poo/*
will invoke BedSheet and return Hello Mum!
.
When running BedSheet under a non-root path, be sure to transform all link hrefs with BedSheetServer.toClientUrl() to ensure the extra path info is added. Similarly, ensure asset URLs are retrieved from the FileHandler service.
Note that each BedSheetWebMod
holds a reference to it's own IoC registry so you can run mulitple BedSheet instances side by side in the same Wisp application.
Go Live with Heroku
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:
- Create a Heroku text file called
Procfile
at the same level as yourbuild.fan
with the following line:web: fan afBedSheet <app-name> $PORT
substituting
<app-name>
with your fully qualified app module name. Type$PORT
verbatim, as it is. Example:web: fan afBedSheet acme::AppModule $PORT
Now Heroku will start BedSheet, passing in your app name.
OR
- 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() { BedSheetBuilder(AppModule#.qname).startWisp(port) } }
Main classes have the advantage of being easy to run from an IDE or cmd line.
See heroku-fantom-buildpack for more details.
Tips
All route handlers and processors are built by IoC so feel free to @Inject
DAOs and other services.
BedSheet itself is built with IoC so look at the BedSheet Source for IoC examples.
Even if your route handlers aren't services, if they're const
classes, they're cached by BedSheet and reused on every request.
Release Notes
v1.4.8
- New: Use
BedSheetBuilder
to create BedSheet IoC Registries and start Wisp servers. - New:
HttpRequestBody
for convenience methods to access the request body. (Similar to Butter.) - Chg: Renamed
Text.json()
->Text.jsonObj()
.Text.json()
now takes a Str. (Potential breaking change.) - Chg:
FileHandler.fromLocalUrl()
andFileHandler.fromServerFile()
now take achecked
parameter. - Chg:
FileAssetProcessor
now copes with duffIf-None-Match
Etag requests headers.
v1.4.6
- Chg: BedSheet now compatible with Fantom v1.0.67 - issues with gzip and the dev proxy.
- Bug: BedSheet sometimes reported a
NullErr
when starting up in dev mode.
v1.4.4
- Bug: Startup Errs thrown by FileHandler (and middleware in general) ended up in limbo and weren't reported.
v1.4.2
- Chg: HTTP requests received when BedSheet is starting up are given a 500 status code and a starting up message. Previous behaviour was to queue up the requests, but that can cause issues under heavy load.
- Chg:
HttpRequest.form
throws aHttpStatusErr
(400 - Bad Request) should the form data be invalid - see sys::Uri.decodeQuery. SpamBots send you all sorts of crap! - Bug: BedSheet welcome page had the 404 page appended to it - Firefox reported the page as junk!
v1.4.0
- New:
Errs
andFuncs
may be used as response objects - newResponseProcessors
added. - New: Added
HttpRequest.parseMultiPartForm()
. - New: Added Wisp Integration section to the docs.
- Chg: Err processing updated; gone are bespoke
ErrProcessors
, contribute generic repsonse objects instead. (Breaking change.) - Chg: HTTP Status processing updated; gone are bespoke
HTTPStatusProcessors
, contribute generic repsonse objects instead. (Breaking change.) - Chg: Replaced the
HttpFlash
class withHttpSession.flash()
. (Breaking change.) - Chg: Converted
Route
to a mixin so users can contribute their own implementations. - Chg:
Route
matches against the entireHttpRequest
, not just the URL and HTTP Method. - Chg: Deprecated
Route
methods:routeRegex
,httpMethod
,response
. - Chg: Overhauled the
Route
matching, argument conversion and documentation. (Possible breaking change.) - Chg: Nullability within
ValueEncoders
has now been property addressed. (Breaking change inValueEncoder
mixin.) - Chg:
Middleware
now returnsVoid
notBool
. (Breaking change.) - Chg: Removed deprecated methods. (Breaking change.)
- Chg:
BedSheetServer.toAbsoluteUrl()
now takes a client URL, not a local URL. - Chg:
HttpResponse.saveAsAttachment()
also sets theContent-Type
HTTP response header. - Chg: Gave
FileAsset
a public ctor. - Chg: All BedSheet services have qualified names.
- Chg:
.csv
files are gzip compressible. - Bug: Route method params are now correctly URL decoded - see URI Encoding / Decoding.
- Bug: Logs were overly spammed with repeated warning msgs should the client close its socket connection early.
- Bug:
MethodCall
responses could not call static methods. - Bug: Route HTTP methods were not case-insenstive.
- Bug:
FileAsset.toStr()
sometimes threw an Err. - Bug: HTTP Flash objects no longer need to be immutable, just serialisable.
v1.3.16
- Chg: Updated to use IoC 2.0.0 and IoC Config 1.0.16.
- Chg: Default cache HTTP headers for
FileAssets
are only set in prod.
v1.3.14
- New:
SafeOutStream
doesn't throw anIOErr
should the client close the connection early. - New:
PodHandler
has a whitelist of files allowed to be served. - Chg: Revamped
PodHandler
, now containsfromLocalUrl()
andfromPodResource()
to parallelFileHandler
. - Chg: Deleted
BedSheetMetaData
; not that you should have been using it anyway! - Chg:
BedSheetWebMod
no longer takes a BedSheet options map, all values have been merged with Registry options. - Bug: Development Proxy process did not work on Mac OS-X - Thanks to LightDye for Reporting.
v1.3.12
- New: Static files are served with a default
Cache-Control
HTTP response header, change via Ioc Config. - New: Added
BedSheetServer
to replaceBedSheetMetaData
, contains methods for generating absolute URLs. - New: Added
toClientUrl()
andtoAbsoluteUrl()
toBedSheetServer
. - New: Added
HttpResponseHeaders.vary
to responses that could be gzipped. - Chg: Updated to use IoC 1.7.2.
- Chg: Renamed
HttpRequest.modRel
->HttpRequest.url
.], deprecatedabsUri
,modBase
,modRel
anduri
. - Chg:
HttpSession
fails fast on attempts to store non-serialisable values. - Chg: Gzip responses also set a HTTP header of
Vary: Accept-Encoding
. - Chg: Added the
Cache-Control
HTTP response header to error pages to ensure they're never cached. - Chg: Renamed
Text.fromMimeType()
->Text.fromContentType()
. - Chg:
HttpRequestHeaders.host
is now aStr
.
v1.3.10
- New:
FileAsset
objects are generated by theFileHandler
service and containclientUrls
for your web page. - Chg:
ValueEncoders
service now letsReProcessErrs
pass through un-hindered. - Chg: Overhauled
FileHandler
API. (Breaking change) - Chg: Renamed
Text.mimeType
->Text.contentType
.
v1.3.8
- New: Set response headers
X-BedSheet-errMsg
,X-BedSheet-errType
andX-BedSheet-errStackTrace
when processing an Err. (Dev mode only) - New: Boring stack frames on the Err500 page are muted (greyed) and may be toggled on and off completely via a checkbox.
- New:
FileHandler
now responds toHEAD
requests. - New: Introduced a
FileMetaCache
to prevent excessive hits on the file system. - Chg: Updated to use IoC 1.6.2 and Bean Utils.
- Chg: BedSheet pages (404, 500 & Welcome) are served up as XHTML.
- Chg:
ReProcessErr
may now re-process non-const response objects. - Bug:
FlashMiddleware
needlessly created http sessions.
v1.3.6
- New: Added
ActorPools
section to Err500 page. - Chg: Updated to use IoC 1.6.0 and Concurrent.
- Chg:
Routes
now perform some basic validation to catch cases where the Uri would never match the method handler. - Chg: Atom (RSS) feeds
application/atom+xml
are now GZip compressible. - Chg: Not found requests for HTTP methods other than GET or POST return a 501, not 404.
- Chg: All BedSheet pages render as valid XML.
- Bug: If 2
Routes
had the same Regex, only 1 was shown on the 404 page. - Bug:
HttpFlash
data could leak into concurrent web requests.
v1.3.4
- New: Added
fromClientUri()
andfromServerFile()
toFileHandler
. - New:
IocConfig
values, BedSheet Routes and Fantom Pods are now printed on the standard Err page. - Chg: Added some handy
toStr
methods toRoute
and response objects. - Chg: Pretty printed the Str maps that get logged on Err.
v1.3.2
- New: Added
appName
toBedSheetMetaData
that returns theproj.name
from the application's pod meta. - Chg: Added
matchesMethod()
andmatchesParams()
helper methods toRouteResponseFactory
. - Chg: Made
ErrPrinterStr
andErrPrinterHtml
public, but@NoDoc
, as they're useful for emails et al. - Chg: Made
HttpRequestHeaders
andHttpResponsetHeaders
const classes, backed byWebReq
andWebRes
. - Bug: Ensured
HttpRequest.modRel
always returns a path absolute uri - see Inconsistent WebReq::modRel() - Bug: Application could NPE on startup if an
AppModule
could not be found.
v1.3.0
- New: Added
HttpCookies
service, removed corresponding cookie methods fromHttpRequest
andHttpResponse
. (Breaking change) - New: Added
stash()
toHttpRequest
- New: Added
fromXhtml(...)
toText
- New: Added
contentLength()
andcookie()
toHttpRequestHeaders
- New:
MethodCallResponseProcessor
uses IoC to call methods so that it may inject any dependencies / services as method arguments. - New: Added
StackFrameFilter
to filter out lines in stack traces. - New: Added
host
toBedSheetConfigIds
, mainly for use by 3rd party libraries. - Chg: Upgraded to IoC 1.5.2.
- Chg: Removed
BedServer
andBedClient
, they have been moved to Bounce. (Breaking change) - Chg: Removed
@Config
, use@afIocConfig::Config
instead. (Breaking change) - Chg: Renamed
HttpPipelineFilter
->Middleware
and updated the corresponding services. Hardcoded the default BedSheet filters / middleware to the start of the pipeline. (Breaking change) - Chg: Renamed
HttpRequestLogFilter
->RequestLogMiddleware
and updated the@Config
values. (Breaking change) - Chg:
@NoDoc
ed some services as they're only referenced by@Contribute
methods:ErrProcessors, HttpStatusProcessors, ResponseProcessor, ValueEncoders
. - Chg:
QualityValues
are nullable fromHttpRequestHeaders
v1.2.4
- Chg: Upgraded to IoC 1.5.0.
- Chg: Upgraded to IoC Config 1.0.0.
v1.2.2
- New: Added
gzip
compression for web fonts. - New: BedSheet connection details printed on startup.
- Chg:
FileHandler
now lets non-existant files fall through. - Chg:
FileHandler
auto addsRoute
mappings to theRoutes
service. - Chg: Added more info to the BedSheet 404 page in dev.
- Chg: Gave more control over the verbose rendering of the standard
BedSheet
pages. - Bug:
BedServer
generated the wrong info forBedSheetMetaData
- required when testing Pillow web apps.
v1.2.0
- New: Route objects may take any response result - not just
Methods
! - New: BedSheet now has a dependency on IoC Env
- Chg:
HttpRequestLogFilter
is now in the Http Pipeline by default - it just needs enabling. - Chg: The detailed BedSheet Err500 page is disabled in
production
environments. - Chg: Rejigged how the default
ErrProcessor
is used, making it easier to plug in your own. (Breaking change.) - Chg:
BedSheetConfigIds
renamed fromConfigIds
. (Breaking change.) - Chg: Removed Route Matching -
Routes
now only takeRoute
objects. (Breaking change.) - Chg: Removed
IeAjaxCacheBustingFilter
with no replacement. (Breaking change.) - Chg: Removed
CorsHandler
with no replacement. (Breaking change.) - Chg: Massaged a lot of the documentation.
v1.1.4
- New: The cause of startup Errs are printed before service shutdown - see this topic.
- Chg: Better Err msg if
AppModule
type not found on startup. - Chg: Disabled afIoc service list on startup.
- Bug:
BedServer
would crash if the app requiredBedSheetMetaData
.
v1.1.2
- New: Added
Causes
section to Err500 page. - Chg: Faster startup times when using a proxy
- Chg: Better Err handling on app startup
- Bug: Transitive dependencies have been re-instated.
- Bug: The
-noTransDeps
startup option now propogates through the proxy.
v1.1.0
- New: Added
BedSheetMetaData
with information on whichAppModule
afbedSheet was started with. - Chg: Renamed
RouteHandler
->MethodInvoker
. (Breaking change.) - Chg: Injectable services are now documented with
(Service)
. - Chg: Moved internal proxy options in
Main
to their own class. - Chg: Enabled multi-line quotes.
- Bug:
IoC Config
was not always added as a transitive dependency. (Thanks toLightDye
for reporting.)
v1.0.16
- New: Added
Available Values
section to Err500 page, fromafIoc::NotFoundErr
. - Chg: Broke
@Config
code out into its own module: IoC Config. - Chg: Added a skull logo to the
Err500
page. - Chg: Rejigged the
Err500
section layout and tweaked the source code styling.
v1.0.14
- New:
SrcCodeErrs
from afPlastic / efan are printed in the default Err500 pages. - New: Added
ConfigSource.getCoerced()
method. - New: Added Template Rendering to fandoc.
v1.0.12
- New: Added
IoC Operation Trace
section to Err500 page. - New: Added
Moustache Compilation Err
section to Err500 page. - Chg: Moved Moustache out into it's own project.
- Chg: Anyone may now contribute sections to the default verbose Err500 page.
- Bug: Module name was not always found correctly on startup.
v1.0.10
- Bug: This documentation page didn't render.
v1.0.8
- Chg: Updated to use
afIoc-1.4.x
- 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
- Chg:
HttpResponse.statusCode
is now a field. - Chg:
HttpResponse.disableGzip
is now a field. - Chg:
HttpResponse.disableBuffering
is now a field.
v1.0.4
- New: Added
BedServer
andBedClient
to test BedSheet apps without using a real web server.
v1.0.2
- New: Initial release