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

spray.routing.directives.MiscDirectives.scala Maven / Gradle / Ivy

Go to download

A suite of lightweight Scala libraries for building and consuming RESTful web services on top of Akka

The newest version!
/*
 * Copyright © 2011-2013 the spray project 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package spray.routing
package directives

import scala.reflect.{ classTag, ClassTag }
import shapeless._
import spray.http._
import spray.http.parser.CharPredicate
import HttpHeaders._
import MediaTypes._

trait MiscDirectives {
  import BasicDirectives._
  import RouteDirectives._

  /**
   * Returns a Directive which checks the given condition before passing on the [[spray.routing.RequestContext]] to
   * its inner Route. If the condition fails the route is rejected with a [[spray.routing.ValidationRejection]].
   */
  def validate(check: ⇒ Boolean, errorMsg: String): Directive0 =
    new Directive0 {
      def happly(f: HNil ⇒ Route) = if (check) f(HNil) else reject(ValidationRejection(errorMsg))
    }

  /**
   * Directive extracting the IP of the client from either the X-Forwarded-For, Remote-Address or X-Real-IP header
   * (in that order of priority).
   */
  def clientIP: Directive1[RemoteAddress] = MiscDirectives._clientIP

  /**
   * Wraps the inner Route with JSONP support. If a query parameter with the given name is present in the request and
   * the inner Route returns content with content-type `application/json` the response content is wrapped with a call
   * to a Javascript function having the name of query parameters value. The name of this function is validated to
   * prevent XSS vulnerabilities. Only alphanumeric, underscore (_), dollar ($) and dot (.) characters are allowed.
   * Additionally the content-type is changed from `application/json` to `application/javascript` in these cases.
   */
  def jsonpWithParameter(parameterName: String): Directive0 = {
    import MiscDirectives._
    import ParameterDirectives._
    parameter(parameterName?).flatMap {
      case Some(wrapper) ⇒
        if (validJsonpChars.matchAll(wrapper)) {
          mapHttpResponseEntity {
            case HttpEntity.NonEmpty(ct @ ContentType(`application/json`, _), data) ⇒ HttpEntity(
              contentType = ct.withMediaType(`application/javascript`),
              string = wrapper + '(' + data.asString(ct.charset.nioCharset) + ')')
            case entity ⇒ entity
          }
        } else reject(MalformedQueryParamRejection(parameterName, "Invalid JSONP callback identifier"))
      case _ ⇒ noop
    }
  }

  /**
   * Adds a TransformationRejection cancelling all rejections for which the given filter function returns true
   * to the list of rejections potentially coming back from the inner route.
   */
  def cancelAllRejections(cancelFilter: Rejection ⇒ Boolean): Directive0 =
    mapRejections(_ :+ TransformationRejection(_.filterNot(cancelFilter)))

  /**
   * Adds a TransformationRejection cancelling all rejections equal to the given one
   * to the list of rejections potentially coming back from the inner route.
   */
  def cancelRejection(rejection: Rejection): Directive0 =
    cancelAllRejections(_ == rejection)

  def ofType[T <: Rejection: ClassTag]: Rejection ⇒ Boolean = {
    val erasure = classTag[T].runtimeClass
    erasure.isInstance(_)
  }

  def ofTypes(classes: Class[_]*): Rejection ⇒ Boolean = { rejection ⇒
    classes.exists(_.isInstance(rejection))
  }

  /**
   * Rejects the request if its entity is not empty.
   */
  def requestEntityEmpty: Directive0 = MiscDirectives._requestEntityEmpty

  /**
   * Rejects empty requests with a RequestEntityExpectedRejection.
   * Non-empty requests are passed on unchanged to the inner route.
   */
  def requestEntityPresent: Directive0 = MiscDirectives._requestEntityPresent

  /**
   * Transforms the unmatchedPath of the RequestContext using the given function.
   */
  def rewriteUnmatchedPath(f: Uri.Path ⇒ Uri.Path): Directive0 =
    mapRequestContext(_.withUnmatchedPathMapped(f))

  /**
   * Extracts the unmatched path from the RequestContext.
   */
  def unmatchedPath: Directive1[Uri.Path] = MiscDirectives._unmatchedPath

  /**
   * Converts responses with an empty entity into (empty) rejections.
   * This way you can, for example, have the marshalling of a ''None'' option be treated as if the request could
   * not be matched.
   */
  def rejectEmptyResponse: Directive0 = MiscDirectives._rejectEmptyResponse

  /**
   * Extracts the complete request.
   */
  def requestInstance: Directive1[HttpRequest] = MiscDirectives._requestInstance

  /**
   * Extracts the complete request URI.
   */
  def requestUri: Directive1[Uri] = MiscDirectives._requestUri
}

object MiscDirectives extends MiscDirectives {
  import BasicDirectives._
  import HeaderDirectives._
  import RouteDirectives._
  import CharPredicate._

  private val validJsonpChars = AlphaNum ++ '.' ++ '_' ++ '$'

  private val _clientIP: Directive1[RemoteAddress] =
    headerValuePF { case `X-Forwarded-For`(Seq(address, _*)) ⇒ address } |
      headerValuePF { case `Remote-Address`(address) ⇒ address } |
      headerValuePF { case h if h.is("x-real-ip") ⇒ RemoteAddress(h.value) }

  private val _requestEntityEmpty: Directive0 =
    extract(_.request.entity.isEmpty).flatMap(if (_) pass else reject)

  private val _requestEntityPresent: Directive0 =
    extract(_.request.entity.isEmpty).flatMap(if (_) reject else pass)

  private val _unmatchedPath: Directive1[Uri.Path] =
    extract(_.unmatchedPath)

  private val _rejectEmptyResponse: Directive0 =
    mapRouteResponse {
      case HttpMessagePartWrapper(HttpResponse(_, HttpEntity.Empty, _, _), _) ⇒ Rejected(Nil)
      case x ⇒ x
    }

  private val _requestInstance: Directive1[HttpRequest] =
    extract(_.request)

  private val _requestUri: Directive1[Uri] =
    extract(_.request.uri)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy