I have a service that consumes RESTCONF API requests. I'm trying to send a PUT request to this service with a request body of type application/yang-data+xml (since RESTCONF requires the request body to be in either application/yang-data+xml or application/yang-data+json). However, I'm receiving a response status 415 Unsupported Media Type error.
I'm sending this request via Http4s in Scala. The code is simplified for brevity.
//Implicit encoder to encode from String to XML
implicit val stringXmlWriter: XmlWriter[String] = { s => XML.loadString(s) }
//Implicit decoder to decode from XML to String
implicit val stringXmlParser: XmlParser[String] = { x => Left(x.toString()) }
//A XML in String format
val reqBody: String = ???
//Preparing headers
private def getHeaders(): Map[String, String] = {
val headers: Map[String, String] = Map(
"Authorization" -> getBasicAuthCredentials(),
"Access-Control-Allow-Origin" -> "https://abc.xyz",
"Content-Type" -> "application/yang-data+xml",
"Accept" -> "*/*"
)
headers
}
val headers = Headers(getHeaders().map(h => Header.Raw(CIString(h._1), h._2)).toList)
//Preparing request
val request = Request[IO](
method = Method.PUT,
uri = Uri.unsafeFromString(endpointUri),
headers = headers).withEntity(reqBody)
//Sending the request with Blaze client
val clientBuilder = BlazeClientBuilder[IO]
.withConnectTimeout(HttpClientConfig.connectTimeout)
.withIdleTimeout(HttpClientConfig.idleTimeout)
.withMaxTotalConnections(HttpClientConfig.maxOpenRequests)
.resource.use {
httpClient => httpClient.expectOr(request)(handleResponse)
}
try {
clientBuilder.unsafeToFuture().map(responseFuture => Right(Option(responseFuture)))
} catch {
case e: Exception =>
logger.error(s"Error sending ${httpMethod.name} to ${requestMetadata.endpointUri}")
Future.successful(Left(OperationNotExecutedException(e.getMessage)))
}
//Handle API exceptions
def handleResponse: Response[IO] => Nothing = {
r: Response[IO] =>
throw new RuntimeException(s"Failed to send API request via the Http4s client. Error status: ${r.status}")
}
Following is the code for XmlSerializer trait which is inherited by the above code.
trait XmlSerializer {
trait XmlWriter[A] {
def write(a: A): Node
}
trait XmlParser[A] {
def parse(node: Node): Either[String, A]
}
implicit def xmlEntityEncoder[F[_] : Monad, X](implicit writer: XmlWriter[X]): EntityEncoder[F, X] = {
EntityEncoder
.stringEncoder
.contramap { x: X =>
val node = writer.write(x)
val s = new PrettyPrinter(80, 2).format(node)
s
}.withContentType(`Content-Type`(MediaType.application.`yang-data+xml`))
}
implicit def xmlEntityDecoder[F[_] : Concurrent, X](implicit parser: XmlParser[X]): EntityDecoder[F, X] =
EntityDecoder
.decodeBy[F, X](MediaType.application.`yang-data+xml`)(msg =>
collectBinary(msg).flatMap { bs =>
val asString = new String(bs.toArray, msg.charset.getOrElse(Charset.`UTF-8`).nioCharset)
try {
val asJValue = XML.loadString(asString)
parser.parse(asJValue) match {
case Right(x) => DecodeResult.success(Concurrent[F].pure(x))
case Left(msg) => DecodeResult.failure(Concurrent[F].pure(InvalidMessageBodyFailure(msg)))
}
} catch {
case e: Exception =>
DecodeResult.failure(Concurrent[F].pure(MalformedMessageBodyFailure("bad Xml", Some(e))))
}
}
)
}
I'm uncertain about what I'm doing wrong here. Any suggestions?