How to call a POST to another service and receive json data - scala

I have two Akka HTTP services (Service 1 and Service 2). One needs to communicate with the other.
Service 2 provides a POST endpoint /lastname that takes in json payload like this {name: "doe"} and returns {"returnVal":false} or {"returnVal":true}
Question
How can Service 1 connect to it, send json payload, and fetch the boolean response?
I am trying below:
val responseFuture: Future[HttpResponse] = Http().singleRequest(
HttpRequest(
uri = s"$host:$port/my/endpoint")
entity = HttpEntity(MediaTypes.`application/json`, method = HttpMethods.POST)
)
but I can't figure out out to send json data {name: "doe"} along with the request.

for creating JSON you need to use json marshaller like upickle, spray-json. I use spray json for converting scala classes to json and vice versa. I assume Service2 take {"name": "paul"} as request and return {"exist": true} as response.
import spray.json.{DefaultJsonProtocol, RootJsonFormat}
case class NameExistsRequest(name: String)
case class NameExistsResponse(exist: Boolean)
object ServiceJsonProtocolNameExists extends DefaultJsonProtocol{
implicit val nameExistsRequestJF: RootJsonFormat[NameExistsRequest] = jsonFormat1(NameExistsRequest)
implicit val nameExistsResponseJF: RootJsonFormat[NameExistsResponse] = jsonFormat1(NameExistsResponse)
}
case object NameExistsError extends RuntimeException with NoStackTrace
case object InternalError extends RuntimeException with NoStackTrace
def sendNameExistsRequest(): Future[NameExistsResponse] = {
import ServiceJsonProtocolNameExists._
import spray.json._
val endpoint = "http://your-endpoint"
val request = NameExistsRequest("paul")
val httpRequest = HttpRequest(
method = HttpMethods.POST,
uri = Uri(endpoint),
headers = List.empty,
entity = HttpEntity(ContentTypes.`application/json`, request.toJson.toString)
)
Http().singleRequest(httpRequest).transformWith {
case Success(response) =>
response match {
case HttpResponse(StatusCodes.OK, _, entity, _) =>
Unmarshal(entity).to[String].map {content =>
content.parseJson.convertTo[NameExistsResponse]
}
case res =>
println("failure response: " + res)
Future.failed(NameExistsError)
}
case Failure(f) =>
println("failed to send request, caused by: " + f)
Future.failed(InternalError)
}
}
which NameExistsRequest represent your request and we convert it to json with toJson method and NameExistsResponse represent your response which returned by Service2. we use Unmarshall and convertTo, to convert json to scala class.

Related

Akka HTTP POST route JSON deserialization

