← Installation & Configuration Circumflex Web Framework Documentation Advanced Concepts →

Basic Concepts

Request Routers

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.

Routes

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:

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")

Matchers

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

Parameters

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

Serving Static files

By default static files are served from <webapp_root>/public directory. You may override this by setting cx.public configuration parameter.

Redirecting & Forwarding

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.

Sending Errors

You can send errors with specific status code and optional message:

get("/") = sendError(500, "We don't work yet.")

Sending Files

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.

Handling AJAX Requests

You can determine if current request is XmlHttpRequest:

get("/") = if (request.body.xhr_?) "AJAX" else "plain old request"

Accessing Headers

You get the contents of request headers using the header helper:

get("/") = "Serving to host: " + headers("Host")

Accessing Session

Dealing with session attributes is fairly easy:

get("/") = {
  // get attribute
  session("attr1")
  // set attribute
  session("attr2") = "My value"
}

Flashes

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 →