Fail to post submittion of form-data - scala

Im trying to perform a POST call in Play that submit a form data with email and password text.
This is what I've tried so far but this does not compiles:
def ws: WSClient
ws.url(railsLoginApi).withHeaders("Content-type" -> "application/json").post(Form("email" -> "xxx", "pass" -> "xxx"))
But I get an error in Form("email" -> "xxx", "pass" -> "xxx") saying:
unspecified value parameters. erro: seq[FormError] value:
Option[NotInferedT]
Does someone knows how to perform this in play using Scala?

As the url is accepting the form data. Content type should be application/x-www-form-urlencoded and body format will be like MyVariableOne=ValueOne&MyVariableTwo=ValueTwo
ws.url(railsLoginApi)
.withHeaders("Content-type" -> "application/x-www-form-urlencoded")
.post[String](Map("email" -> "xxx", "pass" -> "xxx").map { case (k, v) => s"$k=$v"}.mkString("&"))

Related

Scala testing Web Service Client for POST request

So the Play documentation you can find here gives a very simple example for a GET call without anything like auth header or parameters.
Could somebody help me figure out how to use a similar strategy for something more complex like a POST request with JSON data as body and auth header required? I can't get it to work right now.
I want to know how to test a client using ws to do it's external http requests.
Thanks
Here is a snippet of code from one of my projects which sends sms via twilio api:
class SmsServiceImpl #Inject()(config: Configuration, ws: WSClient) extends SmsService {
val twilloAccountSid = config.getString("twillo.accountSid").get
val twilloAuthToken = config.getString("twillo.authToken").get
val smsApiUrl = config.getString("twillo.apiBaseUrl").get + "/" +twilloAccountSid+"/Messages.json"
override def send(phone: String, message: String): Future[Unit] = {
val data = Map(
"To" -> Seq(phone),
"From" -> Seq(config.getString("twillo.fromPhone").get),
"Body" -> Seq(message)
)
val response: Future[WSResponse] = ws.url(smsApiUrl).withAuth(twilloAccountSid, twilloAuthToken, WSAuthScheme.BASIC).post(data)
response.map { response =>
response.status match {
case 201 => Unit
case _ => throw new SmsSendingException(s"Failed to send sms. Got https status: ${response.status}")
}
}
}
It is POST request with authentication.

Invalid Json: No content to map due to end-of-input when using play body parser

