The Cayla Web Framework

Cayla makes easy creating web application with Ceylon.

Creating a simple application in seconds

Import the Cayla module

module my.app "1.0.0" {
  import cayla "0.2.1";
}

Write the controller

import cayla { ... }

object controllers {
  route("/")
  shared class Index() extends Controller() {
    shared actual default Response handle() => ok().body("Hello World");
  }
}

shared void run() {
  value application = Application(controllers);
  application.start().always((Runtime|Exception arg) => print(arg is Runtime then "started" else "failed: ``arg.string``"));
  process.readLine();
}

Build and run!

> ceylon compile my.app
...
> ceylon run my.app/1.0
started

Application

Applications are create via the Application objects that takes as argument a Ceylon declaration or an object that is scanned to discover the controllers:

Package applications

Application(`package my.controllers`)

Object applications

Application(controllers)

Controllers

Controllers must extend the Controller interface, they can declare parameters which shall declares the String type, be shared and immutable. Such parameters are mapped toquery or path request parameters.

Query parameters

route("/greeter")
shared clas Greeter(String name) extends Controller() {
  shared actual default Response handle() => ok().body("Hello ``name``");
}

Path parameters

route("/greeter/:name")
shared clas Greeter(String name) extends Controller() {
  shared actual default Response handle() => ok().body("Hello ``name``");
}

Controller URL

Controllers can be addressed using the redefined Controller.string method. In a controller the expression Greeter("Cayla") will produce the http://localhost:8080/greeter?name=Cayla or the http://localhost:8080/greeter/Cayla URL.

Controller logic

During an invocation Cayla dispatches the request to the Controller.handle method. This method should implement the controller behavior.

It is also possible to override the Controller.invoke method instead that provides access to the RequestContext class that exposes Cayla objects.

Both methods returns a Response object that is sent to the client via Vert.x

Responses

The Response class is the base response, the Status class extends the response class to define the http status and the Body class extends the Status with a response body.

Creating responses is usually done via the fluent API and the top level functions such as ok, notFound or error.

return notFound().body("Not found!!!!");

Http methods

By default a controller is bound to a route for all Http methods. This can be restricted by using annotations like get, post, head, put, etc…

get route("/greeter")
shared clas Greeter(String name) extends Controller() {
  shared actual default Response handle() => ok().body("Hello ``name``");
}
Packages
cayla
cayla.descriptor
cayla.pattern
cayla.router
Dependencies
ceylon.net1.0.0
java.base7
vietj.vertx0.3.0
Annotations
connect
shared Connect connect()

Declares a binding to the CONNECT http method

delete
shared Delete delete()

Declares a binding to the DELETE http method

get
shared Get get()

Declares a binding to the GET http method

shared Head head()

Declares a binding to the HEAD http method

options
shared Options options()

Declares a binding to the OPTIONS http method

post
shared Post post()

Declares a binding to the POST http method

put
shared Put put()

Declares a binding to the PUT http method

route
shared Route route(String path, String[] methods)

Annotate a controller to define its route

trace
shared Trace trace()

Declares a binding to the TRACE http method

Connect
shared Connect

Http CONNECT

Delete
shared Delete

Http DELETE

Get
shared Get

Http GET

shared Head

Http HEAD

Options
shared Options

Http OPTIONS

Post
shared Post

Http POST

Put
shared Put

Http PUT

Route
shared Route

A route configuration

Trace
shared Trace

Http TRACE

Functions
error
shared Status error()

Create an 500 status response

notFound
shared Status notFound()

Create an 404 status response

ok
shared Status ok()

Create an 200 status response

run
shared void run()

Run a basic application example.

Classes
Application

A Cayla application.

Creating a Cayla application

An application is created by provided a container in which controllers are discoved.

Object container

Creates an application with a top level container object, the object is scanned for the nested controllers.

object controllers {
  route("/")
  shared class Index() {
    shared actual default Response handle() => Response.ok().body("Hello World");
  }
}
void run() {
  value application = Application(controllers);
  application.start();
  process.readLine();
}

Package container

Creates an application with a package container object, the package is scanned for the nested controllers.

void run() {
  value application = Application(`package my.application`);
  application.start();
  process.readLine();
}

Application life cycle

The start method starts the application and returns a Runtime Promise which:

  • is resolved with the Runtime object when the application is fully started
  • is failed with the reason of the failure when the application cannot be started

This Promise can be used to be aware of the application life cycle:

Promise<Runtime> runtime = application.start(); 
runtime.always((Runtime|Exception arg) => print(arg is Runtime then "started" else "failed: ``arg.string``"));
Body
shared Body

The body response extends the Status with a body

Controller
shared abstract Controller

The base class for web controllers. A controller must extend this class, its attributes are mapped to the request parameters.

Attributes of this class should:

  • declares the String type (later Boolean, Integer, etc…)
  • be shared
  • be immutable

A controller serves two purposes: . to serve a request . to produce an URL that will provide an address for this controller

route("/")
shared class Index() extends Controller() {
  shared actual default Response handle() => Response.ok().body("Hello World");
}

Our Index controller:

  • extends the Controller interface
  • is mapped to the server root via the [route] annotation
  • overrides the handle method for serving the default page

Mapping a request parameter to the controller can be achieved by declaring a corresponding attribute:

route("/greeter")
shared class Greeter(String name) extends Controller() {
  shared actual default Response handle() => Response.ok().body("Hello ``name``");
}

The Greeter controller is mapped to the /greeter path and can be invoked with an URL like /greeter?name=Cayla.

The parameter can also be mapped to a path parameter by specifying it in the path:

route("/greeter/:name")
shared class Greeter(String name) extends Controller() {
  shared actual default Response handle() => Response.ok().body("Hello ``name``");
}

The controller string produces an URL for invoking this controller, the URL is generated using:

  • the path declared by the route annotation
  • the controller attributes

Let's redefine our Index controller:

route("/")
shared class Index() extends Controller() {
  shared actual default Response handle() => Response.ok().body("Say hello to <a href="``Greeter("Cayla")``">Cayla</a>");
}

The response body will contain an URL that will look like http://localhost:8080/greeter?name=Cayla. The URL would be http://localhost:8080/greeter/Cayla when using path parameter mapping.

The string method redefined by the base Controller class:

  • takes care of generating the correct URL, including the parameter encoding
  • provides a safe way for generating URL that is enforced by the Ceylon compiler and is refactorable in the Ceylon IDE

When more context is required during the request, the invoke method can be overriden instead of the handle method. The RequestContext class provides access to the full content of the request.

RequestContext

The request context provides the information available during a request such as:

  • the Vert.x request
  • generating an URL for a controller
Response
shared abstract Response

The response to a request.

Runtime
shared Runtime

The application runtime.

The runtime is obtained from the Application.start method.

Status
shared Status

A status response provides a simple http response with a status code and headers.