All Downloads are FREE. Search and download functionalities are using the official Maven repository.

.circumflex-core.1.0.source-code.router.scala Maven / Gradle / Ivy

package ru.circumflex.core

import java.io.File

case class RouteMatchedException(val response: Option[HttpResponse]) extends Exception

// ## Request Router

class RequestRouter {

  context.updateUri // to support router forwards

  implicit def textToResponse(text: String): HttpResponse = TextResponse(text)
  implicit def requestRouterToResponse(router: RequestRouter): HttpResponse = error(404)

  /**
   * ## Route
   *
   * Dispatches current request if it passes all matchers.
   * Common matchers are based on HTTP methods, URI and headers.
   */
  class Route(matchingMethods: String*) {

    protected def dispatch(response: =>HttpResponse, matchers: RequestMatcher*): Unit =
      matchingMethods.find(context.method.equalsIgnoreCase(_)) match {
        case Some(_) => {
          var params = Map[String, Match]()
          matchers.toList.foreach(rm => rm(context.request) match {
            case None => return
            case Some(p) => params ++= p
          })
          // All matchers succeeded
          context._matches = params
          throw RouteMatchedException(Some(response))
        } case _ =>
      }

    /**
     * For syntax "get(...) { case Extractors(...) => ... }"
     */
    def apply(matcher: StringMatcher)(f: CircumflexContext => HttpResponse): Unit =
      dispatch(ContextualResponse(f), new UriMatcher(matcher))

    def apply(matcher: StringMatcher, matcher1: RequestMatcher)(f: CircumflexContext => HttpResponse): Unit =
      dispatch(ContextualResponse(f), new UriMatcher(matcher), matcher1)

    /**
     * For syntax "get(...) = response"
     */
    def update(matcher: StringMatcher, response: =>HttpResponse): Unit =
      dispatch(response, new UriMatcher(matcher))

    def update(matcher: StringMatcher, matcher1: RequestMatcher, response: =>HttpResponse): Unit =
      dispatch(response, new UriMatcher(matcher), matcher1)

  }

  // ### Routes

  val get = new Route("get")
  val getOrPost = new Route("get", "post")
  val getOrHead = new Route("get", "head")
  val post = new Route("post")
  val put = new Route("put")
  val delete = new Route("delete")
  val head = new Route("head")
  val options = new Route("options")
  val any = new Route("get", "post", "put", "delete", "head", "options")

  // ### Context shortcuts

  def context = CircumflexContext.context
  def header = context.header
  def session = context.session
  def flash = context.flash

  lazy val uri: Match = matching("uri")

  // ### Helpers

  /**
   * Determines, if the request is XMLHttpRequest (for AJAX applications).
   */
  def isXhr = header("X-Requested-With").getOrElse("") == "XMLHttpRequest"

  /**
   * Retrieves a String from context.
   */
  def param(key: String): Option[String] = context.getString(key)

  /**
   * Retrieves a Match from context.
   * Since route matching has success it supposes the key existence.
   */
  def matching(key: String): Match = context.getMatch(key).get

  /**
   * Sends error with specified status code and message.
   */
  def error(errorCode: Int, message: String = "no message available") =
    ErrorResponse(errorCode, message)

  /**
   * Rewrites request URI. Normally it causes the request to travel all the way through
   * the filters and `RequestRouter` once again but with different URI.
   * You must use this method with caution to prevent infinite loops.
   * You must also add `FORWARD` to filter mapping to
   * allow request processing with certain filters.
   */
  def rewrite(target: String): Nothing = {
    context.request.getRequestDispatcher(context.getAbsoluteUri(target))
                   .forward(context.request, context.response)
    throw RouteMatchedException(None)
  }

  /**
   * Sends a `302 Moved Temporarily` redirect (with optional flashes).
   */
  def redirect(location: String, flashes: (String, Any)*) = {
    for ((key, value) <- flashes) flash(key) = value
    RedirectResponse(context.getAbsoluteUri(location))
  }

