← Installation & Configuration | Circumflex Web Framework Documentation | Advanced Concepts → |
Each Circumflex web application is composed of one or more request routers. Request router is a subclass of RequestRouter
which sequentionally defines routes directly within its body:
class Main extends RequestRouter {
get("/") = "Hello world!"
get("/posts/:id") = "Post #" + uri.get("id")
post("/form") = {
// Do some work
// . . .
// Render FreeMarker template:
ftl("/done.ftl")
}
}
Request routers are essentially the controllers of the application. Since Circumflex Web Framework employs the Front Controller pattern, each web application should have a single main router — a special RequestRouter
which gets executed on every request. It dispatches all requests of web application.
Request routers can also be easily nested:
class MainRouter extends RequestRouter {
// with matching
any("/users/*") => new UsersRouter
any("/posts/*") => new PostsRouter
any("/mail/*") => new MailRouter
any("/downloads/*") => new DownloadsRouter
// unconditionally
new MiscRouter
}
It is generally a good practice to have different routers for different tasks — it makes the code modular, more organized and easier to maintain.
Circumflex Web Framework is designed around the route concept. A route is an HTTP method with matching mechanism and attached block.
Routes are defined using one of the following members of RequestRouter
:
get
(matches HTTP GET
requests);post
(matches HTTP POST
requests);put
(matches HTTP PUT
requests);patch
(matches HTTP PATCH
requests);delete
(matches HTTP DELETE
requests);options
(matches HTTP OPTIONS
requests);head
(matches HTTP HEAD
requests);getOrHead
(matches either HTTP GET
or HTTP HEAD
);getOrPost
(matches either HTTP GET
or HTTP POST
);any
(matches any HTTP request).Each route should define a matcher, which describes the conditions a request must satisfy to be matched by the route.
Each route also has an associated block which gets executed if matching succeeds. A block must evaluate to RouteResponse
which will be sent to client (String
and scala.xml.Node
are converted to RouteResponse
implicitly):
class MyRouter extends RequestRouter {
get("/hello/:name.txt") = "Hello, " + param("name") + "!"
get("/hello/:name.xml") = {
val name = param("name")
<hello to={name}/>
}
}
Upon successful matching the block attached to corresponding route gets executed. Internally we use ResponseSentException
, a special control throwable, to indicate that request processing has been finished. This exception is caught by CircumflexFilter
which performs response flushing and finalizes request processing.
Various helpers throw ResponseSentException
instead of yielding RouteResponse
:
get("/") = redirect("/index.html")
Request matching can be performed against request URI and zero or more request headers. The syntax is self-descriptive:
get("/") // matches GET /
get("/posts") // matches GET /posts
post("/posts") // matches POST /posts
You can combine several matchers in one route using the &
method:
get("/mail" & Accept("text/html") & Host("localhost"))
// matches following request:
// GET /mail
// Host: localhost
// Accept: text/html
Routes can include patterns with named parameters which can be accessed in the attached block. The following route matches GET /posts/43
or GET /posts/foo
; the construct uri("id")
is used to capture the parameter from request URI:
get("/posts/:id") = "Post #" + uri("id")
Route patterns may also include wildcard parameters (*
for zero or more characters, +
for one or more characters), they can be accessed via index (starting with 1
like in regex groups):
get("/files/+") = "Downloading file " + uri(1)
You may also refer to the whole match with 0
index:
get("/files/:name.:ext") = {
println("The URI is: " + uri(0))
"Filename is " + uri("name") + ", extension is " + uri("ext")
}
Named parameters are indexed too:
get("*/:two/+/:four") = {
// uri(2) == uri("two")
// uri(4) == uri("four")
(1 to 4).map(i => i + " -> " + uri(i)).mkString("\n")
}
Parameters can also be extracted using the param
helper. Unlike uri
, which represents match results from URI only, the param
helper can extract named parameters from headers:
get("/" & Accept("text/:format")) = "The format is " + param("format")
You can also extract request parameters using param
:
get("/") = "Limit is " + param("limit") + ", offset is " + param.getOrElse("offset", "0")
// >> GET /?limit=50&offset=10
// << Limit is 50, offset is 10
// >> GET /?limit=5
// << Limit is 5, offset is 0
By default static files are served from <webapp_root>/public
directory. You may override this by setting cx.public
configuration parameter.
You can send 302 Found
HTTP redirect:
get("/") = sendRedirect("/index.html")
You can also perform request forwarding (a.k.a. URI rewriting) — the request will be dispatched again, but with different URI):
get("/") = forward("/index.html")
Note that you should add <dispatcher>FORWARD</dispatcher>
to CircumflexFilter
mapping in your web.xml
to make forwarding work. You should also avoid infinite forwarding loops manually.
You can send errors with specific status code and optional message:
get("/") = sendError(500, "We don't work yet.")
You can use the sendFile
helper to send arbitrary file to client:
get("/") = sendFile(new File("/path/to/file.txt"))
You can also specify optional filename
so that Content-Disposition: attachment
could be added to response:
get("/") = sendFile(new File("/path/to/file.txt"), "greetings.txt")
The content type of the file is guessed based on it's extension. You may override it:
get("/") = {
response.contentType("text/plain")
sendFile(new File("/path/to/file.text"), "greetings.txt")
}
You can also use the more efficient xSendFile
helper to delegate the file transfering to your web server. This feature is configured via cx.XSendFile
configuration parameter. Consult your web server documentation to obtain more information on this feature.
You can determine if current request is XmlHttpRequest
:
get("/") = if (request.body.xhr_?) "AJAX" else "plain old request"
You get the contents of request headers using the header
helper:
get("/") = "Serving to host: " + headers("Host")
Dealing with session attributes is fairly easy:
get("/") = {
// get attribute
session("attr1")
// set attribute
session("attr2") = "My value"
}
Flashes provide a way to pass temporary objects between requests:
get("/") = flash.get("note") match {
case Some(value) => "Having a note: " + value
case None => "No notes for now..."
}
post("/") = {
flash("note") = "Hello from POST, folks!"
redirect("/")
}
Anything you place in flash
helper will be exposed until the first lookup and then cleared out. This is a great way of dealing with notices and alerts which only need to be shown once.
← Installation & Configuration | Circumflex Web Framework Documentation | Advanced Concepts → |