Unfiltered

Plumbing the Web

Internet users

Internet users

Garden walls breached, again.

pin

Unfiltered Application Architecture

“In my opinion Unfiltered’s main strength is in what it doesn’t do.”

—Maxime Lévesque

tool

What exactly does Unfiltered do?

HTTP

Unfiltered is a toolkit for servicing HTTP requests in Scala.

Servlet Filters

Java for the Web (hold the EE)

Works with Google App Engine!

gae

package nescala

import unfiltered.request._
import unfiltered.response._

class Echo extends unfiltered.filter.Plan {
  def intent = {
    case Path(path) =>
      ResponseString(path)
  }
}
<web-app>
  <filter>
    <filter-name>Echo</filter-name>
    <filter-class>nescala.Echo</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>Echo</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

What if we don’t care about containers?

unfiltered.jetty.Http(8080).filter(
  new Echo
).run()
package nescala

import unfiltered.request._
import unfiltered.response._

val echo = unfiltered.netty.cycle.Planify {
  case Path(path) =>
    ResponseString(path)
}

unfiltered.netty.Http(8080).plan(echo).run()
val magic = unfiltered.netty.async.Planify {
  case req@Path("/magic") =>
    val context = req.underlying.context
    val request = req.underlying.request
    val event   = req.underlying.event
    // todo: something magic!

  case req@Path("/meh") =>
    req.respond(
      BadRequest ~> ResponseString("meeeeh")
    )
}

The big deal with small.

herald

Stealthy incursions.

incursion

Purpose-built New I/O servers.

graph

A UNIX program should do one thing well, and leave unrelated tasks to other programs.

—Rob Pike & Brian Kernighan

Kit Composition

Kits alter the response based on the request, by composing around your own “intent” function.

This is how you might serve GZip-encoded responses to clients that support it.

  def intent = {
    case Decodes.GZip(req) =>
      ContentEncoding.GZip ~>
      ResponseFilter.GZip ~>
      handleRequest(req)
    case req =>
      handleRequest(req)
  }

But Unfiltered’s GZip kit can do that for you.

  def intent = unfiltered.kit.GZip {
    case _ => ResponseString(...)
  }

Of course, the raw materials are still there if you need to do your own thing.

  def intent = {
    case Decodes.GZip(req) =>
      ContentEncoding.GZip ~>
      ResponseFilter.GZip ~>
      ResponseString(...)
    case _ =>
      BadRequest ~>
      ResponseString("you must to accept GZip")
  }

Kits can provide alternatives.

  def intent = {
    case Path(Seg("users" :: Nil)) =>
      Users.index()
    case Path(Seg("users" :: id :: Nil)) =>
      Users.display(id)
    case Path(Seg("places" :: Nil)) =>
      Places.index()
    case Path(Seg("places" :: id :: Nil)) =>
      Places.display(id)
    ...
  }
  def intent = unfiltered.kit.Routes.specify(
    "/users" -> Users.index,
    "/users/:id" -> Users.get,
    "/places" -> Places.index,
    "/places/:id" -> Places.get
    ...
  )
  object Users {
    def get(req: HttpRequest[_],
            pmap: Map[String,String]) = ...
  }

Choose your own template adventure

object Users {
  def get(req: HttpRequest[_],
          pmap: Map[String,String]) =
    (for {
      id <- pmap.get("id")
      user <- UserDB.get(id)
    } yield Scalate(req,
                    "userprofile.ssp",
                    "user" -> user)
    ).getOrElse(Pass)
}

old pipes

Unfiltered