Home

Awesome

Kanary

alt text License Download ktlint Code Climate

A minimalist Kotlin web framework for building expressive REST APIs

fun main(args: Array<String>) {

    val app = KanaryApp()
    val server = Server()
    val userRouter = KanaryRouter()
    val userController = UserController()

    userRouter on "users/" use userController
    userRouter.post("new/", userController::createUser)
    userRouter.get("details/", userController::retrieveUser)

    app.mount(userRouter)
    server.handler = AppHandler(app)
    server.listen(8080)
    
}

Installation

Framework resources are contained in the package com.iyanuadelekan.kanary and can be included in your application via Maven, Gradle and Ivy. Include the necessary out of the following in your application:

Maven

Include Jcenter as a plugin repository

<repositories>
   <repository>
     <id>jcenter</id>
     <name>JCenter</name>
     <url>https://jcenter.bintray.com/</url>
   </repository>
</repositories>

Add Kanary as a project dependency

<dependencies>
  ...
  <dependency>
    <groupId>com.iyanuadelekan</groupId>
    <artifactId>kanary</artifactId>
    <version>0.9.2</version>
  </dependency>
  ...
</dependencies>

Gradle

repositories {
    jcenter()
}

dependencies {
    compile 'com.iyanuadelekan:kanary:0.9.2'
}

Ivy

<dependency org='com.iyanuadelekan' name='kanary' rev='0.9.2'>
  <artifact name='kanary'></artifact>
</dependency>

Others

For other use cases, you can download jars from bintray

Features

Quick start

A breakdown of project packages is here.

Creating a Kanary app and starting a server

A simple Kanary app that listens on a port is created by initializing an istance of KanaryApp, creating a Server object, creating an AppHandler instance, setting that instance as the server's handler and starting the server to listen on a specified port.

fun main(args: Array<String>) {
  val app = KanaryApp()
  val server = Server()

  server.handler = AppHandler(app)
  server.listen(8080)
}

Creating controllers

A controller is any instance of a class that extends KanaryController. The class below is a simple controller class that does nothing.

class DummyController : KanaryController()

Creating controller actions

Though the fact that the above class is a controller is correct, generally you'll want to specify actions within your controller to route requests to. An action is a controller function that takes three parameters as its arguments:

A valid action is shown within the controller below

class UserController : KanaryController() {

    fun createUser(baseRequest: Request, request: HttpServletRequest, response: HttpServletResponse) {
        // action code goes here
    }
    
}

Controller action lifecycle callbacks

There are two distinct action lifecycle callbacks that can be declared within a KanaryController. These are:

Declaring these two callbacks is as easy as declaring a function within a controller:

class UserController : KanaryController() {

    override fun beforeAction(request: HttpServletRequest, response: HttpServletResponse?) {
        println("I execute before anything else!")
    }
    
    override fun afterAction(request: HttpServletRequest, response: HttpServletResponse?) {
        println("I execute once an action is completed!")
    }

    fun createUser(baseRequest: Request, request: HttpServletRequest, response: HttpServletResponse) {
        // action code goes here
    }
    
}

Routing

All routing is done by one or more specified routers. A router is an instance of KanaryRouter:

val userRouter = KanaryRouter()

Declaring route paths

userRouter on "users/" use userController //router uses userController to cater for all routes prefixed by '/users'
userRouter.post("new/", userController::createUser) //maps POST '/users/new' to the createUser action in userController

The above can also be done with:

userRouter.post("users/new/", userController::createUser, userController)

Mounting routers to application

A single router can be mounted to a KanaryApp instance as follows:

app.mount(userRouter)

Numerous routers can be mounted at a go:

app.mount(routerA, routerB, routerC, ..., routerN)

Middleware

All middleware take the form of a lambda. A single nullable instance of HttpServletRequest is passed to every middleware added to the application.

app.use { println("I'm middleware!") }
app.use { println("Request path info: ${it.pathInfo}") }

Multiple middleware can be added at a go:

app.use({ println("I'm middleware!") }, { println("Request path info: ${it.pathInfo}") } )

It is important to note that all middleware execute in a non blocking fashion parrallel to the main application thread.

Bundled middleware

The sole middleware bundled with Kanary is 'simpleConsoleRequestLogger'. It prints out succinct information about each request received to the console.

app.use(simpleConsoleRequestLogger)

Working with requests and responses

Handling requests

In most cases, request handling should be done by the use of the immutable HttpServletRequest instance passed to your controller actions. This instance is an object of Java's HttpServletRequest with Kanary specific helper functions. These additional functions provided are:

FunctionDescriptionReturn type
getBody()Used to retrieve HTTP request body contentJsonNode?
getBodyAsJson()Used to retrieve HTTP request body content in the form of a JSON stringString

A mutable request object is exposed in the form of a Request instance. Request implements HttpServletRequest and as such has behaviours and characteristics similar to those possessed by the HttpServletRequest instance passed to an action. In addition to the functions shown in the table above, the Request instance passed has:

FunctionDescriptionReturn type
done()Used to specify that a request has been successfully handledUnit

Responding to a request

Responses are sent to a client with the use of an HttpServletResponse passed to an action. In addition to all characteristics and behaviours exposed by this instance, the following Kanary specific helper functions are available:

FunctionDescriptionReturn type
withStatus(status: Int)Sets the response status codeUnit
send(message: String)Sends a plain text message to a clientUnit
sendJson(responseNode: JsonNode?)Sends a json response to a clientUnit
end()Ends the HTTP response processUnit
sendStatus(status: Int)Sends the response status code as plain textUnit
sendFile(file: File, contentType: String="", contentLength: Int=0)Sends a file to a clientUnit
redirect(url: String)Redirects the request to the specified URLUnit
sendHtml(html: String)Sends HTML content to a clientUnit

All functions above except 'sendFile' can be written in infix notation (the recommended way of writing code in Kanary). This permits the writing of beautiful, clear and expressive code for responding to clients. Thus to send a plain text message to a client:

class UserController : KanaryController() {

    fun createUser(baseRequest: Request, request: HttpServletRequest, response: HttpServletResponse) {
        response withStatus 201 send "User successfully created!"
        baseRequest.done()
    }
    
}

Packages

classpackage
KanaryAppcom.iyanuadelekan.kanary.app
KanaryControllercom.iyanuadelekan.kanary.core
KanaryRoutercom.iyanuadelekan.kanary.core
AppHandlercom.iyanuadelekan.kanary.handlers
Servercom.iyanuadelekan.kanary.server

Dependencies

Philosophy

Kanary was created in order to facilitate the quick implementation of stable and non verbose RESTful APIs with the Kotlin programming language.

Convention versus Configuration

Kanary was designed to utilise a respectful approach in aiding engineers and developers to create micro-service based applications. As a consequence of this approach, no conventions are forced upon developers. The means by which an application is implemented is at the sole discretion of the implementer.

Road map

Contributing

Contributions are welcome from all corners of the world! Read the CONTRIBUTIONS.rst file to get started. Create an issue for bug reports and feature requests.

People

Kanary was created by Iyanu Adelekan.

License

Apache 2.0