Chinook winds /ʃɪˈnʊk/, or simply chinooks, are foehn winds in the interior West of North America, where the Canadian Prairies and Great Plains meet various mountain ranges, although the original usage is in reference to wet, warm coastal winds in the Pacific Northwest.
— Wikipedia

Chinook is a Frege (http://frege-lang.org) port of SparkJava (http://sparkjava.com) for creating web applications with minimum effort.

1. Getting Started

Before going any further make sure you have installed JDK 7+ in your machine.

Clone Chinook project at github and from the project’s root folder execute:

./gradlew :chinook-sample:run
If it is your first execution and you don’t have Gradle or the project dependencies already installed, it may take a while before executing Chinook.

Once the server has started:

[Thread-0] INFO org.eclipse.jetty.server.Server - Started @292ms

You can go to your browser a go to http://localhost:8080/hi You should see the phrase Hello World from Chinook :-).

2. Versions

This is the compatibility version table of the current Chinook release:

Table 1. Compatibility table

name

version

chinook

0.2.1

spark-java

com.sparkjava:spark-core:2.5

frege-core

org.frege-lang:frege:3.24-7.100

frege-repl

org.frege-lang:frege-repl-core:1.3

jdk

1.7+

Because Frege is evolving at fast pace, at least at the moment, Chinook is trying to follow latest versions of Frege. That’s why I thought it would be important to have a chapter where users would see a compatibility table of the current release.

3. HTTP

3.1. Overview

Chinook is a tiny web framework aimed to create microservices. A basic Chinook application needs only three steps to expose an endpoint:

  • Handler

  • Endpoint registration

  • Main method

A whole Chinook application could as simple as:

import chinook.Core
import chinook.Chinook

main _ = Chinook.run [] [
   Get "/hello" $ \req -> do
      return response.{ output = Just "Hello World" }
   ]

3.1.1. Handler

Handlers are the most important part of Chinook, is where client requests are handled. Chinook http handlers normally have the following signature:

IO Request -> IO Reponse

As you may see there’s no difference between handlers used with different http verbs:

GET
helloWorldHandler :: IO Request -> IO Response
helloWorldHandler req = do
   return $ response.{ output = Just "Hello World from Chinook :-)" }
DELETE
deleteHandler :: IO Request -> IO Response
deleteHandler req = do
  id <- req.path ":id"
  return $ response.{ status = 204 }

What makes them different ? The endpoint registration.

3.1.2. Endpoint registration

The way Chinook works at the moment is a main entry point where handlers are registered for a specific http verb and a path.

It always follows the same pattern:

http-verb "/registered-path" handler

If you take a look at the chinook-sample application, the main entry point is located in the App.fr file.

mappings = [
  Before "/hi" securityHandler,
  Get "/hi" helloWorldHandler,
  Get "/hi/:name/:age" greetingsHandler,
  "/json" + [
    Get  "/get" getJSONHandler,
    Get  "/form" getFormHandler,
    Get  "/sender" getJsonSenderHandler,
    Post "/post" postJSONHandler,
    Post "/html" postFormHandler
    ],
  Get "/bye" goodbyeHandler,
  Delete "/deleteme/:id" deleteHandler,
  Put "/updateme/:id" putHandler
  ]

Here you can see all available endpoints in your application. For a given URL there is the related handler function. How you manage your handler files is completely up to you.

All HTTP registration functions have the same structure, here is the function to register a HTTP GET handler:

data Resource = Get     String Handler |
                Post    String Handler |
                Put     String Handler |
                Delete  String Handler |
                Patch   String Handler |
                Options String Handler |
                Trace   String Handler |
                Head    String Handler |
                Before  String Handler |
                After   String Handler |
                Family  String [Resource]

It basically needs:

  • A String representing the URI resource A handler function to

  • A Handler to deal with the request and response originated in this invocation.

a handler is just an alias for a function with the following shape:
type Handler = IO Request -> IO Response

A handler basically receives a IO Request and should return a IO Response. Both are available undere chinook.Core:

import chinook.Core (Request, Response, response, haltingResponse)

Here’s a simple example:

helloWorldHandler :: IO Request -> IO Response
helloWorldHandler req = do
   return $ response.{ output = Just "Hello World from Chinook :-)" }
Core.response is a way of creating a new chinook.Core.Response without having to specify all possible fields in Response. It creates a new copy from the default Response value, but it lets you set different field values. This is not a Chinook thing but Frege’s and it’s call value update.

If you want a GET handler to return an output you should be setting the response’s output field.

Then the only thing remaining is to register the handler to receive a get call in a given URI:

Get "/hi" helloWorldHandler,

3.1.3. Main

To start a Chinook application you only have to invoke the main function containing all the endpoint registrations as we saw in the previous chapter.

main = Chinook.run config routes
  where config = [port 8080, staticFiles "/public"]
        routes = mappings

This is independent of the tool used to call that method. For instance, using gradle you can use the application plugin.

apply plugin: 'java'
apply plugin: "org.frege-lang"
apply plugin: 'application' (1)

dependencies {
    compile project (':chinook-core')

    compile 'com.github.fregelab:diablo-groovy:0.1.1'
}

mainClassName = "chinook.App" (2)
1 Gradle Application plugin
2 Namespace where the main function is located

3.2. Request / Response

3.2.1. Basics

To return simple values

The first endpoint returns a typical hello world message when invoking the /hi endpoint. Here is the handler implementation:

helloWorldHandler :: IO Request -> IO Response
helloWorldHandler req = do
   return $ response.{ output = Just "Hello World from Chinook :-)" }
Changing response

Sometimes depending on the request we may want to change something about the response.

greetingsHandler :: IO Request -> IO Response
greetingsHandler req = do
  name      <- req.path ":name"
  age       <- req.path ":age"
  return $ response.{ status = 200, (1)
                      output = createGreetings name age }
1 Setting response status

Here we’re setting the content type:

getJSONHandler :: IO Request -> IO Response
getJSONHandler req = do
  code  <- req.param "code"
  desc  <- req.param "desc"
  return $ response.{ status  = 200,
                      output  = getLangAsJSON code desc,
                      headers = [("Content-Type", Just "application/json")] } (1)
1 Setting response content type
Instead setting content type headers manually you can use chinook.util.ContentType, e.g. ContentType.json

There are many functions available for Request and Response abstractions, if you want to explore it please don’t hesitate to explore the project’s Frege docs.

For the rest of HTTP verbs it doesn’t change. For instance in a POST request it basically works the same way GET did. The main difference is that in POST calls you would like to do something with the body text sent from the client. Most of the times that body could be a JSON/XML payload. You can get that information with the body field from the request.

Here’s an example building a new Lang instance out of the json payload coming from the client:

postJSONHandler :: IO Request -> IO Response
postJSONHandler req = do
    body  <- req.body
    return $ case (processJSON body) of
        Just Lang { code, desc } -> createdResponse
        Nothing                  -> badRequest

createdResponse :: Response
createdResponse = response.{ status  = 201 , (1)
                             output  = Just "Created",
                             headers = [ContentType.json] }

badRequest :: Response
badRequest = response.{ status = 400 , (2)
                        output  = Just "Bad request",
                        headers = [ContentType.json] }
1 Successful response sending a 201 (created) status response
2 Failure response sending a 400 (bad request) in case the payload wasn’t correct.

Of course it would be nice to send back more feedback about validation ;)

