Migration Guide from Spray

Migration Guide from Spray

General notes

Features which are not ported to the akka-http:

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