Skinny Micro


Skinny Micro is at once a micro Web framework to build Servlet applications in Scala and the core part of Skinny Framework 2.

Skinny Micro started as a fork of Scalatra. After that, many improvements have been made to be safer and more efficient when working with Scala Future values upon it.

Basically, Skinny Micro’s DSLs are source compatible with Scalatra 2.3’s ones. But names of base traits and packages are mostly renamed and the structure of internal modules are re-designed.

https://github.com/skinny-framework/skinny-micro


Getting Started


Taking a look at skinny-micro-heroku-example first would be helpful to understand what you need to do. samples and scalas-samples in this repository are also worth looking at.

When you start new sbt project, add the following dependencies:

lazy val skinnyMicroVersion = "2.3.0"

libraryDependencies ++= Seq(
  // micro Web framework
  "org.skinny-framework" %% "skinny-micro"             % skinnyMicroVersion,
  // jackson integration
  "org.skinny-framework" %% "skinny-micro-jackson"     % skinnyMicroVersion,
  "org.skinny-framework" %% "skinny-micro-jackson-xml" % skinnyMicroVersion,
  // json4s integration
  "org.skinny-framework" %% "skinny-micro-json4s"      % skinnyMicroVersion,
  // Scalate integration
  "org.skinny-framework" %% "skinny-micro-scalate"     % skinnyMicroVersion,
  // Standalone Web server (Jetty 9.3 / Servlet 3.1)
  "org.skinny-framework" %% "skinny-micro-server"      % skinnyMicroVersion
)

Scala API docs



Minimum Examples


We’d love to show you some simple but working examples briefly.

Please also see more examples under samples and scalas-samples.


Simple Application


The following is a minimum Servlet example. skinny.micro.SkinnyListener initializes Skinny Micro’s environment.

As same as Scalatra, _root_.Bootstrap class (instead of _root_.Bootstrap for Scalatra) is detected by default. If you’d like to change the name of the Bootstrap class, it’s also possible by specifying with Servlet’s init parameter.

Also take a look at sbt-servlet-plugin. The plugin will help you much when building Servlet applications in Scala.

See samples for more examples.

src/main/scala/app.scala

import javax.servlet._
import skinny.micro._

object Hello extends WebApp {
  get("/say-hello") {
    s"Hello, ${params.getOrElse("name", "Anonymous")}!\n"
  }
}

class Bootstrap extends LifeCycle {
  override def init(ctx: ServletContext) {
    Hello.mount(ctx)
  }
}

src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3.1.xsd"
         version="3.1">
    <listener>
        <listener-class>skinny.micro.SkinnyListener</listener-class>
    </listener>
</web-app>

Async Native Application

Skinny Micro newly privides original base traits that named as AsyncWebApp (AsyncSkinnyMicorFilter) and AsyncSingleWebApp (AsyncSkinnyMicroServlet).

They are natively suitable for building Future-wired async operations. You will no longer unwantedly feel stressed when working with Future-wired operations.

case class Message(id: Long, text: String)

object Messages {
  def search(keyword: Option[String])(implicit ctx: ExecutionContext): Future[Seq[Message]]
}

object AsyncMessagesApp extends AsyncWebApp with JSONSupport {

  post("/messages/search") { implicit ctx =>
    // You don't need to explicitly wrap results with AsyncResult
    // Of course, doing so is also fine
    Messages.search(params.get("keyword"))
      .map(ms => Ok(toJSONString(ms))) // returns Future[ActionResult]
  }
}

More working examples are available under samples and scalas-samples.


Details


Skinny Micro started as a Scalatra fork. So we basically continue Scalatra’s guides for compatible APIs.


Routes


In Skinny Micro, as same as Scalatra, a route is an HTTP method (GET, PUT, POST, or DELETE) paired with a URL matching pattern. If you set up your application using RESTful conventions, your controller might look something like this:

import skinny.micro._

class Articles extends WebApp {

  get("/articles/:id") {  //  <= this is a route matcher
    // this is an action
    // this action would show the article which has the specified :id
  }

  post("/articles") {
    // submit/create an article
  }

  put("/articles/:id") {
    // update the article which has the specified :id
  }

  delete("/articles/:id") {
    // delete the article with the specified :id
  }
}

Those four example routes, and the actions inside the route blocks, could be the basis of a simple blogging system. The examples just stub out the actions - in a real application, you’d replace the // comments with code to save and retrieve models, and show HTML views.

The first matching route is invoked. Routes are matched from the bottom up, i.e. from the bottom of the Scala class defining your servlet to the top.

Watch out! This is the opposite of Sinatra. Route definitions are executed as part of a Scala constructor; by matching from the bottom up, routes can be overridden in child classes.


Conditions

Routes may include conditions. A condition is any expression that returns Boolean. Conditions are evaluated by-name each time the route matcher runs.

get("/foo") {
  // Matches "GET /foo"
}

get("/foo", request.getRemoteHost == "127.0.0.1") {
  // Overrides "GET /foo" for local users
}

Multiple conditions can be chained together. A route must match all conditions:

