Skip to content

Controllers

MTDdk edited this page Oct 24, 2017 · 9 revisions

Controllers are the heart of the application, and are used to process HTTP requests and form the appropriate responses. Much inspired by Grails and works to some extend like Spring Controllers

Introduction

A simple controller looks like this:

public class MovieController extends Controller {
   public void index(){}
}

public void index(){} is always implicit set, so this is similar:

public class MovieController extends Controller {}

This controller would be mapped to the following URL:

http://host:port/movie

All controllers must be placed in the package app.controllers, or else they will not be found by the framework.

Actions

Actions is the name of the public methods of a controller.

index() is always present, and is called when nothing else is specified in the URL, like we saw in the introduction.

HTTP methods

Actions have a special naming convention in order to break with having annotations for each controller and action.

This means that an action has to have its HTTP method as a part of its name:

public class MovieController extends Controller {
   public void getMovie() {}
   public void postMovie() {}
   public void deleteMovie() {}
   public void getTitle() {}
}

Currently, the following HTTP methods are supported: GET, POST, PUT, DELETE and HEAD.

This convention promotes the REST-style web programming greatly if needed.

Actions always have the return value void. It is possible to let them return something, but it will be ignored by the framework.

Controller paths

The path to a controller is always the name of the controller with the end, "Controller", stripped. If the controller is placed in a subpackage, then this is reflected in the URL to which it is mapped:

package app.controllers;
public class MovieController extends Controller {}
 => http://host:port/movie
package app.controllers.sub;
public class HelloController extends Controller {}
 => http://host:port/sub/hello

Furthermore, the CamelCase naming of a controller is interchangeable translated into underscore_case or hyphen-case in the URL:

package app.controllers;
public class WhatIsUpController extends Controller {} 

=> http://host:port/what_is_up or http://host:port/what-is-up

Another example:

package app.controllers.danish;
public class LocalMoviesController extends Controller {}

=> http://host:port/danish/local_movies or http://host:port/danish/local-movies

Views

More on how controller actions are mapped to views can be studied in the views documentation.

Passing data to views

Every controller action has the method view(name, value), which is used to send data of any type to the view.

public class MovieController extends Controller {
	public void index() {
		// do some work
		view("title", "Movie title");
		view("year", 1999);
		view("producer", "Some name");
	}
}

This can be used in the view like so:

Title of movie $title$
Year $year$
Producer $producer$

Character Encoding of views

Sometimes you might need to send a view, or anything else for that matter, to the client, where the encoding of the data needs to be something else than standard.

UTF-8 will always be set in Application context if nothing else is specified by the user, but this can be overridden by a single controller likes so:

public class MovieController extends Controller {
	@Override
    protected String getEncoding() {
    	return StandardCharsets.ISO_8859_1.toString();
    	// equivalent to : return "ISO-8859";
    }
}

This will set the encoding for every action of this controller (and subcontrollers).

It is also possible to set the encoding for just a single action:

public class MovieController extends Controller {
	public void getEnc() {
        encoding("UTF-16");
        respond().text("Encoding differently");
    }
}

The priority is as such in descending order:

  • action
  • controller
  • application context

Request parameters

Getting, and using, parameters in a web application is essential, and jawn exposes a few ways for this. First off, parameter in this framework covers both URL parameters and POSTed data.

Single parameter

public class MovieController extends Controller {
	public void index() {
		String name = param("name");
	}
}

All parameters

A simple method for getting all parameters is params(). It returns a MultiList, which has some neat properties when working with parameters that essentially are a map of lists.

The MultiList wraps all of this and lets you get the first or last element associated with a given parameter.

public class MovieController extends Controller {
	public void index() {
		MultiList<String> params = params();
		String movie = params.first("movie");
	}
}

All values for a single parameter

public class MovieController extends Controller {
	public void index() {
		List<String> names = params("name");
	}
}

Converting parameters

Sometimes when redirecting or logging you want to convert the parameters into an URL-ready string Calling:

http://host:port/movie?param1=key1&param1=key12&name=Keegan&first=Carl
public class MovieController extends Controller {
	public void index() {
		String paramString = paramsAsUrlString();
		// evaluates to:
		// ?name=Keegan&param1=key1&param1=key12&first=Carl
	}
}