I'm trying to test a controller method that attempts to parse JSON sent in the request:
def addRoomToProfileForTime = Action.async(parse.json[AddRoomToProfileForTimeRequest]) { request =>
profileService.addRoomToProfileForTime(request.body.roomId, request.body.profileId, request.body.timeRange).value.map {
case Xor.Right(_) => Ok
case Xor.Left(err) => BadRequest(Json.toJson(err))
}
}
This is the case class that represents the request:
final case class AddRoomToProfileForTimeRequest(
roomId: Int,
profileId: Int,
timeRange: TimeRange
)
implicit val addRoomToProfileForTimeRequestFormat:Format[AddRoomToProfileForTimeRequest] = Json.format
This code works as expected with I make a request like so:
curl -H "Content-Type: application/json" -X POST -d '{"roomId":3,"profileId":1,"timeRange":{"id":1,"fromTime":"2000-01-01T01:01","toTime":"2000-01-01T02:01"}}' http://localhost:9000/api/profiles/addRoomToProfileForTime
But I'm trying to write a test for this method (note that I am using macwire for dependency injection, hence cannot use WithApplication:
"add a room to profile for time" in new TestContext {
val roomId = 1
val profileId = 1
val from = "2000-01-01T01:01"
val to = "2000-01-01T02:01"
val requestJson = Json.obj(
"roomId" -> roomId,
"profileId" -> profileId,
"timeRange" -> Json.obj(
"id" -> 1,
"fromTime" -> from,
"toTime" -> to
)
)
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val fakeReq = FakeRequest(Helpers.POST, "api/profiles/addRoomToProfileForTime")
.withHeaders(CONTENT_TYPE -> "application/json")
.withJsonBody(requestJson)
val result = profileController.addRoomToProfileForTime()(fakeReq).run
val content = contentAsString(result)
println(content)
status(result) must equalTo(OK)
}
However, this test fails with a Bad Request from Play:
<body>
<h1>Bad Request</h1>
<p id="detail">
For request 'POST api/profiles/addRoomToProfileForTime' [Invalid Json: No content to map due to end-of-input at [Source: akka.util.ByteIterator$ByteArrayIterator$$anon$1#37d14073; line: 1, column: 0]]
</p>
</body>
If I parse the JSON with request.body.asJson the method behaves as expected. It's only using the body parser method above that I get this error.
The short answer is: on your FakeRequest in your controller test use the withBody method instead of withJsonBody.
I had this issue as well, and I embarrassingly spent hours on it until I figured it out. The long answer is that FakeRequest's withJsonBody returns a FakeRequest[AnyContentAsJson], and since your controller is expecting a JsValue (not an AnyContentAsJson), when you call apply() on your action it fails to match this apply method, which is the one you want:
def apply(request: Request[A]): Future[Result]
and instead hits this apply method:
def apply(rh: RequestHeader): Accumulator[ByteString, Result]
and thus since you're not then passing any Bytes to the Accumulator, you get the unexpected end-of-input error message you're getting.
Another reason can be not setting Content-Length in Postman application. By mistake I had disabled it, and forgot to enable it. .

Playframework Scala Transform Request from Json to x-www-form-urlencoded

Hi I am new to Scala and Playframework. I am getting an ajax request that is json format and I need to make another request to another server with x-www-form-urlencoded format.
I have this code in the controller
def getToken = Action.async(parse.json) { request =>
WS.url("https://api.xxxx.com/v1/yyyy")
.withHeaders(
"accept" -> "application/json",
"content-type" -> "application/x-www-form-urlencoded",
"Authorization" -> "Auth %s".format(apiKey)
).post(request.body) map { response =>
Logger.info("response from get user: " + Json.prettyPrint(response.json))
Ok("ok")
}
}
I tried different ways but I can't get this working. Maybe I should do a formatter in the model. Which would be the best way to cast the request json to a request x-www-form-urlencoded?
thank you
Short Answer
You just need to pass a Map[String, Seq[String]] to the post method.
If you have some keys and values you can easily construct such a Map.
For instance:
WS.url(url).post(Map("key1" -> Seq("value1"), "key2" -> Seq("value2")))
Details
As mentioned in Form Submission section of Play WS docs, if you want to submit a form you have to pass a Map[String, Seq[String]] to the post method.
What you are passing here, instead, is a JsValue (because the request.body is of type JsValue because of the body parser type which is parser.json). So first you have to extract the set of key/value pairs from the json object and then construct a Map[String, Seq[String]] from it and pass it to post method.
For example if you are sure that the json object (extracted from the request body by body parser) is a JSON Object (not an Array or String or a Numeric Value) you can construct the needed Map easily (just for the first level key/value pairs):
def getToken = Action.async(parse.json) { request =>
import play.api.libs.json.JsObject
val json = request.body.as[JsObject]
val formParamsMap = json.value.mapValues(_.asOpt[String].toSeq)
WS.url("https://api.xxxx.com/v1/yyyy")
.withHeaders(
"accept" -> "application/json",
"content-type" -> "application/x-www-form-urlencoded",
"Authorization" -> "Auth %s".format(apiKey)
)
.post(formParamsMap) map { response =>
Ok("ok")
}
}

Play framework Ws: how create request without encode?

In this moment, I do integration of our project on Play framework and gis service.
Gis service works with http get queries.
Here the right example of get point request:
http://myGisServer.ru/myservice?token=XYZ%3D&query=[{"id":"123","objectIds":["5"]}]
Where token is token form auth service, and this must be encoded for http request.
And query is not encoded json with specified parameters.
I create this request with ws:
def getPoint = Action.async{
val data = Json.obj(
"id" -> "123",
"objectIds" -> Json.arr("5")
)
val ws = WS.url("http://myGisServer.ru/myservice").withQueryString(
"token" -> currentToken,
"query" -> data.toString()
)
val futureResponse: Future[WSResponse] = ws.get()
futureResponse.map(response => {
Ok(response.json)
})
}
But that doesn't work because ws encode all request, including json string.
How can I exclude json from encoding, or create other query without encoding?

PlayFramework FakeRequest returns 400 error

In routes:
POST /login controllers.ApplicationCtrl.login()
In Controller:
def login = Action(parse.json) { implicit request => {
val email = (request.body \ "email").as[String]
val password = (request.body \ "password").as[String]
Ok(Json.toJson(
Map("status" -> "OK",
"message" -> "%s created".format(email))
))
}
In tests
"login" in new WithApplication{
val request = route( FakeRequest(
Helpers.POST,
controllers.routes.ApplicationCtrl.login.url,
FakeHeaders(Seq(CONTENT_TYPE -> Seq("application/json"))),
""" {"email" : "bob#mail.com", "password" : "secret"} """
)).get
status(request) must equalTo(OK)
}
When I test using command line:
curl --header "Content-type: application/json" --request POST --data '{"email" : "bob#mail.com", "password" : "secret"}' http://localhost:9000/login
It gets desirable response.
{"status":"OK","message":"bob#mail.com created"}
But the test returns 400 error.
What's wrong?
(command line test wins by simplicity and understandability)
What's happening here is that Play sets the content type of the request according to the type of the body. You're using a string body so that the content type header you're setting is later overridden by text/plain; charset=utf-8.
Because you're explicitly parsing the body as Json the body parser will return a bad request 403 if the content type is not either text/json or application/json.
The best thing to do in your case is to use a Json body, i.e:
"login" in new WithApplication {
val request = route( FakeRequest(
POST,
controllers.portal.routes.Portal.test.url,
FakeHeaders(Seq.empty),
play.api.libs.json.Json.obj("email" -> "bob#mail.com", "password" -> "secret")
)).get
status(request) must equalTo(OK)
}
Note that you can make that a bit more succinct by letting an alternate FakeRequest constructor infer the method and URL of your action from the call:
val request = route(FakeRequest(controllers.portal.routes.Portal.test)
.withBody(Json.obj("email" -> "bob#mail.com", "password" -> "secret"))).get
Data types you can use as the body parameter and their content type mapping:
JsValue -> application/json
NodeSeq -> text/xml
String -> text/plain
Map[String, Seq[String]] -> application/x-www-form-urlencoded
Array[Byte] -> nothing
There's also the option of using the tolerantJson as a body parser to skip checking the content type completely.