227 lines
11 KiB
Scala
227 lines
11 KiB
Scala
/*
|
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
*/
|
|
|
|
package akka.http.impl.server
|
|
|
|
import akka.http.impl.util.JavaMapping
|
|
import akka.http.javadsl.server.values.{ PathMatcher, BasicCredentials, OAuth2Credentials }
|
|
import akka.http.scaladsl.model.StatusCodes.Redirection
|
|
import akka.http.scaladsl.server.util.TupleOps.Join
|
|
|
|
import scala.language.implicitConversions
|
|
import scala.annotation.tailrec
|
|
import scala.collection.immutable
|
|
import akka.http.javadsl.model.ContentType
|
|
import akka.http.scaladsl.server.directives.{ Credentials, ContentTypeResolver }
|
|
import akka.http.scaladsl.server.directives.FileAndResourceDirectives.DirectoryRenderer
|
|
import akka.http.scaladsl.model.HttpHeader
|
|
import akka.http.scaladsl.model.headers.{ HttpCookie, CustomHeader }
|
|
import akka.http.scaladsl.server.{ Route ⇒ ScalaRoute, Directive ⇒ ScalaDirective, PathMatcher ⇒ ScalaPathMatcher, PathMatcher1, Directive0, Directive1, Directives }
|
|
import akka.http.impl.util.JavaMapping.Implicits._
|
|
import akka.http.scaladsl.server
|
|
import akka.http.javadsl.server._
|
|
import RouteStructure._
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[http] trait ExtractionMap extends CustomHeader {
|
|
def get[T](key: RequestVal[T]): Option[T]
|
|
def set[T](key: RequestVal[T], value: T): ExtractionMap
|
|
def addAll(values: Map[RequestVal[_], Any]): ExtractionMap
|
|
}
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[http] object ExtractionMap {
|
|
val Empty = ExtractionMap(Map.empty)
|
|
implicit def apply(map: Map[RequestVal[_], Any]): ExtractionMap =
|
|
new ExtractionMap {
|
|
def get[T](key: RequestVal[T]): Option[T] =
|
|
map.get(key).asInstanceOf[Option[T]]
|
|
|
|
def set[T](key: RequestVal[T], value: T): ExtractionMap =
|
|
ExtractionMap(map.updated(key, value))
|
|
|
|
def addAll(values: Map[RequestVal[_], Any]): ExtractionMap =
|
|
ExtractionMap(map ++ values)
|
|
|
|
def renderInRequests = false
|
|
def renderInResponses = false
|
|
def name(): String = "ExtractedValues"
|
|
def value(): String = "<empty>"
|
|
}
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[http] object RouteImplementation extends Directives with server.RouteConcatenation {
|
|
def apply(route: Route): ScalaRoute = {
|
|
def directiveFor(route: DirectiveRoute): Directive0 = route match {
|
|
case RouteAlternatives() ⇒ ScalaDirective.Empty
|
|
case RawPathPrefix(elements) ⇒ pathMatcherDirective[String](elements, rawPathPrefix)
|
|
case RawPathPrefixTest(elements) ⇒ pathMatcherDirective[String](elements, rawPathPrefixTest)
|
|
case PathSuffix(elements) ⇒ pathMatcherDirective[String](elements, pathSuffix)
|
|
case PathSuffixTest(elements) ⇒ pathMatcherDirective[String](elements, pathSuffixTest)
|
|
case RedirectToTrailingSlashIfMissing(code) ⇒ redirectToTrailingSlashIfMissing(code.asScala.asInstanceOf[Redirection])
|
|
case RedirectToNoTrailingSlashIfPresent(code) ⇒ redirectToNoTrailingSlashIfPresent(code.asScala.asInstanceOf[Redirection])
|
|
|
|
case MethodFilter(m) ⇒ method(m.asScala)
|
|
case Extract(extractions) ⇒
|
|
extractRequestContext.flatMap { ctx ⇒
|
|
extractions.map { e ⇒
|
|
e.directive.flatMap(addExtraction(e.asInstanceOf[RequestVal[Any]], _))
|
|
}.reduce(_ & _)
|
|
}
|
|
|
|
case BasicAuthentication(authenticator) ⇒
|
|
authenticateBasicAsync(authenticator.realm, { creds ⇒
|
|
val javaCreds =
|
|
creds match {
|
|
case Credentials.Missing ⇒
|
|
new BasicCredentials {
|
|
def available: Boolean = false
|
|
def identifier: String = throw new IllegalStateException("Credentials missing")
|
|
def verify(secret: String): Boolean = throw new IllegalStateException("Credentials missing")
|
|
}
|
|
case p @ Credentials.Provided(name) ⇒
|
|
new BasicCredentials {
|
|
def available: Boolean = true
|
|
def identifier: String = name
|
|
def verify(secret: String): Boolean = p.verify(secret)
|
|
}
|
|
}
|
|
|
|
authenticator.authenticate(javaCreds)
|
|
}).flatMap { user ⇒
|
|
addExtraction(authenticator.asInstanceOf[RequestVal[Any]], user)
|
|
}
|
|
|
|
case OAuth2Authentication(authenticator) ⇒
|
|
authenticateOAuth2Async(authenticator.realm, { creds ⇒
|
|
val javaCreds =
|
|
creds match {
|
|
case Credentials.Missing ⇒
|
|
new OAuth2Credentials {
|
|
def available: Boolean = false
|
|
def identifier: String = throw new IllegalStateException("Credentials missing")
|
|
def verify(secret: String): Boolean = throw new IllegalStateException("Credentials missing")
|
|
}
|
|
case p @ Credentials.Provided(name) ⇒
|
|
new OAuth2Credentials {
|
|
def available: Boolean = true
|
|
def identifier: String = name
|
|
def verify(secret: String): Boolean = p.verify(secret)
|
|
}
|
|
}
|
|
|
|
authenticator.authenticate(javaCreds)
|
|
}).flatMap { user ⇒
|
|
addExtraction(authenticator.asInstanceOf[RequestVal[Any]], user)
|
|
}
|
|
|
|
case EncodeResponse(coders) ⇒
|
|
val scalaCoders = coders.map(_._underlyingScalaCoder())
|
|
encodeResponseWith(scalaCoders.head, scalaCoders.tail: _*)
|
|
|
|
case DecodeRequest(coders) ⇒ decodeRequestWith(coders.map(_._underlyingScalaCoder()): _*)
|
|
case Conditional(eTag, lastModified) ⇒ conditional(eTag.map(_.asScala), lastModified.map(_.asScala))
|
|
case h: HostFilter ⇒ host(h.filter _)
|
|
case SchemeFilter(schemeName) ⇒ scheme(schemeName)
|
|
|
|
case HandleExceptions(handler) ⇒
|
|
val pf: akka.http.scaladsl.server.ExceptionHandler = akka.http.scaladsl.server.ExceptionHandler {
|
|
case e: RuntimeException ⇒ apply(handler.handle(e))
|
|
}
|
|
handleExceptions(pf)
|
|
|
|
case HandleRejections(handler) ⇒ handleRejections(new RejectionHandlerWrapper(handler))
|
|
case Validated(isValid, errorMsg) ⇒ validate(isValid, errorMsg)
|
|
case RangeSupport() ⇒ withRangeSupport
|
|
case SetCookie(cookie) ⇒ setCookie(cookie.asScala)
|
|
case DeleteCookie(name, domain, path) ⇒ deleteCookie(HttpCookie(name, domain = domain, path = path, value = "deleted"))
|
|
}
|
|
|
|
route match {
|
|
case route: DirectiveRoute ⇒ directiveFor(route).apply(fromAlternatives(route.children))
|
|
case GetFromResource(path, contentType, classLoader) ⇒ getFromResource(path, contentType.asScala, classLoader)
|
|
case GetFromResourceDirectory(path, classLoader, resolver) ⇒ getFromResourceDirectory(path, classLoader)(scalaResolver(resolver))
|
|
case GetFromFile(file, contentType) ⇒ getFromFile(file, contentType.asScala)
|
|
case GetFromDirectory(directory, true, resolver) ⇒
|
|
extractExecutionContext { implicit ec ⇒
|
|
getFromBrowseableDirectory(directory.getPath)(DirectoryRenderer.defaultDirectoryRenderer, scalaResolver(resolver))
|
|
}
|
|
case FileAndResourceRouteWithDefaultResolver(constructor) ⇒
|
|
RouteImplementation(constructor(new directives.ContentTypeResolver {
|
|
def resolve(fileName: String): ContentType = ContentTypeResolver.Default(fileName)
|
|
}))
|
|
|
|
case HandleWebsocketMessages(handler) ⇒ handleWebsocketMessages(JavaMapping.toScala(handler))
|
|
case Redirect(uri, code) ⇒ redirect(uri.asScala, code.asScala.asInstanceOf[Redirection]) // guarded by require in Redirect
|
|
|
|
case dyn: DynamicDirectiveRoute1[t1Type] ⇒
|
|
def runToRoute(t1: t1Type): ScalaRoute =
|
|
apply(dyn.createDirective(t1).route(dyn.innerRoute, dyn.moreInnerRoutes: _*))
|
|
|
|
requestValToDirective(dyn.value1)(runToRoute)
|
|
|
|
case dyn: DynamicDirectiveRoute2[t1Type, t2Type] ⇒
|
|
def runToRoute(t1: t1Type, t2: t2Type): ScalaRoute =
|
|
apply(dyn.createDirective(t1, t2).route(dyn.innerRoute, dyn.moreInnerRoutes: _*))
|
|
|
|
(requestValToDirective(dyn.value1) & requestValToDirective(dyn.value2))(runToRoute)
|
|
|
|
case o: OpaqueRoute ⇒ (ctx ⇒ o.handle(new RequestContextImpl(ctx)).asInstanceOf[RouteResultImpl].underlying)
|
|
case p: Product ⇒ extractExecutionContext { implicit ec ⇒ complete((500, s"Not implemented: ${p.productPrefix}")) }
|
|
}
|
|
}
|
|
def pathMatcherDirective[T](matchers: immutable.Seq[PathMatcher[_]],
|
|
directive: PathMatcher1[T] ⇒ Directive1[T] // this type is too specific and only a placeholder for a proper polymorphic function
|
|
): Directive0 = {
|
|
// Concatenating PathMatchers is a bit complicated as we don't want to build up a tuple
|
|
// but something which we can later split all the separate values and add them to the
|
|
// ExtractionMap.
|
|
//
|
|
// This is achieved by providing a specialized `Join` instance to use with PathMatcher
|
|
// which provides the desired behavior.
|
|
|
|
type ValMap = Tuple1[Map[RequestVal[_], Any]]
|
|
object AddToMapJoin extends Join[ValMap, ValMap] {
|
|
type Out = ValMap
|
|
def apply(prefix: ValMap, suffix: ValMap): AddToMapJoin.Out =
|
|
Tuple1(prefix._1 ++ suffix._1)
|
|
}
|
|
def toScala(matcher: PathMatcher[_]): ScalaPathMatcher[ValMap] =
|
|
matcher.asInstanceOf[PathMatcherImpl[_]].matcher.transform(_.map(v ⇒ Tuple1(Map(matcher -> v._1))))
|
|
def addExtractions(valMap: T): Directive0 = transformExtractionMap(_.addAll(valMap.asInstanceOf[Map[RequestVal[_], Any]]))
|
|
val reduced: ScalaPathMatcher[ValMap] = matchers.map(toScala).reduce(_.~(_)(AddToMapJoin))
|
|
directive(reduced.asInstanceOf[PathMatcher1[T]]).flatMap(addExtractions)
|
|
}
|
|
|
|
def fromAlternatives(alternatives: Seq[Route]): ScalaRoute =
|
|
alternatives.map(RouteImplementation.apply).reduce(_ ~ _)
|
|
|
|
def addExtraction[T](key: RequestVal[T], value: T): Directive0 =
|
|
transformExtractionMap(_.set(key, value))
|
|
|
|
def transformExtractionMap(f: ExtractionMap ⇒ ExtractionMap): Directive0 = {
|
|
@tailrec def updateExtractionMap(headers: immutable.Seq[HttpHeader], prefix: Vector[HttpHeader] = Vector.empty): immutable.Seq[HttpHeader] =
|
|
headers match {
|
|
case (m: ExtractionMap) +: rest ⇒ f(m) +: (prefix ++ rest)
|
|
case other +: rest ⇒ updateExtractionMap(rest, prefix :+ other)
|
|
case Nil ⇒ f(ExtractionMap.Empty) +: prefix
|
|
}
|
|
mapRequest(_.mapHeaders(updateExtractionMap(_)))
|
|
}
|
|
|
|
private def scalaResolver(resolver: directives.ContentTypeResolver): ContentTypeResolver =
|
|
ContentTypeResolver(f ⇒ resolver.resolve(f).asScala)
|
|
|
|
def requestValToDirective[T](value: RequestVal[T]): Directive1[T] =
|
|
value match {
|
|
case s: StandaloneExtractionImpl[_] ⇒ s.directive
|
|
case v: RequestVal[_] ⇒ extract(ctx ⇒ v.get(new RequestContextImpl(ctx)))
|
|
}
|
|
}
|