3.2.2. Request

A chinook.Core.Request is a immutable data structure containing:

data Request = Request { headers     :: [(String, Maybe String)],
                         queryParams :: [(String, [String])],
                         pathParams  :: [(String, String)],
                         body        :: Maybe String }
Headers

Headers are of type (String, Maybe String) meaning (name of the header, possible value). There are two utility methods to get headers from a IO Request.

allHeaders :: IO Request -> IO [(String, Maybe String)]

Returns a list of all headers from the request passed as first argument.

header :: IO Request -> String -> IO (Maybe String)

Returns a specific header. First argument is the request, then the name of the header.

Query Parameters

A typical URL containing a query string is as follows:

http://anysite.com/over/there?name=ferret

In order to get the name parameter we’ll be using the function:

param :: IO Request -> String -> IO (Maybe String)

First parameter is the request, and then the name of the parameter (name in this particular example).

Path Parameters

Bind the value of a path segment to a parameter. For instance:

http://localhost:4567/john/34

Is mapped:

Get "/hi/:name/:age" greetingsHandler,

So how can we retrieve the :name and :age path parameters ? With the following functions:

allPaths :: IO Request -> IO [(String, String)]

This function receives the request as argument and it will return a list of type (String, String).

Why (String, String) and not (String, Maybe String) ? Well if it didn’t have any value it couldn’t match the path. So it’s safe to assume everytime the URI is hit you will get a value.
path :: IO Request -> String -> IO (Maybe String)

path should be use to get a specific path parameter. It receives the request and the name of the path parameter, and it will return a string representation of that parameter.

This way you could be able to use it like the following:

putHandler :: IO Request -> IO Response
putHandler req = do
  id <- req.path ":id"
  return $ response.{ status = 202 }
Body

Specially when trying to save or update data you will need to access the request body.

postJSONHandler :: IO Request -> IO Response
postJSONHandler req = do
    body  <- req.body
    return $ case (processJSON body) of
        Just Lang { code, desc } -> createdResponse
        Nothing                  -> badRequest

