Paris 2013-10-24
Mathias Doenitz
/
/
This presentation: http://spray.io/scala.io/
Isn't HTTP on the JVM a "solved" problem?
Can't we just use Netty?
(or Servlets,
or Restlet,
or Undertow, ...)
(it's being done all the time)
plus:
case class HttpRequest(
method: HttpMethod = HttpMethods.GET,
uri: Uri = Uri./,
headers: List[HttpHeader] = Nil,
entity: HttpEntity = HttpEntity.Empty,
protocol: HttpProtocol = HttpProtocols.`HTTP/1.1`
) extends HttpMessage
case class HttpResponse(
status: StatusCode = StatusCodes.OK,
entity: HttpEntity = HttpEntity.Empty,
headers: List[HttpHeader] = Nil,
protocol: HttpProtocol = HttpProtocols.`HTTP/1.1`
) extends HttpMessage
case class Uri( // proper RFC 3986
scheme: String, // compliant,
authority: Authority, // immutable
path: Path, // URI model
query: Query, // with a fast,
fragment: Option[String]) // custom parser
case class `Accept-Charset`(charsetRanges: Seq[HttpCharsetRange])
extends HttpHeader
case class `Accept-Encoding`(encodings: Seq[HttpEncodingRange])
extends HttpHeader
case class `Set-Cookie`(cookie: HttpCookie)
extends HttpHeader
case class RawHeader(name: String, value: String)
extends HttpHeader
class PingPongService extends Actor {
def receive = {
// when a new connection comes in we register
// ourselves as the connection handler
case _: Http.Connected ⇒ sender ! Http.Register(self)
// can you guess what this does?
case HttpRequest(GET, Uri.Path("/"), _, _, _) ⇒
sender ! HttpResponse(entity = "PONG")
}
}
class MyServiceActor extends HttpServiceActor {
def receive = runRoute {
path("order" / HexIntNumber) { id =>
get {
complete {
"Received GET request for order " + id
}
} ~
put {
complete {
"Received PUT request for order " + id
}
}
}
}
}
alwaysCache, anyParam, anyParams, authenticate, authorize, autoChunk, cache, cachingProhibited, cancelAllRejections, cancelRejection, clientIP, complete, compressResponse, compressResponseIfRequested, cookie, decodeRequest, decompressRequest, delete, deleteCookie, detach, dynamic, dynamicIf, encodeResponse, entity, extract, failWith, formField, formFields, get, getFromBrowseableDirectories, getFromBrowseableDirectory, getFromDirectory, getFromFile, getFromResource, getFromResourceDirectory, handleExceptions, handleRejections, handleWith, head, headerValue, headerValueByName, headerValuePF, hextract, host, hostName, hprovide, jsonpWithParameter, listDirectoryContents, logRequest, logRequestResponse, logResponse, mapHttpResponse, mapHttpResponseEntity, mapHttpResponseHeaders, mapHttpResponsePart, mapInnerRoute, mapRejections, mapRequest, mapRequestContext, mapRouteResponse, mapRouteResponsePF, method, noop, onComplete, onFailure, onSuccess, optionalCookie, optionalHeaderValue, optionalHeaderValueByName, optionalHeaderValuePF, options, overrideMethodWithParameter, parameter, parameterMap, parameterMultiMap, parameters, parameterSeq, pass, patch, path, pathPrefix, pathPrefixTest, pathSuffix, pathSuffixTest, post, produce, provide, put, rawPath, rawPathPrefix, rawPathPrefixTest, redirect, reject, rejectEmptyResponse, requestEncodedWith, requestEntityEmpty, requestEntityPresent, respondWithHeader, respondWithHeaders, respondWithLastModifiedHeader, respondWithMediaType, respondWithSingletonHeader, respondWithSingletonHeaders, respondWithStatus, responseEncodingAccepted, rewriteUnmatchedPath, routeRouteResponse, scheme, schemeName, setCookie, unmatchedPath, validate
lazy val route = {
encodeResponse(Gzip) {
pathSingleSlash {
get {
redirect("/doc")
}
} ~
pathPrefix("api") {
jsonpWithParameter("callback") {
path("top-articles") {
get {
parameter("max".as[Int]) { max =>
validate(max >= 0, "query parameter 'max' must be >= 0") {
complete {
(topArticlesService ? max).mapTo[Seq[Article]]
}
}
}
}
} ~
tokenAuthenticate { user =>
path("ranking") {
get {
countAndTime(user, "ranking") {
parameters("fixed" ? 0, "mobile" ? 0, "sms" ? 0, "mms" ? 0,
"data" ? 0).as(RankingDescriptor) { descr =>
complete {
(rankingService ? Ranking(descr)).mapTo[RankingResult]
}
}
}
}
} ~
path("accounts") {
post {
authorize(user.isAdmin) {
entity(as[AccountDetails]) { details =>
complete {
(accountService ? NewAccount(details)).mapTo[OpResult]
}
}
}
}
} ~
path("account" / IntNumber) { accountId =>
get { ... } ~
put { ... } ~
delete { ... }
}
}
} ~
pathPrefix("v1") {
proxyToDjango
}
} ~
pathPrefix("doc") {
respondWithHeader(`Cache-Control`(`max-age`(3600))) {
transformResponse(_.withContentTransformed(markdown2Html)) {
getFromResourceDirectory("doc/root",
pathRewriter = appendFileExt)
}
}
} ~
} ~
cacheIfEnabled {
encodeResponse(Gzip) {
getFromResourceDirectory("public")
}
}
}