4

When I try to GET amazon identity data like that

val pipeline: HttpRequest => Future[IdentityData] = sendReceive ~> unmarshal[IdentityData]
pipeline(Get("http://169.254.169.254/latest/dynamic/instance-identity/document"))

with appropriate case class and formatter, I receive the following exception

UnsupportedContentType(Expected 'application/json')

because amazon mark their response as text/plain content type. They also don't care about the Accept header param. Is there an easy way to tell spray-json to ignore this on unmarshalling?

Daryl Odnert
  • 522
  • 3
  • 15
eugen-fried
  • 2,111
  • 3
  • 27
  • 48

3 Answers3

5

After digging in spray mail list I wrote a function that works

def mapTextPlainToApplicationJson: HttpResponse => HttpResponse = {
  case r@ HttpResponse(_, entity, _, _) =>
    r.withEntity(entity.flatMap(amazonEntity => HttpEntity(ContentType(MediaTypes.`application/json`), amazonEntity.data)))
  case x => x
}

and the used it in the pipeline

val pipeline: HttpRequest => Future[IdentityData] = sendReceive ~> mapTextPlainToApplicationJson ~> unmarshal[IdentityData]
pipeline(Get("http://169.254.169.254/latest/dynamic/instance-identity/document"))

The cool thing is here you can intercept & alter any HttpResponse as long as your intercepting function has the appropriate signature.

eugen-fried
  • 2,111
  • 3
  • 27
  • 48
  • There is no need to write your function, there is `mapHttpResponseEntity` directive – 4lex1v Jun 30 '14 at 14:26
  • And also there is no need in matching on HttpResponse entity and apply `withEntity`, cause it will check your entity arg and apply it if it is different (including empty entity case), so you can simply do `_.withEntity(..)` – 4lex1v Jun 30 '14 at 14:47
  • This solution is good, but would be improved by making sure that the `HttpCharset` from the original response's `ContentType` is preserved. Set the new content-type value to `ContentType(MediaTypes.\`applicaiton/json\`, amazonEntity.contentType.charset)` – Daryl Odnert Apr 07 '16 at 04:40
3

If you want to extract some IdentityData (which is a case class with a defined jsonFormat) from amazon response, which is a valid json, but with text/plain context type you can simply extract text data, parse it a json and convert into your data, e.g:

entity.asString.parseJson.convertTo(identityDataJsonFormat)
4lex1v
  • 21,367
  • 6
  • 52
  • 86
1

I've come up with a simpler/cleaner version of @yevgeniy-mordovkin's solution.

def setContentType(mediaType: MediaType)(r: HttpResponse): HttpResponse = {
  r.withEntity(HttpEntity(ContentType(mediaType), r.entity.data))
}

Usage:

val pipeline: HttpRequest => Future[IdentityData] = (
       sendReceive
    ~> setContentType(MediaTypes.`application/json`)
    ~> unmarshal[IdentityData]
)
pipeline(Get("http://169.254.169.254/latest/dynamic/instance-identity/document"))
vadipp
  • 877
  • 1
  • 12
  • 22
  • This is going to work only if the producer cares about what you are asking for. In my case it doesn't. – eugen-fried Jul 16 '14 at 07:45
  • I think you misread my post. I do the same thing as you (set the content type _after_ receiving the response), only the code is a bit cleaner. Minor improvement, nothing radical. – vadipp Jul 16 '14 at 11:08