+str #17361: Unified http java/scala projects except marshallers

This commit is contained in:
Endre Sándor Varga 2015-05-21 17:17:55 +02:00
parent 454a393af1
commit be82e85ffc
182 changed files with 13693 additions and 0 deletions

View file

@ -0,0 +1,17 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server.util
import akka.http.scaladsl.server.Route
private[util] abstract class ApplyConverterInstances {
[#implicit def hac1[[#T1#]] = new ApplyConverter[Tuple1[[#T1#]]] {
type In = ([#T1#]) ⇒ Route
def apply(fn: In): (Tuple1[[#T1#]]) ⇒ Route = {
case Tuple1([#t1#]) ⇒ fn([#t1#])
}
}#
]
}

View file

@ -0,0 +1,13 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server.util
private[util] abstract class ConstructFromTupleInstances {
[#implicit def instance1[[#T1#], R](construct: ([#T1#]) => R): ConstructFromTuple[Tuple1[[#T1#]], R] =
new ConstructFromTuple[Tuple1[[#T1#]], R] {
def apply(tup: Tuple1[[#T1#]]): R = construct([#tup._1#])
}#
]
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server.util
import TupleOps.AppendOne
private[util] abstract class TupleAppendOneInstances {
type Aux[P, S, Out0] = AppendOne[P, S] { type Out = Out0 }
implicit def append0[T1]: Aux[Unit, T1, Tuple1[T1]] =
new AppendOne[Unit, T1] {
type Out = Tuple1[T1]
def apply(prefix: Unit, last: T1): Tuple1[T1] = Tuple1(last)
}
[1..21#implicit def append1[[#T1#], L]: Aux[Tuple1[[#T1#]], L, Tuple2[[#T1#], L]] =
new AppendOne[Tuple1[[#T1#]], L] {
type Out = Tuple2[[#T1#], L]
def apply(prefix: Tuple1[[#T1#]], last: L): Tuple2[[#T1#], L] = Tuple2([#prefix._1#], last)
}#
]
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server.util
import TupleOps.FoldLeft
import BinaryPolyFunc.Case
private[util] abstract class TupleFoldInstances {
type Aux[In, T, Op, Out0] = FoldLeft[In, T, Op] { type Out = Out0 }
implicit def t0[In, Op]: Aux[In, Unit, Op, In] =
new FoldLeft[In, Unit, Op] {
type Out = In
def apply(zero: In, tuple: Unit) = zero
}
implicit def t1[In, A, Op](implicit f: Case[In, A, Op]): Aux[In, Tuple1[A], Op, f.Out] =
new FoldLeft[In, Tuple1[A], Op] {
type Out = f.Out
def apply(zero: In, tuple: Tuple1[A]) = f(zero, tuple._1)
}
[2..22#implicit def t1[In, [2..#T0#], X, T1, Op](implicit fold: Aux[In, Tuple0[[2..#T0#]], Op, X], f: Case[X, T1, Op]): Aux[In, Tuple1[[#T1#]], Op, f.Out] =
new FoldLeft[In, Tuple1[[#T1#]], Op] {
type Out = f.Out
def apply(zero: In, t: Tuple1[[#T1#]]) =
f(fold(zero, Tuple0([2..#t._0#])), t._1)
}#
]
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server;
/**
* Helper class to steer around SI-9013.
*
* It's currently impossible to implement a trait containing @varargs methods
* if the trait is written in Scala. Therefore, derive from this class and
* implement the method without varargs.
* FIXME: remove once SI-9013 is fixed.
*
* See https://issues.scala-lang.org/browse/SI-9013
*/
abstract class AbstractDirective implements Directive {
@Override
public Route route(Route first, Route... others) {
return createRoute(first, others);
}
protected abstract Route createRoute(Route first, Route[] others);
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server;
import akka.http.scaladsl.coding.Deflate$;
import akka.http.scaladsl.coding.Gzip$;
import akka.http.scaladsl.coding.NoCoding$;
import akka.stream.FlowMaterializer;
import akka.util.ByteString;
import scala.concurrent.Future;
/**
* A coder is an implementation of the predefined encoders/decoders defined for HTTP.
*/
public enum Coder {
NoCoding(NoCoding$.MODULE$), Deflate(Deflate$.MODULE$), Gzip(Gzip$.MODULE$);
private akka.http.scaladsl.coding.Coder underlying;
Coder(akka.http.scaladsl.coding.Coder underlying) {
this.underlying = underlying;
}
public ByteString encode(ByteString input) {
return underlying.encode(input);
}
public Future<ByteString> decode(ByteString input, FlowMaterializer mat) {
return underlying.decode(input, mat);
}
public akka.http.scaladsl.coding.Coder _underlyingScalaCoder() {
return underlying;
}
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server;
/**
* A directive is the basic building block for building routes by composing
* any kind of request or response processing into the main route a request
* flows through. It is a factory that creates a route when given a sequence of
* route alternatives to be augmented with the function the directive
* represents.
*
* The `path`-Directive, for example, filters incoming requests by checking if
* the URI of the incoming request matches the pattern and only invokes its inner
* routes for those requests.
*/
public interface Directive {
/**
* Creates the Route given a sequence of inner route alternatives.
*/
Route route(Route first, Route... others);
}

View file

@ -0,0 +1,43 @@
#######################################
# akka-http Reference Config File #
#######################################
# This is the reference config file that contains all the default settings.
# Make your edits/overrides in your application.conf.
akka.http.routing {
# Enables/disables the returning of more detailed error messages to the
# client in the error response
# Should be disabled for browser-facing APIs due to the risk of XSS attacks
# and (probably) enabled for internal or non-browser APIs
# (Note that akka-http will always produce log messages containing the full error details)
verbose-error-messages = off
# Enables/disables ETag and `If-Modified-Since` support for FileAndResourceDirectives
file-get-conditional = on
# Enables/disables the rendering of the "rendered by" footer in directory listings
render-vanity-footer = yes
# The maximum size between two requested ranges. Ranges with less space in between will be coalesced.
#
# When multiple ranges are requested, a server may coalesce any of the ranges that overlap or that are separated
# by a gap that is smaller than the overhead of sending multiple parts, regardless of the order in which the
# corresponding byte-range-spec appeared in the received Range header field. Since the typical overhead between
# parts of a multipart/byteranges payload is around 80 bytes, depending on the selected representation's
# media type and the chosen boundary parameter length, it can be less efficient to transfer many small
# disjoint parts than it is to transfer the entire selected representation.
range-coalescing-threshold = 80
# The maximum number of allowed ranges per request.
# Requests with more ranges will be rejected due to DOS suspicion.
range-count-limit = 16
# The maximum number of bytes per ByteString a decoding directive will produce
# for an entity data stream.
decode-max-bytes-per-chunk = 1m
# Fully qualified config path which holds the dispatcher configuration
# to be used by FlowMaterialiser when creating Actors for IO operations.
file-io-dispatcher = ${akka.stream.file-io-dispatcher}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.impl.server
import scala.reflect.ClassTag
import akka.http.javadsl.server.{ RequestContext, RequestVal }
import akka.http.impl.util.JavaMapping.Implicits._
/**
* INTERNAL API
*/
private[http] trait ExtractionImplBase[T] extends RequestVal[T] {
protected[http] implicit def classTag: ClassTag[T]
def resultClass: Class[T] = classTag.runtimeClass.asInstanceOf[Class[T]]
def get(ctx: RequestContext): T =
ctx.request.asScala.header[ExtractionMap].flatMap(_.get(this))
.getOrElse(throw new RuntimeException(s"Value wasn't extracted! $this"))
}
private[http] abstract class ExtractionImpl[T](implicit val classTag: ClassTag[T]) extends ExtractionImplBase[T]

View file

@ -0,0 +1,15 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.impl.server
import scala.concurrent.ExecutionContext
import akka.http.javadsl.server.Marshaller
import akka.http.scaladsl.marshalling
/**
* INTERNAL API
*/
// FIXME: too lenient visibility, currently used to implement Java marshallers, needs proper API, see #16439
case class MarshallerImpl[T](scalaMarshaller: ExecutionContext marshalling.ToResponseMarshaller[T]) extends Marshaller[T]

View file

@ -0,0 +1,25 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.impl.server
import scala.concurrent.ExecutionContext
import scala.reflect.ClassTag
import akka.http.javadsl.server.Parameter
import akka.http.scaladsl.server.directives.{ ParameterDirectives, BasicDirectives }
import akka.http.scaladsl.server.Directive1
import akka.http.scaladsl.server.directives.ParameterDirectives.ParamMagnet
/**
* INTERNAL API
*/
private[http] class ParameterImpl[T: ClassTag](val underlying: ExecutionContext ParamMagnet { type Out = Directive1[T] })
extends StandaloneExtractionImpl[T] with Parameter[T] {
//def extract(ctx: RequestContext): Future[T] =
def directive: Directive1[T] =
BasicDirectives.extractExecutionContext.flatMap { implicit ec
ParameterDirectives.parameter(underlying(ec))
}
}

View file

@ -0,0 +1,15 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.impl.server
import scala.reflect.ClassTag
import akka.http.javadsl.server.PathMatcher
import akka.http.scaladsl.server.{ PathMatcher ScalaPathMatcher }
/**
* INTERNAL API
*/
private[http] class PathMatcherImpl[T: ClassTag](val matcher: ScalaPathMatcher[Tuple1[T]])
extends ExtractionImpl[T] with PathMatcher[T]

View file

@ -0,0 +1,43 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.impl.server
import scala.concurrent.Future
import akka.http.javadsl.{ model jm }
import akka.http.impl.util.JavaMapping.Implicits._
import akka.http.scaladsl.server.{ RequestContext ScalaRequestContext }
import akka.http.javadsl.server._
/**
* INTERNAL API
*/
private[http] final case class RequestContextImpl(underlying: ScalaRequestContext) extends RequestContext {
import underlying.executionContext
// provides auto-conversion to japi.RouteResult
import RouteResultImpl._
def request: jm.HttpRequest = underlying.request
def unmatchedPath: String = underlying.unmatchedPath.toString
def completeWith(futureResult: Future[RouteResult]): RouteResult =
futureResult.flatMap {
case r: RouteResultImpl r.underlying
}
def complete(text: String): RouteResult = underlying.complete(text)
def completeWithStatus(statusCode: Int): RouteResult =
completeWithStatus(jm.StatusCodes.get(statusCode))
def completeWithStatus(statusCode: jm.StatusCode): RouteResult =
underlying.complete(statusCode.asScala)
def completeAs[T](marshaller: Marshaller[T], value: T): RouteResult = marshaller match {
case MarshallerImpl(m)
implicit val marshaller = m(underlying.executionContext)
underlying.complete(value)
case _ throw new IllegalArgumentException("Unsupported marshaller: $marshaller")
}
def complete(response: jm.HttpResponse): RouteResult = underlying.complete(response.asScala)
def notFound(): RouteResult = underlying.reject()
}

View file

@ -0,0 +1,140 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.impl.server
import scala.language.implicitConversions
import scala.annotation.tailrec
import scala.collection.immutable
import akka.http.javadsl.model.ContentType
import akka.http.scaladsl.server.directives.{ UserCredentials, ContentTypeResolver }
import akka.http.scaladsl.server.directives.FileAndResourceDirectives.DirectoryRenderer
import akka.http.scaladsl.model.HttpHeader
import akka.http.scaladsl.model.headers.CustomHeader
import akka.http.scaladsl.server.{ Route ScalaRoute, Directive0, 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
}
/**
* INTERNAL API
*/
private[http] object ExtractionMap {
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))
// CustomHeader methods
override def suppressRendering: Boolean = true
def name(): String = "ExtractedValues"
def value(): String = "<empty>"
}
}
/**
* INTERNAL API
*/
private[http] object RouteImplementation extends Directives with server.RouteConcatenation {
def apply(route: Route): ScalaRoute = route match {
case RouteAlternatives(children)
val converted = children.map(RouteImplementation.apply)
converted.reduce(_ ~ _)
case RawPathPrefix(elements, children)
val inner = apply(RouteAlternatives(children))
def one[T](matcher: PathMatcher[T]): Directive0 =
rawPathPrefix(matcher.asInstanceOf[PathMatcherImpl[T]].matcher) flatMap { value
addExtraction(matcher, value)
}
elements.map(one(_)).reduce(_ & _).apply(inner)
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 MethodFilter(m, children)
val inner = apply(RouteAlternatives(children))
method(m.asScala).apply(inner)
case Extract(extractions, children)
val inner = apply(RouteAlternatives(children))
extractRequestContext.flatMap { ctx
extractions.map { e
e.directive.flatMap(addExtraction(e.asInstanceOf[RequestVal[Any]], _))
}.reduce(_ & _)
}.apply(inner)
case BasicAuthentication(authenticator, children)
val inner = apply(RouteAlternatives(children))
authenticateBasicAsync(authenticator.realm, { creds
val javaCreds =
creds match {
case UserCredentials.Missing
new BasicUserCredentials {
def available: Boolean = false
def userName: String = throw new IllegalStateException("Credentials missing")
def verifySecret(secret: String): Boolean = throw new IllegalStateException("Credentials missing")
}
case p @ UserCredentials.Provided(name)
new BasicUserCredentials {
def available: Boolean = true
def userName: String = name
def verifySecret(secret: String): Boolean = p.verifySecret(secret)
}
}
authenticator.authenticate(javaCreds)
}).flatMap { user
addExtraction(authenticator.asInstanceOf[RequestVal[Any]], user)
}.apply(inner)
case EncodeResponse(coders, children)
val scalaCoders = coders.map(_._underlyingScalaCoder())
encodeResponseWith(scalaCoders.head, scalaCoders.tail: _*).apply(apply(RouteAlternatives(children)))
case Conditional(eTag, lastModified, children)
conditional(eTag.asScala, lastModified.asScala).apply(apply(RouteAlternatives(children)))
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 addExtraction[T](key: RequestVal[T], value: T): Directive0 = {
@tailrec def addToExtractionMap(headers: immutable.Seq[HttpHeader], prefix: Vector[HttpHeader] = Vector.empty): immutable.Seq[HttpHeader] =
headers match {
case (m: ExtractionMap) +: rest m.set(key, value) +: (prefix ++ rest)
case other +: rest addToExtractionMap(rest, prefix :+ other)
case Nil ExtractionMap(Map(key -> value)) +: prefix
}
mapRequest(_.mapHeaders(addToExtractionMap(_)))
}
private def scalaResolver(resolver: directives.ContentTypeResolver): ContentTypeResolver =
ContentTypeResolver(f resolver.resolve(f).asScala)
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.impl.server
import scala.language.implicitConversions
import scala.concurrent.Future
import akka.http.javadsl.{ server js }
import akka.http.scaladsl.{ server ss }
/**
* INTERNAL API
*/
private[http] class RouteResultImpl(val underlying: Future[ss.RouteResult]) extends js.RouteResult
/**
* INTERNAL API
*/
private[http] object RouteResultImpl {
implicit def autoConvert(result: Future[ss.RouteResult]): js.RouteResult =
new RouteResultImpl(result)
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.impl.server
import java.io.File
import scala.language.existentials
import scala.collection.immutable
import akka.http.javadsl.model.{ DateTime, ContentType, HttpMethod }
import akka.http.javadsl.model.headers.EntityTag
import akka.http.javadsl.server.directives.ContentTypeResolver
import akka.http.javadsl.server._
/**
* INTERNAL API
*/
private[http] object RouteStructure {
trait DirectiveRoute extends Route {
def children: immutable.Seq[Route]
require(children.nonEmpty)
}
case class RouteAlternatives(children: immutable.Seq[Route]) extends DirectiveRoute
case class MethodFilter(method: HttpMethod, children: immutable.Seq[Route]) extends DirectiveRoute {
def filter(ctx: RequestContext): Boolean = ctx.request.method == method
}
abstract case class FileAndResourceRouteWithDefaultResolver(routeConstructor: ContentTypeResolver Route) extends Route
case class GetFromResource(resourcePath: String, contentType: ContentType, classLoader: ClassLoader) extends Route
case class GetFromResourceDirectory(resourceDirectory: String, classLoader: ClassLoader, resolver: ContentTypeResolver) extends Route
case class GetFromFile(file: File, contentType: ContentType) extends Route
case class GetFromDirectory(directory: File, browseable: Boolean, resolver: ContentTypeResolver) extends Route
case class RawPathPrefix(pathElements: immutable.Seq[PathMatcher[_]], children: immutable.Seq[Route]) extends DirectiveRoute
case class Extract(extractions: Seq[StandaloneExtractionImpl[_]], children: immutable.Seq[Route]) extends DirectiveRoute
case class BasicAuthentication(authenticator: HttpBasicAuthenticator[_], children: immutable.Seq[Route]) extends DirectiveRoute
case class EncodeResponse(coders: immutable.Seq[Coder], children: immutable.Seq[Route]) extends DirectiveRoute
case class Conditional(entityTag: EntityTag, lastModified: DateTime, children: immutable.Seq[Route]) extends DirectiveRoute
abstract class OpaqueRoute(extractions: RequestVal[_]*) extends Route {
def handle(ctx: RequestContext): RouteResult
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.impl.server
import scala.concurrent.Future
import scala.reflect.ClassTag
import akka.http.javadsl.server.RequestVal
import akka.http.scaladsl.server._
/**
* INTERNAL API
*/
private[http] abstract class StandaloneExtractionImpl[T: ClassTag] extends ExtractionImpl[T] with RequestVal[T] {
def directive: Directive1[T]
}
/**
* INTERNAL API
*/
private[http] abstract class ExtractingStandaloneExtractionImpl[T: ClassTag] extends StandaloneExtractionImpl[T] {
def directive: Directive1[T] = Directives.extract(extract).flatMap(Directives.onSuccess(_))
def extract(ctx: RequestContext): Future[T]
}

View file

@ -0,0 +1,19 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.impl.server
import scala.concurrent.ExecutionContext
import scala.reflect.ClassTag
import akka.stream.FlowMaterializer
import akka.http.javadsl.server.Unmarshaller
import akka.http.scaladsl.unmarshalling.FromMessageUnmarshaller
/**
* INTERNAL API
*
*/
// FIXME: too lenient visibility, currently used to implement Java marshallers, needs proper API, see #16439
case class UnmarshallerImpl[T](scalaUnmarshaller: (ExecutionContext, FlowMaterializer) FromMessageUnmarshaller[T])(implicit val classTag: ClassTag[T])
extends Unmarshaller[T]

View file

@ -0,0 +1,26 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
import akka.http.javadsl.server.directives._
import scala.collection.immutable
import scala.annotation.varargs
import akka.http.javadsl.model.HttpMethods
// FIXME: add support for the remaining directives, see #16436
abstract class AllDirectives extends PathDirectives
/**
*
*/
object Directives extends AllDirectives {
/**
* INTERNAL API
*/
private[http] def custom(f: immutable.Seq[Route] Route): Directive =
new AbstractDirective {
def createRoute(first: Route, others: Array[Route]): Route = f(first +: others.toVector)
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
/**
* A route Handler that handles a request (that is encapsulated in a [[RequestContext]])
* and returns a [[RouteResult]] with the response (or the rejection).
*
* Use the methods in [[RequestContext]] to create a [[RouteResult]]. A handler mustn't
* return [[null]] as the result.
*/
trait Handler {
def handle(ctx: RequestContext): RouteResult
}
/**
* A route handler with one additional argument.
*/
trait Handler1[T1] {
def handle(ctx: RequestContext, t1: T1): RouteResult
}
/**
* A route handler with two additional arguments.
*/
trait Handler2[T1, T2] {
def handle(ctx: RequestContext, t1: T1, t2: T2): RouteResult
}
/**
* A route handler with three additional arguments.
*/
trait Handler3[T1, T2, T3] {
def handle(ctx: RequestContext, t1: T1, t2: T2, t3: T3): RouteResult
}
/**
* A route handler with four additional arguments.
*/
trait Handler4[T1, T2, T3, T4] {
def handle(ctx: RequestContext, t1: T1, t2: T2, t3: T3, t4: T4): RouteResult
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
import scala.concurrent.Future
import akka.actor.ActorSystem
import akka.http.scaladsl.Http.ServerBinding
/**
* A convenience class to derive from to get everything from HttpService and Directives into scope.
* Implement the [[HttpApp.createRoute]] method to provide the Route and then call [[HttpApp.bindRoute]]
* to start the server on the specified interface.
*/
abstract class HttpApp
extends AllDirectives
with HttpServiceBase {
protected def createRoute(): Route
/**
* Starts an HTTP server on the given interface and port. Creates the route by calling the
* user-implemented [[createRoute]] method and uses the route to handle requests of the server.
*/
def bindRoute(interface: String, port: Int, system: ActorSystem): Future[ServerBinding] =
bindRoute(interface, port, createRoute(), system)
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
import akka.http.impl.server.{ ExtractionImplBase, ExtractionImpl, RouteStructure }
import akka.http.scaladsl.util.FastFuture
import scala.annotation.varargs
import scala.concurrent.Future
import scala.reflect
import reflect.ClassTag
/**
* Represents existing or missing HTTP Basic authentication credentials.
*/
trait BasicUserCredentials {
/**
* Were credentials provided in the request?
*/
def available: Boolean
/**
* The username as sent in the request.
*/
def userName: String
/**
* Verifies the given secret against the one sent in the request.
*/
def verifySecret(secret: String): Boolean
}
/**
* Implement this class to provide an HTTP Basic authentication check. The [[authenticate]] method needs to be implemented
* to check if the supplied or missing credentials are authenticated and provide a domain level object representing
* the user as a [[RequestVal]].
*/
abstract class HttpBasicAuthenticator[T](val realm: String) extends AbstractDirective with ExtractionImplBase[T] with RequestVal[T] {
protected[http] implicit def classTag: ClassTag[T] = reflect.classTag[AnyRef].asInstanceOf[ClassTag[T]]
def authenticate(credentials: BasicUserCredentials): Future[Option[T]]
/**
* Creates a return value for use in [[authenticate]] that successfully authenticates the requests and provides
* the given user.
*/
def authenticateAs(user: T): Future[Option[T]] = FastFuture.successful(Some(user))
/**
* Refuses access for this user.
*/
def refuseAccess(): Future[Option[T]] = FastFuture.successful(None)
/**
* INTERNAL API
*/
protected[http] final def createRoute(first: Route, others: Array[Route]): Route =
RouteStructure.BasicAuthentication(this, (first +: others).toVector)
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
import scala.concurrent.Future
import akka.actor.ActorSystem
import akka.http.scaladsl.{ server, Http }
import akka.http.scaladsl.Http.ServerBinding
import akka.http.impl.server.RouteImplementation
import akka.stream.{ ActorFlowMaterializer, FlowMaterializer }
import akka.stream.scaladsl.{ Keep, Sink }
trait HttpServiceBase {
/**
* Starts a server on the given interface and port and uses the route to handle incoming requests.
*/
def bindRoute(interface: String, port: Int, route: Route, system: ActorSystem): Future[ServerBinding] = {
implicit val sys = system
implicit val mat = ActorFlowMaterializer()
handleConnectionsWithRoute(interface, port, route, system, mat)
}
/**
* Starts a server on the given interface and port and uses the route to handle incoming requests.
*/
def bindRoute(interface: String, port: Int, route: Route, system: ActorSystem, flowMaterializer: FlowMaterializer): Future[ServerBinding] =
handleConnectionsWithRoute(interface, port, route, system, flowMaterializer)
/**
* Uses the route to handle incoming connections and requests for the ServerBinding.
*/
def handleConnectionsWithRoute(interface: String, port: Int, route: Route, system: ActorSystem, flowMaterializer: FlowMaterializer): Future[ServerBinding] = {
implicit val sys = system
implicit val mat = flowMaterializer
import system.dispatcher
val r: server.Route = RouteImplementation(route)
Http(system).bind(interface, port).toMat(Sink.foreach(_.handleWith(r)))(Keep.left).run()(flowMaterializer)
}
}
/**
* Provides the entrypoints to create an Http server from a route.
*/
object HttpService extends HttpServiceBase

View file

@ -0,0 +1,11 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
/**
* A marker trait for a marshaller that converts a value of type [[T]] to an
* HttpResponse.
*/
trait Marshaller[T]

View file

@ -0,0 +1,15 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
import akka.http.scaladsl.marshalling.ToResponseMarshaller
import akka.http.impl.server.MarshallerImpl
/**
* A collection of predefined marshallers.
*/
object Marshallers {
def STRING: Marshaller[String] = MarshallerImpl(implicit ctx implicitly[ToResponseMarshaller[String]])
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
import java.{ lang jl }
import scala.concurrent.ExecutionContext
import scala.reflect.ClassTag
import akka.http.scaladsl.server.Directive1
import akka.http.scaladsl.server.directives.ParameterDirectives.ParamMagnet
import akka.http.scaladsl.common.ToNameReceptacleEnhancements
import akka.http.impl.server.ParameterImpl
/**
* A RequestVal representing a query parameter of type T.
*/
trait Parameter[T] extends RequestVal[T]
/**
* A collection of predefined parameters.
* FIXME: add tests, see #16437
*/
object Parameters {
import ToNameReceptacleEnhancements._
/**
* A string query parameter.
*/
def string(name: String): Parameter[String] =
fromScalaParam(implicit ec ParamMagnet(name))
/**
* An integer query parameter.
*/
def integer(name: String): Parameter[jl.Integer] =
fromScalaParam[jl.Integer](implicit ec
ParamMagnet(name.as[Int]).asInstanceOf[ParamMagnet { type Out = Directive1[jl.Integer] }])
private def fromScalaParam[T: ClassTag](underlying: ExecutionContext ParamMagnet { type Out = Directive1[T] }): Parameter[T] =
new ParameterImpl[T](underlying)
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
import java.{ lang jl, util ju }
import scala.reflect.ClassTag
import scala.collection.JavaConverters._
import akka.http.impl.server.PathMatcherImpl
import akka.http.scaladsl.server.{ PathMatchers ScalaPathMatchers, PathMatcher0, PathMatcher1 }
/**
* A PathMatcher is used to match the (yet unmatched) URI path of incoming requests.
* It is also a RequestVal that allows to access dynamic parts of the part in a
* handler.
*
* Using a PathMatcher with the [[Directives.path]] or [[Directives.pathPrefix]] directives
* "consumes" a part of the path which is recorded in [[RequestContext.unmatchedPath]].
*/
trait PathMatcher[T] extends RequestVal[T]
/**
* A collection of predefined path matchers.
*/
object PathMatchers {
val NEUTRAL: PathMatcher[Void] = matcher0(_.Neutral)
val SLASH: PathMatcher[Void] = matcher0(_.Slash)
val END: PathMatcher[Void] = matcher0(_.PathEnd)
def segment(name: String): PathMatcher[String] = matcher(_ name -> name)
def integerNumber: PathMatcher[jl.Integer] = matcher(_.IntNumber.asInstanceOf[PathMatcher1[jl.Integer]])
def hexIntegerNumber: PathMatcher[jl.Integer] = matcher(_.HexIntNumber.asInstanceOf[PathMatcher1[jl.Integer]])
def longNumber: PathMatcher[jl.Long] = matcher(_.LongNumber.asInstanceOf[PathMatcher1[jl.Long]])
def hexLongNumber: PathMatcher[jl.Long] = matcher(_.HexLongNumber.asInstanceOf[PathMatcher1[jl.Long]])
def uuid: PathMatcher[ju.UUID] = matcher(_.JavaUUID)
def segment: PathMatcher[String] = matcher(_.Segment)
def segments: PathMatcher[ju.List[String]] = matcher(_.Segments.map(_.asJava))
def segments(maxNumber: Int): PathMatcher[ju.List[String]] = matcher(_.Segments(maxNumber).map(_.asJava))
def rest: PathMatcher[String] = matcher(_.Rest)
private def matcher[T: ClassTag](scalaMatcher: ScalaPathMatchers.type PathMatcher1[T]): PathMatcher[T] =
new PathMatcherImpl[T](scalaMatcher(ScalaPathMatchers))
private def matcher0(scalaMatcher: ScalaPathMatchers.type PathMatcher0): PathMatcher[Void] =
new PathMatcherImpl[Void](scalaMatcher(ScalaPathMatchers).tmap(_ Tuple1(null)))
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
import akka.http.javadsl.model._
import akka.util.ByteString
import scala.concurrent.Future
/**
* The RequestContext represents the state of the request while it is routed through
* the route structure.
*/
trait RequestContext {
/**
* The incoming request.
*/
def request: HttpRequest
/**
* The still unmatched path of the request.
*/
def unmatchedPath: String
/**
* Completes the request with a value of type T and marshals it using the given
* marshaller.
*/
def completeAs[T](marshaller: Marshaller[T], value: T): RouteResult
/**
* Completes the request with the given response.
*/
def complete(response: HttpResponse): RouteResult
/**
* Completes the request with the given string as an entity of type `text/plain`.
*/
def complete(text: String): RouteResult
/**
* Completes the request with the given status code and no entity.
*/
def completeWithStatus(statusCode: StatusCode): RouteResult
/**
* Completes the request with the given status code and no entity.
*/
def completeWithStatus(statusCode: Int): RouteResult
/**
* Defers completion of the request
*/
def completeWith(futureResult: Future[RouteResult]): RouteResult
/**
* Explicitly rejects the request as not found. Other route alternatives
* may still be able provide a response.
*/
def notFound(): RouteResult
// FIXME: provide proper support for rejections, see #16438
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
/**
* Represents a value that can be extracted from a request.
*/
trait RequestVal[T] { outer
/**
* An accessor for the value given the [[RequestContext]].
*
* Note, that some RequestVals need to be actively specified in the route structure to
* be extracted at a particular point during routing. One example is a [[PathMatcher]]
* that needs to used with a [[directives.PathDirectives]] to specify which part of the
* path should actually be extracted. Another example is an [[HttpBasicAuthenticator]]
* that needs to be used in the route explicitly to be activated.
*/
def get(ctx: RequestContext): T
/**
* The runtime type of the extracted value.
*/
def resultClass: Class[T]
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
import java.{ util ju }
import scala.concurrent.Future
import scala.reflect.ClassTag
import akka.http.javadsl.model.HttpMethod
import akka.http.scaladsl.server
import akka.http.scaladsl.server._
import akka.http.scaladsl.server.directives.{ RouteDirectives, BasicDirectives }
import akka.http.impl.server.{ UnmarshallerImpl, ExtractingStandaloneExtractionImpl, RequestContextImpl, StandaloneExtractionImpl }
import akka.http.scaladsl.util.FastFuture
import akka.http.impl.util.JavaMapping.Implicits._
/**
* A collection of predefined [[RequestVals]].
*/
object RequestVals {
/**
* Creates an extraction that extracts the request body using the supplied Unmarshaller.
*/
def entityAs[T](unmarshaller: Unmarshaller[T]): RequestVal[T] =
new ExtractingStandaloneExtractionImpl[T]()(unmarshaller.classTag) {
def extract(ctx: server.RequestContext): Future[T] = {
val u = unmarshaller.asInstanceOf[UnmarshallerImpl[T]].scalaUnmarshaller(ctx.executionContext, ctx.flowMaterializer)
u(ctx.request)(ctx.executionContext)
}
}
/**
* Extracts the request method.
*/
def requestMethod: RequestVal[HttpMethod] =
new ExtractingStandaloneExtractionImpl[HttpMethod] {
def extract(ctx: server.RequestContext): Future[HttpMethod] = FastFuture.successful(ctx.request.method.asJava)
}
/**
* Creates a new [[RequestVal]] given a [[ju.Map]] and a [[RequestVal]] that represents the key.
* The new RequestVal represents the existing value as looked up in the map. If the key doesn't
* exist the request is rejected.
*/
def lookupInMap[T, U](key: RequestVal[T], clazz: Class[U], map: ju.Map[T, U]): RequestVal[U] =
new StandaloneExtractionImpl[U]()(ClassTag(clazz)) {
import BasicDirectives._
import RouteDirectives._
def directive: Directive1[U] =
extract(ctx key.get(RequestContextImpl(ctx))).flatMap {
case key if map.containsKey(key) provide(map.get(key))
case _ reject()
}
}
}

View file

@ -0,0 +1,14 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
/**
* A marker interface to denote an element that handles a request.
*
* This is an opaque interface that cannot be implemented manually.
* Instead, see the predefined routes in [[Directives]] and use the [[Directives.handleWith]]
* method to create custom routes.
*/
trait Route

View file

@ -0,0 +1,11 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
/**
* A marker trait to denote the result of handling a request. Use the methods in [[RequestContext]]
* to create instances of results.
*/
trait RouteResult

View file

@ -0,0 +1,14 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
import scala.reflect.ClassTag
/**
* A marker trait for an unmarshaller that converts an HttpRequest to a value of type T.
*/
trait Unmarshaller[T] {
def classTag: ClassTag[T]
}

View file

@ -0,0 +1,180 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server.directives
import java.lang.reflect.Method
import scala.annotation.varargs
import akka.http.javadsl.server._
import akka.http.impl.server.RouteStructure._
import akka.http.impl.server._
abstract class BasicDirectives {
/**
* Tries the given routes in sequence until the first one matches.
*/
@varargs
def route(route: Route, others: Route*): Route =
RouteAlternatives(route +: others.toVector)
/**
* A route that completes the request with a static text
*/
def complete(text: String): Route =
new OpaqueRoute() {
def handle(ctx: RequestContext): RouteResult = ctx.complete(text)
}
/**
* A route that completes the request using the given marshaller and value.
*/
def completeAs[T](marshaller: Marshaller[T], value: T): Route =
new OpaqueRoute() {
def handle(ctx: RequestContext): RouteResult = ctx.completeAs(marshaller, value)
}
/**
* A route that extracts a value and completes the request with it.
*/
def extractAndComplete[T](marshaller: Marshaller[T], extraction: RequestVal[T]): Route =
handle(extraction)(ctx ctx.completeAs(marshaller, extraction.get(ctx)))
/**
* A directive that makes sure that all the standalone extractions have been
* executed and validated.
*/
@varargs
def extractHere(extractions: RequestVal[_]*): Directive =
Directives.custom(Extract(extractions.map(_.asInstanceOf[StandaloneExtractionImpl[_ <: AnyRef]]), _))
/**
* A route that handles the request with the given opaque handler. Specify a set of extractions
* that will be used in the handler to make sure they are available.
*/
@varargs
def handleWith[T1](handler: Handler, extractions: RequestVal[_]*): Route =
handle(extractions: _*)(handler.handle(_))
/**
* A route that handles the request given the value of a single [[RequestVal]].
*/
def handleWith[T1](e1: RequestVal[T1], handler: Handler1[T1]): Route =
handle(e1)(ctx handler.handle(ctx, e1.get(ctx)))
/**
* A route that handles the request given the values of the given [[RequestVal]]s.
*/
def handleWith[T1, T2](e1: RequestVal[T1], e2: RequestVal[T2], handler: Handler2[T1, T2]): Route =
handle(e1, e2)(ctx handler.handle(ctx, e1.get(ctx), e2.get(ctx)))
/**
* A route that handles the request given the values of the given [[RequestVal]]s.
*/
def handleWith[T1, T2, T3](
e1: RequestVal[T1], e2: RequestVal[T2], e3: RequestVal[T3], handler: Handler3[T1, T2, T3]): Route =
handle(e1, e2, e3)(ctx handler.handle(ctx, e1.get(ctx), e2.get(ctx), e3.get(ctx)))
/**
* A route that handles the request given the values of the given [[RequestVal]]s.
*/
def handleWith[T1, T2, T3, T4](
e1: RequestVal[T1], e2: RequestVal[T2], e3: RequestVal[T3], e4: RequestVal[T4], handler: Handler4[T1, T2, T3, T4]): Route =
handle(e1, e2, e3, e4)(ctx handler.handle(ctx, e1.get(ctx), e2.get(ctx), e3.get(ctx), e4.get(ctx)))
private[http] def handle(extractions: RequestVal[_]*)(f: RequestContext RouteResult): Route = {
val route =
new OpaqueRoute() {
def handle(ctx: RequestContext): RouteResult = f(ctx)
}
val saExtractions = extractions.collect { case sa: StandaloneExtractionImpl[_] sa }
if (saExtractions.isEmpty) route
else extractHere(saExtractions: _*).route(route)
}
/**
* Handles the route by reflectively calling the instance method specified by `instance`, and `methodName`.
* Additionally, the value of all extractions will be passed to the function.
*
* For extraction types `Extraction[T1]`, `Extraction[T2]`, ... the shape of the method must match this pattern:
*
* public static RouteResult methodName(RequestContext ctx, T1 t1, T2 t2, ...)
*/
@varargs
def handleWith(instance: AnyRef, methodName: String, extractions: RequestVal[_]*): Route =
handleWith(instance.getClass, instance, methodName, extractions: _*)
/**
* Handles the route by reflectively calling the static method specified by `clazz`, and `methodName`.
* Additionally, the value of all extractions will be passed to the function.
*
* For extraction types `Extraction[T1]`, `Extraction[T2]`, ... the shape of the method must match this pattern:
*
* public static RouteResult methodName(RequestContext ctx, T1 t1, T2 t2, ...)
*/
@varargs
def handleWith(clazz: Class[_], methodName: String, extractions: RequestVal[_]*): Route =
handleWith(clazz, null, methodName, extractions: _*)
/**
* Handles the route by calling the method specified by `clazz`, `instance`, and `methodName`. Additionally, the value
* of all extractions will be passed to the function.
*
* For extraction types `Extraction[T1]`, `Extraction[T2]`, ... the shape of the method must match this pattern:
*
* public static RouteResult methodName(RequestContext ctx, T1 t1, T2 t2, ...)
*/
@varargs
def handleWith(clazz: Class[_], instance: AnyRef, methodName: String, extractions: RequestVal[_]*): Route = {
def chooseOverload(methods: Seq[Method]): (RequestContext, Seq[Any]) RouteResult = {
val extractionTypes = extractions.map(_.resultClass).toList
val RequestContextClass = classOf[RequestContext]
import java.{ lang jl }
def paramMatches(expected: Class[_], actual: Class[_]): Boolean = expected match {
case e if e isAssignableFrom actual true
case jl.Long.TYPE if actual == classOf[jl.Long] true
case jl.Integer.TYPE if actual == classOf[jl.Integer] true
case jl.Short.TYPE if actual == classOf[jl.Short] true
case jl.Character.TYPE if actual == classOf[jl.Character] true
case jl.Byte.TYPE if actual == classOf[jl.Byte] true
case jl.Double.TYPE if actual == classOf[jl.Double] true
case jl.Float.TYPE if actual == classOf[jl.Float] true
case _ false
}
def paramsMatch(params: Seq[Class[_]]): Boolean = {
val res =
params.size == extractionTypes.size &&
(params, extractionTypes).zipped.forall(paramMatches)
res
}
def returnTypeMatches(method: Method): Boolean =
method.getReturnType == classOf[RouteResult]
object ParameterTypes {
def unapply(method: Method): Option[List[Class[_]]] = Some(method.getParameterTypes.toList)
}
methods.filter(returnTypeMatches).collectFirst {
case method @ ParameterTypes(RequestContextClass :: rest) if paramsMatch(rest) {
if (!method.isAccessible) method.setAccessible(true) // FIXME: test what happens if this fails
(ctx: RequestContext, params: Seq[Any]) method.invoke(instance, (ctx +: params).toArray.asInstanceOf[Array[AnyRef]]: _*).asInstanceOf[RouteResult]
}
case method @ ParameterTypes(rest) if paramsMatch(rest) {
if (!method.isAccessible) method.setAccessible(true)
(ctx: RequestContext, params: Seq[Any]) method.invoke(instance, params.toArray.asInstanceOf[Array[AnyRef]]: _*).asInstanceOf[RouteResult]
}
}.getOrElse(throw new RuntimeException("No suitable method found"))
}
def lookupMethod() = {
val candidateMethods = clazz.getMethods.filter(_.getName == methodName)
chooseOverload(candidateMethods)
}
val method = lookupMethod()
handle(extractions: _*)(ctx method(ctx, extractions.map(_.get(ctx))))
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
package directives
import akka.http.javadsl.model.DateTime
import akka.http.javadsl.model.headers.EntityTag
import akka.http.impl.server.RouteStructure
import scala.annotation.varargs
abstract class CacheConditionDirectives extends BasicDirectives {
/**
* Wraps its inner route with support for Conditional Requests as defined
* by tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-26
*
* In particular the algorithm defined by tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-26#section-6
* is implemented by this directive.
*/
@varargs
def conditional(entityTag: EntityTag, lastModified: DateTime, innerRoutes: Route*): Route =
RouteStructure.Conditional(entityTag, lastModified, innerRoutes.toVector)
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server.directives
import scala.annotation.varargs
import akka.http.impl.server.RouteStructure
import akka.http.javadsl.server.{ Coder, Directive, Directives, Route }
abstract class CodingDirectives extends CacheConditionDirectives {
/**
* Wraps the inner routes with encoding support. The response will be encoded
* using one of the predefined coders, `Gzip`, `Deflate`, or `NoCoding` depending on
* a potential [[akka.http.javadsl.model.headers.AcceptEncoding]] header from the client.
*/
@varargs def encodeResponse(innerRoutes: Route*): Route =
// FIXME: make sure this list stays synchronized with the Scala one
RouteStructure.EncodeResponse(List(Coder.NoCoding, Coder.Gzip, Coder.Deflate), innerRoutes.toVector)
/**
* A directive that Wraps its inner routes with encoding support.
* The response will be encoded using one of the given coders with the precedence given
* by the order of the coders in this call.
*
* In any case, a potential [[akka.http.javadsl.model.headers.AcceptEncoding]] header from the client
* will be respected (or otherwise, if no matching .
*/
@varargs def encodeResponse(coders: Coder*): Directive =
Directives.custom(RouteStructure.EncodeResponse(coders.toList, _))
}

View file

@ -0,0 +1,137 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server.directives
import java.io.File
import akka.http.javadsl.model.{ MediaType, ContentType }
import akka.http.javadsl.server.Route
import akka.http.scaladsl.server
import akka.http.impl.server.RouteStructure._
/**
* Implement this interface to provide a custom mapping from a file name to a [[ContentType]].
*/
trait ContentTypeResolver {
def resolve(fileName: String): ContentType
}
/**
* A resolver that assumes the given constant [[ContentType]] for all files.
*/
case class StaticContentTypeResolver(contentType: ContentType) extends ContentTypeResolver {
def resolve(fileName: String): ContentType = contentType
}
/**
* Allows to customize one of the predefined routes of [[FileAndResourceRoute]] to respond
* with a particular content type.
*
* The default behavior is to determine the content type by file extension.
*/
trait FileAndResourceRoute extends Route {
/**
* Returns a variant of this route that responds with the given constant [[ContentType]].
*/
def withContentType(contentType: ContentType): Route
/**
* Returns a variant of this route that responds with the given constant [[MediaType]].
*/
def withContentType(mediaType: MediaType): Route
/**
* Returns a variant of this route that uses the specified [[ContentTypeResolver]] to determine
* which [[ContentType]] to respond with by file name.
*/
def resolveContentTypeWith(resolver: ContentTypeResolver): Route
}
object FileAndResourceRoute {
/**
* INTERNAL API
*/
private[http] def apply(f: ContentTypeResolver Route): FileAndResourceRoute =
new FileAndResourceRouteWithDefaultResolver(f) with FileAndResourceRoute {
def withContentType(contentType: ContentType): Route = resolveContentTypeWith(StaticContentTypeResolver(contentType))
def withContentType(mediaType: MediaType): Route = withContentType(mediaType.toContentType)
def resolveContentTypeWith(resolver: ContentTypeResolver): Route = f(resolver)
}
/**
* INTERNAL API
*/
private[http] def forFixedName(fileName: String)(f: ContentType Route): FileAndResourceRoute =
new FileAndResourceRouteWithDefaultResolver(resolver f(resolver.resolve(fileName))) with FileAndResourceRoute {
def withContentType(contentType: ContentType): Route = resolveContentTypeWith(StaticContentTypeResolver(contentType))
def withContentType(mediaType: MediaType): Route = withContentType(mediaType.toContentType)
def resolveContentTypeWith(resolver: ContentTypeResolver): Route = f(resolver.resolve(fileName))
}
}
abstract class FileAndResourceDirectives extends CodingDirectives {
/**
* Completes GET requests with the content of the given resource loaded from the default ClassLoader.
* If the resource cannot be found or read the Route rejects the request.
*/
def getFromResource(path: String): Route =
getFromResource(path, defaultClassLoader)
/**
* Completes GET requests with the content of the given resource loaded from the given ClassLoader.
* If the resource cannot be found or read the Route rejects the request.
*/
def getFromResource(path: String, classLoader: ClassLoader): Route =
FileAndResourceRoute.forFixedName(path)(GetFromResource(path, _, classLoader))
/**
* Completes GET requests with the content from the resource identified by the given
* directoryPath and the unmatched path.
*/
def getFromResourceDirectory(directoryPath: String): FileAndResourceRoute =
getFromResourceDirectory(directoryPath, defaultClassLoader)
/**
* Completes GET requests with the content from the resource identified by the given
* directoryPath and the unmatched path from the given ClassLoader.
*/
def getFromResourceDirectory(directoryPath: String, classLoader: ClassLoader): FileAndResourceRoute =
FileAndResourceRoute(GetFromResourceDirectory(directoryPath, classLoader, _))
/**
* Completes GET requests with the content of the given file.
*/
def getFromFile(file: File): FileAndResourceRoute = FileAndResourceRoute.forFixedName(file.getPath)(GetFromFile(file, _))
/**
* Completes GET requests with the content of the file at the path.
*/
def getFromFile(path: String): FileAndResourceRoute = getFromFile(new File(path))
/**
* Completes GET requests with the content from the file identified by the given
* directory and the unmatched path of the request.
*/
def getFromDirectory(directory: File): FileAndResourceRoute = FileAndResourceRoute(GetFromDirectory(directory, browseable = false, _))
/**
* Completes GET requests with the content from the file identified by the given
* directoryPath and the unmatched path of the request.
*/
def getFromDirectory(directoryPath: String): FileAndResourceRoute = getFromDirectory(new File(directoryPath))
/**
* Same as [[getFromDirectory]] but generates a listing of files if the path is a directory.
*/
def getFromBrowseableDirectory(directory: File): FileAndResourceRoute = FileAndResourceRoute(GetFromDirectory(directory, browseable = true, _))
/**
* Same as [[getFromDirectory]] but generates a listing of files if the path is a directory.
*/
def getFromBrowseableDirectory(directoryPath: String): FileAndResourceRoute = FileAndResourceRoute(GetFromDirectory(new File(directoryPath), browseable = true, _))
protected def defaultClassLoader: ClassLoader = server.directives.FileAndResourceDirectives.defaultClassLoader
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server.directives
import akka.http.javadsl.model.{ HttpMethods, HttpMethod }
import akka.http.javadsl.server.Route
import akka.http.impl.server.RouteStructure
import scala.annotation.varargs
abstract class MethodDirectives extends FileAndResourceDirectives {
/** Handles the inner routes if the incoming request is a GET request, rejects the request otherwise */
@varargs
def get(innerRoutes: Route*): Route = method(HttpMethods.GET, innerRoutes: _*)
/** Handles the inner routes if the incoming request is a POST request, rejects the request otherwise */
@varargs
def post(innerRoutes: Route*): Route = method(HttpMethods.POST, innerRoutes: _*)
/** Handles the inner routes if the incoming request is a PUT request, rejects the request otherwise */
@varargs
def put(innerRoutes: Route*): Route = method(HttpMethods.PUT, innerRoutes: _*)
/** Handles the inner routes if the incoming request is a DELETE request, rejects the request otherwise */
@varargs
def delete(innerRoutes: Route*): Route = method(HttpMethods.DELETE, innerRoutes: _*)
/** Handles the inner routes if the incoming request is a HEAD request, rejects the request otherwise */
@varargs
def head(innerRoutes: Route*): Route = method(HttpMethods.HEAD, innerRoutes: _*)
/** Handles the inner routes if the incoming request is a OPTIONS request, rejects the request otherwise */
@varargs
def options(innerRoutes: Route*): Route = method(HttpMethods.OPTIONS, innerRoutes: _*)
/** Handles the inner routes if the incoming request is a PATCH request, rejects the request otherwise */
@varargs
def patch(innerRoutes: Route*): Route = method(HttpMethods.PATCH, innerRoutes: _*)
/** Handles the inner routes if the incoming request is a request with the given method, rejects the request otherwise */
@varargs
def method(method: HttpMethod, innerRoutes: Route*): Route = RouteStructure.MethodFilter(method, innerRoutes.toVector)
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.server
package directives
import akka.http.impl.server.RouteStructure
import scala.annotation.varargs
import scala.collection.immutable
abstract class PathDirectives extends MethodDirectives {
/**
* Tries to consumes the complete unmatched path given a number of PathMatchers. Between each
* of the matchers a `/` will be matched automatically.
*
* A matcher can either be a matcher of type `PathMatcher`, or a literal string.
*/
@varargs
def path(matchers: AnyRef*): Directive =
forMatchers(joinWithSlash(convertMatchers(matchers)) :+ PathMatchers.END)
@varargs
def pathPrefix(matchers: AnyRef*): Directive =
forMatchers(joinWithSlash(convertMatchers(matchers)))
def pathSingleSlash: Directive = forMatchers(List(PathMatchers.SLASH, PathMatchers.END))
@varargs
def rawPathPrefix(matchers: AnyRef*): Directive =
forMatchers(convertMatchers(matchers))
private def forMatchers(matchers: immutable.Seq[PathMatcher[_]]): Directive =
Directives.custom(RouteStructure.RawPathPrefix(matchers, _))
private def joinWithSlash(matchers: immutable.Seq[PathMatcher[_]]): immutable.Seq[PathMatcher[_]] = {
def join(result: immutable.Seq[PathMatcher[_]], next: PathMatcher[_]): immutable.Seq[PathMatcher[_]] =
result :+ PathMatchers.SLASH :+ next
matchers.foldLeft(immutable.Seq.empty[PathMatcher[_]])(join)
}
private def convertMatchers(matchers: Seq[AnyRef]): immutable.Seq[PathMatcher[_]] = {
def parse(matcher: AnyRef): PathMatcher[_] = matcher match {
case p: PathMatcher[_] p
case name: String PathMatchers.segment(name)
}
matchers.map(parse).toVector
}
}

View file

@ -0,0 +1,99 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.client
import scala.collection.immutable
import scala.concurrent.{ Await, ExecutionContext }
import scala.concurrent.duration._
import scala.reflect.ClassTag
import akka.util.Timeout
import akka.event.{ Logging, LoggingAdapter }
import akka.http.scaladsl.marshalling._
import akka.http.scaladsl.model._
import headers.HttpCredentials
import HttpMethods._
trait RequestBuilding extends TransformerPipelineSupport {
type RequestTransformer = HttpRequest HttpRequest
class RequestBuilder(val method: HttpMethod) {
def apply(): HttpRequest =
apply("/")
def apply(uri: String): HttpRequest =
apply(uri, HttpEntity.Empty)
def apply[T](uri: String, content: T)(implicit m: ToEntityMarshaller[T], ec: ExecutionContext): HttpRequest =
apply(uri, Some(content))
def apply[T](uri: String, content: Option[T])(implicit m: ToEntityMarshaller[T], ec: ExecutionContext): HttpRequest =
apply(Uri(uri), content)
def apply(uri: String, entity: RequestEntity): HttpRequest =
apply(Uri(uri), entity)
def apply(uri: Uri): HttpRequest =
apply(uri, HttpEntity.Empty)
def apply[T](uri: Uri, content: T)(implicit m: ToEntityMarshaller[T], ec: ExecutionContext): HttpRequest =
apply(uri, Some(content))
def apply[T](uri: Uri, content: Option[T])(implicit m: ToEntityMarshaller[T], timeout: Timeout = Timeout(1.second), ec: ExecutionContext): HttpRequest =
content match {
case None apply(uri, HttpEntity.Empty)
case Some(value)
val entity = Await.result(Marshal(value).to[RequestEntity], timeout.duration)
apply(uri, entity)
}
def apply(uri: Uri, entity: RequestEntity): HttpRequest =
HttpRequest(method, uri, Nil, entity)
}
val Get = new RequestBuilder(GET)
val Post = new RequestBuilder(POST)
val Put = new RequestBuilder(PUT)
val Patch = new RequestBuilder(PATCH)
val Delete = new RequestBuilder(DELETE)
val Options = new RequestBuilder(OPTIONS)
val Head = new RequestBuilder(HEAD)
// TODO: reactivate after HTTP message encoding has been ported
//def encode(encoder: Encoder): RequestTransformer = encoder.encode(_, flow)
def addHeader(header: HttpHeader): RequestTransformer = _.mapHeaders(header +: _)
def addHeader(headerName: String, headerValue: String): RequestTransformer =
HttpHeader.parse(headerName, headerValue) match {
case HttpHeader.ParsingResult.Ok(h, Nil) addHeader(h)
case result throw new IllegalArgumentException(result.errors.head.formatPretty)
}
def addHeaders(first: HttpHeader, more: HttpHeader*): RequestTransformer = _.mapHeaders(_ ++ (first +: more))
def mapHeaders(f: immutable.Seq[HttpHeader] immutable.Seq[HttpHeader]): RequestTransformer = _.mapHeaders(f)
def removeHeader(headerName: String): RequestTransformer =
_ mapHeaders (_ filterNot (_.name equalsIgnoreCase headerName))
def removeHeader[T <: HttpHeader: ClassTag]: RequestTransformer =
removeHeader(implicitly[ClassTag[T]].runtimeClass)
def removeHeader(clazz: Class[_]): RequestTransformer =
_ mapHeaders (_ filterNot clazz.isInstance)
def removeHeaders(names: String*): RequestTransformer =
_ mapHeaders (_ filterNot (header names exists (_ equalsIgnoreCase header.name)))
def addCredentials(credentials: HttpCredentials) = addHeader(headers.Authorization(credentials))
def logRequest(log: LoggingAdapter, level: Logging.LogLevel = Logging.DebugLevel) = logValue[HttpRequest](log, level)
def logRequest(logFun: HttpRequest Unit) = logValue[HttpRequest](logFun)
implicit def header2AddHeader(header: HttpHeader): RequestTransformer = addHeader(header)
}
object RequestBuilding extends RequestBuilding

View file

@ -0,0 +1,49 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.client
import scala.concurrent.{ Future, ExecutionContext }
import akka.event.{ Logging, LoggingAdapter }
trait TransformerPipelineSupport {
def logValue[T](log: LoggingAdapter, level: Logging.LogLevel = Logging.DebugLevel): T T =
logValue { value log.log(level, value.toString) }
def logValue[T](logFun: T Unit): T T = { response
logFun(response)
response
}
implicit class WithTransformation[A](value: A) {
def ~>[B](f: A B): B = f(value)
}
implicit class WithTransformerConcatenation[A, B](f: A B) extends (A B) {
def apply(input: A) = f(input)
def ~>[AA, BB, R](g: AA BB)(implicit aux: TransformerAux[A, B, AA, BB, R]) =
new WithTransformerConcatenation[A, R](aux(f, g))
}
}
object TransformerPipelineSupport extends TransformerPipelineSupport
trait TransformerAux[A, B, AA, BB, R] {
def apply(f: A B, g: AA BB): A R
}
object TransformerAux {
implicit def aux1[A, B, C] = new TransformerAux[A, B, B, C, C] {
def apply(f: A B, g: B C): A C = f andThen g
}
implicit def aux2[A, B, C](implicit ec: ExecutionContext) =
new TransformerAux[A, Future[B], B, C, Future[C]] {
def apply(f: A Future[B], g: B C): A Future[C] = f(_).map(g)
}
implicit def aux3[A, B, C](implicit ec: ExecutionContext) =
new TransformerAux[A, Future[B], B, Future[C], Future[C]] {
def apply(f: A Future[B], g: B Future[C]): A Future[C] = f(_).flatMap(g)
}
}

View file

@ -0,0 +1,8 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.coding
/** Marker trait for A combined Encoder and Decoder */
trait Coder extends Encoder with Decoder

View file

@ -0,0 +1,35 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.coding
import akka.http.scaladsl.model.{ HttpRequest, HttpResponse, ResponseEntity, RequestEntity }
import akka.util.ByteString
import akka.stream.scaladsl.Flow
/** An abstraction to transform data bytes of HttpMessages or HttpEntities */
sealed trait DataMapper[T] {
def transformDataBytes(t: T, transformer: Flow[ByteString, ByteString, _]): T
}
object DataMapper {
implicit val mapRequestEntity: DataMapper[RequestEntity] =
new DataMapper[RequestEntity] {
def transformDataBytes(t: RequestEntity, transformer: Flow[ByteString, ByteString, _]): RequestEntity =
t.transformDataBytes(transformer)
}
implicit val mapResponseEntity: DataMapper[ResponseEntity] =
new DataMapper[ResponseEntity] {
def transformDataBytes(t: ResponseEntity, transformer: Flow[ByteString, ByteString, _]): ResponseEntity =
t.transformDataBytes(transformer)
}
implicit val mapRequest: DataMapper[HttpRequest] = mapMessage(mapRequestEntity)((m, f) m.withEntity(f(m.entity)))
implicit val mapResponse: DataMapper[HttpResponse] = mapMessage(mapResponseEntity)((m, f) m.withEntity(f(m.entity)))
def mapMessage[T, E](entityMapper: DataMapper[E])(mapEntity: (T, E E) T): DataMapper[T] =
new DataMapper[T] {
def transformDataBytes(t: T, transformer: Flow[ByteString, ByteString, _]): T =
mapEntity(t, entityMapper.transformDataBytes(_, transformer))
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.coding
import akka.http.scaladsl.model._
import akka.stream.FlowMaterializer
import akka.stream.stage.Stage
import akka.util.ByteString
import headers.HttpEncoding
import akka.stream.scaladsl.{ Sink, Source, Flow }
import scala.concurrent.Future
trait Decoder {
def encoding: HttpEncoding
def decode[T <: HttpMessage](message: T)(implicit mapper: DataMapper[T]): T#Self =
if (message.headers exists Encoder.isContentEncodingHeader)
decodeData(message).withHeaders(message.headers filterNot Encoder.isContentEncodingHeader)
else message.self
def decodeData[T](t: T)(implicit mapper: DataMapper[T]): T = mapper.transformDataBytes(t, decoderFlow)
def maxBytesPerChunk: Int
def withMaxBytesPerChunk(maxBytesPerChunk: Int): Decoder
def decoderFlow: Flow[ByteString, ByteString, Unit]
def decode(input: ByteString)(implicit mat: FlowMaterializer): Future[ByteString] =
Source.single(input).via(decoderFlow).runWith(Sink.head)
}
object Decoder {
val MaxBytesPerChunkDefault: Int = 65536
}
/** A decoder that is implemented in terms of a [[Stage]] */
trait StreamDecoder extends Decoder { outer
protected def newDecompressorStage(maxBytesPerChunk: Int): () Stage[ByteString, ByteString]
def maxBytesPerChunk: Int = Decoder.MaxBytesPerChunkDefault
def withMaxBytesPerChunk(newMaxBytesPerChunk: Int): Decoder =
new StreamDecoder {
def encoding: HttpEncoding = outer.encoding
override def maxBytesPerChunk: Int = newMaxBytesPerChunk
def newDecompressorStage(maxBytesPerChunk: Int): () Stage[ByteString, ByteString] =
outer.newDecompressorStage(maxBytesPerChunk)
}
def decoderFlow: Flow[ByteString, ByteString, Unit] =
Flow[ByteString].transform(newDecompressorStage(maxBytesPerChunk))
}

View file

@ -0,0 +1,138 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.coding
import java.util.zip.{ Inflater, Deflater }
import akka.stream.stage._
import akka.util.{ ByteStringBuilder, ByteString }
import scala.annotation.tailrec
import akka.http.impl.util._
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.HttpEncodings
class Deflate(val messageFilter: HttpMessage Boolean) extends Coder with StreamDecoder {
val encoding = HttpEncodings.deflate
def newCompressor = new DeflateCompressor
def newDecompressorStage(maxBytesPerChunk: Int) = () new DeflateDecompressor(maxBytesPerChunk)
}
object Deflate extends Deflate(Encoder.DefaultFilter)
class DeflateCompressor extends Compressor {
protected lazy val deflater = new Deflater(Deflater.BEST_COMPRESSION, false)
override final def compressAndFlush(input: ByteString): ByteString = {
val buffer = newTempBuffer(input.size)
compressWithBuffer(input, buffer) ++ flushWithBuffer(buffer)
}
override final def compressAndFinish(input: ByteString): ByteString = {
val buffer = newTempBuffer(input.size)
compressWithBuffer(input, buffer) ++ finishWithBuffer(buffer)
}
override final def compress(input: ByteString): ByteString = compressWithBuffer(input, newTempBuffer())
override final def flush(): ByteString = flushWithBuffer(newTempBuffer())
override final def finish(): ByteString = finishWithBuffer(newTempBuffer())
protected def compressWithBuffer(input: ByteString, buffer: Array[Byte]): ByteString = {
assert(deflater.needsInput())
deflater.setInput(input.toArray)
drain(buffer)
}
protected def flushWithBuffer(buffer: Array[Byte]): ByteString = {
// trick the deflater into flushing: switch compression level
// FIXME: use proper APIs and SYNC_FLUSH when Java 6 support is dropped
deflater.deflate(EmptyByteArray, 0, 0)
deflater.setLevel(Deflater.NO_COMPRESSION)
val res1 = drain(buffer)
deflater.setLevel(Deflater.BEST_COMPRESSION)
val res2 = drain(buffer)
res1 ++ res2
}
protected def finishWithBuffer(buffer: Array[Byte]): ByteString = {
deflater.finish()
val res = drain(buffer)
deflater.end()
res
}
@tailrec
protected final def drain(buffer: Array[Byte], result: ByteStringBuilder = new ByteStringBuilder()): ByteString = {
val len = deflater.deflate(buffer)
if (len > 0) {
result ++= ByteString.fromArray(buffer, 0, len)
drain(buffer, result)
} else {
assert(deflater.needsInput())
result.result()
}
}
private def newTempBuffer(size: Int = 65536): Array[Byte] =
// The default size is somewhat arbitrary, we'd like to guess a better value but Deflater/zlib
// is buffering in an unpredictable manner.
// `compress` will only return any data if the buffered compressed data has some size in
// the region of 10000-50000 bytes.
// `flush` and `finish` will return any size depending on the previous input.
// This value will hopefully provide a good compromise between memory churn and
// excessive fragmentation of ByteStrings.
new Array[Byte](size)
}
class DeflateDecompressor(maxBytesPerChunk: Int = Decoder.MaxBytesPerChunkDefault) extends DeflateDecompressorBase(maxBytesPerChunk) {
protected def createInflater() = new Inflater()
def initial: State = StartInflate
def afterInflate: State = StartInflate
protected def afterBytesRead(buffer: Array[Byte], offset: Int, length: Int): Unit = {}
protected def onTruncation(ctx: Context[ByteString]): SyncDirective = ctx.finish()
}
abstract class DeflateDecompressorBase(maxBytesPerChunk: Int = Decoder.MaxBytesPerChunkDefault) extends ByteStringParserStage[ByteString] {
protected def createInflater(): Inflater
val inflater = createInflater()
protected def afterInflate: State
protected def afterBytesRead(buffer: Array[Byte], offset: Int, length: Int): Unit
/** Start inflating */
case object StartInflate extends IntermediateState {
def onPush(data: ByteString, ctx: Context[ByteString]): SyncDirective = {
require(inflater.needsInput())
inflater.setInput(data.toArray)
becomeWithRemaining(Inflate()(data), ByteString.empty, ctx)
}
}
/** Inflate */
case class Inflate()(data: ByteString) extends IntermediateState {
override def onPull(ctx: Context[ByteString]): SyncDirective = {
val buffer = new Array[Byte](maxBytesPerChunk)
val read = inflater.inflate(buffer)
if (read > 0) {
afterBytesRead(buffer, 0, read)
ctx.push(ByteString.fromArray(buffer, 0, read))
} else {
val remaining = data.takeRight(inflater.getRemaining)
val next =
if (inflater.finished()) afterInflate
else StartInflate
becomeWithRemaining(next, remaining, ctx)
}
}
def onPush(elem: ByteString, ctx: Context[ByteString]): SyncDirective =
throw new IllegalStateException("Don't expect a new Element")
}
def becomeWithRemaining(next: State, remaining: ByteString, ctx: Context[ByteString]) = {
become(next)
if (remaining.isEmpty) current.onPull(ctx)
else current.onPush(remaining, ctx)
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.coding
import akka.http.scaladsl.model._
import akka.http.impl.util.StreamUtils
import akka.stream.stage.Stage
import akka.util.ByteString
import headers._
import akka.stream.scaladsl.Flow
trait Encoder {
def encoding: HttpEncoding
def messageFilter: HttpMessage Boolean
def encode[T <: HttpMessage](message: T)(implicit mapper: DataMapper[T]): T#Self =
if (messageFilter(message) && !message.headers.exists(Encoder.isContentEncodingHeader))
encodeData(message).withHeaders(`Content-Encoding`(encoding) +: message.headers)
else message.self
def encodeData[T](t: T)(implicit mapper: DataMapper[T]): T =
mapper.transformDataBytes(t, Flow[ByteString].transform(newEncodeTransformer))
def encode(input: ByteString): ByteString = newCompressor.compressAndFinish(input)
def newCompressor: Compressor
def newEncodeTransformer(): Stage[ByteString, ByteString] = {
val compressor = newCompressor
def encodeChunk(bytes: ByteString): ByteString = compressor.compressAndFlush(bytes)
def finish(): ByteString = compressor.finish()
StreamUtils.byteStringTransformer(encodeChunk, finish)
}
}
object Encoder {
val DefaultFilter: HttpMessage Boolean = {
case req: HttpRequest isCompressible(req)
case res @ HttpResponse(status, _, _, _) isCompressible(res) && status.isSuccess
}
private[coding] def isCompressible(msg: HttpMessage): Boolean =
msg.entity.contentType.mediaType.compressible
private[coding] val isContentEncodingHeader: HttpHeader Boolean = _.isInstanceOf[`Content-Encoding`]
}
/** A stateful object representing ongoing compression. */
abstract class Compressor {
/**
* Compresses the given input and returns compressed data. The implementation
* can and will choose to buffer output data to improve compression. Use
* `flush` or `compressAndFlush` to make sure that all input data has been
* compressed and pending output data has been returned.
*/
def compress(input: ByteString): ByteString
/**
* Flushes any output data and returns the currently remaining compressed data.
*/
def flush(): ByteString
/**
* Closes this compressed stream and return the remaining compressed data. After
* calling this method, this Compressor cannot be used any further.
*/
def finish(): ByteString
/** Combines `compress` + `flush` */
def compressAndFlush(input: ByteString): ByteString
/** Combines `compress` + `finish` */
def compressAndFinish(input: ByteString): ByteString
}

View file

@ -0,0 +1,145 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.coding
import akka.util.ByteString
import akka.stream.stage._
import akka.http.impl.util.ByteReader
import java.util.zip.{ Inflater, CRC32, ZipException, Deflater }
import akka.http.scaladsl.model._
import headers.HttpEncodings
class Gzip(val messageFilter: HttpMessage Boolean) extends Coder with StreamDecoder {
val encoding = HttpEncodings.gzip
def newCompressor = new GzipCompressor
def newDecompressorStage(maxBytesPerChunk: Int) = () new GzipDecompressor(maxBytesPerChunk)
}
/**
* An encoder and decoder for the HTTP 'gzip' encoding.
*/
object Gzip extends Gzip(Encoder.DefaultFilter) {
def apply(messageFilter: HttpMessage Boolean) = new Gzip(messageFilter)
}
class GzipCompressor extends DeflateCompressor {
override protected lazy val deflater = new Deflater(Deflater.BEST_COMPRESSION, true)
private val checkSum = new CRC32 // CRC32 of uncompressed data
private var headerSent = false
private var bytesRead = 0L
override protected def compressWithBuffer(input: ByteString, buffer: Array[Byte]): ByteString = {
updateCrc(input)
header() ++ super.compressWithBuffer(input, buffer)
}
override protected def flushWithBuffer(buffer: Array[Byte]): ByteString = header() ++ super.flushWithBuffer(buffer)
override protected def finishWithBuffer(buffer: Array[Byte]): ByteString = super.finishWithBuffer(buffer) ++ trailer()
private def updateCrc(input: ByteString): Unit = {
checkSum.update(input.toArray)
bytesRead += input.length
}
private def header(): ByteString =
if (!headerSent) {
headerSent = true
GzipDecompressor.Header
} else ByteString.empty
private def trailer(): ByteString = {
def int32(i: Int): ByteString = ByteString(i, i >> 8, i >> 16, i >> 24)
val crc = checkSum.getValue.toInt
val tot = bytesRead.toInt // truncated to 32bit as specified in https://tools.ietf.org/html/rfc1952#section-2
val trailer = int32(crc) ++ int32(tot)
trailer
}
}
class GzipDecompressor(maxBytesPerChunk: Int = Decoder.MaxBytesPerChunkDefault) extends DeflateDecompressorBase(maxBytesPerChunk) {
protected def createInflater(): Inflater = new Inflater(true)
def initial: State = Initial
/** No bytes were received yet */
case object Initial extends State {
def onPush(data: ByteString, ctx: Context[ByteString]): SyncDirective =
if (data.isEmpty) ctx.pull()
else becomeWithRemaining(ReadHeaders, data, ctx)
override def onPull(ctx: Context[ByteString]): SyncDirective =
if (ctx.isFinishing) {
ctx.finish()
} else super.onPull(ctx)
}
var crc32: CRC32 = new CRC32
protected def afterInflate: State = ReadTrailer
/** Reading the header bytes */
case object ReadHeaders extends ByteReadingState {
def read(reader: ByteReader, ctx: Context[ByteString]): SyncDirective = {
import reader._
if (readByte() != 0x1F || readByte() != 0x8B) fail("Not in GZIP format") // check magic header
if (readByte() != 8) fail("Unsupported GZIP compression method") // check compression method
val flags = readByte()
skip(6) // skip MTIME, XFL and OS fields
if ((flags & 4) > 0) skip(readShortLE()) // skip optional extra fields
if ((flags & 8) > 0) skipZeroTerminatedString() // skip optional file name
if ((flags & 16) > 0) skipZeroTerminatedString() // skip optional file comment
if ((flags & 2) > 0 && crc16(fromStartToHere) != readShortLE()) fail("Corrupt GZIP header")
inflater.reset()
crc32.reset()
becomeWithRemaining(StartInflate, remainingData, ctx)
}
}
protected def afterBytesRead(buffer: Array[Byte], offset: Int, length: Int): Unit =
crc32.update(buffer, offset, length)
/** Reading the trailer */
case object ReadTrailer extends ByteReadingState {
def read(reader: ByteReader, ctx: Context[ByteString]): SyncDirective = {
import reader._
if (readIntLE() != crc32.getValue.toInt) fail("Corrupt data (CRC32 checksum error)")
if (readIntLE() != inflater.getBytesWritten.toInt /* truncated to 32bit */ ) fail("Corrupt GZIP trailer ISIZE")
becomeWithRemaining(Initial, remainingData, ctx)
}
}
override def onUpstreamFinish(ctx: Context[ByteString]): TerminationDirective = ctx.absorbTermination()
private def crc16(data: ByteString) = {
val crc = new CRC32
crc.update(data.toArray)
crc.getValue.toInt & 0xFFFF
}
override protected def onTruncation(ctx: Context[ByteString]): SyncDirective = ctx.fail(new ZipException("Truncated GZIP stream"))
private def fail(msg: String) = throw new ZipException(msg)
}
/** INTERNAL API */
private[http] object GzipDecompressor {
// RFC 1952: http://tools.ietf.org/html/rfc1952 section 2.2
val Header = ByteString(
0x1F, // ID1
0x8B, // ID2
8, // CM = Deflate
0, // FLG
0, // MTIME 1
0, // MTIME 2
0, // MTIME 3
0, // MTIME 4
0, // XFL
0 // OS
)
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.coding
import akka.http.scaladsl.model._
import akka.http.impl.util.StreamUtils
import akka.stream.stage.Stage
import akka.util.ByteString
import headers.HttpEncodings
/**
* An encoder and decoder for the HTTP 'identity' encoding.
*/
object NoCoding extends Coder with StreamDecoder {
val encoding = HttpEncodings.identity
override def encode[T <: HttpMessage](message: T)(implicit mapper: DataMapper[T]): T#Self = message.self
override def encodeData[T](t: T)(implicit mapper: DataMapper[T]): T = t
override def decode[T <: HttpMessage](message: T)(implicit mapper: DataMapper[T]): T#Self = message.self
override def decodeData[T](t: T)(implicit mapper: DataMapper[T]): T = t
val messageFilter: HttpMessage Boolean = _ false
def newCompressor = NoCodingCompressor
def newDecompressorStage(maxBytesPerChunk: Int): () Stage[ByteString, ByteString] =
() StreamUtils.limitByteChunksStage(maxBytesPerChunk)
}
object NoCodingCompressor extends Compressor {
def compress(input: ByteString): ByteString = input
def flush() = ByteString.empty
def finish() = ByteString.empty
def compressAndFlush(input: ByteString): ByteString = input
def compressAndFinish(input: ByteString): ByteString = input
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.common
import akka.http.scaladsl.unmarshalling.{ FromStringUnmarshaller FSU }
private[http] trait ToNameReceptacleEnhancements {
implicit def symbol2NR(symbol: Symbol) = new NameReceptacle[String](symbol.name)
implicit def string2NR(string: String) = new NameReceptacle[String](string)
}
object ToNameReceptacleEnhancements extends ToNameReceptacleEnhancements
class NameReceptacle[T](val name: String) {
def as[B] = new NameReceptacle[B](name)
def as[B](unmarshaller: FSU[B]) = new NameUnmarshallerReceptacle(name, unmarshaller)
def ? = new NameOptionReceptacle[T](name)
def ?[B](default: B) = new NameDefaultReceptacle(name, default)
def ![B](requiredValue: B) = new RequiredValueReceptacle(name, requiredValue)
}
class NameUnmarshallerReceptacle[T](val name: String, val um: FSU[T]) {
def ? = new NameOptionUnmarshallerReceptacle[T](name, um)
def ?(default: T) = new NameDefaultUnmarshallerReceptacle(name, default, um)
def !(requiredValue: T) = new RequiredValueUnmarshallerReceptacle(name, requiredValue, um)
}
class NameOptionReceptacle[T](val name: String)
class NameDefaultReceptacle[T](val name: String, val default: T)
class RequiredValueReceptacle[T](val name: String, val requiredValue: T)
class NameOptionUnmarshallerReceptacle[T](val name: String, val um: FSU[T])
class NameDefaultUnmarshallerReceptacle[T](val name: String, val default: T, val um: FSU[T])
class RequiredValueUnmarshallerReceptacle[T](val name: String, val requiredValue: T, val um: FSU[T])

View file

@ -0,0 +1,134 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.common
import scala.annotation.implicitNotFound
import scala.collection.immutable
import scala.concurrent.{ ExecutionContext, Future }
import scala.concurrent.duration._
import akka.stream.FlowMaterializer
import akka.http.scaladsl.unmarshalling._
import akka.http.scaladsl.model._
import akka.http.scaladsl.util.FastFuture
import FastFuture._
/**
* Read-only abstraction on top of `application/x-www-form-urlencoded` and multipart form data,
* allowing joint unmarshalling access to either kind, **if** you supply both, a [[FromStringUnmarshaller]]
* as well as a [[FromEntityUnmarshaller]] for the target type `T`.
* Note: In order to allow for random access to the field values streamed multipart form data are strictified!
* Don't use this abstraction on potentially unbounded forms (e.g. large file uploads).
*
* If you only need to consume one type of form (`application/x-www-form-urlencoded` *or* multipart) then
* simply unmarshal directly to the respective form abstraction ([[FormData]] or [[Multipart.FormData]])
* rather than going through [[StrictForm]].
*
* Simple usage example:
* {{{
* val strictFormFuture = Unmarshal(entity).to[StrictForm]
* val fooFieldUnmarshalled: Future[T] =
* strictFormFuture flatMap { form =>
* Unmarshal(form field "foo").to[T]
* }
* }}}
*/
sealed abstract class StrictForm {
def fields: immutable.Seq[(String, StrictForm.Field)]
def field(name: String): Option[StrictForm.Field] = fields collectFirst { case (`name`, field) field }
}
object StrictForm {
sealed trait Field
object Field {
private[StrictForm] final case class FromString(value: String) extends Field
private[StrictForm] final case class FromPart(value: Multipart.FormData.BodyPart.Strict) extends Field
implicit def unmarshaller[T](implicit um: FieldUnmarshaller[T]): FromStrictFormFieldUnmarshaller[T] =
Unmarshaller(implicit ec {
case FromString(value) um.unmarshalString(value)
case FromPart(value) um.unmarshalPart(value)
})
def unmarshallerFromFSU[T](fsu: FromStringUnmarshaller[T]): FromStrictFormFieldUnmarshaller[T] =
Unmarshaller(implicit ec {
case FromString(value) fsu(value)
case FromPart(value) fsu(value.entity.data.decodeString(value.entity.contentType.charset.nioCharset.name))
})
@implicitNotFound("In order to unmarshal a `StrictForm.Field` to type `${T}` you need to supply a " +
"`FromStringUnmarshaller[${T}]` and/or a `FromEntityUnmarshaller[${T}]`")
sealed trait FieldUnmarshaller[T] {
def unmarshalString(value: String)(implicit ec: ExecutionContext): Future[T]
def unmarshalPart(value: Multipart.FormData.BodyPart.Strict)(implicit ec: ExecutionContext): Future[T]
}
object FieldUnmarshaller extends LowPrioImplicits {
implicit def fromBoth[T](implicit fsu: FromStringUnmarshaller[T], feu: FromEntityUnmarshaller[T]) =
new FieldUnmarshaller[T] {
def unmarshalString(value: String)(implicit ec: ExecutionContext) = fsu(value)
def unmarshalPart(value: Multipart.FormData.BodyPart.Strict)(implicit ec: ExecutionContext) = feu(value.entity)
}
}
sealed abstract class LowPrioImplicits {
implicit def fromFSU[T](implicit fsu: FromStringUnmarshaller[T]) =
new FieldUnmarshaller[T] {
def unmarshalString(value: String)(implicit ec: ExecutionContext) = fsu(value)
def unmarshalPart(value: Multipart.FormData.BodyPart.Strict)(implicit ec: ExecutionContext) =
fsu(value.entity.data.decodeString(value.entity.contentType.charset.nioCharset.name))
}
implicit def fromFEU[T](implicit feu: FromEntityUnmarshaller[T]) =
new FieldUnmarshaller[T] {
def unmarshalString(value: String)(implicit ec: ExecutionContext) = feu(HttpEntity(value))
def unmarshalPart(value: Multipart.FormData.BodyPart.Strict)(implicit ec: ExecutionContext) = feu(value.entity)
}
}
}
implicit def unmarshaller(implicit formDataUM: FromEntityUnmarshaller[FormData],
multipartUM: FromEntityUnmarshaller[Multipart.FormData],
fm: FlowMaterializer): FromEntityUnmarshaller[StrictForm] =
Unmarshaller { implicit ec
entity
def tryUnmarshalToQueryForm: Future[StrictForm] =
for (formData formDataUM(entity).fast) yield {
new StrictForm {
val fields = formData.fields.map { case (name, value) name -> Field.FromString(value) }(collection.breakOut)
}
}
def tryUnmarshalToMultipartForm: Future[StrictForm] =
for {
multiPartFD multipartUM(entity).fast
strictMultiPartFD multiPartFD.toStrict(10.seconds).fast // TODO: make timeout configurable
} yield {
new StrictForm {
val fields = strictMultiPartFD.strictParts.map {
case x: Multipart.FormData.BodyPart.Strict x.name -> Field.FromPart(x)
}(collection.breakOut)
}
}
tryUnmarshalToQueryForm.fast.recoverWith {
case Unmarshaller.UnsupportedContentTypeException(supported1)
tryUnmarshalToMultipartForm.fast.recoverWith {
case Unmarshaller.UnsupportedContentTypeException(supported2)
FastFuture.failed(Unmarshaller.UnsupportedContentTypeException(supported1 ++ supported2))
}
}
}
/**
* Simple model for strict file content in a multipart form data part.
*/
final case class FileData(filename: Option[String], entity: HttpEntity.Strict)
object FileData {
implicit val unmarshaller: FromStrictFormFieldUnmarshaller[FileData] =
Unmarshaller strict {
case Field.FromString(_) throw Unmarshaller.UnsupportedContentTypeException(MediaTypes.`application/x-www-form-urlencoded`)
case Field.FromPart(part) FileData(part.filename, part.entity)
}
}
}

View file

@ -0,0 +1,16 @@
/**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.marshalling
import scala.collection.immutable
import akka.http.scaladsl.model._
class EmptyValue[+T] private (val emptyValue: T)
object EmptyValue {
implicit def emptyEntity = new EmptyValue[UniversalEntity](HttpEntity.Empty)
implicit val emptyHeadersAndEntity = new EmptyValue[(immutable.Seq[HttpHeader], UniversalEntity)](Nil -> HttpEntity.Empty)
implicit val emptyResponse = new EmptyValue[HttpResponse](HttpResponse(entity = emptyEntity.emptyValue))
}

View file

@ -0,0 +1,44 @@
/**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.marshalling
import scala.concurrent.Future
import scala.util.{ Try, Failure, Success }
import akka.http.scaladsl.util.FastFuture
import FastFuture._
trait GenericMarshallers extends LowPriorityToResponseMarshallerImplicits {
implicit def throwableMarshaller[T]: Marshaller[Throwable, T] = Marshaller(_ FastFuture.failed)
implicit def optionMarshaller[A, B](implicit m: Marshaller[A, B], empty: EmptyValue[B]): Marshaller[Option[A], B] =
Marshaller { implicit ec
{
case Some(value) m(value)
case None FastFuture.successful(Marshalling.Opaque(() empty.emptyValue) :: Nil)
}
}
implicit def eitherMarshaller[A1, A2, B](implicit m1: Marshaller[A1, B], m2: Marshaller[A2, B]): Marshaller[Either[A1, A2], B] =
Marshaller { implicit ec
{
case Left(a1) m1(a1)
case Right(a2) m2(a2)
}
}
implicit def futureMarshaller[A, B](implicit m: Marshaller[A, B]): Marshaller[Future[A], B] =
Marshaller(implicit ec _.fast.flatMap(m(_)))
implicit def tryMarshaller[A, B](implicit m: Marshaller[A, B]): Marshaller[Try[A], B] =
Marshaller { implicit ec
{
case Success(value) m(value)
case Failure(error) FastFuture.failed(error)
}
}
}
object GenericMarshallers extends GenericMarshallers

View file

@ -0,0 +1,75 @@
/**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.marshalling
import scala.concurrent.{ ExecutionContext, Future }
import akka.http.scaladsl.model.HttpCharsets._
import akka.http.scaladsl.model._
import akka.http.scaladsl.util.FastFuture._
object Marshal {
def apply[T](value: T): Marshal[T] = new Marshal(value)
case class UnacceptableResponseContentTypeException(supported: Set[ContentType]) extends RuntimeException
private class MarshallingWeight(val weight: Float, val marshal: () HttpResponse)
}
class Marshal[A](val value: A) {
/**
* Marshals `value` using the first available [[Marshalling]] for `A` and `B` provided by the given [[Marshaller]].
* If the marshalling is flexible with regard to the used charset `UTF-8` is chosen.
*/
def to[B](implicit m: Marshaller[A, B], ec: ExecutionContext): Future[B] =
m(value).fast.map {
_.head match {
case Marshalling.WithFixedCharset(_, _, marshal) marshal()
case Marshalling.WithOpenCharset(_, marshal) marshal(HttpCharsets.`UTF-8`)
case Marshalling.Opaque(marshal) marshal()
}
}
/**
* Marshals `value` to an `HttpResponse` for the given `HttpRequest` with full content-negotiation.
*/
def toResponseFor(request: HttpRequest)(implicit m: ToResponseMarshaller[A], ec: ExecutionContext): Future[HttpResponse] = {
import akka.http.scaladsl.marshalling.Marshal._
val mediaRanges = request.acceptedMediaRanges // cache for performance
val charsetRanges = request.acceptedCharsetRanges // cache for performance
def qValueMT(mediaType: MediaType) = request.qValueForMediaType(mediaType, mediaRanges)
def qValueCS(charset: HttpCharset) = request.qValueForCharset(charset, charsetRanges)
m(value).fast.map { marshallings
val defaultMarshallingWeight = new MarshallingWeight(0f, { ()
val supportedContentTypes = marshallings collect {
case Marshalling.WithFixedCharset(mt, cs, _) ContentType(mt, cs)
case Marshalling.WithOpenCharset(mt, _) ContentType(mt)
}
throw UnacceptableResponseContentTypeException(supportedContentTypes.toSet)
})
def choose(acc: MarshallingWeight, mt: MediaType, cs: HttpCharset, marshal: () HttpResponse) = {
val weight = math.min(qValueMT(mt), qValueCS(cs))
if (weight > acc.weight) new MarshallingWeight(weight, marshal) else acc
}
val best = marshallings.foldLeft(defaultMarshallingWeight) {
case (acc, Marshalling.WithFixedCharset(mt, cs, marshal))
choose(acc, mt, cs, marshal)
case (acc, Marshalling.WithOpenCharset(mt, marshal))
def withCharset(cs: HttpCharset) = choose(acc, mt, cs, () marshal(cs))
// logic for choosing the charset adapted from http://tools.ietf.org/html/rfc7231#section-5.3.3
if (qValueCS(`UTF-8`) == 1f) withCharset(`UTF-8`) // prefer UTF-8 if fully accepted
else charsetRanges match {
// pick the charset which the highest q-value (head of charsetRanges) if it isn't explicitly rejected
case (HttpCharsetRange.One(cs, qValue)) :: _ if qValue > 0f withCharset(cs)
case _ acc
}
case (acc, Marshalling.Opaque(marshal))
if (acc.weight == 0f) new MarshallingWeight(Float.MinPositiveValue, marshal) else acc
}
best.marshal()
}
}
}

View file

@ -0,0 +1,153 @@
/**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.marshalling
import scala.concurrent.{ Future, ExecutionContext }
import scala.util.control.NonFatal
import akka.http.scaladsl.model._
import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.util.FastFuture._
sealed abstract class Marshaller[-A, +B] {
def apply(value: A)(implicit ec: ExecutionContext): Future[List[Marshalling[B]]]
def map[C](f: B C): Marshaller[A, C] =
Marshaller(implicit ec value this(value).fast map (_ map (_ map f)))
/**
* Reuses this Marshaller's logic to produce a new Marshaller from another type `C` which overrides
* the produced [[ContentType]] with another one.
* Depending on whether the given [[ContentType]] has a defined charset or not and whether the underlying
* marshaller marshals with a fixed charset it can happen, that the wrapping becomes illegal.
* For example, a marshaller producing content encoded with UTF-16 cannot be wrapped with a [[ContentType]]
* that has a defined charset of UTF-8, since akka-http will never recode entities.
* If the wrapping is illegal the [[Future]] produced by the resulting marshaller will contain a [[RuntimeException]].
*/
def wrap[C, D >: B](contentType: ContentType)(f: C A)(implicit mto: MediaTypeOverrider[D]): Marshaller[C, D] =
wrapWithEC[C, D](contentType)(_ f)
/**
* Reuses this Marshaller's logic to produce a new Marshaller from another type `C` which overrides
* the produced [[ContentType]] with another one.
* Depending on whether the given [[ContentType]] has a defined charset or not and whether the underlying
* marshaller marshals with a fixed charset it can happen, that the wrapping becomes illegal.
* For example, a marshaller producing content encoded with UTF-16 cannot be wrapped with a [[ContentType]]
* that has a defined charset of UTF-8, since akka-http will never recode entities.
* If the wrapping is illegal the [[Future]] produced by the resulting marshaller will contain a [[RuntimeException]].
*/
def wrapWithEC[C, D >: B](contentType: ContentType)(f: ExecutionContext C A)(implicit mto: MediaTypeOverrider[D]): Marshaller[C, D] =
Marshaller { implicit ec
value
import Marshalling._
this(f(ec)(value)).fast map {
_ map {
case WithFixedCharset(_, cs, marshal) if contentType.hasOpenCharset || contentType.charset == cs
WithFixedCharset(contentType.mediaType, cs, () mto(marshal(), contentType.mediaType))
case WithOpenCharset(_, marshal) if contentType.hasOpenCharset
WithOpenCharset(contentType.mediaType, cs mto(marshal(cs), contentType.mediaType))
case WithOpenCharset(_, marshal)
WithFixedCharset(contentType.mediaType, contentType.charset, () mto(marshal(contentType.charset), contentType.mediaType))
case Opaque(marshal) if contentType.definedCharset.isEmpty Opaque(() mto(marshal(), contentType.mediaType))
case x sys.error(s"Illegal marshaller wrapping. Marshalling `$x` cannot be wrapped with ContentType `$contentType`")
}
}
}
def compose[C](f: C A): Marshaller[C, B] =
Marshaller(implicit ec c apply(f(c)))
def composeWithEC[C](f: ExecutionContext C A): Marshaller[C, B] =
Marshaller(implicit ec c apply(f(ec)(c)))
}
object Marshaller
extends GenericMarshallers
with PredefinedToEntityMarshallers
with PredefinedToResponseMarshallers
with PredefinedToRequestMarshallers {
/**
* Creates a [[Marshaller]] from the given function.
*/
def apply[A, B](f: ExecutionContext A Future[List[Marshalling[B]]]): Marshaller[A, B] =
new Marshaller[A, B] {
def apply(value: A)(implicit ec: ExecutionContext) =
try f(ec)(value)
catch { case NonFatal(e) FastFuture.failed(e) }
}
/**
* Helper for creating a [[Marshaller]] using the given function.
*/
def strict[A, B](f: A Marshalling[B]): Marshaller[A, B] =
Marshaller { _ a FastFuture.successful(f(a) :: Nil) }
/**
* Helper for creating a "super-marshaller" from a number of "sub-marshallers".
* Content-negotiation determines, which "sub-marshaller" eventually gets to do the job.
*/
def oneOf[A, B](marshallers: Marshaller[A, B]*): Marshaller[A, B] =
Marshaller { implicit ec a FastFuture.sequence(marshallers.map(_(a))).fast.map(_.flatten.toList) }
/**
* Helper for creating a "super-marshaller" from a number of values and a function producing "sub-marshallers"
* from these values. Content-negotiation determines, which "sub-marshaller" eventually gets to do the job.
*/
def oneOf[T, A, B](values: T*)(f: T Marshaller[A, B]): Marshaller[A, B] =
oneOf(values map f: _*)
/**
* Helper for creating a synchronous [[Marshaller]] to content with a fixed charset from the given function.
*/
def withFixedCharset[A, B](mediaType: MediaType, charset: HttpCharset)(marshal: A B): Marshaller[A, B] =
strict { value Marshalling.WithFixedCharset(mediaType, charset, () marshal(value)) }
/**
* Helper for creating a synchronous [[Marshaller]] to content with a negotiable charset from the given function.
*/
def withOpenCharset[A, B](mediaType: MediaType)(marshal: (A, HttpCharset) B): Marshaller[A, B] =
strict { value Marshalling.WithOpenCharset(mediaType, charset marshal(value, charset)) }
/**
* Helper for creating a synchronous [[Marshaller]] to non-negotiable content from the given function.
*/
def opaque[A, B](marshal: A B): Marshaller[A, B] =
strict { value Marshalling.Opaque(() marshal(value)) }
}
/**
* Describes one possible option for marshalling a given value.
*/
sealed trait Marshalling[+A] {
def map[B](f: A B): Marshalling[B]
}
object Marshalling {
/**
* A Marshalling to a specific MediaType and charset.
*/
final case class WithFixedCharset[A](mediaType: MediaType,
charset: HttpCharset,
marshal: () A) extends Marshalling[A] {
def map[B](f: A B): WithFixedCharset[B] = copy(marshal = () f(marshal()))
}
/**
* A Marshalling to a specific MediaType and a potentially flexible charset.
*/
final case class WithOpenCharset[A](mediaType: MediaType,
marshal: HttpCharset A) extends Marshalling[A] {
def map[B](f: A B): WithOpenCharset[B] = copy(marshal = cs f(marshal(cs)))
}
/**
* A Marshalling to an unknown MediaType and charset.
* Circumvents content negotiation.
*/
final case class Opaque[A](marshal: () A) extends Marshalling[A] {
def map[B](f: A B): Opaque[B] = copy(marshal = () f(marshal()))
}
}

View file

@ -0,0 +1,30 @@
/**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.marshalling
import scala.collection.immutable
import akka.http.scaladsl.model._
sealed trait MediaTypeOverrider[T] {
def apply(value: T, mediaType: MediaType): T
}
object MediaTypeOverrider {
implicit def forEntity[T <: HttpEntity]: MediaTypeOverrider[T] = new MediaTypeOverrider[T] {
def apply(value: T, mediaType: MediaType) =
value.withContentType(value.contentType withMediaType mediaType).asInstanceOf[T] // can't be expressed in types
}
implicit def forHeadersAndEntity[T <: HttpEntity] = new MediaTypeOverrider[(immutable.Seq[HttpHeader], T)] {
def apply(value: (immutable.Seq[HttpHeader], T), mediaType: MediaType) =
value._1 -> value._2.withContentType(value._2.contentType withMediaType mediaType).asInstanceOf[T]
}
implicit val forResponse = new MediaTypeOverrider[HttpResponse] {
def apply(value: HttpResponse, mediaType: MediaType) =
value.mapEntity(forEntity(_: ResponseEntity, mediaType))
}
implicit val forRequest = new MediaTypeOverrider[HttpRequest] {
def apply(value: HttpRequest, mediaType: MediaType) =
value.mapEntity(forEntity(_: RequestEntity, mediaType))
}
}

View file

@ -0,0 +1,45 @@
/**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.marshalling
import scala.concurrent.forkjoin.ThreadLocalRandom
import akka.parboiled2.util.Base64
import akka.event.{ NoLogging, LoggingAdapter }
import akka.stream.scaladsl.FlattenStrategy
import akka.http.impl.engine.rendering.BodyPartRenderer
import akka.http.scaladsl.model._
trait MultipartMarshallers {
protected val multipartBoundaryRandom: java.util.Random = ThreadLocalRandom.current()
/**
* Creates a new random 144-bit number and base64 encodes it (using a custom "safe" alphabet, yielding 24 characters).
*/
def randomBoundary: String = {
val array = new Array[Byte](18)
multipartBoundaryRandom.nextBytes(array)
Base64.custom.encodeToString(array, false)
}
implicit def multipartMarshaller[T <: Multipart](implicit log: LoggingAdapter = NoLogging): ToEntityMarshaller[T] =
Marshaller strict { value
val boundary = randomBoundary
val contentType = ContentType(value.mediaType withBoundary boundary)
Marshalling.WithOpenCharset(contentType.mediaType, { charset
value match {
case x: Multipart.Strict
val data = BodyPartRenderer.strict(x.strictParts, boundary, charset.nioCharset, partHeadersSizeHint = 128, log)
HttpEntity(contentType, data)
case _
val chunks = value.parts
.transform(() BodyPartRenderer.streamed(boundary, charset.nioCharset, partHeadersSizeHint = 128, log))
.flatten(FlattenStrategy.concat)
HttpEntity.Chunked(contentType, chunks)
}
})
}
}
object MultipartMarshallers extends MultipartMarshallers

View file

@ -0,0 +1,67 @@
/**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.marshalling
import java.nio.CharBuffer
import akka.http.impl.model.parser.CharacterClasses
import akka.http.scaladsl.model.MediaTypes._
import akka.http.scaladsl.model._
import akka.http.impl.util.StringRendering
import akka.util.ByteString
trait PredefinedToEntityMarshallers extends MultipartMarshallers {
implicit val ByteArrayMarshaller: ToEntityMarshaller[Array[Byte]] = byteArrayMarshaller(`application/octet-stream`)
def byteArrayMarshaller(mediaType: MediaType, charset: HttpCharset): ToEntityMarshaller[Array[Byte]] = {
val ct = ContentType(mediaType, charset)
Marshaller.withFixedCharset(ct.mediaType, ct.definedCharset.get) { bytes HttpEntity(ct, bytes) }
}
def byteArrayMarshaller(mediaType: MediaType): ToEntityMarshaller[Array[Byte]] = {
val ct = ContentType(mediaType)
// since we don't want to recode we simply ignore the charset determined by content negotiation here
Marshaller.withOpenCharset(ct.mediaType) { (bytes, _) HttpEntity(ct, bytes) }
}
implicit val ByteStringMarshaller: ToEntityMarshaller[ByteString] = byteStringMarshaller(`application/octet-stream`)
def byteStringMarshaller(mediaType: MediaType, charset: HttpCharset): ToEntityMarshaller[ByteString] = {
val ct = ContentType(mediaType, charset)
Marshaller.withFixedCharset(ct.mediaType, ct.definedCharset.get) { bytes HttpEntity(ct, bytes) }
}
def byteStringMarshaller(mediaType: MediaType): ToEntityMarshaller[ByteString] = {
val ct = ContentType(mediaType)
// since we don't want to recode we simply ignore the charset determined by content negotiation here
Marshaller.withOpenCharset(ct.mediaType) { (bytes, _) HttpEntity(ct, bytes) }
}
implicit val CharArrayMarshaller: ToEntityMarshaller[Array[Char]] = charArrayMarshaller(`text/plain`)
def charArrayMarshaller(mediaType: MediaType): ToEntityMarshaller[Array[Char]] =
Marshaller.withOpenCharset(mediaType) { (value, charset)
if (value.length > 0) {
val charBuffer = CharBuffer.wrap(value)
val byteBuffer = charset.nioCharset.encode(charBuffer)
val array = new Array[Byte](byteBuffer.remaining())
byteBuffer.get(array)
HttpEntity(ContentType(mediaType, charset), array)
} else HttpEntity.Empty
}
implicit val StringMarshaller: ToEntityMarshaller[String] = stringMarshaller(`text/plain`)
def stringMarshaller(mediaType: MediaType): ToEntityMarshaller[String] =
Marshaller.withOpenCharset(mediaType) { (s, cs) HttpEntity(ContentType(mediaType, cs), s) }
implicit val FormDataMarshaller: ToEntityMarshaller[FormData] =
Marshaller.withOpenCharset(`application/x-www-form-urlencoded`) { (formData, charset)
val query = Uri.Query(formData.fields: _*)
val string = UriRendering.renderQuery(new StringRendering, query, charset.nioCharset, CharacterClasses.unreserved).get
HttpEntity(ContentType(`application/x-www-form-urlencoded`, charset), string)
}
implicit val HttpEntityMarshaller: ToEntityMarshaller[MessageEntity] = Marshaller strict { value
Marshalling.WithFixedCharset(value.contentType.mediaType, value.contentType.charset, () value)
}
}
object PredefinedToEntityMarshallers extends PredefinedToEntityMarshallers

View file

@ -0,0 +1,29 @@
/**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.marshalling
import scala.collection.immutable
import akka.http.scaladsl.model._
import akka.http.scaladsl.util.FastFuture._
trait PredefinedToRequestMarshallers {
private type TRM[T] = ToRequestMarshaller[T] // brevity alias
implicit val fromRequest: TRM[HttpRequest] = Marshaller.opaque(identity)
implicit def fromUri: TRM[Uri] =
Marshaller strict { uri Marshalling.Opaque(() HttpRequest(uri = uri)) }
implicit def fromMethodAndUriAndValue[S, T](implicit mt: ToEntityMarshaller[T]): TRM[(HttpMethod, Uri, T)] =
fromMethodAndUriAndHeadersAndValue[T] compose { case (m, u, v) (m, u, Nil, v) }
implicit def fromMethodAndUriAndHeadersAndValue[T](implicit mt: ToEntityMarshaller[T]): TRM[(HttpMethod, Uri, immutable.Seq[HttpHeader], T)] =
Marshaller(implicit ec {
case (m, u, h, v) mt(v).fast map (_ map (_ map (HttpRequest(m, u, h, _))))
})
}
object PredefinedToRequestMarshallers extends PredefinedToRequestMarshallers

View file

@ -0,0 +1,49 @@
/**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.marshalling
import scala.collection.immutable
import akka.http.scaladsl.util.FastFuture._
import akka.http.scaladsl.model.MediaTypes._
import akka.http.scaladsl.model._
trait PredefinedToResponseMarshallers extends LowPriorityToResponseMarshallerImplicits {
private type TRM[T] = ToResponseMarshaller[T] // brevity alias
def fromToEntityMarshaller[T](status: StatusCode = StatusCodes.OK,
headers: immutable.Seq[HttpHeader] = Nil)(
implicit m: ToEntityMarshaller[T]): ToResponseMarshaller[T] =
fromStatusCodeAndHeadersAndValue compose (t (status, headers, t))
implicit val fromResponse: TRM[HttpResponse] = Marshaller.opaque(identity)
implicit val fromStatusCode: TRM[StatusCode] =
Marshaller.withOpenCharset(`text/plain`) { (status, charset)
HttpResponse(status, entity = HttpEntity(ContentType(`text/plain`, charset), status.defaultMessage))
}
implicit def fromStatusCodeAndValue[S, T](implicit sConv: S StatusCode, mt: ToEntityMarshaller[T]): TRM[(S, T)] =
fromStatusCodeAndHeadersAndValue[T] compose { case (status, value) (sConv(status), Nil, value) }
implicit def fromStatusCodeConvertibleAndHeadersAndT[S, T](implicit sConv: S StatusCode,
mt: ToEntityMarshaller[T]): TRM[(S, immutable.Seq[HttpHeader], T)] =
fromStatusCodeAndHeadersAndValue[T] compose { case (status, headers, value) (sConv(status), headers, value) }
implicit def fromStatusCodeAndHeadersAndValue[T](implicit mt: ToEntityMarshaller[T]): TRM[(StatusCode, immutable.Seq[HttpHeader], T)] =
Marshaller(implicit ec {
case (status, headers, value) mt(value).fast map (_ map (_ map (HttpResponse(status, headers, _))))
})
}
trait LowPriorityToResponseMarshallerImplicits {
implicit def liftMarshallerConversion[T](m: ToEntityMarshaller[T]): ToResponseMarshaller[T] =
liftMarshaller(m)
implicit def liftMarshaller[T](implicit m: ToEntityMarshaller[T]): ToResponseMarshaller[T] =
PredefinedToResponseMarshallers.fromToEntityMarshaller()
}
object PredefinedToResponseMarshallers extends PredefinedToResponseMarshallers

View file

@ -0,0 +1,30 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.marshalling
import scala.concurrent.{ Future, ExecutionContext }
import akka.http.scaladsl.model._
/** Something that can later be marshalled into a response */
trait ToResponseMarshallable {
type T
def value: T
implicit def marshaller: ToResponseMarshaller[T]
def apply(request: HttpRequest)(implicit ec: ExecutionContext): Future[HttpResponse] =
Marshal(value).toResponseFor(request)
}
object ToResponseMarshallable {
implicit def apply[A](_value: A)(implicit _marshaller: ToResponseMarshaller[A]): ToResponseMarshallable =
new ToResponseMarshallable {
type T = A
def value: T = _value
def marshaller: ToResponseMarshaller[T] = _marshaller
}
implicit val marshaller: ToResponseMarshaller[ToResponseMarshallable] =
Marshaller { implicit ec marshallable marshallable.marshaller(marshallable.value) }
}

View file

@ -0,0 +1,15 @@
/**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl
import scala.collection.immutable
import akka.http.scaladsl.model._
package object marshalling {
type ToEntityMarshaller[T] = Marshaller[T, MessageEntity]
type ToHeadersAndEntityMarshaller[T] = Marshaller[T, (immutable.Seq[HttpHeader], MessageEntity)]
type ToResponseMarshaller[T] = Marshaller[T, HttpResponse]
type ToRequestMarshaller[T] = Marshaller[T, HttpRequest]
}

View file

@ -0,0 +1,161 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import scala.collection.immutable
import akka.http.scaladsl.server.directives.RouteDirectives
import akka.http.scaladsl.server.util._
import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.util.FastFuture._
/**
* A directive that provides a tuple of values of type `L` to create an inner route.
*/
abstract class Directive[L](implicit val ev: Tuple[L]) {
/**
* Calls the inner route with a tuple of extracted values of type `L`.
*
* `tapply` is short for "tuple-apply". Usually, you will use the regular `apply` method instead,
* which is added by an implicit conversion (see `Directive.addDirectiveApply`).
*/
def tapply(f: L Route): Route
/**
* Joins two directives into one which runs the second directive if the first one rejects.
*/
def |[R >: L](that: Directive[R]): Directive[R] =
recover(rejections directives.BasicDirectives.mapRejections(rejections ++ _) & that)(that.ev)
/**
* Joins two directives into one which extracts the concatenation of its base directive extractions.
* NOTE: Extraction joining is an O(N) operation with N being the number of extractions on the right-side.
*/
def &(magnet: ConjunctionMagnet[L]): magnet.Out = magnet(this)
/**
* Converts this directive into one which, instead of a tuple of type ``L``, creates an
* instance of type ``A`` (which is usually a case class).
*/
def as[A](constructor: ConstructFromTuple[L, A]): Directive1[A] = tmap(constructor)
/**
* Maps over this directive using the given function, which can produce either a tuple or any other value
* (which will then we wrapped into a [[Tuple1]]).
*/
def tmap[R](f: L R)(implicit tupler: Tupler[R]): Directive[tupler.Out] =
Directive[tupler.Out] { inner tapply { values inner(tupler(f(values))) } }(tupler.OutIsTuple)
/**
* Flatmaps this directive using the given function.
*/
def tflatMap[R: Tuple](f: L Directive[R]): Directive[R] =
Directive[R] { inner tapply { values f(values) tapply inner } }
/**
* Creates a new [[Directive0]], which passes if the given predicate matches the current
* extractions or rejects with the given rejections.
*/
def trequire(predicate: L Boolean, rejections: Rejection*): Directive0 =
tfilter(predicate, rejections: _*).tflatMap(_ Directive.Empty)
/**
* Creates a new directive of the same type, which passes if the given predicate matches the current
* extractions or rejects with the given rejections.
*/
def tfilter(predicate: L Boolean, rejections: Rejection*): Directive[L] =
Directive[L] { inner tapply { values ctx if (predicate(values)) inner(values)(ctx) else ctx.reject(rejections: _*) } }
/**
* Creates a new directive that is able to recover from rejections that were produced by `this` Directive
* **before the inner route was applied**.
*/
def recover[R >: L: Tuple](recovery: immutable.Seq[Rejection] Directive[R]): Directive[R] =
Directive[R] { inner
ctx
import ctx.executionContext
@volatile var rejectedFromInnerRoute = false
tapply({ list c rejectedFromInnerRoute = true; inner(list)(c) })(ctx).fast.flatMap {
case RouteResult.Rejected(rejections) if !rejectedFromInnerRoute recovery(rejections).tapply(inner)(ctx)
case x FastFuture.successful(x)
}
}
/**
* Variant of `recover` that only recovers from rejections handled by the given PartialFunction.
*/
def recoverPF[R >: L: Tuple](recovery: PartialFunction[immutable.Seq[Rejection], Directive[R]]): Directive[R] =
recover { rejections recovery.applyOrElse(rejections, (rejs: Seq[Rejection]) RouteDirectives.reject(rejs: _*)) }
}
object Directive {
/**
* Constructs a directive from a function literal.
*/
def apply[T: Tuple](f: (T Route) Route): Directive[T] =
new Directive[T] { def tapply(inner: T Route) = f(inner) }
/**
* A Directive that always passes the request on to its inner route (i.e. does nothing).
*/
val Empty: Directive0 = Directive(_())
/**
* Adds `apply` to all Directives with 1 or more extractions,
* which allows specifying an n-ary function to receive the extractions instead of a Function1[TupleX, Route].
*/
implicit def addDirectiveApply[L](directive: Directive[L])(implicit hac: ApplyConverter[L]): hac.In Route =
f directive.tapply(hac(f))
/**
* Adds `apply` to Directive0. Note: The `apply` parameter is call-by-name to ensure consistent execution behavior
* with the directives producing extractions.
*/
implicit def addByNameNullaryApply(directive: Directive0): ( Route) Route =
r directive.tapply(_ r)
implicit class SingleValueModifiers[T](underlying: Directive1[T]) extends AnyRef {
def map[R](f: T R)(implicit tupler: Tupler[R]): Directive[tupler.Out] =
underlying.tmap { case Tuple1(value) f(value) }
def flatMap[R: Tuple](f: T Directive[R]): Directive[R] =
underlying.tflatMap { case Tuple1(value) f(value) }
def require(predicate: T Boolean, rejections: Rejection*): Directive0 =
underlying.filter(predicate, rejections: _*).tflatMap(_ Empty)
def filter(predicate: T Boolean, rejections: Rejection*): Directive1[T] =
underlying.tfilter({ case Tuple1(value) predicate(value) }, rejections: _*)
}
}
trait ConjunctionMagnet[L] {
type Out
def apply(underlying: Directive[L]): Out
}
object ConjunctionMagnet {
implicit def fromDirective[L, R](other: Directive[R])(implicit join: TupleOps.Join[L, R]): ConjunctionMagnet[L] { type Out = Directive[join.Out] } =
new ConjunctionMagnet[L] {
type Out = Directive[join.Out]
def apply(underlying: Directive[L]) =
Directive[join.Out] { inner
underlying.tapply { prefix other.tapply { suffix inner(join(prefix, suffix)) } }
}(Tuple.yes) // we know that join will only ever produce tuples
}
implicit def fromStandardRoute[L](route: StandardRoute) =
new ConjunctionMagnet[L] {
type Out = StandardRoute
def apply(underlying: Directive[L]) = StandardRoute(underlying.tapply(_ route))
}
implicit def fromRouteGenerator[T, R <: Route](generator: T R) =
new ConjunctionMagnet[Unit] {
type Out = RouteGenerator[T]
def apply(underlying: Directive0) = value underlying.tapply(_ generator(value))
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import directives._
trait Directives extends RouteConcatenation
with BasicDirectives
with CacheConditionDirectives
with CookieDirectives
with DebuggingDirectives
with CodingDirectives
with ExecutionDirectives
with FileAndResourceDirectives
with FormFieldDirectives
with FutureDirectives
with HeaderDirectives
with HostDirectives
with MarshallingDirectives
with MethodDirectives
with MiscDirectives
with ParameterDirectives
with PathDirectives
with RangeDirectives
with RespondWithDirectives
with RouteDirectives
with SchemeDirectives
with SecurityDirectives
with WebsocketDirectives
object Directives extends Directives

View file

@ -0,0 +1,51 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import scala.util.control.NonFatal
import akka.http.scaladsl.model._
import StatusCodes._
trait ExceptionHandler extends ExceptionHandler.PF {
/**
* Creates a new [[ExceptionHandler]] which uses the given one as fallback for this one.
*/
def withFallback(that: ExceptionHandler): ExceptionHandler
/**
* "Seals" this handler by attaching a default handler as fallback if necessary.
*/
def seal(settings: RoutingSettings): ExceptionHandler
}
object ExceptionHandler {
type PF = PartialFunction[Throwable, Route]
implicit def apply(pf: PF): ExceptionHandler = apply(knownToBeSealed = false)(pf)
private def apply(knownToBeSealed: Boolean)(pf: PF): ExceptionHandler =
new ExceptionHandler {
def isDefinedAt(error: Throwable) = pf.isDefinedAt(error)
def apply(error: Throwable) = pf(error)
def withFallback(that: ExceptionHandler): ExceptionHandler =
if (!knownToBeSealed) ExceptionHandler(knownToBeSealed = false)(this orElse that) else this
def seal(settings: RoutingSettings): ExceptionHandler =
if (!knownToBeSealed) ExceptionHandler(knownToBeSealed = true)(this orElse default(settings)) else this
}
def default(settings: RoutingSettings): ExceptionHandler =
apply(knownToBeSealed = true) {
case IllegalRequestException(info, status) ctx {
ctx.log.warning("Illegal request {}\n\t{}\n\tCompleting with '{}' response",
ctx.request, info.formatPretty, status)
ctx.complete(status, info.format(settings.verboseErrorMessages))
}
case NonFatal(e) ctx {
ctx.log.error(e, "Error during processing of request {}", ctx.request)
ctx.complete(InternalServerError)
}
}
}

View file

@ -0,0 +1,475 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import java.util.UUID
import scala.util.matching.Regex
import scala.annotation.tailrec
import akka.http.scaladsl.server.util.Tuple
import akka.http.scaladsl.server.util.TupleOps._
import akka.http.scaladsl.common.NameOptionReceptacle
import akka.http.scaladsl.model.Uri.Path
import akka.http.impl.util._
/**
* A PathMatcher tries to match a prefix of a given string and returns either a PathMatcher.Matched instance
* if matched, otherwise PathMatcher.Unmatched.
*/
abstract class PathMatcher[L](implicit val ev: Tuple[L]) extends (Path PathMatcher.Matching[L]) { self
import PathMatcher._
def / : PathMatcher[L] = this ~ PathMatchers.Slash
def /[R](other: PathMatcher[R])(implicit join: Join[L, R]): PathMatcher[join.Out] =
this ~ PathMatchers.Slash ~ other
def |[R >: L: Tuple](other: PathMatcher[_ <: R]): PathMatcher[R] =
new PathMatcher[R] {
def apply(path: Path) = self(path) orElse other(path)
}
def ~[R](other: PathMatcher[R])(implicit join: Join[L, R]): PathMatcher[join.Out] = {
implicit def joinProducesTuple = Tuple.yes[join.Out]
transform(_.andThen((restL, valuesL) other(restL).map(join(valuesL, _))))
}
def unary_!(): PathMatcher0 =
new PathMatcher[Unit] {
def apply(path: Path) = if (self(path) eq Unmatched) Matched(path, ()) else Unmatched
}
def transform[R: Tuple](f: Matching[L] Matching[R]): PathMatcher[R] =
new PathMatcher[R] { def apply(path: Path) = f(self(path)) }
def tmap[R: Tuple](f: L R): PathMatcher[R] = transform(_.map(f))
def tflatMap[R: Tuple](f: L Option[R]): PathMatcher[R] = transform(_.flatMap(f))
/**
* Same as ``repeat(min = count, max = count)``.
*/
def repeat(count: Int)(implicit lift: PathMatcher.Lift[L, List]): PathMatcher[lift.Out] =
repeat(min = count, max = count)
/**
* Same as ``repeat(min = count, max = count, separator = separator)``.
*/
def repeat(count: Int, separator: PathMatcher0)(implicit lift: PathMatcher.Lift[L, List]): PathMatcher[lift.Out] =
repeat(min = count, max = count, separator = separator)
/**
* Turns this ``PathMatcher`` into one that matches a number of times (with the given separator)
* and potentially extracts a ``List`` of the underlying matcher's extractions.
* If less than ``min`` applications of the underlying matcher have succeeded the produced matcher fails,
* otherwise it matches up to the given ``max`` number of applications.
* Note that it won't fail even if more than ``max`` applications could succeed!
* The "surplus" path elements will simply be left unmatched.
*
* The result type depends on the type of the underlying matcher:
*
* <table>
* <th><td>If a ``matcher`` is of type</td><td>then ``matcher.repeat(...)`` is of type</td></th>
* <tr><td>``PathMatcher0``</td><td>``PathMatcher0``</td></tr>
* <tr><td>``PathMatcher1[T]``</td><td>``PathMatcher1[List[T]``</td></tr>
* <tr><td>``PathMatcher[L :Tuple]``</td><td>``PathMatcher[List[L]]``</td></tr>
* </table>
*/
def repeat(min: Int, max: Int, separator: PathMatcher0 = PathMatchers.Neutral)(implicit lift: PathMatcher.Lift[L, List]): PathMatcher[lift.Out] =
new PathMatcher[lift.Out]()(lift.OutIsTuple) {
require(min >= 0, "`min` must be >= 0")
require(max >= min, "`max` must be >= `min`")
def apply(path: Path) = rec(path, 1)
def rec(path: Path, count: Int): Matching[lift.Out] = {
def done = if (count >= min) Matched(path, lift()) else Unmatched
if (count <= max) {
self(path) match {
case Matched(remaining, extractions)
def done1 = if (count >= min) Matched(remaining, lift(extractions)) else Unmatched
separator(remaining) match {
case Matched(remaining2, _) rec(remaining2, count + 1) match {
case Matched(`remaining2`, _) done1 // we made no progress, so "go back" to before the separator
case Matched(rest, result) Matched(rest, lift(extractions, result))
case Unmatched Unmatched
}
case Unmatched done1
}
case Unmatched done
}
} else done
}
}
}
object PathMatcher extends ImplicitPathMatcherConstruction {
sealed abstract class Matching[+L: Tuple] {
def map[R: Tuple](f: L R): Matching[R]
def flatMap[R: Tuple](f: L Option[R]): Matching[R]
def andThen[R: Tuple](f: (Path, L) Matching[R]): Matching[R]
def orElse[R >: L](other: Matching[R]): Matching[R]
}
case class Matched[L: Tuple](pathRest: Path, extractions: L) extends Matching[L] {
def map[R: Tuple](f: L R) = Matched(pathRest, f(extractions))
def flatMap[R: Tuple](f: L Option[R]) = f(extractions) match {
case Some(valuesR) Matched(pathRest, valuesR)
case None Unmatched
}
def andThen[R: Tuple](f: (Path, L) Matching[R]) = f(pathRest, extractions)
def orElse[R >: L](other: Matching[R]) = this
}
object Matched { val Empty = Matched(Path.Empty, ()) }
case object Unmatched extends Matching[Nothing] {
def map[R: Tuple](f: Nothing R) = this
def flatMap[R: Tuple](f: Nothing Option[R]) = this
def andThen[R: Tuple](f: (Path, Nothing) Matching[R]) = this
def orElse[R](other: Matching[R]) = other
}
/**
* Creates a PathMatcher that always matches, consumes nothing and extracts the given Tuple of values.
*/
def provide[L: Tuple](extractions: L): PathMatcher[L] =
new PathMatcher[L] {
def apply(path: Path) = Matched(path, extractions)(ev)
}
/**
* Creates a PathMatcher that matches and consumes the given path prefix and extracts the given list of extractions.
* If the given prefix is empty the returned PathMatcher matches always and consumes nothing.
*/
def apply[L: Tuple](prefix: Path, extractions: L): PathMatcher[L] =
if (prefix.isEmpty) provide(extractions)
else new PathMatcher[L] {
def apply(path: Path) =
if (path startsWith prefix) Matched(path dropChars prefix.charCount, extractions)(ev)
else Unmatched
}
def apply[L](magnet: PathMatcher[L]): PathMatcher[L] = magnet
implicit class PathMatcher1Ops[T](matcher: PathMatcher1[T]) {
def map[R](f: T R): PathMatcher1[R] = matcher.tmap { case Tuple1(e) Tuple1(f(e)) }
def flatMap[R](f: T Option[R]): PathMatcher1[R] =
matcher.tflatMap { case Tuple1(e) f(e).map(x Tuple1(x)) }
}
implicit class EnhancedPathMatcher[L](underlying: PathMatcher[L]) {
def ?(implicit lift: PathMatcher.Lift[L, Option]): PathMatcher[lift.Out] =
new PathMatcher[lift.Out]()(lift.OutIsTuple) {
def apply(path: Path) = underlying(path) match {
case Matched(rest, extractions) Matched(rest, lift(extractions))
case Unmatched Matched(path, lift())
}
}
}
sealed trait Lift[L, M[+_]] {
type Out
def OutIsTuple: Tuple[Out]
def apply(): Out
def apply(value: L): Out
def apply(value: L, more: Out): Out
}
object Lift extends LowLevelLiftImplicits {
trait MOps[M[+_]] {
def apply(): M[Nothing]
def apply[T](value: T): M[T]
def apply[T](value: T, more: M[T]): M[T]
}
object MOps {
implicit object OptionMOps extends MOps[Option] {
def apply(): Option[Nothing] = None
def apply[T](value: T): Option[T] = Some(value)
def apply[T](value: T, more: Option[T]): Option[T] = Some(value)
}
implicit object ListMOps extends MOps[List] {
def apply(): List[Nothing] = Nil
def apply[T](value: T): List[T] = value :: Nil
def apply[T](value: T, more: List[T]): List[T] = value :: more
}
}
implicit def liftUnit[M[+_]] = new Lift[Unit, M] {
type Out = Unit
def OutIsTuple = implicitly[Tuple[Out]]
def apply() = ()
def apply(value: Unit) = value
def apply(value: Unit, more: Out) = value
}
implicit def liftSingleElement[A, M[+_]](implicit mops: MOps[M]) = new Lift[Tuple1[A], M] {
type Out = Tuple1[M[A]]
def OutIsTuple = implicitly[Tuple[Out]]
def apply() = Tuple1(mops())
def apply(value: Tuple1[A]) = Tuple1(mops(value._1))
def apply(value: Tuple1[A], more: Out) = Tuple1(mops(value._1, more._1))
}
}
trait LowLevelLiftImplicits {
import Lift._
implicit def default[T, M[+_]](implicit mops: MOps[M]) = new Lift[T, M] {
type Out = Tuple1[M[T]]
def OutIsTuple = implicitly[Tuple[Out]]
def apply() = Tuple1(mops())
def apply(value: T) = Tuple1(mops(value))
def apply(value: T, more: Out) = Tuple1(mops(value, more._1))
}
}
}
trait ImplicitPathMatcherConstruction {
import PathMatcher._
/**
* Creates a PathMatcher that consumes (a prefix of) the first path segment
* (if the path begins with a segment) and extracts a given value.
*/
implicit def stringExtractionPair2PathMatcher[T](tuple: (String, T)): PathMatcher1[T] =
PathMatcher(tuple._1 :: Path.Empty, Tuple1(tuple._2))
/**
* Creates a PathMatcher that consumes (a prefix of) the first path segment
* (if the path begins with a segment).
*/
implicit def segmentStringToPathMatcher(segment: String): PathMatcher0 =
PathMatcher(segment :: Path.Empty, ())
implicit def stringNameOptionReceptacle2PathMatcher(nr: NameOptionReceptacle[String]): PathMatcher0 =
PathMatcher(nr.name).?
/**
* Creates a PathMatcher that consumes (a prefix of) the first path segment
* if the path begins with a segment (a prefix of) which matches the given regex.
* Extracts either the complete match (if the regex doesn't contain a capture group) or
* the capture group (if the regex contains exactly one).
* If the regex contains more than one capture group the method throws an IllegalArgumentException.
*/
implicit def regex2PathMatcher(regex: Regex): PathMatcher1[String] = regex.groupCount match {
case 0 new PathMatcher1[String] {
def apply(path: Path) = path match {
case Path.Segment(segment, tail) regex findPrefixOf segment match {
case Some(m) Matched(segment.substring(m.length) :: tail, Tuple1(m))
case None Unmatched
}
case _ Unmatched
}
}
case 1 new PathMatcher1[String] {
def apply(path: Path) = path match {
case Path.Segment(segment, tail) regex findPrefixMatchOf segment match {
case Some(m) Matched(segment.substring(m.end) :: tail, Tuple1(m.group(1)))
case None Unmatched
}
case _ Unmatched
}
}
case _ throw new IllegalArgumentException("Path regex '" + regex.pattern.pattern +
"' must not contain more than one capturing group")
}
/**
* Creates a PathMatcher from the given Map of path segments (prefixes) to extracted values.
* If the unmatched path starts with a segment having one of the maps keys as a prefix
* the matcher consumes this path segment (prefix) and extracts the corresponding map value.
*/
implicit def valueMap2PathMatcher[T](valueMap: Map[String, T]): PathMatcher1[T] =
if (valueMap.isEmpty) PathMatchers.nothingMatcher
else valueMap.map { case (prefix, value) stringExtractionPair2PathMatcher(prefix, value) }.reduceLeft(_ | _)
}
trait PathMatchers {
import PathMatcher._
/**
* Converts a path string containing slashes into a PathMatcher that interprets slashes as
* path segment separators.
*/
def separateOnSlashes(string: String): PathMatcher0 = {
@tailrec def split(ix: Int = 0, matcher: PathMatcher0 = null): PathMatcher0 = {
val nextIx = string.indexOf('/', ix)
def append(m: PathMatcher0) = if (matcher eq null) m else matcher / m
if (nextIx < 0) append(string.substring(ix))
else split(nextIx + 1, append(string.substring(ix, nextIx)))
}
split()
}
/**
* A PathMatcher that matches a single slash character ('/').
*/
object Slash extends PathMatcher0 {
def apply(path: Path) = path match {
case Path.Slash(tail) Matched(tail, ())
case _ Unmatched
}
}
/**
* A PathMatcher that matches the very end of the requests URI path.
*/
object PathEnd extends PathMatcher0 {
def apply(path: Path) = path match {
case Path.Empty Matched.Empty
case _ Unmatched
}
}
/**
* A PathMatcher that matches and extracts the complete remaining,
* unmatched part of the request's URI path as an (encoded!) String.
* If you need access to the remaining unencoded elements of the path
* use the `RestPath` matcher!
*/
object Rest extends PathMatcher1[String] {
def apply(path: Path) = Matched(Path.Empty, Tuple1(path.toString))
}
/**
* A PathMatcher that matches and extracts the complete remaining,
* unmatched part of the request's URI path.
*/
object RestPath extends PathMatcher1[Path] {
def apply(path: Path) = Matched(Path.Empty, Tuple1(path))
}
/**
* A PathMatcher that efficiently matches a number of digits and extracts their (non-negative) Int value.
* The matcher will not match 0 digits or a sequence of digits that would represent an Int value larger
* than Int.MaxValue.
*/
object IntNumber extends NumberMatcher[Int](Int.MaxValue, 10) {
def fromChar(c: Char) = fromDecimalChar(c)
}
/**
* A PathMatcher that efficiently matches a number of digits and extracts their (non-negative) Long value.
* The matcher will not match 0 digits or a sequence of digits that would represent an Long value larger
* than Long.MaxValue.
*/
object LongNumber extends NumberMatcher[Long](Long.MaxValue, 10) {
def fromChar(c: Char) = fromDecimalChar(c)
}
/**
* A PathMatcher that efficiently matches a number of hex-digits and extracts their (non-negative) Int value.
* The matcher will not match 0 digits or a sequence of digits that would represent an Int value larger
* than Int.MaxValue.
*/
object HexIntNumber extends NumberMatcher[Int](Int.MaxValue, 16) {
def fromChar(c: Char) = fromHexChar(c)
}
/**
* A PathMatcher that efficiently matches a number of hex-digits and extracts their (non-negative) Long value.
* The matcher will not match 0 digits or a sequence of digits that would represent an Long value larger
* than Long.MaxValue.
*/
object HexLongNumber extends NumberMatcher[Long](Long.MaxValue, 16) {
def fromChar(c: Char) = fromHexChar(c)
}
// common implementation of Number matchers
abstract class NumberMatcher[@specialized(Int, Long) T](max: T, base: T)(implicit x: Integral[T])
extends PathMatcher1[T] {
import x._ // import implicit conversions for numeric operators
val minusOne = x.zero - x.one
val maxDivBase = max / base
def apply(path: Path) = path match {
case Path.Segment(segment, tail)
@tailrec def digits(ix: Int = 0, value: T = minusOne): Matching[Tuple1[T]] = {
val a = if (ix < segment.length) fromChar(segment charAt ix) else minusOne
if (a == minusOne) {
if (value == minusOne) Unmatched
else Matched(if (ix < segment.length) segment.substring(ix) :: tail else tail, Tuple1(value))
} else {
if (value == minusOne) digits(ix + 1, a)
else if (value <= maxDivBase && value * base <= max - a) // protect from overflow
digits(ix + 1, value * base + a)
else Unmatched
}
}
digits()
case _ Unmatched
}
def fromChar(c: Char): T
def fromDecimalChar(c: Char): T = if ('0' <= c && c <= '9') x.fromInt(c - '0') else minusOne
def fromHexChar(c: Char): T =
if ('0' <= c && c <= '9') x.fromInt(c - '0') else {
val cn = c | 0x20 // normalize to lowercase
if ('a' <= cn && cn <= 'f') x.fromInt(cn - 'a' + 10) else minusOne
}
}
/**
* A PathMatcher that matches and extracts a Double value. The matched string representation is the pure decimal,
* optionally signed form of a double value, i.e. without exponent.
*/
val DoubleNumber: PathMatcher1[Double] =
PathMatcher("""[+-]?\d*\.?\d*""".r) flatMap { string
try Some(java.lang.Double.parseDouble(string))
catch { case _: NumberFormatException None }
}
/**
* A PathMatcher that matches and extracts a java.util.UUID instance.
*/
val JavaUUID: PathMatcher1[UUID] =
PathMatcher("""[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}""".r) flatMap { string
try Some(UUID.fromString(string))
catch { case _: IllegalArgumentException None }
}
/**
* A PathMatcher that always matches, doesn't consume anything and extracts nothing.
* Serves mainly as a neutral element in PathMatcher composition.
*/
val Neutral: PathMatcher0 = PathMatcher.provide(())
/**
* A PathMatcher that matches if the unmatched path starts with a path segment.
* If so the path segment is extracted as a String.
*/
object Segment extends PathMatcher1[String] {
def apply(path: Path) = path match {
case Path.Segment(segment, tail) Matched(tail, Tuple1(segment))
case _ Unmatched
}
}
/**
* A PathMatcher that matches up to 128 remaining segments as a List[String].
* This can also be no segments resulting in the empty list.
* If the path has a trailing slash this slash will *not* be matched.
*/
val Segments: PathMatcher1[List[String]] = Segments(min = 0, max = 128)
/**
* A PathMatcher that matches the given number of path segments (separated by slashes) as a List[String].
* If there are more than ``count`` segments present the remaining ones will be left unmatched.
* If the path has a trailing slash this slash will *not* be matched.
*/
def Segments(count: Int): PathMatcher1[List[String]] = Segment.repeat(count, separator = Slash)
/**
* A PathMatcher that matches between ``min`` and ``max`` (both inclusively) path segments (separated by slashes)
* as a List[String]. If there are more than ``count`` segments present the remaining ones will be left unmatched.
* If the path has a trailing slash this slash will *not* be matched.
*/
def Segments(min: Int, max: Int): PathMatcher1[List[String]] = Segment.repeat(min, max, separator = Slash)
/**
* A PathMatcher that never matches anything.
*/
def nothingMatcher[L: Tuple]: PathMatcher[L] =
new PathMatcher[L] {
def apply(p: Path) = Unmatched
}
}
object PathMatchers extends PathMatchers

View file

@ -0,0 +1,204 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import scala.collection.immutable
import akka.http.scaladsl.model._
import headers._
/**
* A rejection encapsulates a specific reason why a Route was not able to handle a request. Rejections are gathered
* up over the course of a Route evaluation and finally converted to [[spray.http.HttpResponse]]s by the
* `handleRejections` directive, if there was no way for the request to be completed.
*/
trait Rejection
/**
* Rejection created by method filters.
* Signals that the request was rejected because the HTTP method is unsupported.
*/
case class MethodRejection(supported: HttpMethod) extends Rejection
/**
* Rejection created by scheme filters.
* Signals that the request was rejected because the Uri scheme is unsupported.
*/
case class SchemeRejection(supported: String) extends Rejection
/**
* Rejection created by parameter filters.
* Signals that the request was rejected because a query parameter was not found.
*/
case class MissingQueryParamRejection(parameterName: String) extends Rejection
/**
* Rejection created by parameter filters.
* Signals that the request was rejected because a query parameter could not be interpreted.
*/
case class MalformedQueryParamRejection(parameterName: String, errorMsg: String,
cause: Option[Throwable] = None) extends Rejection
/**
* Rejection created by form field filters.
* Signals that the request was rejected because a form field was not found.
*/
case class MissingFormFieldRejection(fieldName: String) extends Rejection
/**
* Rejection created by form field filters.
* Signals that the request was rejected because a form field could not be interpreted.
*/
case class MalformedFormFieldRejection(fieldName: String, errorMsg: String,
cause: Option[Throwable] = None) extends Rejection
/**
* Rejection created by header directives.
* Signals that the request was rejected because a required header could not be found.
*/
case class MissingHeaderRejection(headerName: String) extends Rejection
/**
* Rejection created by header directives.
* Signals that the request was rejected because a header value is malformed.
*/
case class MalformedHeaderRejection(headerName: String, errorMsg: String,
cause: Option[Throwable] = None) extends Rejection
/**
* Rejection created by unmarshallers.
* Signals that the request was rejected because the requests content-type is unsupported.
*/
case class UnsupportedRequestContentTypeRejection(supported: Set[ContentTypeRange]) extends Rejection
/**
* Rejection created by decoding filters.
* Signals that the request was rejected because the requests content encoding is unsupported.
*/
case class UnsupportedRequestEncodingRejection(supported: HttpEncoding) extends Rejection
/**
* Rejection created by range directives.
* Signals that the request was rejected because the requests contains only unsatisfiable ByteRanges.
* The actualEntityLength gives the client a hint to create satisfiable ByteRanges.
*/
case class UnsatisfiableRangeRejection(unsatisfiableRanges: Seq[ByteRange], actualEntityLength: Long) extends Rejection
/**
* Rejection created by range directives.
* Signals that the request contains too many ranges. An irregular high number of ranges
* indicates a broken client or a denial of service attack.
*/
case class TooManyRangesRejection(maxRanges: Int) extends Rejection
/**
* Rejection created by unmarshallers.
* Signals that the request was rejected because unmarshalling failed with an error that wasn't
* an `IllegalArgumentException`. Usually that means that the request content was not of the expected format.
* Note that semantic issues with the request content (e.g. because some parameter was out of range)
* will usually trigger a `ValidationRejection` instead.
*/
case class MalformedRequestContentRejection(message: String, cause: Option[Throwable] = None) extends Rejection
/**
* Rejection created by unmarshallers.
* Signals that the request was rejected because an message body entity was expected but not supplied.
*/
case object RequestEntityExpectedRejection extends Rejection
/**
* Rejection created by marshallers.
* Signals that the request was rejected because the service is not capable of producing a response entity whose
* content type is accepted by the client
*/
case class UnacceptedResponseContentTypeRejection(supported: Set[ContentType]) extends Rejection
/**
* Rejection created by encoding filters.
* Signals that the request was rejected because the service is not capable of producing a response entity whose
* content encoding is accepted by the client
*/
case class UnacceptedResponseEncodingRejection(supported: Set[HttpEncoding]) extends Rejection
object UnacceptedResponseEncodingRejection {
def apply(supported: HttpEncoding): UnacceptedResponseEncodingRejection = UnacceptedResponseEncodingRejection(Set(supported))
}
/**
* Rejection created by an [[akka.http.scaladsl.server.authentication.HttpAuthenticator]].
* Signals that the request was rejected because the user could not be authenticated. The reason for the rejection is
* specified in the cause.
*/
case class AuthenticationFailedRejection(cause: AuthenticationFailedRejection.Cause,
challenge: HttpChallenge) extends Rejection
object AuthenticationFailedRejection {
/**
* Signals the cause of the failed authentication.
*/
sealed trait Cause
/**
* Signals the cause of the rejecting was that the user could not be authenticated, because the `WWW-Authenticate`
* header was not supplied.
*/
case object CredentialsMissing extends Cause
/**
* Signals the cause of the rejecting was that the user could not be authenticated, because the supplied credentials
* are invalid.
*/
case object CredentialsRejected extends Cause
}
/**
* Rejection created by the 'authorize' directive.
* Signals that the request was rejected because the user is not authorized.
*/
case object AuthorizationFailedRejection extends Rejection
/**
* Rejection created by the `cookie` directive.
* Signals that the request was rejected because a cookie was not found.
*/
case class MissingCookieRejection(cookieName: String) extends Rejection
/**
* Rejection created when a websocket request was expected but none was found.
*/
case object ExpectedWebsocketRequestRejection extends Rejection
/**
* Rejection created by the `validation` directive as well as for `IllegalArgumentExceptions`
* thrown by domain model constructors (e.g. via `require`).
* It signals that an expected value was semantically invalid.
*/
case class ValidationRejection(message: String, cause: Option[Throwable] = None) extends Rejection
/**
* A special Rejection that serves as a container for a transformation function on rejections.
* It is used by some directives to "cancel" rejections that are added by later directives of a similar type.
*
* Consider this route structure for example:
*
* put { reject(ValidationRejection("no") } ~ get { ... }
*
* If this structure is applied to a PUT request the list of rejections coming back contains three elements:
*
* 1. A ValidationRejection
* 2. A MethodRejection
* 3. A TransformationRejection holding a function filtering out the MethodRejection
*
* so that in the end the RejectionHandler will only see one rejection (the ValidationRejection), because the
* MethodRejection added by the ``get`` directive is cancelled by the ``put`` directive (since the HTTP method
* did indeed match eventually).
*/
case class TransformationRejection(transform: immutable.Seq[Rejection] immutable.Seq[Rejection]) extends Rejection
/**
* A Throwable wrapping a Rejection.
* Can be used for marshalling `Future[T]` or `Try[T]` instances, whose failure side is supposed to trigger a route
* rejection rather than an Exception that is handled by the nearest ExceptionHandler.
* (Custom marshallers can of course use it as well.)
*/
case class RejectionError(rejection: Rejection) extends RuntimeException

View file

@ -0,0 +1,223 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import scala.annotation.tailrec
import scala.reflect.ClassTag
import scala.collection.immutable
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.model._
import StatusCodes._
import AuthenticationFailedRejection._
trait RejectionHandler extends (immutable.Seq[Rejection] Option[Route]) { self
import RejectionHandler._
/**
* Creates a new [[RejectionHandler]] which uses the given one as fallback for this one.
*/
def withFallback(that: RejectionHandler): RejectionHandler =
(this, that) match {
case (a: BuiltRejectionHandler, _) if a.isDefault this // the default handler already handles everything
case (a: BuiltRejectionHandler, b: BuiltRejectionHandler)
new BuiltRejectionHandler(a.cases ++ b.cases, a.notFound orElse b.notFound, b.isDefault)
case _ new RejectionHandler {
def apply(rejections: immutable.Seq[Rejection]): Option[Route] =
self(rejections) orElse that(rejections)
}
}
/**
* "Seals" this handler by attaching a default handler as fallback if necessary.
*/
def seal: RejectionHandler =
this match {
case x: BuiltRejectionHandler if x.isDefault x
case _ withFallback(default)
}
}
object RejectionHandler {
/**
* Creates a new [[RejectionHandler]] builder.
*/
def newBuilder(): Builder = new Builder(isDefault = false)
final class Builder private[RejectionHandler] (isDefault: Boolean) {
private[this] val cases = new immutable.VectorBuilder[Handler]
private[this] var notFound: Option[Route] = None
/**
* Handles a single [[Rejection]] with the given partial function.
*/
def handle(pf: PartialFunction[Rejection, Route]): this.type = {
cases += CaseHandler(pf)
this
}
/**
* Handles several Rejections of the same type at the same time.
* The seq passed to the given function is guaranteed to be non-empty.
*/
def handleAll[T <: Rejection: ClassTag](f: immutable.Seq[T] Route): this.type = {
val runtimeClass = implicitly[ClassTag[T]].runtimeClass
cases += TypeHandler[T](runtimeClass, f)
this
}
/**
* Handles the special "not found" case using the given [[Route]].
*/
def handleNotFound(route: Route): this.type = {
notFound = Some(route)
this
}
def result(): RejectionHandler =
new BuiltRejectionHandler(cases.result(), notFound, isDefault)
}
private sealed abstract class Handler
private final case class CaseHandler(pf: PartialFunction[Rejection, Route]) extends Handler
private final case class TypeHandler[T <: Rejection](
runtimeClass: Class[_], f: immutable.Seq[T] Route) extends Handler with PartialFunction[Rejection, T] {
def isDefinedAt(rejection: Rejection) = runtimeClass isInstance rejection
def apply(rejection: Rejection) = rejection.asInstanceOf[T]
}
private class BuiltRejectionHandler(val cases: Vector[Handler],
val notFound: Option[Route],
val isDefault: Boolean) extends RejectionHandler {
def apply(rejections: immutable.Seq[Rejection]): Option[Route] =
if (rejections.nonEmpty) {
@tailrec def rec(ix: Int): Option[Route] =
if (ix < cases.length) {
cases(ix) match {
case CaseHandler(pf)
val route = rejections collectFirst pf
if (route.isEmpty) rec(ix + 1) else route
case x @ TypeHandler(_, f)
val rejs = rejections collect x
if (rejs.isEmpty) rec(ix + 1) else Some(f(rejs))
}
} else None
rec(0)
} else notFound
}
import Directives._
/**
* Creates a new default [[RejectionHandler]] instance.
*/
def default =
newBuilder()
.handleAll[SchemeRejection] { rejections
val schemes = rejections.map(_.supported).mkString(", ")
complete(BadRequest, "Uri scheme not allowed, supported schemes: " + schemes)
}
.handleAll[MethodRejection] { rejections
val (methods, names) = rejections.map(r r.supported -> r.supported.name).unzip
complete(MethodNotAllowed, List(Allow(methods)), "HTTP method not allowed, supported methods: " + names.mkString(", "))
}
.handle {
case AuthorizationFailedRejection
complete(Forbidden, "The supplied authentication is not authorized to access this resource")
}
.handle {
case MalformedFormFieldRejection(name, msg, _)
complete(BadRequest, "The form field '" + name + "' was malformed:\n" + msg)
}
.handle {
case MalformedHeaderRejection(headerName, msg, _)
complete(BadRequest, s"The value of HTTP header '$headerName' was malformed:\n" + msg)
}
.handle {
case MalformedQueryParamRejection(name, msg, _)
complete(BadRequest, "The query parameter '" + name + "' was malformed:\n" + msg)
}
.handle {
case MalformedRequestContentRejection(msg, _)
complete(BadRequest, "The request content was malformed:\n" + msg)
}
.handle {
case MissingCookieRejection(cookieName)
complete(BadRequest, "Request is missing required cookie '" + cookieName + '\'')
}
.handle {
case MissingFormFieldRejection(fieldName)
complete(BadRequest, "Request is missing required form field '" + fieldName + '\'')
}
.handle {
case MissingHeaderRejection(headerName)
complete(BadRequest, "Request is missing required HTTP header '" + headerName + '\'')
}
.handle {
case MissingQueryParamRejection(paramName)
complete(NotFound, "Request is missing required query parameter '" + paramName + '\'')
}
.handle {
case RequestEntityExpectedRejection
complete(BadRequest, "Request entity expected but not supplied")
}
.handle {
case TooManyRangesRejection(_)
complete(RequestedRangeNotSatisfiable, "Request contains too many ranges.")
}
.handle {
case UnsatisfiableRangeRejection(unsatisfiableRanges, actualEntityLength)
complete(RequestedRangeNotSatisfiable, List(`Content-Range`(ContentRange.Unsatisfiable(actualEntityLength))),
unsatisfiableRanges.mkString("None of the following requested Ranges were satisfiable:\n", "\n", ""))
}
.handleAll[AuthenticationFailedRejection] { rejections
val rejectionMessage = rejections.head.cause match {
case CredentialsMissing "The resource requires authentication, which was not supplied with the request"
case CredentialsRejected "The supplied authentication is invalid"
}
// Multiple challenges per WWW-Authenticate header are allowed per spec,
// however, it seems many browsers will ignore all challenges but the first.
// Therefore, multiple WWW-Authenticate headers are rendered, instead.
//
// See https://code.google.com/p/chromium/issues/detail?id=103220
// and https://bugzilla.mozilla.org/show_bug.cgi?id=669675
val authenticateHeaders = rejections.map(r `WWW-Authenticate`(r.challenge))
complete(Unauthorized, authenticateHeaders, rejectionMessage)
}
.handleAll[UnacceptedResponseContentTypeRejection] { rejections
val supported = rejections.flatMap(_.supported)
complete(NotAcceptable, "Resource representation is only available with these Content-Types:\n" +
supported.map(_.value).mkString("\n"))
}
.handleAll[UnacceptedResponseEncodingRejection] { rejections
val supported = rejections.flatMap(_.supported)
complete(NotAcceptable, "Resource representation is only available with these Content-Encodings:\n" +
supported.map(_.value).mkString("\n"))
}
.handleAll[UnsupportedRequestContentTypeRejection] { rejections
val supported = rejections.flatMap(_.supported).mkString(" or ")
complete(UnsupportedMediaType, "The request's Content-Type is not supported. Expected:\n" + supported)
}
.handleAll[UnsupportedRequestEncodingRejection] { rejections
val supported = rejections.map(_.supported.value).mkString(" or ")
complete(BadRequest, "The request's Content-Encoding is not supported. Expected:\n" + supported)
}
.handle { case ExpectedWebsocketRequestRejection complete(BadRequest, "Expected Websocket Upgrade request") }
.handle { case ValidationRejection(msg, _) complete(BadRequest, msg) }
.handle { case x sys.error("Unhandled rejection: " + x) }
.handleNotFound { complete(NotFound, "The requested resource could not be found.") }
.result()
/**
* Filters out all TransformationRejections from the given sequence and applies them (in order) to the
* remaining rejections.
*/
def applyTransformations(rejections: immutable.Seq[Rejection]): immutable.Seq[Rejection] = {
val (transformations, rest) = rejections.partition(_.isInstanceOf[TransformationRejection])
(rest.distinct /: transformations.asInstanceOf[Seq[TransformationRejection]]) {
case (remaining, transformation) transformation.transform(remaining)
}
}
}

View file

@ -0,0 +1,115 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import scala.concurrent.{ Future, ExecutionContext }
import akka.stream.FlowMaterializer
import akka.event.LoggingAdapter
import akka.http.scaladsl.marshalling.ToResponseMarshallable
import akka.http.scaladsl.model._
/**
* Immutable object encapsulating the context of an [[akka.http.scaladsl.model.HttpRequest]]
* as it flows through a akka-http Route structure.
*/
trait RequestContext {
/** The request this context represents. Modelled as a ``val`` so as to enable an ``import ctx.request._``. */
val request: HttpRequest
/** The unmatched path of this context. Modelled as a ``val`` so as to enable an ``import ctx.unmatchedPath._``. */
val unmatchedPath: Uri.Path
/**
* The default ExecutionContext to be used for scheduling asynchronous logic related to this request.
*/
implicit def executionContext: ExecutionContext
/**
* The default FlowMaterializer.
*/
implicit def flowMaterializer: FlowMaterializer
/**
* The default LoggingAdapter to be used for logging messages related to this request.
*/
def log: LoggingAdapter
/**
* The default RoutingSettings to be used for configuring directives.
*/
def settings: RoutingSettings
/**
* Returns a copy of this context with the given fields updated.
*/
def reconfigure(
executionContext: ExecutionContext = executionContext,
flowMaterializer: FlowMaterializer = flowMaterializer,
log: LoggingAdapter = log,
settings: RoutingSettings = settings): RequestContext
/**
* Completes the request with the given ToResponseMarshallable.
*/
def complete(obj: ToResponseMarshallable): Future[RouteResult]
/**
* Rejects the request with the given rejections.
*/
def reject(rejections: Rejection*): Future[RouteResult]
/**
* Bubbles the given error up the response chain where it is dealt with by the closest `handleExceptions`
* directive and its ``ExceptionHandler``, unless the error is a ``RejectionError``. In this case the
* wrapped rejection is unpacked and "executed".
*/
def fail(error: Throwable): Future[RouteResult]
/**
* Returns a copy of this context with the new HttpRequest.
*/
def withRequest(req: HttpRequest): RequestContext
/**
* Returns a copy of this context with the new HttpRequest.
*/
def withExecutionContext(ec: ExecutionContext): RequestContext
/**
* Returns a copy of this context with the new HttpRequest.
*/
def withFlowMaterializer(materializer: FlowMaterializer): RequestContext
/**
* Returns a copy of this context with the new LoggingAdapter.
*/
def withLog(log: LoggingAdapter): RequestContext
/**
* Returns a copy of this context with the new RoutingSettings.
*/
def withSettings(settings: RoutingSettings): RequestContext
/**
* Returns a copy of this context with the HttpRequest transformed by the given function.
*/
def mapRequest(f: HttpRequest HttpRequest): RequestContext
/**
* Returns a copy of this context with the unmatched path updated to the given one.
*/
def withUnmatchedPath(path: Uri.Path): RequestContext
/**
* Returns a copy of this context with the unmatchedPath transformed by the given function.
*/
def mapUnmatchedPath(f: Uri.Path Uri.Path): RequestContext
/**
* Removes a potentially existing Accept header from the request headers.
*/
def withAcceptAll: RequestContext
}

View file

@ -0,0 +1,91 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import scala.concurrent.{ Future, ExecutionContext }
import akka.stream.FlowMaterializer
import akka.event.LoggingAdapter
import akka.http.scaladsl.marshalling.{ Marshal, ToResponseMarshallable }
import akka.http.scaladsl.model._
import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.util.FastFuture._
/**
* INTERNAL API
*/
private[http] class RequestContextImpl(
val request: HttpRequest,
val unmatchedPath: Uri.Path,
val executionContext: ExecutionContext,
val flowMaterializer: FlowMaterializer,
val log: LoggingAdapter,
val settings: RoutingSettings) extends RequestContext {
def this(request: HttpRequest, log: LoggingAdapter, settings: RoutingSettings)(implicit ec: ExecutionContext, materializer: FlowMaterializer) =
this(request, request.uri.path, ec, materializer, log, settings)
def reconfigure(executionContext: ExecutionContext, flowMaterializer: FlowMaterializer, log: LoggingAdapter, settings: RoutingSettings): RequestContext =
copy(executionContext = executionContext, flowMaterializer = flowMaterializer, log = log, settings = settings)
override def complete(trm: ToResponseMarshallable): Future[RouteResult] =
trm(request)(executionContext)
.fast.map(res RouteResult.Complete(res))(executionContext)
.fast.recover {
case Marshal.UnacceptableResponseContentTypeException(supported)
RouteResult.Rejected(UnacceptedResponseContentTypeRejection(supported) :: Nil)
case RejectionError(rej) RouteResult.Rejected(rej :: Nil)
}(executionContext)
override def reject(rejections: Rejection*): Future[RouteResult] =
FastFuture.successful(RouteResult.Rejected(rejections.toList))
override def fail(error: Throwable): Future[RouteResult] =
FastFuture.failed(error)
override def withRequest(request: HttpRequest): RequestContext =
if (request != this.request) copy(request = request) else this
override def withExecutionContext(executionContext: ExecutionContext): RequestContext =
if (executionContext != this.executionContext) copy(executionContext = executionContext) else this
override def withFlowMaterializer(flowMaterializer: FlowMaterializer): RequestContext =
if (flowMaterializer != this.flowMaterializer) copy(flowMaterializer = flowMaterializer) else this
override def withLog(log: LoggingAdapter): RequestContext =
if (log != this.log) copy(log = log) else this
override def withSettings(settings: RoutingSettings): RequestContext =
if (settings != this.settings) copy(settings = settings) else this
override def mapRequest(f: HttpRequest HttpRequest): RequestContext =
copy(request = f(request))
override def withUnmatchedPath(path: Uri.Path): RequestContext =
if (path != unmatchedPath) copy(unmatchedPath = path) else this
override def mapUnmatchedPath(f: Uri.Path Uri.Path): RequestContext =
copy(unmatchedPath = f(unmatchedPath))
override def withAcceptAll: RequestContext = request.header[headers.Accept] match {
case Some(accept @ headers.Accept(ranges)) if !accept.acceptsAll
mapRequest(_.mapHeaders(_.map {
case `accept`
val acceptAll =
if (ranges.exists(_.isWildcard)) ranges.map(r if (r.isWildcard) MediaRanges.`*/*;q=MIN` else r)
else ranges :+ MediaRanges.`*/*;q=MIN`
accept.copy(mediaRanges = acceptAll)
case x x
}))
case _ this
}
private def copy(request: HttpRequest = request,
unmatchedPath: Uri.Path = unmatchedPath,
executionContext: ExecutionContext = executionContext,
flowMaterializer: FlowMaterializer = flowMaterializer,
log: LoggingAdapter = log,
settings: RoutingSettings = settings) =
new RequestContextImpl(request, unmatchedPath, executionContext, flowMaterializer, log, settings)
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import scala.concurrent.Future
import akka.stream.scaladsl.Flow
import akka.http.scaladsl.model.{ HttpRequest, HttpResponse }
import akka.http.scaladsl.util.FastFuture._
object Route {
/**
* Helper for constructing a Route from a function literal.
*/
def apply(f: Route): Route = f
/**
* "Seals" a route by wrapping it with exception handling and rejection conversion.
*/
def seal(route: Route)(implicit setup: RoutingSetup): Route = {
import directives.ExecutionDirectives._
import setup._
handleExceptions(exceptionHandler.seal(setup.settings)) {
handleRejections(rejectionHandler.seal) {
route
}
}
}
/**
* Turns a `Route` into a server flow.
*/
def handlerFlow(route: Route)(implicit setup: RoutingSetup): Flow[HttpRequest, HttpResponse, Unit] =
Flow[HttpRequest].mapAsync(1)(asyncHandler(route))
/**
* Turns a `Route` into an async handler function.
*/
def asyncHandler(route: Route)(implicit setup: RoutingSetup): HttpRequest Future[HttpResponse] = {
import setup._
val sealedRoute = seal(route)
request
sealedRoute(new RequestContextImpl(request, routingLog.requestLog(request), setup.settings)).fast.map {
case RouteResult.Complete(response) response
case RouteResult.Rejected(rejected) throw new IllegalStateException(s"Unhandled rejections '$rejected', unsealed RejectionHandler?!")
}
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.util.FastFuture._
trait RouteConcatenation {
implicit def enhanceRouteWithConcatenation(route: Route) =
new RouteConcatenation.RouteWithConcatenation(route: Route)
}
object RouteConcatenation extends RouteConcatenation {
class RouteWithConcatenation(route: Route) {
/**
* Returns a Route that chains two Routes. If the first Route rejects the request the second route is given a
* chance to act upon the request.
*/
def ~(other: Route): Route = { ctx
import ctx.executionContext
route(ctx).fast.flatMap {
case x: RouteResult.Complete FastFuture.successful(x)
case RouteResult.Rejected(outerRejections)
other(ctx).fast.map {
case x: RouteResult.Complete x
case RouteResult.Rejected(innerRejections) RouteResult.Rejected(outerRejections ++ innerRejections)
}
}
}
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import scala.collection.immutable
import akka.stream.scaladsl.Flow
import akka.http.scaladsl.model.{ HttpRequest, HttpResponse }
/**
* The result of handling a request.
*
* As a user you typically don't create RouteResult instances directly.
* Instead, use the methods on the [[RequestContext]] to achieve the desired effect.
*/
sealed trait RouteResult
object RouteResult {
final case class Complete(response: HttpResponse) extends RouteResult
final case class Rejected(rejections: immutable.Seq[Rejection]) extends RouteResult
implicit def route2HandlerFlow(route: Route)(implicit setup: RoutingSetup): Flow[HttpRequest, HttpResponse, Unit] =
Route.handlerFlow(route)
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import com.typesafe.config.Config
import akka.actor.ActorRefFactory
import akka.http.impl.util._
case class RoutingSettings(
verboseErrorMessages: Boolean,
fileGetConditional: Boolean,
renderVanityFooter: Boolean,
rangeCountLimit: Int,
rangeCoalescingThreshold: Long,
decodeMaxBytesPerChunk: Int,
fileIODispatcher: String)
object RoutingSettings extends SettingsCompanion[RoutingSettings]("akka.http.routing") {
def fromSubConfig(c: Config) = apply(
c getBoolean "verbose-error-messages",
c getBoolean "file-get-conditional",
c getBoolean "render-vanity-footer",
c getInt "range-count-limit",
c getBytes "range-coalescing-threshold",
c getIntBytes "decode-max-bytes-per-chunk",
c getString "file-io-dispatcher")
implicit def default(implicit refFactory: ActorRefFactory) =
apply(actorSystem)
}

View file

@ -0,0 +1,77 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import scala.concurrent.ExecutionContext
import akka.event.LoggingAdapter
import akka.actor.{ ActorSystem, ActorContext }
import akka.stream.FlowMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpRequest
/**
* Provides a ``RoutingSetup`` for a given connection.
*/
trait RoutingSetupProvider {
def apply(connection: Http.IncomingConnection): RoutingSetup
}
object RoutingSetupProvider {
def apply(f: Http.IncomingConnection RoutingSetup): RoutingSetupProvider =
new RoutingSetupProvider {
def apply(connection: Http.IncomingConnection) = f(connection)
}
implicit def default(implicit setup: RoutingSetup) = RoutingSetupProvider(_ setup)
}
/**
* Provides all dependencies required for route execution.
*/
class RoutingSetup(
val settings: RoutingSettings,
val exceptionHandler: ExceptionHandler,
val rejectionHandler: RejectionHandler,
val executionContext: ExecutionContext,
val flowMaterializer: FlowMaterializer,
val routingLog: RoutingLog) {
// enable `import setup._` to properly bring implicits in scope
implicit def executor: ExecutionContext = executionContext
implicit def materializer: FlowMaterializer = flowMaterializer
}
object RoutingSetup {
implicit def apply(implicit routingSettings: RoutingSettings,
exceptionHandler: ExceptionHandler = null,
rejectionHandler: RejectionHandler = null,
executionContext: ExecutionContext = null,
flowMaterializer: FlowMaterializer,
routingLog: RoutingLog): RoutingSetup =
new RoutingSetup(
routingSettings,
if (exceptionHandler ne null) exceptionHandler else ExceptionHandler.default(routingSettings),
if (rejectionHandler ne null) rejectionHandler else RejectionHandler.default,
if (executionContext ne null) executionContext else flowMaterializer.executionContext,
flowMaterializer,
routingLog)
}
trait RoutingLog {
def log: LoggingAdapter
def requestLog(request: HttpRequest): LoggingAdapter
}
object RoutingLog extends LowerPriorityRoutingLogImplicits {
def apply(defaultLog: LoggingAdapter): RoutingLog =
new RoutingLog {
def log = defaultLog
def requestLog(request: HttpRequest) = defaultLog
}
implicit def fromActorContext(implicit ac: ActorContext): RoutingLog = RoutingLog(ac.system.log)
}
sealed abstract class LowerPriorityRoutingLogImplicits {
implicit def fromActorSystem(implicit system: ActorSystem): RoutingLog = RoutingLog(system.log)
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import akka.http.scaladsl.server.util.Tuple
/**
* A Route that can be implicitly converted into a Directive (fitting any signature).
*/
abstract class StandardRoute extends Route {
def toDirective[L: Tuple]: Directive[L] = StandardRoute.toDirective(this)
}
object StandardRoute {
def apply(route: Route): StandardRoute = route match {
case x: StandardRoute x
case x new StandardRoute { def apply(ctx: RequestContext) = x(ctx) }
}
/**
* Converts the StandardRoute into a directive that never passes the request to its inner route
* (and always returns its underlying route).
*/
implicit def toDirective[L: Tuple](route: StandardRoute) = Directive[L] { _ route }
}

View file

@ -0,0 +1,199 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import scala.concurrent.{ Future, ExecutionContext }
import scala.collection.immutable
import akka.event.LoggingAdapter
import akka.stream.FlowMaterializer
import akka.http.scaladsl.server.util.Tuple
import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.model._
import akka.http.scaladsl.util.FastFuture._
trait BasicDirectives {
def mapInnerRoute(f: Route Route): Directive0 =
Directive { inner f(inner(())) }
def mapRequestContext(f: RequestContext RequestContext): Directive0 =
mapInnerRoute { inner ctx inner(f(ctx)) }
def mapRequest(f: HttpRequest HttpRequest): Directive0 =
mapRequestContext(_ mapRequest f)
def mapRouteResultFuture(f: Future[RouteResult] Future[RouteResult]): Directive0 =
Directive { inner ctx f(inner(())(ctx)) }
def mapRouteResult(f: RouteResult RouteResult): Directive0 =
Directive { inner ctx inner(())(ctx).fast.map(f)(ctx.executionContext) }
def mapRouteResultWith(f: RouteResult Future[RouteResult]): Directive0 =
Directive { inner ctx inner(())(ctx).fast.flatMap(f)(ctx.executionContext) }
def mapRouteResultPF(f: PartialFunction[RouteResult, RouteResult]): Directive0 =
mapRouteResult(f.applyOrElse(_, akka.http.impl.util.identityFunc[RouteResult]))
def mapRouteResultWithPF(f: PartialFunction[RouteResult, Future[RouteResult]]): Directive0 =
mapRouteResultWith(f.applyOrElse(_, FastFuture.successful[RouteResult]))
def recoverRejections(f: immutable.Seq[Rejection] RouteResult): Directive0 =
mapRouteResultPF { case RouteResult.Rejected(rejections) f(rejections) }
def recoverRejectionsWith(f: immutable.Seq[Rejection] Future[RouteResult]): Directive0 =
mapRouteResultWithPF { case RouteResult.Rejected(rejections) f(rejections) }
def mapRejections(f: immutable.Seq[Rejection] immutable.Seq[Rejection]): Directive0 =
recoverRejections(rejections RouteResult.Rejected(f(rejections)))
def mapResponse(f: HttpResponse HttpResponse): Directive0 =
mapRouteResultPF { case RouteResult.Complete(response) RouteResult.Complete(f(response)) }
def mapResponseEntity(f: ResponseEntity ResponseEntity): Directive0 =
mapResponse(_ mapEntity f)
def mapResponseHeaders(f: immutable.Seq[HttpHeader] immutable.Seq[HttpHeader]): Directive0 =
mapResponse(_ mapHeaders f)
/**
* A Directive0 that always passes the request on to its inner route
* (i.e. does nothing with the request or the response).
*/
def pass: Directive0 = Directive.Empty
/**
* Injects the given value into a directive.
*/
def provide[T](value: T): Directive1[T] = tprovide(Tuple1(value))
/**
* Injects the given values into a directive.
*/
def tprovide[L: Tuple](values: L): Directive[L] =
Directive { _(values) }
/**
* Extracts a single value using the given function.
*/
def extract[T](f: RequestContext T): Directive1[T] =
textract(ctx Tuple1(f(ctx)))
/**
* Extracts a number of values using the given function.
*/
def textract[L: Tuple](f: RequestContext L): Directive[L] =
Directive { inner ctx inner(f(ctx))(ctx) }
/**
* 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 =
cancelRejections(_ == rejection)
/**
* Adds a TransformationRejection cancelling all rejections of one of the given classes
* to the list of rejections potentially coming back from the inner route.
*/
def cancelRejections(classes: Class[_]*): Directive0 =
cancelRejections(r classes.exists(_ isInstance r))
/**
* 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 cancelRejections(cancelFilter: Rejection Boolean): Directive0 =
mapRejections(_ :+ TransformationRejection(_ filterNot cancelFilter))
/**
* Transforms the unmatchedPath of the RequestContext using the given function.
*/
def mapUnmatchedPath(f: Uri.Path Uri.Path): Directive0 =
mapRequestContext(_ mapUnmatchedPath f)
/**
* Extracts the unmatched path from the RequestContext.
*/
def extractUnmatchedPath: Directive1[Uri.Path] = BasicDirectives._extractUnmatchedPath
/**
* Extracts the complete request.
*/
def extractRequest: Directive1[HttpRequest] = BasicDirectives._extractRequest
/**
* Extracts the complete request URI.
*/
def extractUri: Directive1[Uri] = BasicDirectives._extractUri
/**
* Runs its inner route with the given alternative [[ExecutionContext]].
*/
def withExecutionContext(ec: ExecutionContext): Directive0 =
mapRequestContext(_ withExecutionContext ec)
/**
* Extracts the [[ExecutionContext]] from the [[RequestContext]].
*/
def extractExecutionContext: Directive1[ExecutionContext] = BasicDirectives._extractExecutionContext
/**
* Runs its inner route with the given alternative [[FlowMaterializer]].
*/
def withFlowMaterializer(materializer: FlowMaterializer): Directive0 =
mapRequestContext(_ withFlowMaterializer materializer)
/**
* Extracts the [[FlowMaterializer]] from the [[RequestContext]].
*/
def extractFlowMaterializer: Directive1[FlowMaterializer] = BasicDirectives._extractFlowMaterializer
/**
* Runs its inner route with the given alternative [[LoggingAdapter]].
*/
def withLog(log: LoggingAdapter): Directive0 =
mapRequestContext(_ withLog log)
/**
* Extracts the [[LoggingAdapter]] from the [[RequestContext]].
*/
def extractLog: Directive1[LoggingAdapter] =
BasicDirectives._extractLog
/**
* Runs its inner route with the given alternative [[RoutingSettings]].
*/
def withSettings(settings: RoutingSettings): Directive0 =
mapRequestContext(_ withSettings settings)
/**
* Runs the inner route with settings mapped by the given function.
*/
def mapSettings(f: RoutingSettings RoutingSettings): Directive0 =
mapRequestContext(ctx ctx.withSettings(f(ctx.settings)))
/**
* Extracts the [[RoutingSettings]] from the [[RequestContext]].
*/
def extractSettings: Directive1[RoutingSettings] =
BasicDirectives._extractSettings
/**
* Extracts the [[RequestContext]] itself.
*/
def extractRequestContext: Directive1[RequestContext] = BasicDirectives._extractRequestContext
}
object BasicDirectives extends BasicDirectives {
private val _extractUnmatchedPath: Directive1[Uri.Path] = extract(_.unmatchedPath)
private val _extractRequest: Directive1[HttpRequest] = extract(_.request)
private val _extractUri: Directive1[Uri] = extract(_.request.uri)
private val _extractExecutionContext: Directive1[ExecutionContext] = extract(_.executionContext)
private val _extractFlowMaterializer: Directive1[FlowMaterializer] = extract(_.flowMaterializer)
private val _extractLog: Directive1[LoggingAdapter] = extract(_.log)
private val _extractSettings: Directive1[RoutingSettings] = extract(_.settings)
private val _extractRequestContext: Directive1[RequestContext] = extract(akka.http.impl.util.identityFunc)
}

View file

@ -0,0 +1,131 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.DateTime
import headers._
import HttpMethods._
import StatusCodes._
import EntityTag._
trait CacheConditionDirectives {
import BasicDirectives._
import RouteDirectives._
/**
* Wraps its inner route with support for Conditional Requests as defined
* by http://tools.ietf.org/html/rfc7232
*
* In particular the algorithm defined by http://tools.ietf.org/html/rfc7232#section-6
* is implemented by this directive.
*
* Note: if you want to combine this directive with `withRangeSupport(...)` you need to put
* it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)`
* must be on a deeper level in your route structure in order to function correctly.
*/
def conditional(eTag: EntityTag): Directive0 = conditional(Some(eTag), None)
/**
* Wraps its inner route with support for Conditional Requests as defined
* by http://tools.ietf.org/html/rfc7232
*
* In particular the algorithm defined by http://tools.ietf.org/html/rfc7232#section-6
* is implemented by this directive.
*
* Note: if you want to combine this directive with `withRangeSupport(...)` you need to put
* it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)`
* must be on a deeper level in your route structure in order to function correctly.
*/
def conditional(lastModified: DateTime): Directive0 = conditional(None, Some(lastModified))
/**
* Wraps its inner route with support for Conditional Requests as defined
* by http://tools.ietf.org/html/rfc7232
*
* In particular the algorithm defined by http://tools.ietf.org/html/rfc7232#section-6
* is implemented by this directive.
*
* Note: if you want to combine this directive with `withRangeSupport(...)` you need to put
* it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)`
* must be on a deeper level in your route structure in order to function correctly.
*/
def conditional(eTag: EntityTag, lastModified: DateTime): Directive0 = conditional(Some(eTag), Some(lastModified))
/**
* Wraps its inner route with support for Conditional Requests as defined
* by http://tools.ietf.org/html/rfc7232
*
* In particular the algorithm defined by http://tools.ietf.org/html/rfc7232#section-6
* is implemented by this directive.
*
* Note: if you want to combine this directive with `withRangeSupport(...)` you need to put
* it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)`
* must be on a deeper level in your route structure in order to function correctly.
*/
def conditional(eTag: Option[EntityTag], lastModified: Option[DateTime]): Directive0 = {
def addResponseHeaders: Directive0 =
mapResponse(_.withDefaultHeaders(eTag.map(ETag(_)).toList ++ lastModified.map(`Last-Modified`(_)).toList))
// TODO: also handle Cache-Control and Vary
def complete304(): Route = addResponseHeaders(complete(HttpResponse(NotModified)))
def complete412(): Route = _.complete(PreconditionFailed)
extractRequest.flatMap { request
import request._
mapInnerRoute { route
def innerRouteWithRangeHeaderFilteredOut: Route =
(mapRequest(_.mapHeaders(_.filterNot(_.isInstanceOf[Range]))) &
addResponseHeaders)(route)
def isGetOrHead = method == HEAD || method == GET
def unmodified(ifModifiedSince: DateTime) =
lastModified.get <= ifModifiedSince && ifModifiedSince.clicks < System.currentTimeMillis()
def step1(): Route =
header[`If-Match`] match {
case Some(`If-Match`(im)) if eTag.isDefined
if (matchesRange(eTag.get, im, weakComparison = false)) step3() else complete412()
case None step2()
}
def step2(): Route =
header[`If-Unmodified-Since`] match {
case Some(`If-Unmodified-Since`(ius)) if lastModified.isDefined && !unmodified(ius) complete412()
case _ step3()
}
def step3(): Route =
header[`If-None-Match`] match {
case Some(`If-None-Match`(inm)) if eTag.isDefined
if (!matchesRange(eTag.get, inm, weakComparison = true)) step5()
else if (isGetOrHead) complete304() else complete412()
case None step4()
}
def step4(): Route =
if (isGetOrHead) {
header[`If-Modified-Since`] match {
case Some(`If-Modified-Since`(ims)) if lastModified.isDefined && unmodified(ims) complete304()
case _ step5()
}
} else step5()
def step5(): Route =
if (method == GET && header[Range].isDefined)
header[`If-Range`] match {
case Some(`If-Range`(Left(tag))) if eTag.isDefined && !matches(eTag.get, tag, weakComparison = false)
innerRouteWithRangeHeaderFilteredOut
case Some(`If-Range`(Right(ims))) if lastModified.isDefined && !unmodified(ims)
innerRouteWithRangeHeaderFilteredOut
case _ step6()
}
else step6()
def step6(): Route = addResponseHeaders(route)
step1()
}
}
}
}
object CacheConditionDirectives extends CacheConditionDirectives

View file

@ -0,0 +1,149 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import scala.annotation.tailrec
import scala.collection.immutable
import scala.util.control.NonFatal
import akka.http.scaladsl.model.headers.{ HttpEncodings, `Accept-Encoding`, HttpEncoding, HttpEncodingRange }
import akka.http.scaladsl.model._
import akka.http.scaladsl.coding._
import akka.http.impl.util._
trait CodingDirectives {
import BasicDirectives._
import MiscDirectives._
import RouteDirectives._
import CodingDirectives._
// encoding
/**
* Rejects the request with an UnacceptedResponseEncodingRejection
* if the given encoding is not accepted for the response.
*/
def responseEncodingAccepted(encoding: HttpEncoding): Directive0 =
extract(_.request.isEncodingAccepted(encoding))
.flatMap(if (_) pass else reject(UnacceptedResponseEncodingRejection(Set(encoding))))
/**
* Encodes the response with the encoding that is requested by the client with the `Accept-
* Encoding` header. The response encoding is determined by the rules specified in
* http://tools.ietf.org/html/rfc7231#section-5.3.4.
*
* If the `Accept-Encoding` header is missing or empty or specifies an encoding other than
* identity, gzip or deflate then no encoding is used.
*/
def encodeResponse: Directive0 =
encodeResponseWith(NoCoding, Gzip, Deflate)
/**
* Encodes the response with the encoding that is requested by the client with the `Accept-
* Encoding` header. The response encoding is determined by the rules specified in
* http://tools.ietf.org/html/rfc7231#section-5.3.4.
*
* If the `Accept-Encoding` header is missing then the response is encoded using the `first`
* encoder.
*
* If the `Accept-Encoding` header is empty and `NoCoding` is part of the encoders then no
* response encoding is used. Otherwise the request is rejected.
*/
def encodeResponseWith(first: Encoder, more: Encoder*): Directive0 =
_encodeResponse(immutable.Seq(first +: more: _*))
// decoding
/**
* Decodes the incoming request using the given Decoder.
* If the request encoding doesn't match the request is rejected with an `UnsupportedRequestEncodingRejection`.
*/
def decodeRequestWith(decoder: Decoder): Directive0 = {
def applyDecoder =
extractSettings flatMap { settings
val effectiveDecoder = decoder.withMaxBytesPerChunk(settings.decodeMaxBytesPerChunk)
mapRequest { request
effectiveDecoder.decode(request).mapEntity(StreamUtils.mapEntityError {
case NonFatal(e)
IllegalRequestException(
StatusCodes.BadRequest,
ErrorInfo("The request's encoding is corrupt", e.getMessage))
})
}
}
requestEntityEmpty | (
requestEncodedWith(decoder.encoding) &
applyDecoder &
cancelRejections(classOf[UnsupportedRequestEncodingRejection]))
}
/**
* Rejects the request with an UnsupportedRequestEncodingRejection if its encoding doesn't match the given one.
*/
def requestEncodedWith(encoding: HttpEncoding): Directive0 =
extract(_.request.encoding).flatMap {
case `encoding` pass
case _ reject(UnsupportedRequestEncodingRejection(encoding))
}
/**
* Decodes the incoming request if it is encoded with one of the given
* encoders. If the request encoding doesn't match one of the given encoders
* the request is rejected with an `UnsupportedRequestEncodingRejection`.
* If no decoders are given the default encoders (``Gzip``, ``Deflate``, ``NoCoding``) are used.
*/
def decodeRequestWith(decoders: Decoder*): Directive0 =
theseOrDefault(decoders).map(decodeRequestWith).reduce(_ | _)
/**
* Decompresses the incoming request if it is ``gzip`` or ``deflate`` compressed.
* Uncompressed requests are passed through untouched.
* If the request encoded with another encoding the request is rejected with an `UnsupportedRequestEncodingRejection`.
*/
def decodeRequest: Directive0 =
decodeRequestWith(DefaultCoders: _*)
}
object CodingDirectives extends CodingDirectives {
val DefaultCoders: immutable.Seq[Coder] = immutable.Seq(Gzip, Deflate, NoCoding)
def theseOrDefault[T >: Coder](these: Seq[T]): Seq[T] = if (these.isEmpty) DefaultCoders else these
import BasicDirectives._
import HeaderDirectives._
import RouteDirectives._
private def _encodeResponse(encoders: immutable.Seq[Encoder]): Directive0 =
optionalHeaderValueByType(classOf[`Accept-Encoding`]) flatMap { accept
val acceptedEncoder = accept match {
case None
// use first defined encoder when Accept-Encoding is missing
encoders.headOption
case Some(`Accept-Encoding`(encodings))
// provide fallback to identity
val withIdentity =
if (encodings.exists {
case HttpEncodingRange.One(HttpEncodings.identity, _) true
case _ false
}) encodings
else encodings :+ HttpEncodings.`identity;q=MIN`
// sort client-accepted encodings by q-Value (and orig. order) and find first matching encoder
@tailrec def find(encodings: List[HttpEncodingRange]): Option[Encoder] = encodings match {
case encoding :: rest
encoders.find(e encoding.matches(e.encoding)) match {
case None find(rest)
case x x
}
case _ None
}
find(withIdentity.sortBy(e (-e.qValue, withIdentity.indexOf(e))).toList)
}
acceptedEncoder match {
case Some(encoder) mapResponse(encoder.encode(_))
case _ reject(UnacceptedResponseEncodingRejection(encoders.map(_.encoding).toSet))
}
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import akka.http.scaladsl.model._
import headers._
import akka.http.impl.util._
trait CookieDirectives {
import HeaderDirectives._
import RespondWithDirectives._
import RouteDirectives._
/**
* Extracts an HttpCookie with the given name. If the cookie is not present the
* request is rejected with a respective [[MissingCookieRejection]].
*/
def cookie(name: String): Directive1[HttpCookie] =
headerValue(findCookie(name)) | reject(MissingCookieRejection(name))
/**
* Extracts an HttpCookie with the given name.
* If the cookie is not present a value of `None` is extracted.
*/
def optionalCookie(name: String): Directive1[Option[HttpCookie]] =
optionalHeaderValue(findCookie(name))
private def findCookie(name: String): HttpHeader Option[HttpCookie] = {
case Cookie(cookies) cookies.find(_.name == name)
case _ None
}
/**
* Adds a Set-Cookie header with the given cookies to all responses of its inner route.
*/
def setCookie(first: HttpCookie, more: HttpCookie*): Directive0 =
respondWithHeaders((first :: more.toList).map(`Set-Cookie`(_)))
/**
* Adds a Set-Cookie header expiring the given cookies to all responses of its inner route.
*/
def deleteCookie(first: HttpCookie, more: HttpCookie*): Directive0 =
respondWithHeaders((first :: more.toList).map { c
`Set-Cookie`(c.copy(content = "deleted", expires = Some(DateTime.MinValue)))
})
/**
* Adds a Set-Cookie header expiring the given cookie to all responses of its inner route.
*/
def deleteCookie(name: String, domain: String = "", path: String = ""): Directive0 =
deleteCookie(HttpCookie(name, "", domain = domain.toOption, path = path.toOption))
}
object CookieDirectives extends CookieDirectives

View file

@ -0,0 +1,86 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import akka.event.Logging._
import akka.event.LoggingAdapter
import akka.http.scaladsl.model._
trait DebuggingDirectives {
import BasicDirectives._
def logRequest(magnet: LoggingMagnet[HttpRequest Unit]): Directive0 =
extractRequestContext.flatMap { ctx
magnet.f(ctx.log)(ctx.request)
pass
}
def logResult(magnet: LoggingMagnet[RouteResult Unit]): Directive0 =
extractRequestContext.flatMap { ctx
mapRouteResult { result
magnet.f(ctx.log)(result)
result
}
}
def logRequestResult(magnet: LoggingMagnet[HttpRequest RouteResult Unit]): Directive0 =
extractRequestContext.flatMap { ctx
val logResult = magnet.f(ctx.log)(ctx.request)
mapRouteResult { result
logResult(result)
result
}
}
}
object DebuggingDirectives extends DebuggingDirectives
case class LoggingMagnet[T](f: LoggingAdapter T) // # logging-magnet
object LoggingMagnet {
implicit def forMessageFromMarker[T](marker: String) = // # message-magnets
forMessageFromMarkerAndLevel[T](marker -> DebugLevel)
implicit def forMessageFromMarkerAndLevel[T](markerAndLevel: (String, LogLevel)) = // # message-magnets
forMessageFromFullShow[T] {
val (marker, level) = markerAndLevel
Message LogEntry(Message, marker, level)
}
implicit def forMessageFromShow[T](show: T String) = // # message-magnets
forMessageFromFullShow[T](msg LogEntry(show(msg), DebugLevel))
implicit def forMessageFromFullShow[T](show: T LogEntry): LoggingMagnet[T Unit] = // # message-magnets
LoggingMagnet(log show(_).logTo(log))
implicit def forRequestResponseFromMarker(marker: String) = // # request-response-magnets
forRequestResponseFromMarkerAndLevel(marker -> DebugLevel)
implicit def forRequestResponseFromMarkerAndLevel(markerAndLevel: (String, LogLevel)) = // # request-response-magnets
forRequestResponseFromFullShow {
val (marker, level) = markerAndLevel
request response Some(
LogEntry("Response for\n Request : " + request + "\n Response: " + response, marker, level))
}
implicit def forRequestResponseFromFullShow(show: HttpRequest RouteResult Option[LogEntry]): LoggingMagnet[HttpRequest RouteResult Unit] = // # request-response-magnets
LoggingMagnet { log
request
val showResult = show(request)
result showResult(result).foreach(_.logTo(log))
}
}
case class LogEntry(obj: Any, level: LogLevel = DebugLevel) {
def logTo(log: LoggingAdapter): Unit = {
log.log(level, obj.toString)
}
}
object LogEntry {
def apply(obj: Any, marker: String, level: LogLevel): LogEntry =
LogEntry(if (marker.isEmpty) obj else marker + ": " + obj, level)
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import scala.collection.immutable
import scala.concurrent.Future
import scala.util.control.NonFatal
import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.util.FastFuture._
trait ExecutionDirectives {
import BasicDirectives._
/**
* Transforms exceptions thrown during evaluation of its inner route using the given
* [[akka.http.scaladsl.server.ExceptionHandler]].
*/
def handleExceptions(handler: ExceptionHandler): Directive0 =
Directive { innerRouteBuilder
ctx
import ctx.executionContext
def handleException: PartialFunction[Throwable, Future[RouteResult]] =
handler andThen (_(ctx.withAcceptAll))
try innerRouteBuilder(())(ctx).fast.recoverWith(handleException)
catch {
case NonFatal(e) handleException.applyOrElse[Throwable, Future[RouteResult]](e, throw _)
}
}
/**
* Transforms rejections produced by its inner route using the given
* [[akka.http.scaladsl.server.RejectionHandler]].
*/
def handleRejections(handler: RejectionHandler): Directive0 =
extractRequestContext flatMap { ctx
val maxIterations = 8
// allow for up to `maxIterations` nested rejections from RejectionHandler before bailing out
def handle(rejections: immutable.Seq[Rejection], originalRejections: immutable.Seq[Rejection], iterationsLeft: Int = maxIterations): Future[RouteResult] =
if (iterationsLeft > 0) {
handler(rejections) match {
case Some(route) recoverRejectionsWith(handle(_, originalRejections, iterationsLeft - 1))(route)(ctx.withAcceptAll)
case None FastFuture.successful(RouteResult.Rejected(rejections))
}
} else
sys.error(s"Rejection handler still produced new rejections after $maxIterations iterations. " +
s"Is there an infinite handler cycle? Initial rejections: $originalRejections final rejections: $rejections")
recoverRejectionsWith { rejections
val transformed = RejectionHandler.applyTransformations(rejections)
handle(transformed, transformed)
}
}
}
object ExecutionDirectives extends ExecutionDirectives

View file

@ -0,0 +1,334 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import java.io.{ File, FileInputStream }
import java.net.URL
import scala.annotation.tailrec
import akka.actor.ActorSystem
import akka.event.LoggingAdapter
import akka.http.scaladsl.marshalling.{ Marshaller, ToEntityMarshaller }
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers._
import akka.http.impl.util._
trait FileAndResourceDirectives {
import CacheConditionDirectives._
import MethodDirectives._
import FileAndResourceDirectives._
import RouteDirectives._
import BasicDirectives._
import RouteConcatenation._
import RangeDirectives._
/**
* Completes GET requests with the content of the given file. The actual I/O operation is
* running detached in a `Future`, so it doesn't block the current thread (but potentially
* some other thread !). If the file cannot be found or read the request is rejected.
*/
def getFromFile(fileName: String)(implicit resolver: ContentTypeResolver): Route =
getFromFile(new File(fileName))
/**
* Completes GET requests with the content of the given file. The actual I/O operation is
* running detached in a `Future`, so it doesn't block the current thread (but potentially
* some other thread !). If the file cannot be found or read the request is rejected.
*/
def getFromFile(file: File)(implicit resolver: ContentTypeResolver): Route =
getFromFile(file, resolver(file.getName))
/**
* Completes GET requests with the content of the given file. The actual I/O operation is
* running detached in a `Future`, so it doesn't block the current thread (but potentially
* some other thread !). If the file cannot be found or read the request is rejected.
*/
def getFromFile(file: File, contentType: ContentType): Route =
get {
if (file.isFile && file.canRead)
conditionalFor(file.length, file.lastModified) {
withRangeSupport {
extractSettings { settings
complete {
HttpEntity.Default(contentType, file.length,
StreamUtils.fromInputStreamSource(new FileInputStream(file), settings.fileIODispatcher))
}
}
}
}
else reject
}
private def conditionalFor(length: Long, lastModified: Long): Directive0 =
extractSettings.flatMap(settings
if (settings.fileGetConditional) {
val tag = java.lang.Long.toHexString(lastModified ^ java.lang.Long.reverse(length))
val lastModifiedDateTime = DateTime(math.min(lastModified, System.currentTimeMillis))
conditional(EntityTag(tag), lastModifiedDateTime)
} else pass)
/**
* Completes GET requests with the content of the given resource. The actual I/O operation is
* running detached in a `Future`, so it doesn't block the current thread (but potentially
* some other thread !).
* If the resource cannot be found or read the Route rejects the request.
*/
def getFromResource(resourceName: String)(implicit resolver: ContentTypeResolver): Route =
getFromResource(resourceName, resolver(resourceName))
/**
* Completes GET requests with the content of the given resource. The actual I/O operation is
* running detached in a `Future`, so it doesn't block the current thread (but potentially
* some other thread !).
* If the resource is a directory or cannot be found or read the Route rejects the request.
*/
def getFromResource(resourceName: String, contentType: ContentType, classLoader: ClassLoader = defaultClassLoader): Route =
if (!resourceName.endsWith("/"))
get {
Option(classLoader.getResource(resourceName)) flatMap ResourceFile.apply match {
case Some(ResourceFile(url, length, lastModified))
conditionalFor(length, lastModified) {
withRangeSupport {
extractSettings { settings
complete {
HttpEntity.Default(contentType, length,
StreamUtils.fromInputStreamSource(url.openStream(), settings.fileIODispatcher))
}
}
}
}
case _ reject // not found or directory
}
}
else reject // don't serve the content of resource "directories"
/**
* Completes GET requests with the content of a file underneath the given directory.
* If the file cannot be read the Route rejects the request.
*/
def getFromDirectory(directoryName: String)(implicit resolver: ContentTypeResolver): Route = {
val base = withTrailingSlash(directoryName)
extractUnmatchedPath { path
extractLog { log
fileSystemPath(base, path, log) match {
case "" reject
case fileName getFromFile(fileName)
}
}
}
}
/**
* Completes GET requests with a unified listing of the contents of all given directories.
* The actual rendering of the directory contents is performed by the in-scope `Marshaller[DirectoryListing]`.
*/
def listDirectoryContents(directories: String*)(implicit renderer: DirectoryRenderer): Route =
get {
extractRequestContext { ctx
val path = ctx.unmatchedPath
val fullPath = ctx.request.uri.path.toString
val matchedLength = fullPath.lastIndexOf(path.toString)
require(matchedLength >= 0)
val pathPrefix = fullPath.substring(0, matchedLength)
val pathString = withTrailingSlash(fileSystemPath("/", path, ctx.log, '/'))
val dirs = directories flatMap { dir
fileSystemPath(withTrailingSlash(dir), path, ctx.log) match {
case "" None
case fileName
val file = new File(fileName)
if (file.isDirectory && file.canRead) Some(file) else None
}
}
implicit val marshaller: ToEntityMarshaller[DirectoryListing] = renderer.marshaller(ctx.settings.renderVanityFooter)
if (dirs.isEmpty) reject
else complete(DirectoryListing(pathPrefix + pathString, isRoot = pathString == "/", dirs.flatMap(_.listFiles)))
}
}
/**
* Same as `getFromBrowseableDirectories` with only one directory.
*/
def getFromBrowseableDirectory(directory: String)(implicit renderer: DirectoryRenderer, resolver: ContentTypeResolver): Route =
getFromBrowseableDirectories(directory)
/**
* Serves the content of the given directories as a file system browser, i.e. files are sent and directories
* served as browseable listings.
*/
def getFromBrowseableDirectories(directories: String*)(implicit renderer: DirectoryRenderer, resolver: ContentTypeResolver): Route = {
directories.map(getFromDirectory).reduceLeft(_ ~ _) ~ listDirectoryContents(directories: _*)
}
/**
* Same as "getFromDirectory" except that the file is not fetched from the file system but rather from a
* "resource directory".
* If the requested resource is itself a directory or cannot be found or read the Route rejects the request.
*/
def getFromResourceDirectory(directoryName: String, classLoader: ClassLoader = defaultClassLoader)(implicit resolver: ContentTypeResolver): Route = {
val base = if (directoryName.isEmpty) "" else withTrailingSlash(directoryName)
extractUnmatchedPath { path
extractLog { log
fileSystemPath(base, path, log, separator = '/') match {
case "" reject
case resourceName getFromResource(resourceName, resolver(resourceName), classLoader)
}
}
}
}
protected[http] def defaultClassLoader: ClassLoader = classOf[ActorSystem].getClassLoader
}
object FileAndResourceDirectives extends FileAndResourceDirectives {
private def withTrailingSlash(path: String): String = if (path endsWith "/") path else path + '/'
private def fileSystemPath(base: String, path: Uri.Path, log: LoggingAdapter, separator: Char = File.separatorChar): String = {
import java.lang.StringBuilder
@tailrec def rec(p: Uri.Path, result: StringBuilder = new StringBuilder(base)): String =
p match {
case Uri.Path.Empty result.toString
case Uri.Path.Slash(tail) rec(tail, result.append(separator))
case Uri.Path.Segment(head, tail)
if (head.indexOf('/') >= 0 || head == "..") {
log.warning("File-system path for base [{}] and Uri.Path [{}] contains suspicious path segment [{}], " +
"GET access was disallowed", base, path, head)
""
} else rec(tail, result.append(head))
}
rec(if (path.startsWithSlash) path.tail else path)
}
object ResourceFile {
def apply(url: URL): Option[ResourceFile] = url.getProtocol match {
case "file"
val file = new File(url.toURI)
if (file.isDirectory) None
else Some(ResourceFile(url, file.length(), file.lastModified()))
case "jar"
val jarFile = url.getFile
val startIndex = if (jarFile.startsWith("file:")) 5 else 0
val bangIndex = jarFile.indexOf("!")
val jarFilePath = jarFile.substring(startIndex, bangIndex)
val resourcePath = jarFile.substring(bangIndex + 2)
val jar = new java.util.zip.ZipFile(jarFilePath)
try {
val entry = jar.getEntry(resourcePath)
Option(jar.getInputStream(entry)) map { is
is.close()
ResourceFile(url, entry.getSize, entry.getTime)
}
} finally jar.close()
case _ None
}
}
case class ResourceFile(url: URL, length: Long, lastModified: Long)
trait DirectoryRenderer {
def marshaller(renderVanityFooter: Boolean): ToEntityMarshaller[DirectoryListing]
}
trait LowLevelDirectoryRenderer {
implicit def defaultDirectoryRenderer: DirectoryRenderer =
new DirectoryRenderer {
def marshaller(renderVanityFooter: Boolean): ToEntityMarshaller[DirectoryListing] =
DirectoryListing.directoryMarshaller(renderVanityFooter)
}
}
object DirectoryRenderer extends LowLevelDirectoryRenderer {
implicit def liftMarshaller(implicit _marshaller: ToEntityMarshaller[DirectoryListing]): DirectoryRenderer =
new DirectoryRenderer {
def marshaller(renderVanityFooter: Boolean): ToEntityMarshaller[DirectoryListing] = _marshaller
}
}
}
trait ContentTypeResolver {
def apply(fileName: String): ContentType
}
object ContentTypeResolver {
/**
* The default way of resolving a filename to a ContentType is by looking up the file extension in the
* registry of all defined media-types. By default all non-binary file content is assumed to be UTF-8 encoded.
*/
implicit val Default = withDefaultCharset(HttpCharsets.`UTF-8`)
def withDefaultCharset(charset: HttpCharset): ContentTypeResolver =
new ContentTypeResolver {
def apply(fileName: String) = {
val ext = fileName.lastIndexOf('.') match {
case -1 ""
case x fileName.substring(x + 1)
}
val mediaType = MediaTypes.forExtension(ext) getOrElse MediaTypes.`application/octet-stream`
ContentType(mediaType) withDefaultCharset charset
}
}
def apply(f: String ContentType): ContentTypeResolver =
new ContentTypeResolver {
def apply(fileName: String): ContentType = f(fileName)
}
}
case class DirectoryListing(path: String, isRoot: Boolean, files: Seq[File])
object DirectoryListing {
private val html =
"""<html>
|<head><title>Index of $</title></head>
|<body>
|<h1>Index of $</h1>
|<hr>
|<pre>
|$</pre>
|<hr>$
|<div style="width:100%;text-align:right;color:gray">
|<small>rendered by <a href="http://akka.io">Akka Http</a> on $</small>
|</div>$
|</body>
|</html>
|""".stripMarginWithNewline("\n") split '$'
def directoryMarshaller(renderVanityFooter: Boolean): ToEntityMarshaller[DirectoryListing] =
Marshaller.StringMarshaller.wrapWithEC(MediaTypes.`text/html`) { implicit ec
listing
val DirectoryListing(path, isRoot, files) = listing
val filesAndNames = files.map(file file -> file.getName).sortBy(_._2)
val deduped = filesAndNames.zipWithIndex.flatMap {
case (fan @ (file, name), ix)
if (ix == 0 || filesAndNames(ix - 1)._2 != name) Some(fan) else None
}
val (directoryFilesAndNames, fileFilesAndNames) = deduped.partition(_._1.isDirectory)
def maxNameLength(seq: Seq[(File, String)]) = if (seq.isEmpty) 0 else seq.map(_._2.length).max
val maxNameLen = math.max(maxNameLength(directoryFilesAndNames) + 1, maxNameLength(fileFilesAndNames))
val sb = new java.lang.StringBuilder
sb.append(html(0)).append(path).append(html(1)).append(path).append(html(2))
if (!isRoot) {
val secondToLastSlash = path.lastIndexOf('/', path.lastIndexOf('/', path.length - 1) - 1)
sb.append("<a href=\"%s/\">../</a>\n" format path.substring(0, secondToLastSlash))
}
def lastModified(file: File) = DateTime(file.lastModified).toIsoLikeDateTimeString
def start(name: String) =
sb.append("<a href=\"").append(path + name).append("\">").append(name).append("</a>")
.append(" " * (maxNameLen - name.length))
def renderDirectory(file: File, name: String) =
start(name + '/').append(" ").append(lastModified(file)).append('\n')
def renderFile(file: File, name: String) = {
val size = akka.http.impl.util.humanReadableByteCount(file.length, si = true)
start(name).append(" ").append(lastModified(file))
sb.append(" ".substring(size.length)).append(size).append('\n')
}
for ((file, name) directoryFilesAndNames) renderDirectory(file, name)
for ((file, name) fileFilesAndNames) renderFile(file, name)
if (isRoot && files.isEmpty) sb.append("(no files)\n")
sb.append(html(3))
if (renderVanityFooter) sb.append(html(4)).append(DateTime.now.toIsoLikeDateTimeString).append(html(5))
sb.append(html(6)).toString
}
}

View file

@ -0,0 +1,126 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import scala.concurrent.Future
import scala.util.{ Failure, Success }
import akka.http.scaladsl.unmarshalling.Unmarshaller.UnsupportedContentTypeException
import akka.http.scaladsl.common._
import akka.http.impl.util._
import akka.http.scaladsl.util.FastFuture._
trait FormFieldDirectives extends ToNameReceptacleEnhancements {
import FormFieldDirectives._
/**
* Rejects the request if the defined form field matcher(s) don't match.
* Otherwise the form field value(s) are extracted and passed to the inner route.
*/
def formField(pdm: FieldMagnet): pdm.Out = pdm()
/**
* Rejects the request if the defined form field matcher(s) don't match.
* Otherwise the form field value(s) are extracted and passed to the inner route.
*/
def formFields(pdm: FieldMagnet): pdm.Out = pdm()
}
object FormFieldDirectives extends FormFieldDirectives {
sealed trait FieldMagnet {
type Out
def apply(): Out
}
object FieldMagnet {
implicit def apply[T](value: T)(implicit fdef: FieldDef[T]) =
new FieldMagnet {
type Out = fdef.Out
def apply() = fdef(value)
}
}
sealed trait FieldDef[T] {
type Out
def apply(value: T): Out
}
object FieldDef {
def fieldDef[A, B](f: A B) =
new FieldDef[A] {
type Out = B
def apply(value: A) = f(value)
}
import akka.http.scaladsl.unmarshalling.{ FromStrictFormFieldUnmarshaller FSFFU, _ }
import BasicDirectives._
import RouteDirectives._
import FutureDirectives._
type SFU = FromEntityUnmarshaller[StrictForm]
type FSFFOU[T] = Unmarshaller[Option[StrictForm.Field], T]
//////////////////// "regular" formField extraction ////////////////////
private def extractField[A, B](f: A Directive1[B]) = fieldDef(f)
private def fieldOfForm[T](fieldName: String, fu: Unmarshaller[Option[StrictForm.Field], T])(implicit sfu: SFU): RequestContext Future[T] = { ctx
import ctx.executionContext
sfu(ctx.request.entity).fast.flatMap(form fu(form field fieldName))
}
private def filter[T](fieldName: String, fu: FSFFOU[T])(implicit sfu: SFU): Directive1[T] = {
extract(fieldOfForm(fieldName, fu)).flatMap {
onComplete(_).flatMap {
case Success(x) provide(x)
case Failure(Unmarshaller.NoContentException) reject(MissingFormFieldRejection(fieldName))
case Failure(x: UnsupportedContentTypeException) reject(UnsupportedRequestContentTypeRejection(x.supported))
case Failure(x) reject(MalformedFormFieldRejection(fieldName, x.getMessage.nullAsEmpty, Option(x.getCause)))
}
}
}
implicit def forString(implicit sfu: SFU, fu: FSFFU[String]) =
extractField[String, String] { fieldName filter(fieldName, fu) }
implicit def forSymbol(implicit sfu: SFU, fu: FSFFU[String]) =
extractField[Symbol, String] { symbol filter(symbol.name, fu) }
implicit def forNR[T](implicit sfu: SFU, fu: FSFFU[T]) =
extractField[NameReceptacle[T], T] { nr filter(nr.name, fu) }
implicit def forNUR[T](implicit sfu: SFU) =
extractField[NameUnmarshallerReceptacle[T], T] { nr filter(nr.name, StrictForm.Field.unmarshallerFromFSU(nr.um)) }
implicit def forNOR[T](implicit sfu: SFU, fu: FSFFOU[T]) =
extractField[NameOptionReceptacle[T], Option[T]] { nr filter[Option[T]](nr.name, fu) }
implicit def forNDR[T](implicit sfu: SFU, fu: FSFFOU[T]) =
extractField[NameDefaultReceptacle[T], T] { nr filter(nr.name, fu withDefaultValue nr.default) }
implicit def forNOUR[T](implicit sfu: SFU) =
extractField[NameOptionUnmarshallerReceptacle[T], Option[T]] { nr filter[Option[T]](nr.name, StrictForm.Field.unmarshallerFromFSU(nr.um): FSFFOU[T]) }
implicit def forNDUR[T](implicit sfu: SFU) =
extractField[NameDefaultUnmarshallerReceptacle[T], T] { nr filter(nr.name, (StrictForm.Field.unmarshallerFromFSU(nr.um): FSFFOU[T]) withDefaultValue nr.default) }
//////////////////// required formField support ////////////////////
private def requiredFilter[T](fieldName: String, fu: Unmarshaller[Option[StrictForm.Field], T],
requiredValue: Any)(implicit sfu: SFU): Directive0 =
extract(fieldOfForm(fieldName, fu)).flatMap {
onComplete(_).flatMap {
case Success(value) if value == requiredValue pass
case _ reject
}
}
implicit def forRVR[T](implicit sfu: SFU, fu: FSFFU[T]) =
fieldDef[RequiredValueReceptacle[T], Directive0] { rvr requiredFilter(rvr.name, fu, rvr.requiredValue) }
implicit def forRVDR[T](implicit sfu: SFU) =
fieldDef[RequiredValueUnmarshallerReceptacle[T], Directive0] { rvr requiredFilter(rvr.name, StrictForm.Field.unmarshallerFromFSU(rvr.um), rvr.requiredValue) }
//////////////////// tuple support ////////////////////
import akka.http.scaladsl.server.util.TupleOps._
import akka.http.scaladsl.server.util.BinaryPolyFunc
implicit def forTuple[T](implicit fold: FoldLeft[Directive0, T, ConvertParamDefAndConcatenate.type]) =
fieldDef[T, fold.Out](fold(pass, _))
object ConvertParamDefAndConcatenate extends BinaryPolyFunc {
implicit def from[P, TA, TB](implicit fdef: FieldDef[P] { type Out = Directive[TB] }, ev: Join[TA, TB]) =
at[Directive[TA], P] { (a, t) a & fdef(t) }
}
}
}

View file

@ -0,0 +1,81 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import scala.concurrent.Future
import scala.util.{ Failure, Success, Try }
import akka.http.scaladsl.marshalling.ToResponseMarshaller
import akka.http.scaladsl.server.util.Tupler
import akka.http.scaladsl.util.FastFuture._
// format: OFF
trait FutureDirectives {
/**
* "Unwraps" a ``Future[T]`` and runs its inner route after future
* completion with the future's value as an extraction of type ``Try[T]``.
*/
def onComplete[T](future: Future[T]): Directive1[Try[T]] =
Directive { inner ctx
import ctx.executionContext
future.fast.transformWith(t inner(Tuple1(t))(ctx))
}
/**
* "Unwraps" a ``Future[T]`` and runs its inner route after future
* completion with the future's value as an extraction of type ``T``.
* If the future fails its failure Throwable is bubbled up to the nearest
* ExceptionHandler.
* If type ``T`` is already a Tuple it is directly expanded into the respective
* number of extractions.
*/
def onSuccess(magnet: OnSuccessMagnet): Directive[magnet.Out] = magnet.directive
/**
* "Unwraps" a ``Future[T]`` and runs its inner route when the future has failed
* with the future's failure exception as an extraction of type ``Throwable``.
* If the future succeeds the request is completed using the values marshaller
* (This directive therefore requires a marshaller for the futures type to be
* implicitly available.)
*/
def completeOrRecoverWith(magnet: CompleteOrRecoverWithMagnet): Directive1[Throwable] = magnet.directive
}
object FutureDirectives extends FutureDirectives
trait OnSuccessMagnet {
type Out
def directive: Directive[Out]
}
object OnSuccessMagnet {
implicit def apply[T](future: Future[T])(implicit tupler: Tupler[T]) =
new OnSuccessMagnet {
type Out = tupler.Out
val directive = Directive[tupler.Out] { inner ctx
import ctx.executionContext
future.fast.flatMap(t inner(tupler(t))(ctx))
}(tupler.OutIsTuple)
}
}
trait CompleteOrRecoverWithMagnet {
def directive: Directive1[Throwable]
}
object CompleteOrRecoverWithMagnet {
implicit def apply[T](future: Future[T])(implicit m: ToResponseMarshaller[T]) =
new CompleteOrRecoverWithMagnet {
val directive = Directive[Tuple1[Throwable]] { inner ctx
import ctx.executionContext
future.fast.transformWith {
case Success(res) ctx.complete(res)
case Failure(error) inner(Tuple1(error))(ctx)
}
}
}
}

View file

@ -0,0 +1,108 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import scala.util.control.NonFatal
import akka.http.scaladsl.server.util.ClassMagnet
import akka.http.scaladsl.model._
import akka.http.impl.util._
trait HeaderDirectives {
import BasicDirectives._
import RouteDirectives._
/**
* Extracts an HTTP header value using the given function. If the function result is undefined for all headers the
* request is rejected with an empty rejection set. If the given function throws an exception the request is rejected
* with a [[spray.routing.MalformedHeaderRejection]].
*/
def headerValue[T](f: HttpHeader Option[T]): Directive1[T] = {
val protectedF: HttpHeader Option[Either[Rejection, T]] = header
try f(header).map(Right.apply)
catch {
case NonFatal(e) Some(Left(MalformedHeaderRejection(header.name, e.getMessage.nullAsEmpty, Some(e))))
}
extract(_.request.headers.collectFirst(Function.unlift(protectedF))).flatMap {
case Some(Right(a)) provide(a)
case Some(Left(rejection)) reject(rejection)
case None reject
}
}
/**
* Extracts an HTTP header value using the given partial function. If the function is undefined for all headers the
* request is rejected with an empty rejection set.
*/
def headerValuePF[T](pf: PartialFunction[HttpHeader, T]): Directive1[T] = headerValue(pf.lift)
/**
* Extracts the value of the HTTP request header with the given name.
* If no header with a matching name is found the request is rejected with a [[spray.routing.MissingHeaderRejection]].
*/
def headerValueByName(headerName: Symbol): Directive1[String] = headerValueByName(headerName.toString)
/**
* Extracts the value of the HTTP request header with the given name.
* If no header with a matching name is found the request is rejected with a [[spray.routing.MissingHeaderRejection]].
*/
def headerValueByName(headerName: String): Directive1[String] =
headerValue(optionalValue(headerName.toLowerCase)) | reject(MissingHeaderRejection(headerName))
/**
* Extracts the HTTP request header of the given type.
* If no header with a matching type is found the request is rejected with a [[spray.routing.MissingHeaderRejection]].
*/
def headerValueByType[T <: HttpHeader](magnet: ClassMagnet[T]): Directive1[T] =
headerValuePF(magnet.extractPF) | reject(MissingHeaderRejection(magnet.runtimeClass.getSimpleName))
/**
* Extracts an optional HTTP header value using the given function.
* If the given function throws an exception the request is rejected
* with a [[spray.routing.MalformedHeaderRejection]].
*/
def optionalHeaderValue[T](f: HttpHeader Option[T]): Directive1[Option[T]] =
headerValue(f).map(Some(_): Option[T]).recoverPF {
case Nil provide(None)
}
/**
* Extracts an optional HTTP header value using the given partial function.
* If the given function throws an exception the request is rejected
* with a [[spray.routing.MalformedHeaderRejection]].
*/
def optionalHeaderValuePF[T](pf: PartialFunction[HttpHeader, T]): Directive1[Option[T]] =
optionalHeaderValue(pf.lift)
/**
* Extracts the value of the optional HTTP request header with the given name.
*/
def optionalHeaderValueByName(headerName: Symbol): Directive1[Option[String]] =
optionalHeaderValueByName(headerName.toString)
/**
* Extracts the value of the optional HTTP request header with the given name.
*/
def optionalHeaderValueByName(headerName: String): Directive1[Option[String]] = {
val lowerCaseName = headerName.toLowerCase
extract(_.request.headers.collectFirst {
case HttpHeader(`lowerCaseName`, value) value
})
}
/**
* Extract the header value of the optional HTTP request header with the given type.
*/
def optionalHeaderValueByType[T <: HttpHeader](magnet: ClassMagnet[T]): Directive1[Option[T]] =
optionalHeaderValuePF(magnet.extractPF)
private def optionalValue(lowerCaseName: String): HttpHeader Option[String] = {
case HttpHeader(`lowerCaseName`, value) Some(value)
case _ None
}
}
object HeaderDirectives extends HeaderDirectives

View file

@ -0,0 +1,61 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import scala.util.matching.Regex
import akka.http.impl.util._
trait HostDirectives {
import BasicDirectives._
import RouteDirectives._
/**
* Extracts the hostname part of the Host header value in the request.
*/
def extractHost: Directive1[String] = HostDirectives._extractHost
/**
* Rejects all requests with a host name different from the given ones.
*/
def host(hostNames: String*): Directive0 = host(hostNames.contains(_))
/**
* Rejects all requests for whose host name the given predicate function returns false.
*/
def host(predicate: String Boolean): Directive0 = extractHost.require(predicate)
/**
* Rejects all requests with a host name that doesn't have a prefix matching the given regular expression.
* For all matching requests the prefix string matching the regex is extracted and passed to the inner route.
* If the regex contains a capturing group only the string matched by this group is extracted.
* If the regex contains more than one capturing group an IllegalArgumentException is thrown.
*/
def host(regex: Regex): Directive1[String] = {
def forFunc(regexMatch: String Option[String]): Directive1[String] = {
extractHost.flatMap { name
regexMatch(name) match {
case Some(matched) provide(matched)
case None reject
}
}
}
regex.groupCount match {
case 0 forFunc(regex.findPrefixOf(_))
case 1 forFunc(regex.findPrefixMatchOf(_).map(_.group(1)))
case _ throw new IllegalArgumentException("Path regex '" + regex.pattern.pattern +
"' must not contain more than one capturing group")
}
}
}
object HostDirectives extends HostDirectives {
import BasicDirectives._
private val _extractHost: Directive1[String] =
extract(_.request.uri.authority.host.address)
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import scala.concurrent.Promise
import scala.util.{ Failure, Success }
import akka.http.scaladsl.marshalling.ToResponseMarshaller
import akka.http.scaladsl.unmarshalling.{ Unmarshaller, FromRequestUnmarshaller }
import akka.http.impl.util._
trait MarshallingDirectives {
import BasicDirectives._
import FutureDirectives._
import RouteDirectives._
/**
* Unmarshalls the requests entity to the given type passes it to its inner Route.
* If there is a problem with unmarshalling the request is rejected with the [[Rejection]]
* produced by the unmarshaller.
*/
def entity[T](um: FromRequestUnmarshaller[T]): Directive1[T] =
extractRequestContext.flatMap[Tuple1[T]] { ctx
import ctx.executionContext
onComplete(um(ctx.request)) flatMap {
case Success(value) provide(value)
case Failure(Unmarshaller.NoContentException) reject(RequestEntityExpectedRejection)
case Failure(Unmarshaller.UnsupportedContentTypeException(x)) reject(UnsupportedRequestContentTypeRejection(x))
case Failure(x: IllegalArgumentException) reject(ValidationRejection(x.getMessage.nullAsEmpty, Some(x)))
case Failure(x) reject(MalformedRequestContentRejection(x.getMessage.nullAsEmpty, Option(x.getCause)))
}
} & cancelRejections(RequestEntityExpectedRejection.getClass, classOf[UnsupportedRequestContentTypeRejection])
/**
* Returns the in-scope [[FromRequestUnmarshaller]] for the given type.
*/
def as[T](implicit um: FromRequestUnmarshaller[T]) = um
/**
* Uses the marshaller for the given type to produce a completion function that is passed to its inner function.
* You can use it do decouple marshaller resolution from request completion.
*/
def completeWith[T](marshaller: ToResponseMarshaller[T])(inner: (T Unit) Unit): Route =
extractExecutionContext { implicit ec
implicit val m = marshaller
complete {
val promise = Promise[T]()
inner(promise.success(_))
promise.future
}
}
/**
* Returns the in-scope Marshaller for the given type.
*/
def instanceOf[T](implicit m: ToResponseMarshaller[T]): ToResponseMarshaller[T] = m
/**
* Completes the request using the given function. The input to the function is produced with the in-scope
* entity unmarshaller and the result value of the function is marshalled with the in-scope marshaller.
*/
def handleWith[A, B](f: A B)(implicit um: FromRequestUnmarshaller[A], m: ToResponseMarshaller[B]): Route =
entity(um) { a complete(f(a)) }
}
object MarshallingDirectives extends MarshallingDirectives

View file

@ -0,0 +1,99 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import akka.http.scaladsl.model.{ StatusCodes, HttpMethod }
import akka.http.scaladsl.model.HttpMethods._
trait MethodDirectives {
import BasicDirectives._
import RouteDirectives._
import ParameterDirectives._
import MethodDirectives._
/**
* A route filter that rejects all non-DELETE requests.
*/
def delete: Directive0 = _delete
/**
* A route filter that rejects all non-GET requests.
*/
def get: Directive0 = _get
/**
* A route filter that rejects all non-HEAD requests.
*/
def head: Directive0 = _head
/**
* A route filter that rejects all non-OPTIONS requests.
*/
def options: Directive0 = _options
/**
* A route filter that rejects all non-PATCH requests.
*/
def patch: Directive0 = _patch
/**
* A route filter that rejects all non-POST requests.
*/
def post: Directive0 = _post
/**
* A route filter that rejects all non-PUT requests.
*/
def put: Directive0 = _put
/**
* Extracts the request method.
*/
def extractMethod: Directive1[HttpMethod] = _extractMethod
/**
* Rejects all requests whose HTTP method does not match the given one.
*/
def method(httpMethod: HttpMethod): Directive0 =
extractMethod.flatMap[Unit] {
case `httpMethod` pass
case _ reject(MethodRejection(httpMethod))
} & cancelRejections(classOf[MethodRejection])
/**
* Changes the HTTP method of the request to the value of the specified query string parameter. If the query string
* parameter is not specified this directive has no effect. If the query string is specified as something that is not
* a HTTP method, then this directive completes the request with a `501 Not Implemented` response.
*
* This directive is useful for:
* - Use in combination with JSONP (JSONP only supports GET)
* - Supporting older browsers that lack support for certain HTTP methods. E.g. IE8 does not support PATCH
*/
def overrideMethodWithParameter(paramName: String): Directive0 =
parameter(paramName?) flatMap {
case Some(method)
getForKey(method.toUpperCase) match {
case Some(m) mapRequest(_.copy(method = m))
case _ complete(StatusCodes.NotImplemented)
}
case None pass
}
}
object MethodDirectives extends MethodDirectives {
private val _extractMethod: Directive1[HttpMethod] =
BasicDirectives.extract(_.request.method)
// format: OFF
private val _delete : Directive0 = method(DELETE)
private val _get : Directive0 = method(GET)
private val _head : Directive0 = method(HEAD)
private val _options: Directive0 = method(OPTIONS)
private val _patch : Directive0 = method(PATCH)
private val _post : Directive0 = method(POST)
private val _put : Directive0 = method(PUT)
// format: ON
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import akka.http.scaladsl.model._
import headers._
trait MiscDirectives {
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 =
Directive { inner if (check) inner() 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 extractClientIP: Directive1[RemoteAddress] = MiscDirectives._extractClientIP
/**
* 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
/**
* 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
}
object MiscDirectives extends MiscDirectives {
import BasicDirectives._
import HeaderDirectives._
import RouteDirectives._
import RouteResult._
private val _extractClientIP: 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.isKnownEmpty).flatMap(if (_) pass else reject)
private val _requestEntityPresent: Directive0 =
extract(_.request.entity.isKnownEmpty).flatMap(if (_) reject else pass)
private val _rejectEmptyResponse: Directive0 =
mapRouteResult {
case Complete(response) if response.entity.isKnownEmpty Rejected(Nil)
case x x
}
}

View file

@ -0,0 +1,143 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import scala.collection.immutable
import scala.util.{ Failure, Success }
import akka.http.scaladsl.common._
import akka.http.impl.util._
trait ParameterDirectives extends ToNameReceptacleEnhancements {
import ParameterDirectives._
/**
* Extracts the requests query parameters as a Map[String, String].
*/
def parameterMap: Directive1[Map[String, String]] = _parameterMap
/**
* Extracts the requests query parameters as a Map[String, List[String]].
*/
def parameterMultiMap: Directive1[Map[String, List[String]]] = _parameterMultiMap
/**
* Extracts the requests query parameters as a Seq[(String, String)].
*/
def parameterSeq: Directive1[immutable.Seq[(String, String)]] = _parameterSeq
/**
* Rejects the request if the defined query parameter matcher(s) don't match.
* Otherwise the parameter value(s) are extracted and passed to the inner route.
*/
def parameter(pdm: ParamMagnet): pdm.Out = pdm()
/**
* Rejects the request if the defined query parameter matcher(s) don't match.
* Otherwise the parameter value(s) are extracted and passed to the inner route.
*/
def parameters(pdm: ParamMagnet): pdm.Out = pdm()
}
object ParameterDirectives extends ParameterDirectives {
import BasicDirectives._
private val _parameterMap: Directive1[Map[String, String]] =
extract(_.request.uri.query.toMap)
private val _parameterMultiMap: Directive1[Map[String, List[String]]] =
extract(_.request.uri.query.toMultiMap)
private val _parameterSeq: Directive1[immutable.Seq[(String, String)]] =
extract(_.request.uri.query.toSeq)
sealed trait ParamMagnet {
type Out
def apply(): Out
}
object ParamMagnet {
implicit def apply[T](value: T)(implicit pdef: ParamDef[T]) =
new ParamMagnet {
type Out = pdef.Out
def apply() = pdef(value)
}
}
sealed trait ParamDef[T] {
type Out
def apply(value: T): Out
}
object ParamDef {
def paramDef[A, B](f: A B) =
new ParamDef[A] {
type Out = B
def apply(value: A) = f(value)
}
import akka.http.scaladsl.unmarshalling.{ FromStringUnmarshaller FSU, _ }
import BasicDirectives._
import RouteDirectives._
import FutureDirectives._
type FSOU[T] = Unmarshaller[Option[String], T]
//////////////////// "regular" parameter extraction //////////////////////
private def extractParameter[A, B](f: A Directive1[B]) = paramDef(f)
private def filter[T](paramName: String, fsou: FSOU[T]): Directive1[T] =
extractRequestContext flatMap { ctx
import ctx.executionContext
onComplete(fsou(ctx.request.uri.query get paramName)) flatMap {
case Success(x) provide(x)
case Failure(Unmarshaller.NoContentException) reject(MissingQueryParamRejection(paramName))
case Failure(x) reject(MalformedQueryParamRejection(paramName, x.getMessage.nullAsEmpty, Option(x.getCause)))
}
}
implicit def forString(implicit fsu: FSU[String]) =
extractParameter[String, String] { string filter(string, fsu) }
implicit def forSymbol(implicit fsu: FSU[String]) =
extractParameter[Symbol, String] { symbol filter(symbol.name, fsu) }
implicit def forNR[T](implicit fsu: FSU[T]) =
extractParameter[NameReceptacle[T], T] { nr filter(nr.name, fsu) }
implicit def forNUR[T] =
extractParameter[NameUnmarshallerReceptacle[T], T] { nr filter(nr.name, nr.um) }
implicit def forNOR[T](implicit fsou: FSOU[T]) =
extractParameter[NameOptionReceptacle[T], Option[T]] { nr filter[Option[T]](nr.name, fsou) }
implicit def forNDR[T](implicit fsou: FSOU[T]) =
extractParameter[NameDefaultReceptacle[T], T] { nr filter[T](nr.name, fsou withDefaultValue nr.default) }
implicit def forNOUR[T] =
extractParameter[NameOptionUnmarshallerReceptacle[T], Option[T]] { nr filter(nr.name, nr.um: FSOU[T]) }
implicit def forNDUR[T] =
extractParameter[NameDefaultUnmarshallerReceptacle[T], T] { nr filter[T](nr.name, (nr.um: FSOU[T]) withDefaultValue nr.default) }
//////////////////// required parameter support ////////////////////
private def requiredFilter[T](paramName: String, fsou: FSOU[T], requiredValue: Any): Directive0 =
extractRequestContext flatMap { ctx
import ctx.executionContext
onComplete(fsou(ctx.request.uri.query get paramName)) flatMap {
case Success(value) if value == requiredValue pass
case _ reject
}
}
implicit def forRVR[T](implicit fsu: FSU[T]) =
paramDef[RequiredValueReceptacle[T], Directive0] { rvr requiredFilter(rvr.name, fsu, rvr.requiredValue) }
implicit def forRVDR[T] =
paramDef[RequiredValueUnmarshallerReceptacle[T], Directive0] { rvr requiredFilter(rvr.name, rvr.um, rvr.requiredValue) }
//////////////////// tuple support ////////////////////
import akka.http.scaladsl.server.util.TupleOps._
import akka.http.scaladsl.server.util.BinaryPolyFunc
implicit def forTuple[T](implicit fold: FoldLeft[Directive0, T, ConvertParamDefAndConcatenate.type]) =
paramDef[T, fold.Out](fold(BasicDirectives.pass, _))
object ConvertParamDefAndConcatenate extends BinaryPolyFunc {
implicit def from[P, TA, TB](implicit pdef: ParamDef[P] { type Out = Directive[TB] }, ev: Join[TA, TB]) =
at[Directive[TA], P] { (a, t) a & pdef(t) }
}
}
}

View file

@ -0,0 +1,169 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import akka.http.scaladsl.common.ToNameReceptacleEnhancements
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.model.Uri.Path
trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction with ToNameReceptacleEnhancements {
import BasicDirectives._
import RouteDirectives._
import PathMatcher._
/**
* Consumes a leading slash from the unmatched path of the [[akka.http.scaladsl.server.RequestContext]]
* before applying the given matcher. The matcher has to match the remaining path completely
* or leave only a single trailing slash.
* If matched the value extracted by the PathMatcher is extracted on the directive level.
*/
def path[L](pm: PathMatcher[L]): Directive[L] = pathPrefix(pm ~ PathEnd)
/**
* Consumes a leading slash from the unmatched path of the [[akka.http.scaladsl.server.RequestContext]]
* before applying the given matcher. The matcher has to match a prefix of the remaining path.
* If matched the value extracted by the PathMatcher is extracted on the directive level.
*/
def pathPrefix[L](pm: PathMatcher[L]): Directive[L] = rawPathPrefix(Slash ~ pm)
/**
* Applies the given matcher directly to the unmatched path of the [[akka.http.scaladsl.server.RequestContext]]
* (i.e. without implicitly consuming a leading slash).
* The matcher has to match a prefix of the remaining path.
* If matched the value extracted by the PathMatcher is extracted on the directive level.
*/
def rawPathPrefix[L](pm: PathMatcher[L]): Directive[L] = {
implicit def LIsTuple = pm.ev
extract(ctx pm(ctx.unmatchedPath)).flatMap {
case Matched(rest, values) tprovide(values) & mapRequestContext(_ withUnmatchedPath rest)
case Unmatched reject
}
}
/**
* Checks whether the unmatchedPath of the [[akka.http.scaladsl.server.RequestContext]] has a prefix matched by the
* given PathMatcher. In analogy to the `pathPrefix` directive a leading slash is implied.
*/
def pathPrefixTest[L](pm: PathMatcher[L]): Directive[L] = rawPathPrefixTest(Slash ~ pm)
/**
* Checks whether the unmatchedPath of the [[akka.http.scaladsl.server.RequestContext]] has a prefix matched by the
* given PathMatcher. However, as opposed to the `pathPrefix` directive the matched path is not
* actually "consumed".
*/
def rawPathPrefixTest[L](pm: PathMatcher[L]): Directive[L] = {
implicit def LIsTuple = pm.ev
extract(ctx pm(ctx.unmatchedPath)).flatMap {
case Matched(_, values) tprovide(values)
case Unmatched reject
}
}
/**
* Rejects the request if the unmatchedPath of the [[akka.http.scaladsl.server.RequestContext]] does not have a suffix
* matched the given PathMatcher. If matched the value extracted by the PathMatcher is extracted
* and the matched parts of the path are consumed.
* Note that, for efficiency reasons, the given PathMatcher must match the desired suffix in reversed-segment
* order, i.e. `pathSuffix("baz" / "bar")` would match `/foo/bar/baz`!
*/
def pathSuffix[L](pm: PathMatcher[L]): Directive[L] = {
implicit def LIsTuple = pm.ev
extract(ctx pm(ctx.unmatchedPath.reverse)).flatMap {
case Matched(rest, values) tprovide(values) & mapRequestContext(_.withUnmatchedPath(rest.reverse))
case Unmatched reject
}
}
/**
* Checks whether the unmatchedPath of the [[akka.http.scaladsl.server.RequestContext]] has a suffix matched by the
* given PathMatcher. However, as opposed to the pathSuffix directive the matched path is not
* actually "consumed".
* Note that, for efficiency reasons, the given PathMatcher must match the desired suffix in reversed-segment
* order, i.e. `pathSuffixTest("baz" / "bar")` would match `/foo/bar/baz`!
*/
def pathSuffixTest[L](pm: PathMatcher[L]): Directive[L] = {
implicit def LIsTuple = pm.ev
extract(ctx pm(ctx.unmatchedPath.reverse)).flatMap {
case Matched(_, values) tprovide(values)
case Unmatched reject
}
}
/**
* Rejects the request if the unmatchedPath of the [[akka.http.scaladsl.server.RequestContext]] is non-empty,
* or said differently: only passes on the request to its inner route if the request path
* has been matched completely.
*/
def pathEnd: Directive0 = rawPathPrefix(PathEnd)
/**
* Only passes on the request to its inner route if the request path has been matched
* completely or only consists of exactly one remaining slash.
*
* Note that trailing slash and non-trailing slash URLs are '''not''' the same, although they often serve
* the same content. It is recommended to serve only one URL version and make the other redirect to it using
* [[redirectToTrailingSlashIfMissing]] or [[redirectToNoTrailingSlashIfPresent]] directive.
*
* For example:
* {{{
* def route = {
* // redirect '/users/' to '/users', '/users/:userId/' to '/users/:userId'
* redirectToNoTrailingSlashIfPresent(Found) {
* pathPrefix("users") {
* pathEnd {
* // user list ...
* } ~
* path(UUID) { userId =>
* // user profile ...
* }
* }
* }
* }
* }}}
*
* For further information, refer to:
* [[http://googlewebmastercentral.blogspot.de/2010/04/to-slash-or-not-to-slash.html]]
*/
def pathEndOrSingleSlash: Directive0 = rawPathPrefix(Slash.? ~ PathEnd)
/**
* Only passes on the request to its inner route if the request path
* consists of exactly one remaining slash.
*/
def pathSingleSlash: Directive0 = pathPrefix(PathEnd)
/**
* If the request path doesn't end with a slash, redirect to the same uri with trailing slash in the path.
*
* '''Caveat''': [[path]] without trailing slash and [[pathEnd]] directives will not match inside of this directive.
*/
def redirectToTrailingSlashIfMissing(redirectionType: StatusCodes.Redirection): Directive0 =
extractUri.flatMap { uri
if (uri.path.endsWithSlash) pass
else {
val newPath = uri.path ++ Path.SingleSlash
val newUri = uri.withPath(newPath)
redirect(newUri, redirectionType)
}
}
/**
* If the request path ends with a slash, redirect to the same uri without trailing slash in the path.
*
* '''Caveat''': [[pathSingleSlash]] directive will not match inside of this directive.
*/
def redirectToNoTrailingSlashIfPresent(redirectionType: StatusCodes.Redirection): Directive0 =
extractUri.flatMap { uri
if (uri.path.endsWithSlash) {
val newPath = uri.path.reverse.tail.reverse
val newUri = uri.withPath(newPath)
redirect(newUri, redirectionType)
} else pass
}
}
object PathDirectives extends PathDirectives

View file

@ -0,0 +1,136 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server.RouteResult.Complete
import akka.http.impl.util._
import akka.stream.scaladsl.Source
import scala.collection.immutable
trait RangeDirectives {
import akka.http.scaladsl.server.directives.BasicDirectives._
import akka.http.scaladsl.server.directives.RouteDirectives._
/**
* Answers GET requests with an `Accept-Ranges: bytes` header and converts HttpResponses coming back from its inner
* route into partial responses if the initial request contained a valid `Range` request header. The requested
* byte-ranges may be coalesced.
* This directive is transparent to non-GET requests
* Rejects requests with unsatisfiable ranges `UnsatisfiableRangeRejection`.
* Rejects requests with too many expected ranges.
*
* Note: if you want to combine this directive with `conditional(...)` you need to put
* it on the *inside* of the `conditional(...)` directive, i.e. `conditional(...)` must be
* on a higher level in your route structure in order to function correctly.
*
* @see https://tools.ietf.org/html/rfc7233
*/
def withRangeSupport: Directive0 =
extractRequestContext.flatMap { ctx
import ctx.flowMaterializer
val settings = ctx.settings
implicit val log = ctx.log
import settings.{ rangeCountLimit, rangeCoalescingThreshold }
class IndexRange(val start: Long, val end: Long) {
def length = end - start
def apply(entity: UniversalEntity): UniversalEntity = entity.transformDataBytes(length, StreamUtils.sliceBytesTransformer(start, length))
def distance(other: IndexRange) = mergedEnd(other) - mergedStart(other) - (length + other.length)
def mergeWith(other: IndexRange) = new IndexRange(mergedStart(other), mergedEnd(other))
def contentRange(entityLength: Long) = ContentRange(start, end - 1, entityLength)
private def mergedStart(other: IndexRange) = math.min(start, other.start)
private def mergedEnd(other: IndexRange) = math.max(end, other.end)
}
def indexRange(entityLength: Long)(range: ByteRange): IndexRange =
range match {
case ByteRange.Slice(start, end) new IndexRange(start, math.min(end + 1, entityLength))
case ByteRange.FromOffset(first) new IndexRange(first, entityLength)
case ByteRange.Suffix(suffixLength) new IndexRange(math.max(0, entityLength - suffixLength), entityLength)
}
// See comment of the `range-coalescing-threshold` setting in `reference.conf` for the rationale of this behavior.
def coalesceRanges(iRanges: Seq[IndexRange]): Seq[IndexRange] =
iRanges.foldLeft(Seq.empty[IndexRange]) { (acc, iRange)
val (mergeCandidates, otherCandidates) = acc.partition(_.distance(iRange) <= rangeCoalescingThreshold)
val merged = mergeCandidates.foldLeft(iRange)(_ mergeWith _)
otherCandidates :+ merged
}
def multipartRanges(ranges: Seq[ByteRange], entity: UniversalEntity): Multipart.ByteRanges = {
val length = entity.contentLength
val iRanges: Seq[IndexRange] = ranges.map(indexRange(length))
// It's only possible to run once over the input entity data stream because it's not known if the
// source is reusable.
// Therefore, ranges need to be sorted to prevent that some selected ranges already start to accumulate data
// but cannot be sent out because another range is blocking the queue.
val coalescedRanges = coalesceRanges(iRanges).sortBy(_.start)
val bodyPartTransformers = coalescedRanges.map(ir StreamUtils.sliceBytesTransformer(ir.start, ir.length)).toVector
val bodyPartByteStreams = StreamUtils.transformMultiple(entity.dataBytes, bodyPartTransformers)
val bodyParts = (coalescedRanges, bodyPartByteStreams).zipped.map { (range, bytes)
Multipart.ByteRanges.BodyPart(range.contentRange(length), HttpEntity(entity.contentType, range.length, bytes))
}
Multipart.ByteRanges(Source(bodyParts.toVector))
}
def rangeResponse(range: ByteRange, entity: UniversalEntity, length: Long, headers: immutable.Seq[HttpHeader]) = {
val aiRange = indexRange(length)(range)
HttpResponse(PartialContent, `Content-Range`(aiRange.contentRange(length)) +: headers, aiRange(entity))
}
def satisfiable(entityLength: Long)(range: ByteRange): Boolean =
range match {
case ByteRange.Slice(firstPos, _) firstPos < entityLength
case ByteRange.FromOffset(firstPos) firstPos < entityLength
case ByteRange.Suffix(length) length > 0
}
def universal(entity: HttpEntity): Option[UniversalEntity] = entity match {
case u: UniversalEntity Some(u)
case _ None
}
def applyRanges(ranges: Seq[ByteRange]): Directive0 =
extractRequestContext.flatMap { ctx
mapRouteResultWithPF {
case Complete(HttpResponse(OK, headers, entity, protocol))
universal(entity) match {
case Some(entity)
val length = entity.contentLength
ranges.filter(satisfiable(length)) match {
case Nil ctx.reject(UnsatisfiableRangeRejection(ranges, length))
case Seq(satisfiableRange) ctx.complete(rangeResponse(satisfiableRange, entity, length, headers))
case satisfiableRanges
ctx.complete(PartialContent, headers, multipartRanges(satisfiableRanges, entity))
}
case None
// Ranges not supported for Chunked or CloseDelimited responses
ctx.reject(UnsatisfiableRangeRejection(ranges, -1)) // FIXME: provide better error
}
}
}
def rangeHeaderOfGetRequests(ctx: RequestContext): Option[Range] =
if (ctx.request.method == HttpMethods.GET) ctx.request.header[Range] else None
extract(rangeHeaderOfGetRequests).flatMap {
case Some(Range(RangeUnits.Bytes, ranges))
if (ranges.size <= rangeCountLimit) applyRanges(ranges) & RangeDirectives.respondWithAcceptByteRangesHeader
else reject(TooManyRangesRejection(rangeCountLimit))
case _ MethodDirectives.get & RangeDirectives.respondWithAcceptByteRangesHeader | pass
}
}
}
object RangeDirectives extends RangeDirectives {
private val respondWithAcceptByteRangesHeader: Directive0 =
RespondWithDirectives.respondWithHeader(`Accept-Ranges`(RangeUnits.Bytes))
}

View file

@ -0,0 +1,53 @@
package akka.http.scaladsl.server
package directives
import akka.http.scaladsl.model._
import scala.collection.immutable
trait RespondWithDirectives {
import BasicDirectives._
/**
* Overrides the given response status on all HTTP responses of its inner Route.
*/
def overrideStatusCode(responseStatus: StatusCode): Directive0 =
mapResponse(_.copy(status = responseStatus))
/**
* Unconditionally adds the given response header to all HTTP responses of its inner Route.
*/
def respondWithHeader(responseHeader: HttpHeader): Directive0 = respondWithHeaders(responseHeader)
/**
* Adds the given response header to all HTTP responses of its inner Route,
* if the response from the inner Route doesn't already contain a header with the same name.
*/
def respondWithDefaultHeader(responseHeader: HttpHeader): Directive0 = respondWithDefaultHeaders(responseHeader)
/**
* Unconditionally adds the given response headers to all HTTP responses of its inner Route.
*/
def respondWithHeaders(responseHeaders: HttpHeader*): Directive0 =
respondWithHeaders(responseHeaders.toList)
/**
* Unconditionally adds the given response headers to all HTTP responses of its inner Route.
*/
def respondWithHeaders(responseHeaders: immutable.Seq[HttpHeader]): Directive0 =
mapResponseHeaders(responseHeaders ++ _)
/**
* Adds the given response headers to all HTTP responses of its inner Route,
* if a header already exists it is not added again.
*/
def respondWithDefaultHeaders(responseHeaders: HttpHeader*): Directive0 =
respondWithDefaultHeaders(responseHeaders.toList)
/* Adds the given response headers to all HTTP responses of its inner Route,
* if a header already exists it is not added again.
*/
def respondWithDefaultHeaders(responseHeaders: immutable.Seq[HttpHeader]): Directive0 =
mapResponse(_.withDefaultHeaders(responseHeaders))
}
object RespondWithDirectives extends RespondWithDirectives

View file

@ -0,0 +1,59 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import akka.http.scaladsl.marshalling.ToResponseMarshallable
import akka.http.scaladsl.model._
import StatusCodes._
trait RouteDirectives {
/**
* Rejects the request with an empty set of rejections.
*/
def reject: StandardRoute = RouteDirectives._reject
/**
* Rejects the request with the given rejections.
*/
def reject(rejections: Rejection*): StandardRoute =
StandardRoute(_.reject(rejections: _*))
/**
* Completes the request with redirection response of the given type to the given URI.
*/
def redirect(uri: Uri, redirectionType: Redirection): StandardRoute =
StandardRoute {
_. //# red-impl
complete {
HttpResponse(
status = redirectionType,
headers = headers.Location(uri) :: Nil,
entity = redirectionType.htmlTemplate match {
case "" HttpEntity.Empty
case template HttpEntity(MediaTypes.`text/html`, template format uri)
})
}
//#
}
/**
* Completes the request using the given arguments.
*/
def complete(m: ToResponseMarshallable): StandardRoute =
StandardRoute(_.complete(m))
/**
* Bubbles the given error up the response chain, where it is dealt with by the closest `handleExceptions`
* directive and its ExceptionHandler.
*/
def failWith(error: Throwable): StandardRoute =
StandardRoute(_.fail(error))
}
object RouteDirectives extends RouteDirectives {
private val _reject = StandardRoute(_.reject())
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
trait SchemeDirectives {
import BasicDirectives._
/**
* Extracts the Uri scheme from the request.
*/
def extractScheme: Directive1[String] = SchemeDirectives._extractScheme
/**
* Rejects all requests whose Uri scheme does not match the given one.
*/
def scheme(name: String): Directive0 =
extractScheme.require(_ == name, SchemeRejection(name)) & cancelRejections(classOf[SchemeRejection])
}
object SchemeDirectives extends SchemeDirectives {
import BasicDirectives._
private val _extractScheme: Directive1[String] = extract(_.request.uri.scheme)
}

View file

@ -0,0 +1,187 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import scala.reflect.ClassTag
import scala.concurrent.Future
import akka.http.impl.util._
import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.util.FastFuture._
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server.AuthenticationFailedRejection.{ CredentialsRejected, CredentialsMissing }
/**
* Provides directives for securing an inner route using the standard Http authentication headers [[`WWW-Authenticate`]]
* and [[Authorization]]. Most prominently, HTTP Basic authentication as defined in RFC 2617.
*/
trait SecurityDirectives {
import BasicDirectives._
import HeaderDirectives._
import FutureDirectives._
import RouteDirectives._
/**
* The result of an HTTP authentication attempt is either the user object or
* an HttpChallenge to present to the browser.
*/
type AuthenticationResult[+T] = Either[HttpChallenge, T]
type Authenticator[T] = UserCredentials Option[T]
type AsyncAuthenticator[T] = UserCredentials Future[Option[T]]
type AuthenticatorPF[T] = PartialFunction[UserCredentials, T]
type AsyncAuthenticatorPF[T] = PartialFunction[UserCredentials, Future[T]]
/**
* Extracts the potentially present [[HttpCredentials]] provided with the request's [[Authorization]] header.
*/
def extractCredentials: Directive1[Option[HttpCredentials]] =
optionalHeaderValueByType[Authorization]().map(_.map(_.credentials))
/**
* A directive that wraps the inner route with Http Basic authentication support.
* The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route.
*/
def authenticateBasic[T](realm: String, authenticator: Authenticator[T]): AuthenticationDirective[T] =
authenticateBasicAsync(realm, cred FastFuture.successful(authenticator(cred)))
/**
* A directive that wraps the inner route with Http Basic authentication support.
* The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route.
*/
def authenticateBasicAsync[T](realm: String, authenticator: AsyncAuthenticator[T]): AuthenticationDirective[T] =
extractExecutionContext.flatMap { implicit ec
authenticateOrRejectWithChallenge[BasicHttpCredentials, T] { basic
authenticator(UserCredentials(basic)).fast.map {
case Some(t) AuthenticationResult.success(t)
case None AuthenticationResult.failWithChallenge(challengeFor(realm))
}
}
}
/**
* A directive that wraps the inner route with Http Basic authentication support.
* The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route.
*/
def authenticateBasicPF[T](realm: String, authenticator: AuthenticatorPF[T]): AuthenticationDirective[T] =
authenticateBasic(realm, authenticator.lift)
/**
* A directive that wraps the inner route with Http Basic authentication support.
* The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route.
*/
def authenticateBasicPFAsync[T](realm: String, authenticator: AsyncAuthenticatorPF[T]): AuthenticationDirective[T] =
extractExecutionContext.flatMap { implicit ec
authenticateBasicAsync(realm, credentials
if (authenticator isDefinedAt credentials) authenticator(credentials).fast.map(Some(_))
else FastFuture.successful(None))
}
/**
* Lifts an authenticator function into a directive. The authenticator function gets passed in credentials from the
* [[Authorization]] header of the request. If the function returns ``Right(user)`` the user object is provided
* to the inner route. If the function returns ``Left(challenge)`` the request is rejected with an
* [[AuthenticationFailedRejection]] that contains this challenge to be added to the response.
*
*/
def authenticateOrRejectWithChallenge[T](authenticator: Option[HttpCredentials] Future[AuthenticationResult[T]]): AuthenticationDirective[T] =
extractExecutionContext.flatMap { implicit ec
extractCredentials.flatMap { cred
onSuccess(authenticator(cred)).flatMap {
case Right(user) provide(user)
case Left(challenge)
val cause = if (cred.isEmpty) CredentialsMissing else CredentialsRejected
reject(AuthenticationFailedRejection(cause, challenge)): Directive1[T]
}
}
}
/**
* Lifts an authenticator function into a directive. Same as ``authenticateOrRejectWithChallenge``
* but only applies the authenticator function with a certain type of credentials.
*/
def authenticateOrRejectWithChallenge[C <: HttpCredentials: ClassTag, T](
authenticator: Option[C] Future[AuthenticationResult[T]]): AuthenticationDirective[T] =
authenticateOrRejectWithChallenge[T](cred authenticator(cred collect { case c: C c }))
/**
* Applies the given authorization check to the request.
* If the check fails the route is rejected with an [[AuthorizationFailedRejection]].
*/
def authorize(check: Boolean): Directive0 = authorize(_ check)
/**
* Applies the given authorization check to the request.
* If the check fails the route is rejected with an [[AuthorizationFailedRejection]].
*/
def authorize(check: RequestContext Boolean): Directive0 =
extract(check).flatMap[Unit](if (_) pass else reject(AuthorizationFailedRejection)) &
cancelRejection(AuthorizationFailedRejection)
/**
* Creates a ``Basic`` [[HttpChallenge]] for the given realm.
*/
def challengeFor(realm: String) = HttpChallenge(scheme = "Basic", realm = realm, params = Map.empty)
}
object SecurityDirectives extends SecurityDirectives
/**
* Represents authentication credentials supplied with a request. Credentials can either be
* [[UserCredentials.Missing]] or can be [[UserCredentials.Provided]] in which case a username is
* supplied and a function to check the known secret against the provided one in a secure fashion.
*/
sealed trait UserCredentials
object UserCredentials {
case object Missing extends UserCredentials
abstract case class Provided(username: String) extends UserCredentials {
def verifySecret(secret: String): Boolean
}
def apply(cred: Option[BasicHttpCredentials]): UserCredentials =
cred match {
case Some(BasicHttpCredentials(username, receivedSecret))
new UserCredentials.Provided(username) {
def verifySecret(secret: String): Boolean = secret secure_== receivedSecret
}
case None UserCredentials.Missing
}
}
import SecurityDirectives._
object AuthenticationResult {
def success[T](user: T): AuthenticationResult[T] = Right(user)
def failWithChallenge(challenge: HttpChallenge): AuthenticationResult[Nothing] = Left(challenge)
}
trait AuthenticationDirective[T] extends Directive1[T] {
import BasicDirectives._
import RouteDirectives._
/**
* Returns a copy of this [[AuthenticationDirective]] that will provide ``Some(user)`` if credentials
* were supplied and otherwise ``None``.
*/
def optional: Directive1[Option[T]] =
this.map(Some(_): Option[T]) recover {
case AuthenticationFailedRejection(CredentialsMissing, _) +: _ provide(None)
case rejs reject(rejs: _*)
}
/**
* Returns a copy of this [[AuthenticationDirective]] that uses the given object as the
* anonymous user which will be used if no credentials were supplied in the request.
*/
def withAnonymousUser(anonymous: T): Directive1[T] = optional map (_ getOrElse anonymous)
}
object AuthenticationDirective {
implicit def apply[T](other: Directive1[T]): AuthenticationDirective[T] =
new AuthenticationDirective[T] { def tapply(inner: Tuple1[T] Route) = other.tapply(inner) }
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
package directives
import akka.http.scaladsl.model.ws.{ UpgradeToWebsocket, Message }
import akka.stream.scaladsl.Flow
trait WebsocketDirectives {
import BasicDirectives._
import RouteDirectives._
import HeaderDirectives._
/**
* Handles websocket requests with the given handler and rejects other requests with a
* [[ExpectedWebsocketRequestRejection]].
*/
def handleWebsocketMessages(handler: Flow[Message, Message, Any]): Route =
extractFlowMaterializer { implicit mat
optionalHeaderValueByType[UpgradeToWebsocket]() {
case Some(upgrade) complete(upgrade.handleMessages(handler))
case None reject(ExpectedWebsocketRequestRejection)
}
}
}

View file

@ -0,0 +1,19 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl
import scala.concurrent.Future
package object server {
type Route = RequestContext Future[RouteResult]
type RouteGenerator[T] = T Route
type Directive0 = Directive[Unit]
type Directive1[T] = Directive[Tuple1[T]]
type PathMatcher0 = PathMatcher[Unit]
type PathMatcher1[T] = PathMatcher[Tuple1[T]]
def FIXME = throw new RuntimeException("Not yet implemented")
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server.util
import akka.http.scaladsl.server._
/**
* ApplyConverter allows generic conversion of functions of type `(T1, T2, ...) => Route` to
* `(TupleX(T1, T2, ...)) => Route`.
*/
abstract class ApplyConverter[L] {
type In
def apply(f: In): L Route
}
object ApplyConverter extends ApplyConverterInstances

View file

@ -0,0 +1,29 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server.util
/**
* Allows the definition of binary poly-functions (e.g. for folding over tuples).
*
* Note: the poly-function implementation seen here is merely a stripped down version of
* what Miles Sabin made available with his awesome shapeless library. All credit goes to him!
*/
trait BinaryPolyFunc {
def at[A, B] = new CaseBuilder[A, B]
class CaseBuilder[A, B] {
def apply[R](f: (A, B) R) = new BinaryPolyFunc.Case[A, B, BinaryPolyFunc.this.type] {
type Out = R
def apply(a: A, b: B) = f(a, b)
}
}
}
object BinaryPolyFunc {
sealed trait Case[A, B, Op] {
type Out
def apply(a: A, b: B): Out
}
}

Some files were not shown because too many files have changed in this diff Show more