Remember that parameters also can be POSTed parameters, so a usecase could be, that you wanted to convert these data into URL parameters in order to call a different service or URL.

File handling

Receiving files

Sending files to a web application is done by POST data, and this is usually some sort of a HTML <form>. This is represented in the framework as FormItem. Of course this kind of data can be sent by any means of POSTing.

To retrieve all the data from a POST or PUT, use the multipartFormItems().

public class MovieController extends Controller {
	public void postMovieImage() {
		MultiList<FormItem> items = multipartFormItems();
		// process items
		FormItem item = items.first("movieimage");
		if (!item.empty())
			image(item).save();
	}
	
	public void postPdf() {
		MultiList<FormItem> items = multipartFormItems();
        InputStream stream = items.first("pdf").getInputStream();
	}
}

With the MultiList you can access your data by name. Oftenmost, the data is just simple strings, but can be anything like a file or a stream, and the content can be fetched as byte[] or InputStream.

In the example, an image with the name 'movieimage' has been sent to the server, and is wrapped by image(FormItem).

For more on the possibilities with images, see Image manipulation.

Sending files

Sending a file to the client can be done in multiple ways depending on how the file has been obtained.

public class MovieController extends Controller {
	public void getInfo() {
		File f = /*obtain file*/;
		sendFile(f).contentType("application/pdf").status().ok();
	}
}
public class MovieController extends Controller {
	public void getInfo() {
		byte[] b = /*obtain bytes*/;
		outputStream("application/pdf").write(b);
	}
}
public class MovieController extends Controller {
	public void getInfo() {
		InputStream[] in = /*obtain stream*/;
		streamOut(in)
				.contentType("application/pdf")
				.header("Expires", "Fri, 31 Dec 1999 23:59:59 GMT")
				.status(200);
	}
}

AJAX

Detecting if a request is AJAX

public class MovieController extends Controller {
	public void index() {
		if (isXhr()) {
			// ajax request
		} else {
			// something else
		}
	}
}

Responding

Often you do not want to respond to an AJAX request, or some other requests, with a view, but instead send some minor information or information in a different format.

Text

public class MovieController extends Controller {
	public void postMovie() {
		// do work
		respond().text("went fine").contentType("text/plain");
	}
	
	public void getTitle() {
		// fetch
		respond().text("The movie title is: {0}", movie.title);
	}
}

The respond().text() always sets the content-type to "text/plain" per default, but it is possible to alter it. Besides this, the method is overloaded, so you can use it as a formatted text with arguments injected at runtime.

JSON

public class MovieController extends Controller {
	public void getMovie() {
		Movie m = new Movie("Guardians of the Galaxy", 2014);
		respond().json(m); // sets the content-type to application/json
	}
}

XML

public class MovieController extends Controller {
	public void getMovie() {
		Movie m = new Movie("Guardians of the Galaxy", 2014);
		respond().xml(m); // sets the content-type to application/xml
	}
}

Session

It is possible to save some states for a period of a session:

session("name", value); 			// put an object
 
session("name"); 					// get the object
sessionString("name");				// get the value as string

session().remove("name");  			// delete the object
session().setTimeToLive(seconds);	// max inactive interval
session().invalidate();				// discard all attributes

Cookies

Sending:

sendCookie(Cookie);               // sends a cookie to client
sendCookie(name, value);          // simple short cut to do the same as above 
sendPermanentCookie(name, value); // will send a cookie with time to live == 20 years

Retrieving:

List<Cookie> cookies(); // gets a list of all cookies sent in request
Cookie cookie(name);    // retrieve an individual cookie
cookieValue(name);      // retrieve a cookie value by name of cookie

Logging

All controllers have a SLF4J logging system integrated. Simply call log() to access it:

log().debug("--- reading from DB ----");
log().info("Created");
log().warn("Don't do that");
log().error("Going down");

You just plug in to it with a logging framework of your choice.

HTTP Status

When sending a respond of any kind in the controller actions you sometimes have to state whether something went well or failed somehow. This is done by using HTTP status codes, and jawn has some quick access methods for this:

public class MovieController extends Controller {
	public void postMovie() {
		// do work
		if (ok)
			respond().text("went fine").status().ok();
		else
			respond().text("could not find").status().notFound();
	}
}

Not every possible status code is implemented like this, so you also have the option to specify the code of your likings:

respond().json(object).status(statusCode);