package.scala

package ru.circumflex

import core._
import javax.servlet.{FilterChain, FilterConfig}
import collection.Iterator
import javax.activation.MimetypesFileTypeMap
import java.io._
import org.apache.commons.io.IOUtils
import collection.mutable.Map

The web Package

Package web contains different shortcuts, utilities and helpers — the basis of routing DSL of Circumflex Web Framework.

You should import this package to use Circumflex Web Framework in your application:

import ru.circumflex.web._

If you don't wish to import all helpers into your global scope, then import this package under an alias:

import ru.circumflex.{web => cx}    // import under alias "cx"
cx.request                          // access members
package object web {

  val WEB_LOG = new Logger("ru.circumflex.web")

Heplers

Helpers are tiny methods and structures which provide common functionality for web applications like accessing current request, response, session, headers, cookies and other stuff.

  def request = ctx("cx.request").asInstanceOf[HttpRequest]
  def response = ctx("cx.response").asInstanceOf[HttpResponse]
  def filterChain = ctx("cx.filterChain").asInstanceOf[FilterChain]
  def filterConfig = cx("cx.filterConfig").asInstanceOf[FilterConfig]
  def servletContext = filterConfig.getServletContext
  def session = request.session

The headers Helper

The headers object of package ru.circumflex.web lets you access request headers and set response headers (i.e. mix functionality of request.headers and response.headers).

  object headers extends Map[String, String] {
    def +=(kv: (String, String)): this.type = {
      response.headers + kv
      this
    }
    def -=(key: String): this.type = {
      response.headers - key
      this
    }
    def iterator: Iterator[(String, String)] = request.headers.iterator
    def get(key: String): Option[String] = request.headers.get(key)
  }

The cookies Helper

The cookies object of package ru.circumflex.web lets you access request cookies and set response cookies (i.e. mix functionality of request.cookies and response.cookies).

  object cookies extends Map[String, HttpCookie] {
    def +=(kv: (String, HttpCookie)): this.type = {
      response.cookies += kv._2.asInstanceOf[HttpCookie]
      this
    }
    def -=(key: String): this.type = {
      response.cookies.find(_.name == key).map(c => response.cookies -= c)
      this
    }
    def iterator: Iterator[(String, HttpCookie)] =
      request.cookies.iterator.map(c => (c.name -> c))
    def get(key: String): Option[HttpCookie] =
      request.cookies.find(c => c.name == key)
  }

The flash Helper

The flash object provides a way to pass temporary objects between requests. Flash variables are stored in session until first access.

  object flash extends Map[String, Any] with UntypedContainer {
    val SESSION_KEY = "cx.flash"
    protected def flashMap = session
        .getOrElse(SESSION_KEY, Map[String, Any]())
        .asInstanceOf[Map[String, Any]]
    def +=(kv: (String, Any)): this.type = {
      session(SESSION_KEY) = flashMap + (kv)
      this
    }
    def -=(key: String): this.type = {
      session(SESSION_KEY) = flashMap - key
      this
    }
    def iterator: Iterator[(String, Any)] = flashMap.iterator
    def get(key: String): Option[Any] = {
      val m = flashMap
      flashMap.get(key) map { v =>
        session(SESSION_KEY) = m - key
        v
      }
    }
    override def contains(key: String): Boolean = flashMap.contains(key)
  }

The param Helper

The param object of package ru.circumflex.web is a convenient helper to retrieve the parameters of current match or current request. The parameters are first resolved from MatchResult objects found in context. If no match result contain a parameter with specified name, then the parameter is resolved from request parameters.

  object param extends Map[String, String] {
    def +=(kv: (String, String)): this.type = this
    def -=(key: String): this.type = this
    def iterator: Iterator[(String, String)] = ctx.iterator.flatMap(p => p._2 match {
      case m: MatchResult => m.params.iterator
      case s: String => Seq(p._1 -> s).iterator
      case _ => Iterator.empty
    }) ++ request.params.iterator
    def get(key: String): Option[String] = iterator.find(_._1 == key).map(_._2)
    override def default(key: String): String = ""
    def list(key: String): Seq[String] = iterator.filter(_._1 == key).map(_._2).toList
  }

Response Helpers

Circumflex Web Framework provides following helpers for sending standard HTTP responses:

  • send writes specified text to response buffer and, if specified, sets statusCode;
  • sendError sends an error to the client using specified statusCode and message;
  • sendRedirect sends 302 MOVED TEMPORARILY to the client using specified url and optional flashes;
  • sendFile sends specified file to the client; if filename is provided, Content-Disposition: attachment is also added to the response with specified filename;
  • xSendFile delegates filesending to the web server; refer to documentation of your web server to understand how it works;
  • sendStream accepts a function, which uses OutputStream to send binary data;
  • sendChars accepts a function, which uses Writer to send character data;
  • forward delegates further request processing to another component located at specified url and immediately flushes the response at the end; note that if you want to forward the request to another Circumflex route, you must make sure that CircumflexFilter is mapped with <dispatcher>FORWARD</dispatcher> in web.xml;
  • pass() sends request and response down the filter chain and then immediately flushes response.

All helpers by convention throw ResponseSentException which is caught by CircumflexFilter to indicate that the response have been processed successfully.

  def send(text: String = "", statusCode: Int = -1): Nothing = {
    if (statusCode != -1)
      response.statusCode(statusCode)
    response.body(r => r.getWriter.write(text)).flush()
  }
  def sendError(statusCode: Int, message: String = "No message available."): Nothing =
    response.body(r => r.sendError(statusCode, message)).flush()
  def sendRedirect(url: String, flashes: (String, Any)*): Nothing = {
    flashes.foreach(kv => flash(kv._1) = kv._2)
    response.body(r => r.sendRedirect(url)).flush()
  }
  def sendFile(file: File, filename: String = ""): Nothing = {
    // if filename is provided, add `Content-Disposition` header
    if (filename != "") response.attachment(filename)
    // if not set explicitly, infer content type from extension
    if (response.contentType == "")
      response.contentType(new MimetypesFileTypeMap().getContentType(file))
    // send file by copying streams
    response.body { r =>
      val is = new FileInputStream(file)
      try {
        IOUtils.copy(is, r.getOutputStream)
      } finally {
        is.close()
      }
    } flush()
  }
  def xSendFile(file: File, filename: String = ""): Nothing = {
    // if filename is provided, add `Content-Disposition` header
    if (filename != "") response.attachment(filename)
    val xsf = cx.instantiate[XSendFileHeader]("cx.xSendFile", DefaultXSendFileHeader)
    response.headers(xsf.name) = xsf.value(file)
    send()
  }
  def sendStream(streamFunc: OutputStream => Unit): Nothing =
    response.body(r => streamFunc(r.getOutputStream)).flush()
  def sendChars(writerFunc: Writer => Unit): Nothing =
    response.body(r => writerFunc(r.getWriter)).flush()
  def forward(url: String): Nothing = {
    request.forward(url)
    response.flush()
  }
  def pass(): Nothing = {
    filterChain.doFilter(request.raw, response.raw)
    response.flush()
  }

The matchers Helper

The matchers helper contains shortcuts for various matchers (for example, by known HTTP headers). You should import this object if you want to use it:

import ru.circumflex.web.{matchers => m}

get("/" & m.ACCEPT(":mime")) = "You are accepting " + param("mime")
  object matchers {
    val ACCEPT = new HeaderMatcherHelper("Accept")
    val ACCEPT_CHARSET = new HeaderMatcherHelper("Accept-Charset")
    val ACCEPT_ENCODING = new HeaderMatcherHelper("Accept-Encoding")
    val ACCEPT_LANGUAGE = new HeaderMatcherHelper("Accept-Language")
    val ACCEPT_RANGES = new HeaderMatcherHelper("Accept-Ranges")
    val AUTHORIZARION = new HeaderMatcherHelper("Authorization")
    val CACHE_CONTROL = new HeaderMatcherHelper("Cache-Control")
    val CONNECTION = new HeaderMatcherHelper("Connection")
    val CONTENT_LENGTH = new HeaderMatcherHelper("Content-Length")
    val COOKIE = new HeaderMatcherHelper("Cookie")
    val CONTENT_TYPE = new HeaderMatcherHelper("Content-Type")
    val DATE = new HeaderMatcherHelper("Date")
    val EXPECT = new HeaderMatcherHelper("Expect")
    val FROM = new HeaderMatcherHelper("From")
    val HOST = new HeaderMatcherHelper("Host")
    val IF_MATCH = new HeaderMatcherHelper("If-Match")
    val IF_MODIFIED_SINCE = new HeaderMatcherHelper("If-Modified-Since")
    val IF_NONE_MATCH = new HeaderMatcherHelper("If-None-Match")
    val IF_RANGE = new HeaderMatcherHelper("If-Range")
    val IF_UNMODIFIED_SINCE = new HeaderMatcherHelper("If-Unmodified-Since")
    val MAX_FORWARDS = new HeaderMatcherHelper("Max-Forwards")
    val PRAGMA = new HeaderMatcherHelper("Pragma")
    val PROXY_AUTHORIZATION = new HeaderMatcherHelper("Proxy-Authorization")
    val RANGE = new HeaderMatcherHelper("Range")
    val REFERER = new HeaderMatcherHelper("Referer")
    val TE = new HeaderMatcherHelper("TE")
    val UPGRADE = new HeaderMatcherHelper("Upgrade")
    val USER_AGENT = new HeaderMatcherHelper("User-Agent")
    val VIA = new HeaderMatcherHelper("Via")
    val WARNING = new HeaderMatcherHelper("Warning")
  }

}