get("/foo", request.getRemoteHost == "127.0.0.1", request.getRemoteUser == "admin") {
  // Only matches if you're the admin, and you're localhost
}

No path pattern is necessary. A route may consist of solely a condition:

get(isMaintenanceMode) {
  <h1>Go away!</h1>
}

Query/Form/Path Parameters


Scala API docs for “skinny.micro.SkinnyMicroParams”

params is the basic accessor for request parameters. Both of GET query string params and POST form params end up in the params bag - you shouldn’t need to read anything off the request.body.

val name: Option[String] = params.get("id")
val id: Option[Int] = params.getAs[Int]("id")
val id: Long = params.getAsOrElse[Long]("id", -1L)

multiParams returns multiple values for the same key if exists.

val ids: Option[Seq[Int]] = multiParams.getAs[Int]("ids")

If you need to distinguish query string parameters and form parameters, use the following APIs instead.


Route patterns may also include wildcard parameters, accessible through the “splat” key.

multiParams("splat") returns wildcard (“*”) path params.

get("/say/*/to/*") {
  // Matches "GET /say/hello/to/world"
  multiParams("splat") // == Seq("hello", "world")
}

get("/download/*.*") {
  // Matches "GET /download/path/to/file.xml"
  multiParams("splat") // == Seq("path/to/file", "xml")
}

The route matcher may also be a regular expression. Capture groups are accessible through the “captures” key.

multiParams("captures") returns captured

get("""^\/f(.*)/b(.*)""".r) {
  // Matches "GET /foo/bar"
  multiParams("captures") // == Seq("oo", "ar")
}

Rails-like pattern matching

By default, route patterns parsing is based on Sinatra. Rails has a similar, but not identical, syntax, based on Rack::Mount’s Strexp. The path pattern parser is resolved implicitly, and may be overridden if you prefer an alternate syntax:

import skinny.micro._

class RailsLikeRouting extends WebApp {
  implicit override def string2RouteMatcher(path: String) = RailsPathPatternParser(path)

  get("/:file(.:ext)") { // matched Rails-style }
}

Accessing and setting Cookies


Scala API docs for “skinny.micro.request.RichRequest”

val cookies: Map[String, String] = request.cookies
val multiCookies: Map[String, Seq[String]] = request.multiCookies

Scala API docs for “skinny.micro.cookie”

Useful Cookie operation DSLs are available. See Scala API docs for “skinny.micro.cookie.SweetCookies” for details.

def hello = {
  cookies += "name" -> "value"
  cookies -= "name"
}

def helloWithOptions = {
  cookies.set("name" -> "value")
  cookies.set("name" -> "value").withOptions(CookieOptions(secure = true))
}

Request / Response Headers


Scala API docs for “skinny.micro.request.RichRequest”

// access request headers
val v: Option[String] = request.header(name)

Scala API docs for “skinny.micro.response.RichResponse”

// access response headers
response.headers += "name" -> "value"
response.headers -= "name"

Servlet Session


Scala API docs for “skinny.micro.request.RichHttpServletSession”

val v: Any = session("name")
session += "name" -> "value"
session -= "name"

Skinny Micro’s Flash and CSRF protection features are using servlet sessions by default. Even if you don’t use sessions in your application code, be careful to avoid using them or implement your own implementation that don’t use Servlet sessions.

As you know, Servlet sessions are stateful. If you need to share the same session among several Servlet containers, consider using Skinny Framework’s SkinnySession or your own session management (e.g. storing session into memcached/redis servers instead).


Response handling


Action methods in Skinny Micro apps should finally return ActionResult value which represents the request’s response.

If you just return String value or Array[Byte] value, it would be the response body. It’s also allowed style. You can always use setter to modify the Servlet response’s mutable state.

Scala API docs for “skinny.micro.response.ActionResult”

Meanwhile, halting immediately returns ActionResult by throwing HaltException within a filter or route.

halt(404) // throws HaltException

halt(
  status = 400,
  charset = Some("utf-8"),
  contentType = Some("application/json"),
  cookies = Seq(Cookie("foo" -> "bar")),
  headers = Map("foo" -> "bar")
)

Scala API docs for “skinny.micro.base.RedirectionDsl”

If you prefer specifying status code for redirection, use more explicit DSLs.

// default redirection
redirect("/top") // 302

// explicit redirection DSLs
redirect301("/new_url") // 301
redirect302("/somewhere") // 302
redirect303("/complete") // 303

A route can punt processing to the next matching route using pass(). Remember, unlike Sinatra, routes are matched from the bottom up.

get("/guess/*") {
  "You missed!"
}

get("/guess/:who") {
  params("who") match {
    case "Frank" => "You got me!"
    case _ => pass()
  }
}

The route block is immediately exited and control continues with the next matching route. If no matching route is found, the notFound handler is invoked. The notFound handler allows you to execute code when there is no matching route for the current request’s URL.

The default behavior is:

notFound {
  <h1>Not found. Bummer.</h1>
}

What happens next differs slightly based on whether you’ve set your application up using SkinnyMicroFilter or SkinnyMicroServlet. Async base traits follow the same rules.


Flash


Notice: Flash uses servlet sessions by default. Be aware of sticky session mode.

Skinny Micro doesn’t activate FlashMapSupport by default. If you need to use Flash support, mixin the following trait. Meanwhile, Skinny Framework enables FlashMapSupport by default because it’s a full stack framework.

Scala API docs for “skinny.micro.contrib.FlashMapSupport”

class Controller extends WebApp with FlashMapSupport {
  post("/article/create") {
    // create session
    flash("notice") = "article created succesfully"
    redirect("/home")
  }
  get("/home") {
    // this will access the value set in previous action
    stuff_with(flash("notice"))
  }
}

Scala API docs for “skinny.micro.contrib.flash.FlashMap”

flash(name) = value
flash += (name -> value)
flash.now += (name -> value)

Request Body


Scala API docs for “skinny.micro.request.RichRequest”

val body: String = request.body
val stream: InputStream = request.inputStream // raw HTTP POST data

File Upload


WARNING: Extend not Filter base traits but Servlet base traits. You cannot use FileUploadFeature with WebApp (SkinnyMicroFilter) and SkinnyController.

Scala API docs for “skinny.micro.contrib.FileUploadSupport”

FileUploadSupport can be mixed into a skinny.micro.SkinnyMicroServlet to provide easy access to data submitted as part of a multipart HTTP request. Commonly this is used for retrieving uploaded files.

When the configuration has been done, you can access any files using fileParams(“myFile”) where myFile is the name of the parameter used to upload the file being retrieved. If you are expecting multiple files with the same name, you can use fileMultiParams(“files[]”) to access them all.

To handle any errors that are caused by multipart handling, you need to configure an error handler to your handler class:

import skinny.micro._
import skinny.micro.contrib.FileUploadFeature

// SkinnyMicroServlet or SingleWebApp !!!
class FilesController extends SkinnyMicroServlet with FileUploadFeature {

