0

UPDATED: Method is returning type ANY rather than type Future[string]. Require return of type String.

I'm making a http request using the play.ws library 2.6. This was previously done with a curl request but this only uses basic authentication.

Below is my code and I'm trying to return a json string from this function to be deserialised in another method.

import java.io.{File, InputStream}
import java.nio.file.Paths
import javax.inject._

import org.apache.commons.io.FileUtils

import play.api._
import play.api.http.HttpEntity
import play.api.libs.ws._
import play.api.mvc._
import play.api.Play.current

import scala.collection.mutable.ListBuffer
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

import sys.process._

@Singleton
class BuildService @Inject() (
ws: WSClient,
ec: ExecutionContext,
config: Configuration) {

    def bbApiRequest(requestUrl: String, timeout: FiniteDuration): 
        Future[String] = {
        val request = ws
            .url(requestUrl)
            .withAuth(
                "user", 
                "pw1234",
                WSAuthScheme.BASIC)
            .get()
        Await.result(request, timeout)
        val returner = request.map(_.json)
    } // <-- line 72 in below error points here.
} 

When run it produces the error:

[error] C:\my_path\app\services\BuildService.scala:72: type mismatch;
[error]  found   : Unit
[error]  required: scala.concurrent.Future[String]
[error]         }
[error]         ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed
[info] Compiling 1 Scala source to C:\my_path\restapi\target\scala-
       2.12\classes...
[error] C:\my_path\restapi\app\services\BuildService.scala:72: type 
        mismatch;
[error]  found   : Unit
[error]  required: scala.concurrent.Future[String]
[error]         }
[error]         ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed

I'm trying to get ideally:

  • A string return (case class and json method to unpack a string)
  • Synchronous request (if async I'll need to wait to complete to progress application)
  • Secure (allows use of tokens for verification)

Appreciate any help!

crowgers
  • 232
  • 3
  • 19
  • Your method is returning `Unit` because Scala normally returns the last line of a block. However your last line is an assignment to `val returner`, which is a `Unit` expression. Remove the last line and it will return something. However, it seems like you are trying to map the HTTP response to a JSON, you can either apply that as a map function on the Future, or just do it directly on the value returned by `Await.result`. Either way, you'll likely have to do some class casting. – Jack Leow May 15 '18 at 13:53
  • I want the method to work with several json requests so better to do the class casting outside the method. Why is the type of returner a unit, is it because of the library type or the Future? – crowgers May 16 '18 at 10:07
  • It's because of the language. A value assignment is a `Unit` expression, as I mentioned in my earlier comment. – Jack Leow May 16 '18 at 20:37

2 Answers2

1

Here is the function I use:

// first work with Futures the Play Controller will support that!
def bbApiRequest(requestUrl: String): Future[String] = {

  // call the webservice with basic authentication
  wsClient.url(requestUrl)
    .withAuth("tester", "pwd123", WSAuthScheme.BASIC)
    .get()
    .map(checkStatus) // function that evaluates the HTTP Status
    .map(_.json) // get the json
    .map(Json.prettyPrint) // get it as string

}

I would create a case class directly like: .map(jsValue => handleJson(jsValue.validate[YourModel])) // or create a model of it (instead) instead of .map(Json.prettyPrint)

Edit

Here an example of checkStatus:

  protected def checkStatus(resp: WSResponse): WSResponse = {
    resp.status match {
      case Status.OK => resp
      case Status.NOT_FOUND => throw WebNotFoundException()
      case Status.FORBIDDEN | Status.UNAUTHORIZED => throw WebAccessForbiddenException()
      case Status.NOT_ACCEPTABLE => throw WebNotAcceptableException()
      case _ => throw WebBadStatusException(resp.status + " - " + resp.statusText.toString)
    }
  }

The Exception are created by myself.

pme
  • 14,156
  • 3
  • 52
  • 95
  • so here checkStatus is giving me an error, is there a way of doing some kind of wait or callback or something? It'd be better not to use a future here at all seen as the application depends on the return from this request. – crowgers May 15 '18 at 13:01
  • as mentioned in my answer - that is bad practice - learn a bit about async programming - and you will see that. – pme May 15 '18 at 15:45
  • Yes I can appreciate why it's bad however it's a fairly simple application where the webpage displays a simple list which users interact with. There's nothing else to be processed background or foreground. If you want to forward a suggestion for a synchronous request rather than blocking an async one I'd be only happy to see this. – crowgers May 15 '18 at 16:13
0

The authorization can also be inputs into the function or retrieved from environment variables within the method (much easier to manage).

Simply needed to use .body on the Await call, which converts the output to generic type string.

package utils

import javax.inject._

import play.api._
import play.api.http.HttpEntity
import play.api.libs.ws._
import play.api.mvc._
import play.api.Play.current

import scala.collection.mutable.ListBuffer
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

import sys.process._

@Singleton
class HTTPRequest @Inject() (
ws: WSClient,
ec: ExecutionContext,
config: Configuration) {

    def bbApiRequest(requestUrl: String, timeout: FiniteDuration) = {
        val request = ws
            .url(requestUrl)
            .withAuth(
                "user", 
                "PW123", 
                WSAuthScheme.BASIC)
            .get()
        Await.result(request, timeout).body
    }
}
crowgers
  • 232
  • 3
  • 19