Migration Guide from Spray
General notes
Features which are not ported to the akka-http:
respondWithStatus
also known asoverrideStatusCode
has not been forward ported to Akka HTTP, as it has been seen mostly as an anti-pattern. More information here: https://github.com/akka/akka/issues/18626respondWithMediaType
was considered an anti-pattern in spray and is not ported to Akka HTTP. Instead users should rely on content type negotiation as Akka HTTP implements it. More information here: https://github.com/akka/akka/issues/18625- Registering Custom Media Types changed from Spray in order not to rely on global state.
Removed HttpService
Spray’s HttpService
was removed. This means that scala code like this:
val service = system.actorOf(Props(new HttpServiceActor(routes)))
IO(Http)(system) ! Http.Bind(service, "0.0.0.0", port = 8080)
needs to be changed into:
Http().bindAndHandle(routes, "0.0.0.0", port = 8080)
Changes in Marshalling
Marshaller.of can be replaced with Marshaller.withFixedContentType
.
Was:
Marshaller.of[JsonApiObject](`application/json`) { (value, contentType, ctx) =>
ctx.marshalTo(HttpEntity(contentType, value.toJson.toString))
}
Replace with:
Marshaller.withFixedContentType(`application/json`) { obj =>
HttpEntity(`application/json`, obj.toJson.compactPrint)
}
Akka HTTP marshallers support content negotiation, now it's not necessary to specify content type when creating one “super” marshaller from other marshallers:
Before:
ToResponseMarshaller.oneOf(
`application/vnd.api+json`,
`application/json`
)(
jsonApiMarshaller,
jsonMarshaller
}
After:
Marshaller.oneOf(
jsonApiMarshaller,
jsonMarshaller
)
Changes in Unmarshalling
Akka Http contains a set of predefined unmarshallers. This means that scala code like this:
Unmarshaller[Entity](`application/json`) {
case HttpEntity.NonEmpty(contentType, data) =>
data.asString.parseJson.convertTo[Entity]
}
needs to be changed into:
Unmarshaller
.stringUnmarshaller
.forContentTypes(`application/json`)
.map(_.parseJson.convertTo[Entity])
Changes in MediaTypes
MediaType.custom
can be replaced with specific methods in MediaType
object.
Was:
MediaType.custom("application/vnd.acme+json")
Replace with:
MediaType.applicationWithFixedCharset("application/vnd.acme+json", HttpCharsets.`UTF-8`)
Changes in Rejection Handling
RejectionHandler
now uses a builder pattern – see the example:
Before:
def rootRejectionHandler = RejectionHandler {
case Nil =>
requestUri { uri =>
logger.error("Route: {} does not exist.", uri)
complete((NotFound, mapErrorToRootObject(notFoundError)))
}
case AuthenticationFailedRejection(cause, challengeHeaders) :: _ => {
logger.error(s"Request is rejected with cause: $cause")
complete((Unauthorized, mapErrorToRootObject(unauthenticatedError)))
}
}
After:
RejectionHandler
.newBuilder()
.handle {
case AuthenticationFailedRejection(cause, challengeHeaders) =>
logger.error(s"Request is rejected with cause: $cause")
complete((Unauthorized, mapErrorToRootObject(unauthenticatedError)))
.handleNotFound { ctx =>
logger.error("Route: {} does not exist.", ctx.request.uri.toString())
ctx.complete((NotFound, mapErrorToRootObject(notFoundError)))
}
.result()
.withFallback(RejectionHandler.default)
Changes in HTTP Client
The Spray-client pipeline was removed. Http’s singleRequest
should be used instead of sendReceive
:
//this will not longer work
val token = Authorization(OAuth2BearerToken(accessToken))
val pipeline: HttpRequest => Future[HttpResponse] = (addHeader(token) ~> sendReceive)
val patch: HttpRequest = Patch(uri, object))
pipeline(patch).map { response ⇒
…
}
needs to be changed into:
val request = HttpRequest(
method = PATCH,
uri = Uri(uri),
headers = List(Authorization(OAuth2BearerToken(accessToken))),
entity = HttpEntity(MediaTypes.`application/json`, object)
)
http.singleRequest(request).map {
case … => …
}
Changes in Headers
All HTTP headers have been moved to the akka.http.scaladsl.model.headers._
package.
Changes in form fields and file upload directives
With the streaming nature of http entity, it’s important to have a strict http entity before accessing multiple form fields or use file upload directives. One solution might be using next directive before working with form fields:
val toStrict: Directive0 = extractRequest flatMap { request =>
onComplete(request.entity.toStrict(5.seconds)) flatMap {
case Success(strict) =>
mapRequest( req => req.copy(entity = strict))
case _ => reject
}
}
And one can use it like this:
toStrict {
formFields("name".as[String]) { name =>
...
}
}
Contents