Idiomatic way to create a basic HTTP Post request with Akka HTTP - scala

I'm trying to figure out how to create a basic HTTP POST request with the Akka HTTP library. This is what I came up with:
val formData = Await.result(Marshal(FormData(combinedParams)).to[RequestEntity], Duration.Inf)
val r = HttpRequest(POST, url, headers, formData)
The thing is that it seems a bit non-idiomatic to me. Are there other ways to create a HttpEntity from FormData? Especially the fact that I have to use Await or return a Future even though the data is readily available seems overly complex for such a simple task.

You can use Marshal in a for comprehension with other Futures, such as the ones you need to send the request and unmarshall the response:
val content = for {
request <- Marshal(formData).to[RequestEntity]
response <- Http().singleRequest(HttpRequest(method = HttpMethods.POST, uri = s"http://example.com/test", entity = request))
entity <- Unmarshal(response.entity).to[String]
} yield entity

Apparently a toEntity method was added to the FormData class at some point. So this now seems like the simplest solution to the problem:
val formData = FormData(combinedParams).toEntity
val r = HttpRequest(POST, url, headers, formData)

You can also use RequestBuilding:
Http().singleRequest(RequestBuilding.Post(url, formData)).flatMap(Unmarshal(_).to[String])

Related

Print the JSON response from GET request in Finagle

I am trying to do a GET request and print the JSON response that I get back. The JSON response should look like this.
{
"request_status": "Success",
"workflow_step": "End"
}
Here's my code:
handle(GetStatus) { args: GetStatus.Args =>
val client: Service[http.Request, http.Response] =
Http.client.withTlsWithoutValidation.newService("idm-preprod-fe1.twitter.biz:80")
val request = http.Request(
http.Method.Get,
"/plugin/rest/groupmanagement/getStatus/Create Group Request for mygroup (1638800484991)")
request.host = "idm-preprod-fe1.twitter.biz"
Future.value(GetStatusResponse(Option(client(request).toString)))
}
Now when my client sends the request to the server, I want to print the JSON but the format that I am getting is. Can someone please guide me as how to achieve this.
Promise#1589131174(state=Transforming(WaitQueue(size=1),Promise#1823443423(state=Transforming(WaitQueue(size=1),Promise#859399396(state=Transforming(WaitQueue(size=4),Promise#1441370332(state=Transforming(WaitQueue(size=2),Promise#1459834(state=Transforming(WaitQueue(size=2),Promise#156947070(state=Transforming(WaitQueue(size=1),Promise#1739595981(state=Transforming(WaitQueue(size=1),Promise#273198152(state=Transforming(WaitQueue(size=1),Promise#478329071(state=Transforming(WaitQueue(size=2),Promise#1175786031(state=Transforming(WaitQueue(size=1),Promise#1749285277(state=Transforming(WaitQueue(size=1),Promise#1733124454(state=Transforming(WaitQueue(size=1),Promise#1257379837(state=Transforming(WaitQueue(size=1),Promise#1192050340(state=Transforming(WaitQueue(size=1),Promise#1114225943(state=Transforming(WaitQueue(size=1),Promise#1417620904(state=Transforming(WaitQueue(size=1),Promise#1638767611(state=Interruptible(WaitQueue(size=2),<function1>))))))))))))))))))))))))))))))))))
client(request) is giving you a Future[http.Response]: it represents a response that will be available some time in the future.
You cannot use toString() on it directly because it's a Future and also because even if it was not a Future it would be a Response, which is a whole HTTP response, not only the body.
I'd recommend reading more about how Future works and how to use it.
In your case, you should do something like following (pseudo code as I'm not familiar with Twitter's Future):
client(request).map { httpResponse =>
val responseBody = httpResponse.body
GetStatusResponse(Option(responseBody))
}

Play WS API Making nested requests - WSClient