I am new in Scala Akka httpd and I am trying to create a post route that can accept JSON data for serialization and return another JSON response. So far I created the below code but the code has some error and I am unable to solve it.
I also upload the project in github: https://github.com/devawal/akka-http-bidder
// This class will take POST JSON data and process for internal matching
case class BidRequest(id: String, imp: Option[List[Impression]], site: Site, user: Option[User], device: Option[Device])
// BidResponse protocol:
// This will return as HTTP JSON response
case class BidResponse(id: String, bidRequestId: String, price: Double, adid: Option[String], banner: Option[Banner])
trait ServiceJsonProtoocol extends DefaultJsonProtocol {
implicit val customerProtocol = jsonFormat5(BidResponse)
}
object Bidder extends App with ServiceJsonProtoocol{
implicit val system = ActorSystem("bid_request")
implicit val meterializer = ActorMaterializer()
import system.dispatcher
import akka.http.scaladsl.server.Directives._
// Define common HTTP entity
def toHttpEntity(payload: String) = HttpEntity(ContentTypes.`application/json`, payload)
//implicit val timeOut = Timeout(2 seconds)
// Server code
val httpServerRoute =
pathPrefix("api" / "bid-request") {
post {
entity(as[requestData]){ request =>
}
}
}
Http().bindAndHandle(httpServerRoute, "localhost", 8080)
}
My request JSON is:
{"id":"XN2zZQABxJsKK0jU4QnIzw","imp":[{"id":"1","banner":{"w":300,"h":250,"pos":3,"format":[{"w":300,"h":250}]},"displaymanager":"GOOGLE","tagid":"4254117042","bidfloor":0.19,"bidfloorcur":"USD","secure":1,"metric":[{"type":"click_through_rate","value":0,"vendor":"EXCHANGE"},{"type":"viewability","value":0.80000000000000004,"vendor":"EXCHANGE"},{"type":"session_depth","value":13,"vendor":"EXCHANGE"}],"ext":{"billing_id":["71151605235"],"publisher_settings_list_id":["18260683268513888323","2151365641960790044"],"allowed_vendor_type":[79,138,144,237,238,331,342,414,445,474,485,489,523,550,566,698,704,743,745,767,776,780,785,797,828,4374,4513,4648,4651,4680,4697],"ampad":3}}],"site":{"page":"http://jobs.bdjobs.com/jobsearch.asp","publisher":{"id":"pub-5130888087776673","ext":{"country":"BD"}},"content":{"contentrating":"DV-G","language":"en"},"ext":{"amp":0}},"device":{"ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0","ip":"103.219.170.0","geo":{"lat":23.818607330322266,"lon":90.433006286621094,"country":"BGD","utcoffset":360},"os":"Windows","devicetype":2,"pxratio":1},"user":{"id":"CAESEGTB3gVLV019BLOZ9saadwo","data":[{"id":"DetectedVerticals","name":"DoubleClick","segment":[{"id":"5076","value":"0.8"},{"id":"960","value":"1"},{"id":"61","value":"0"},{"id":"330","value":"0.1"},{"id":"959","value":"0.1"}]}]},"at":2,"tmax":122,"cur":["USD"],"ext":{"google_query_id":"ANy-zJH36AVK9h5uCf4Z0xfyWjrBZU6M7mxGfgQE9A_qw_HahXk9khANxU7o1KDEIsfldK0DAw"}}
How can I process Input JSON and send response?
Update
I updated my route to point with case class but got error in console
val httpServerRoute =
post {
path("bid-request")
entity(implicitly[FromRequestUnmarshaller[requestData]]) { request =>
complete((dataBiddermap ? getBidderData(request)).map(
_ => StatusCodes.OK
))
}
}
Error message:
Error:(95, 25) could not find implicit value for parameter e: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[bidder.requestData]
entity(implicitly[FromRequestUnmarshaller[requestData]]) { request =>
Error:(95, 25) not enough arguments for method implicitly: (implicit e: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[bidder.requestData])akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[bidder.requestData].Unspecified value parameter e.
entity(implicitly[FromRequestUnmarshaller[requestData]]) { request =>
I use this pattern for post requests in my server, hope this helps:
post {
decodeRequest {
entity(as[BidRequest]) { bidRequest =>
val response: BidResponse = process(bidRequest)
complete(response)
}
}
}
The marshalers for the request and response types are in scope.

How to check the request body parameters type validation in akka http micro-services?

my Object is
case class Request(id:Int,name:String,phone:String)
my request in postman is
{
"id": "1205", **here i have changed the request body parameter type Int to String**
"name": "sekhar",
"phone":"1234567890"
}
how can I check the request parameter is valid or invalid when my request body field is the wrong data type
I have used
implicit def myRejectionHandler = RejectionHandler.newBuilder()
.handle {
case MissingQueryParamRejection(param) =>
println(" Test1 ")
val errorResponse = ErrorResponse(BadRequest.intValue, "Missing Parameter", s"The required $param was not found.")
var json:JsValue=Json.toJson(errorResponse)
complete(HttpResponse(BadRequest, entity = HttpEntity(ContentTypes.`application/json`, json.toString())))
}
.handle { case MissingFormFieldRejection(msg) =>
println(" Test2 ")
complete(BadRequest, msg)
}
.handle { case MalformedQueryParamRejection(msg,error,cause) =>
println(" Test3 ")
complete(BadRequest, msg)
}
.handleAll[MethodRejection] { methodRejections =>
val names = methodRejections.map(_.supported.name)
println(" Test4 ")
complete((MethodNotAllowed, s"Can't do that! Supported: ${names mkString " or "}!"))
}
.handleNotFound { complete((NotFound, "Not here!")) }
.result()
val routes: Route = handleRejections(myRejectionHandler) {
//Routes
}
Http().bindAndHandle(routes, "localhost", 8090)
it's, again and again, takes only handleAll[MethodRejection] when being changed the query params(for the false parameter too) on that time too.
If you are using Spray Json, then you might have created a format for your case class, it should look like this:
Assuming:
case class Request(id:Int, name:String, phone:String)
You should have a trait like:
import spray.json._
trait RequestJsonSupport extends DefaultJsonProtocol {
implicit val requestFormat = jsonFormat3(Request.apply)
}
And then extend it on your route class:
class MyRouteClass(...) extends RequestJsonSupport {...}
That way your Akka Http instance knows how to parse a Json input and convert it into your case class. Then you can worry about missing fields and such. Spray will take care of this.
For example, if you sent this:
{
"id": "1205",
"name": "sekhar",
"phone":"1234567890"
}
Spray will throw an:
The request content was malformed:
Expected Int as JsNumber, but got "1205"
Check out Spray Json repo here.

Akka-http logrequest not logging the request body

I am using akka-http and trying to log a request on a specific path using logrequest :
path(Segment / "account") { id =>
logRequest("users/account", Logging.InfoLevel) {
post {
entity(as[Account]) { account => ???
complete(HttpResponse(StatusCodes.NoContent))
}
}
}
however on my log I see something like
HttpRequest(HttpMethod(POST),https://localhost:9009/api/users/123/account,List(Host: localhost:9009, User-Agent: akka-http/10.0.6, Timeout-Access: <function1>),HttpEntity.Chunked(application/json),HttpProtocol(HTTP/1.1))
what I am looking for is the exact request including the body (json) as it was sent by the requestor.
The "HttpEntity.Chunked(application/json)" segment of the log is the output of HttpEntity.Chunked#toString. To get the entire request body, which is implemented as a stream, you need to call HttpEntity#toStrict to convert the Chunked request entity into a Strict request entity. You can make this call in a custom route:
def logRequestEntity(route: Route, level: LogLevel)
(implicit m: Materializer, ex: ExecutionContext) = {
def requestEntityLoggingFunction(loggingAdapter: LoggingAdapter)(req: HttpRequest): Unit = {
val timeout = 900.millis
val bodyAsBytes: Future[ByteString] = req.entity.toStrict(timeout).map(_.data)
val bodyAsString: Future[String] = bodyAsBytes.map(_.utf8String)
bodyAsString.onComplete {
case Success(body) =>
val logMsg = s"$req\nRequest body: $body"
loggingAdapter.log(level, logMsg)
case Failure(t) =>
val logMsg = s"Failed to get the body for: $req"
loggingAdapter.error(t, logMsg)
}
}
DebuggingDirectives.logRequest(LoggingMagnet(requestEntityLoggingFunction(_)))(route)
}
To use the above, pass your route to it:
val loggedRoute = logRequestEntity(route, Logging.InfoLevel)

RequestHeader doesn’t contain the request body at Play Framework 2.0 till now

I wonder, how to get a body from the request if in the doc:
trait RequestHeader extends AnyRef
The HTTP request header. Note that it doesn’t contain the request body yet.
That seems from very 2.0 version ..
Trying to get example of async handing the request's body. In order to log it to file.
object AccessLoggingFilter extends EssentialFilter {
def apply(inputAction: EssentialAction) = new EssentialAction { request =>
val accessLogger = Logger("access")
def apply(requestHeader: RequestHeader): Iteratee[Array[Byte], Result] = { ...
Logger.info(s"""Request:
Body = ${requestHeader.???} """)
There are some philosophical answers on SO, here for example. But I would not call it answer..
Yes, play doesn't allow to access the body of the request in the filter stage.
If you only want to log the body, you can create a new action for that and compose it.
This is an example from play 2.6
def withLogging(action: Action[AnyContent]): Action[AnyContent] = {
Action.async(action.parser) { request =>
request.body match {
case AnyContentAsJson(json) => Logger.info("JSON body was: " + Json.stringify(json))
case _ => //implement more logging of different body types
}
action(request)
}
}
def foo = withLogging(Action.async(cc.parsers.anyContent) { implicit request =>
// do your stuff
}))
If you have only json endpoints, you can write a specific action for that.

Return exact response/header?

From the client-side of a webapp, I hit a server-side route which is just a wrapper for a third-party API. Using dispatch, I am trying to make that server-side request return the exact header and response of the third-party API to the client-side AJAX call.
When I do this:
val req = host("third-pary.api.com, 80)
val post = req.as("user", "pass") / "route" << Map("key" -> "akey", "val" -> "aval")
Http(post > as.String)
I always see a 200 response returned to the AJAX call (kind of expectedly). I have seen an Either syntax used, but I'm really more of an Any, as it's just the exact response and header. How would this be written?
I should mention I'm using Scalatra on the server-side, so the local route is:
post("/route") {
}
EDIT:
Here is the suggested Either matching example, which I'm playing with, but the match syntax doesn't make sense - I don't care if there is an error, I just want to return it. Also, I can't seem to get the BODY returned with this method.
val asHeaders = as.Response { response =>
println("BODY: " + response.getResponseBody())
scala.collection.JavaConverters.mapAsScalaMapConverter(
response.getHeaders).asScala.toMap.mapValues(_.asScala.toList)
}
val response: Either[Throwable, Map[String, List[String]]] =
Http(post > asHeaders).either()
response match {
case Left(wrong) =>
println("Left: " + wrong.getMessage())
// return Action with header + body
case Right(good) =>
println("Right: " + good)
// return Action with header + body
}
Ideally, the solutions returns the Scalatra ActionResult(responseStatus(status, reason), body, headers).
It's actually very easy to get response headers while using Dispatch. For example with 0.9.4:
import dispatch._
import scala.collection.JavaConverters._
val headers: java.util.Map[String, java.util.List[String]] = Http(
url("http://www.google.com")
)().getHeaders
And now, for example:
scala> headers.asScala.mapValues(_.asScala).foreach {
| case (k, v) => println(k + ": " + v)
| }
X-Frame-Options: Buffer(SAMEORIGIN)
Transfer-Encoding: Buffer(chunked)
Date: Buffer(Fri, 30 Nov 2012 20:42:45 GMT)
...
If you do this often it's better to encapsulate it, like this, for example:
val asHeaders = as.Response { response =>
scala.collection.JavaConverters.mapAsScalaMapConverter(
response.getHeaders
).asScala.toMap.mapValues(_.asScala.toList)
}
Now you can write the following:
val response: Either[Throwable, Map[String, List[String]]] =
Http(url("http://www.google.com") OK asHeaders).either()
And you've got error checking, nice immutable collections, etc.
We needed the response body of failed requests to an API, so we came up with this solution:
Define your own ApiHttpError class with code and body (for the body text):
case class ApiHttpError(code: Int, body: String)
extends Exception("Unexpected response status: %d".format(code))
Define OkWithBodyHandler similar to what is used in the source of displatch:
class OkWithBodyHandler[T](f: Response => T) extends AsyncCompletionHandler[T] {
def onCompleted(response: Response) = {
if (response.getStatusCode / 100 == 2) {
f(response)
} else {
throw ApiHttpError(response.getStatusCode, response.getResponseBody)
}
}
}
Now, near your call to the code that might throw and exception (calling API), add implicit override to the ToupleBuilder (again similar to the source code) and call OkWithBody on request:
class MyApiService {
implicit class MyRequestHandlerTupleBuilder(req: Req) {
def OKWithBody[T](f: Response => T) =
(req.toRequest, new OkWithBodyHandler(f))
}
def callApi(request: Req) = {
Http(request OKWithBody as.String).either
}
}
From now on, fetching either will give you the [Throwable, String] (using as.String), and the Throwable is our ApiHttpError with code and body.
Hope it helped.