415 Unsupported Media Type error when sending a PUT request to a RESTCONF API via Http4s

53 views Asked by At

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?

0

There are 0 answers