I want to make two HTTP requests(POST) using play2 WSRequest where some information from the first response is send to the second request. I tried to do this in following manner to make sure second request is only triggers after the first one is completed. But I get Type mismatch: cannot convert from F.Promise to F.Promise error
public Promise<Result> executeAPI(String apiName,JsonNode requestBody){
WSRequest pcLoginRequest = ws.url("http://xxxxx.qa.local:8080/mytest/rest/login");
pcLoginRequest.setContentType("application/json");
pcLoginRequest.setHeader("X-x-Password", "xxxxx")
.setHeader("X-x-Username", "xxxxx")
.setHeader("X-x-Content-Type", "application/json");
Promise<Result> myPromise = pcLoginRequest.post("").map(response -> {
ProApiSession.getInstanceOf().setProToeken(response.asJson().get("token").asText());
WSRequest pcrequest = ws.url("http://xxxxx.qa.local:8080/mytest/rest/api/" + apiName);
pcrequest.setContentType("application/json");
pcrequest.setHeader("X-x-Token",ProApiSession.getInstanceOf().getProToeken() )
.setBody(requestBody)
.setHeader("X-x-Content-Type", "application/json");
Promise<Result> myPromise2 = pcLoginRequest.post(requestBody).map(response2 -> {
return Results.ok(response2.asJson());
});
return myPromise;
});
Can someone please suggest how to do nested request using WSRequest in play. (import play.libs.ws.* )
Java 8 type inference errors are bad at the best of times. Since the result of the lambda you're passing to the first map is Promise<Result>, what you're trying to assign to myPromise is Promise<Promise<Result>>. What you actually want to do is replace the map call with flatMap, which is so named because is "flattens" the nested promise to just be a single promise.

PlayFramework Testing: Uploading File in Fake Request Errors

I want to test my method, which requires uploading a file. It is initialized like this:
val tempFile = TemporaryFile(new java.io.File("/home/ophelia/Desktop/students"))
val part = FilePart[TemporaryFile](
key = "students",
filename = "students",
contentType = Some("text/plain"),
ref = tempFile)
val files = Seq[FilePart[TemporaryFile]](part)
val formData = MultipartFormData(
dataParts = Map(),
files = Seq(part),
badParts = Seq(),
missingFileParts = Seq())
I pass it into the FakeRequest:
val result = route(
FakeRequest(POST, "/api/courses/"+"4f3c4ec9-46bf-4a05-a0b2-886c2040f2f6"+"/import" )
.withHeaders("Authorization" -> ("Session " + testSessionA.id.string))
.withMultipartFormDataBody(formData)
)
But when I run the test I get the following error:
Cannot write an instance of play.api.mvc.AnyContentAsMultipartFormData to HTTP response. Try to define a Writeable[play.api.mvc.AnyContentAsMultipartFormData]
What am I doing wrong and how to fix it? I looked on the internet, I didnt find any useful way to understand and resolve this problem.
It's important to remember that http requests are entirely text. route() takes an implicit Writeable to convert the body type of the provided request into text. Without the right Writeable, there is no way to know how to turn MultipartFormData into a request body.
There doesn't seem to be a Writeable for MultipartFormData, but you can provide your own. jroper has a great Writeable you could use for reference. (EDIT: That code is buggy, here's a working Writeable for AnyContentAsMultipartFormData)
Once you have your Writeable, you will need to make it accessible to your call to route(). Bear in mind, you currently have a FakeRequest[AnyContentAsMultipartFormData], not a FakeRequest[MultipartFormData]. You can either convert your request first:
val request = FakeRequest(POST,
"/api/courses/"+"4f3c4ec9-46bf-4a05-a0b2-886c2040f2f6"+"/import" )
.withHeaders("Authorization" -> ("Session "))
.withMultipartFormDataBody(formData)
route(request.map(_.mdf).asInstanceOf[FakeRequest[MultipartFormData[TemporaryFile]]])
or make your Writeable a Writeable[AnyContentAsMultipartFormData].
route for a given Request[T] requires an implicit parameter of type Writeable[T] that knows how to serialize the request body, because it will actually call the controller action just like an actual web request would, by pushing bytes onto it.
The problem is that there is no Writeable[MultipartFormData] predefined (you can see which are in play.api.test.Writeables).
This means you basically have two options:
write your own Writeable that serializes a MultipartFormData into bytes
Skip the routing part and call the action directly instead, like in the accepted answer in Play Framework Testing using MultipartFormData in a FakeRequest. This way of testing actions takes a shortcut and does not actually serialize and deserialize the request.
IMHO the first option is way too much pain for the gain, but if you go down that road, maybe contribute it to play when you succeed.
One of the possible solutions is to use wsUrl. For example
"File uploading action" should {
"upload sent file and result in ID" in {
val file = Paths.get(getClass.getResource("/1.txt").toURI)
val action = wsUrl("/upload").post(Source.single(FilePart("file", "hello.txt", Option("text/plain"), FileIO.fromPath(file))))
val res = Await.result(action, timeout)
res.status mustBe OK
res.body contains "123"
}
}

Sending a custom POST https request in dispatch (+cookies, + headers)

There is some documentation about sending post request in dispatch http://dispatch.databinder.net/Combined+Pages.html but yet it's not clear. What are myRequest and myPost there?
I want to send a https post request + add some cookies manually via headers + add some customs headers like form data, etc, and then read the response by reading the headers and cookies.
I only know how to prepare url for sending post request:
val url = host(myUrl + "?check=1&val1=123").secure
What do I do next?
Dispatch is built on top of Async Http Client. Therefore, myRequest in the example:
val myRequest = url("http://example.com/some/path")
is com.ning.http.client.RequestBuilder.
Calling the POST method on RequestBuilder turns the request into a POST request. That's what's happening in the myPost example:
def myPost = myRequest.POST
I often find the Dispatch documentation difficult to follow. For a quick overview of the all the various Dispatch operators, see: Periodic Table of Dispatch Operators
If you're asking how to build a POST request and add custom form parameters, you probably want to use the <<(values) operator like this:
val params = Map("param1" -> "val1", "param2" -> "val2")
val req = url("http://www.example.com/some/path" <<(params)
Likewise if you want to add some custom headers you can use the <:<(map) operator like this:
val headers = Map("x-custom1" -> "val1", "x-custom2" -> "val2")
val req = url("http://www.example.com/some/path" <<(params) <:<(headers)
Update: Actually, there is no POST method on RequestBuilder. The call to POST is part of Dispatch and calls setMethod on the underlying RequestBuilder. See dispatch.MethodVerbs for more details.

Getting an HTTP response as Array[Byte] with Scala and Dispatch

I'm trying to download the response of an HTTP GET request as an Array[Byte] in Scala using dispatch, but the documentation is not helping and online searches didn't turn out to be helpful.
Additionally, I need to retrieve the value of a header in the response.
Could anyone please provide a working snippet, possibly with a custom header?
Thanks in advance!
Came up with my own way:
val (someHeaderVal, buf) = Http x (url(fileUrl) <:< Map("ACustomHeader" -> "MyValue") >:+ {
(headers, req) => req >> {
stream => (headers("ResponseCustomHeader").head, IOUtils.toByteArray(stream))
}
})
This seems to work just fine.