createdResponse :: Response
createdResponse = response.{ status  = 201 , (1)
                             output  = Just "Created",
                             headers = [ContentType.json] }

badRequest :: Response
badRequest = response.{ status = 400 , (2)
                        output  = Just "Bad request",
                        headers = [ContentType.json] }

3.2.3. Response

chinook.Core.Response is just a data type:

data Response = Response { status  :: Int,
                           halting :: Bool,
                           output  :: Maybe String,
                           headers :: [(String, Maybe String)]} where

Unlike other web frameworks, you won’t be mutating the response via functions modifiying internal fields. Chinook forces you to create an immutable structure.

Well, but, does it means I should be building from the scratch a Response every time ? Yes but Chinook (well Frege actually) will help you in this task. Chinook has a default Response value available through the constant chinook.Core.response. This value looks like this:

response = Response 200 false Nothing []

So e.g next time you wanted to return a message with no headers and returning a 200 http status you don’t have to set everything, you can take advantage of Frege’s value update and write something like this:

helloWorldHandler :: IO Request -> IO Response
helloWorldHandler req = do
   return $ response.{ output = Just "Hello World from Chinook :-)" }

A Response may contain:

Headers

(String, Maybe String)

When setting content type, or setting browser cache…​etc we should be using http response headers. A header is of type (String, Maybe String) where the first tuple argument is the name of the header and the second part of the tuple is the possible value of the header.

Output

Maybe String

If you would like to respond back with a message, you can set the output field with a Maybe String. That string could be anything: text, json…​

Status

Int

HTTP statuses are used often when doing Rest. For instance if you would like to create a resource, you would send a message to a resource using a POST endpoint and if it succeed it will return a 201 code meaning resource created. A status code is of type Int.

3.2.4. Interceptors

Sometimes you may need to check something for every request on a given path. An example could be a security interceptor, or just an audit trace.

For that purpose Chinook has two types of routes that don’t represent a rest action but a handler capable of intercepting a given request, or set of requests and do something about it. Those routes are Before and After and as their name implies, one can be used to intercept before the request has been processed and the latter once the request has been processed.

Chinook interceptors are not filters. They can only access to the request and modify only the response.

In the sample application, there’s a silly security interceptor that may help as a starting point.

Mapping configuration
Before "/hi" securityHandler,

All requests found under /hi path will be intercepted by the securityHandler

Handler implementation
securityHandler :: IO Request -> IO Response
securityHandler req = do
  token <- req.param "token"
  return $ case token of
    Just _  -> response
    Nothing -> haltingResponse.{ status = 401,
                                 output = Just "Please add a token to your query params '?token='" }

In an interceptor in you would like to stop the process and return inmediately to the user, you should return a halting Response with its halting property set to true. There’s a shortcut which is to make use of the haltingResponse value. If you would like to allow further execution just return a Response value making sure its halting property is set to false (which is the default value).

There are two alias for a normal response and a halting response: response and haltingResponse.

3.3. Configuration

Settings are passed when bootstraping the application. Instead of passing a list of tuples Chinook expects values of type chinook.Chinook.Configuration.

Anyway Chinook provides a set of functions as a shortcut to produce these settings.

main = Chinook.run config routes
  where config = [port 8080, staticFiles "/public"]
        routes = mappings

3.3.1. Port

To produce server port settings you can use the port function:

port :: Int -> Configuration

3.3.2. Static Files

You can assign a folder in the classpath to serve static files, with the staticFiles function.

For instance staticFiles "/public" will make files located at "src/main/resources/public" be exposed at http://{server}:{port}

staticFiles :: String -> Configuration

3.4. Templating

Chinook doesn’t have any templating solution by default.

However chinook-sample application is using Diablo. Diablo is a small templating engine abstraction. It tries to expose different templating engines with a unified api.

In the chinook-sample application Diablo is used to render html using Groovy templates. These templates are located in the classpath, precisely at src/main/resources. In order to be able to use Diablo first you need some import statements.

import diablo.Diablo (fromPath)
import diablo.Groovy (GroovyEngine)

Then a helper function is created to execute any template available in the classpath (See Diablo docs for more options):

render :: String -> IO (Maybe String)
render tplName = do
  groovy <- GroovyEngine.new ()
  Just <$> fromPath groovy tplName []

Finally we use the helper function in our handlers:

getFormHandler :: IO Request -> IO Response
getFormHandler req = do
  html <- render "chinook/form.gtpl"
  return response.{ status  = 200,
                    output  = html,
                    headers = [ContentType.html]
                  }

4. Development

4.1. License

Chinook uses Apache 2.0 license

4.2. Frege docs

At the moment there is an issue with how FregeDoc that prevents Chinook from publishing its fregedocs.

4.3. Github

Chinook source code is hosted at Github