  def upload = fileParams.get("file") match {
    case Some(file) =>
      Ok(file.get(), Map(
        "Content-Type"        -> (file.contentType.getOrElse("application/octet-stream")),
        "Content-Disposition" -> ("attachment; filename=\"" + file.name + "\"")
      ))
    case None =>
      BadRequest(displayPage(
        <p>
          Hey! You forgot to select a file.
        </p>))
  }
}

CSRF Protection


Notice: CSRF protection implementation uses servlet sessions by default. Be aware of sticky session mode.

Scala API docs for “skinny.micro.contrib.CSRFTokenSupport”

csrfKey and csrfToken are available when activating CSRFTokenSupport.

class SampleApp extends WebApp with CSRFTokenSupport {
}

Filters


Skinny Micro offers a way for you too hook into the request chain of your application via before and after filters, which both accept a block to yield. Filters optionally take a URL pattern to match to the request.

before filter

The before method will let you pass a block to be evaluated before each and every route gets processed.

before() {
  MyDb.connect
  contentType="text/html"
}

get("/") {
  val list = MyDb.findAll()
  templateEngine.layout("index.ssp", list)
}

In this example, we’ve set up a before filter to connect using a contrived MyDb module, and set the contentType for all requests to text/html.

after filter

The after method lets you pass a block to be evaluated after each and every route gets processed.

after() {
  MyDb.disconnect
}

As you can see from this example, we’re asking the MyDB module to disconnect after the request has been processed.

Pattern matching

Filters optionally take a pattern to be matched against the requested URI during processing. Here’s a quick example you could use to run a contrived authenticate! method before accessing any “admin” type requests.

before("/admin/*") { basicAuth }
after("/admin/*") { user.logout }

Route actions, errors and filters run in the following order:


Check execution time


Scala API docs for “skinny.logging.TimeLogging”

val result = warnElapsedTime(1) {
  Thread.sleep(10)
}
// will output "[SLOW EXECUTION DETECTED] Elapsed time: 10 millis"

Reverse Routing


Page relative url:

get("/"){
  // This will redirect to http://<host>/page-relative
  redirect(url("page-relative"))
}

Context relative url:

get("/"){
  // This will redirect to http://<host>/<context>/context-relative
  redirect(url("/context-relative"))
}

Mapped params:

get("/") {
  // This will redirect to http://<host>/<context>/en-to-es?one=uno&two=dos
  redirect( url("/en-to-es", Map("one" -> "uno", "two" -> "dos")) )
}

Reverse routes:

It is possible to save your routes as variables so that they have convenient handles:

class MyApp extends WebApp {
  // When you create a route, you can save it as a variable
  val viewUser = get("/user/:id") {
    // your user action would go here
  }

  post("/user/new") {
    // url method provided by UrlGeneratorSupport.  Pass it the route
    // and the params.
    redirect(url(viewUser, "id" -> newUser.id))
  }
}

There’s also a ScalateUrlGeneratorSupport. It reflectively finds all members of your app of type Route (e.g., viewUser above) and makes them available in your templates. You should then be able to do something like this right in your templates:

url(viewUser, "id" -> 1)

License


(The BSD 2-Clause License)

Copyright (c) Alan Dipert
Copyright (c) skinny-framework.org
If you find a typo or mistake in this page, please report or fix it. How?