  /**
   * Sends empty response with specified status code (default is `200 OK`).
   */
  def done(statusCode: Int = 200): HttpResponse = {
    context.statusCode = statusCode
    new EmptyResponse
  }

  /**
   * Immediately stops processing with `400 Bad Request` if one of specified
   * parameters is not provided.
   */
  def requireParams(names: String*) =
    for (name <- names if param(name).isEmpty)
      throw new RouteMatchedException(error(400, "Missing " + name + " parameter."))

  /**
   * Sends a file with Content-Disposition: attachment with specified UTF-8 filename.
   */
  def sendFile(file: File, filename: String = null): FileResponse = {
    if (filename != null) attachment(filename)
    FileResponse(file)
  }

  /**
   * Sends a file with the help of Web server using X-SendFile feature, also setting
   * Content-Disposition: attachment with specified UTF-8 filename.
   */
  def xSendFile(file: File, filename: String = null): HttpResponse = {
    if (filename != null) attachment(filename)
    val xsf = Circumflex.newObject("cx.XSendFileHeader", DefaultXSendFileHeader)
    header(xsf.name) = xsf.value(file)
    done()
  }

  /**
   * Adds an Content-Disposition header with "attachment" content and specified UTF-8 filename.
   */
  def attachment(filename: String): this.type = {
    header("Content-Disposition") =
        "attachment; filename=\"" + new String(filename.getBytes("UTF-8"), "ISO-8859-1") + "\""
    return this
  }

  /**
   * Sets content type header.
   */
  def contentType(ct: String): this.type = {
    context.contentType = ct
    return this
  }

  /**
   * A helper to sets appropriate headers for disabling client-side caching.
   */
  def noCache() {
    header('Pragma) = "no-cache"
    header("Cache-Control") = "no-store"
    header('Expires) = 0l
  }

  /* ## Request extractors */

  case class HeaderExtractor(name: String) {
    def apply(matcher: StringMatcher) = new HeaderMatcher(name, matcher)
    
    def unapplySeq(ctx: CircumflexContext): Option[Seq[String]] =
      ctx.getMatch(name) match {
        case Some(m) => m.unapplySeq(ctx)
        case None    => ctx.header(name) map { List(_) }
      }
  }

  val accept = HeaderExtractor("Accept")
  val accept_charset = HeaderExtractor("Accept-Charset")
  val accept_encoding = HeaderExtractor("Accept-Encoding")
  val accept_language = HeaderExtractor("Accept-Language")
  val accept_ranges = HeaderExtractor("Accept-Ranges")
  val authorization = HeaderExtractor("Authorization")
  val cache_control = HeaderExtractor("Cache-Control")
  val connection = HeaderExtractor("Connection")
  val cookie = HeaderExtractor("Cookie")
  val content_length = HeaderExtractor("Content-Length")
  val content_type = HeaderExtractor("Content-Type")
  val header_date = HeaderExtractor("Date")
  val expect = HeaderExtractor("Expect")
  val from = HeaderExtractor("From")
  val host = HeaderExtractor("Host")
  val if_match = HeaderExtractor("If-Match")
  val if_modified_since = HeaderExtractor("If-Modified-Since")
  val if_none_match = HeaderExtractor("If-None-Match")
  val if_range = HeaderExtractor("If-Range")
  val if_unmodified_since = HeaderExtractor("If-Unmodified-Since")
  val max_forwards = HeaderExtractor("Max-Forwards")
  val pragma = HeaderExtractor("Pragma")
  val proxy_authorization = HeaderExtractor("Proxy-Authorization")
  val range = HeaderExtractor("Range")
  val referer = HeaderExtractor("Referer")
  val te = HeaderExtractor("TE")
  val upgrade = HeaderExtractor("Upgrade")
  val user_agent = HeaderExtractor("User-Agent")
  val via = HeaderExtractor("Via")
  val war = HeaderExtractor("War")
  
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy