ReactiveMongo with Play Silhouette JWT Authenticator - scala

I am currently building a REST Api using Play 2.5 (Scala) using Play Silhouette 4.0 as my authentication library.
I have a need to invalidate JWT per user therefore I will need to persist the authenticator in the database (mongodb). I am using ReactiveMongo 0.12.1.
Following the code examples of Silhouette's CacheAuthenticatorRepository and MongoAuthInfoDAO for ReactiveMongo, I have created an authenticator repository just for the JWT Authenticator like so:
package modules
import javax.inject.Inject
import com.mohiva.play.silhouette.api.StorableAuthenticator
import com.mohiva.play.silhouette.api.repositories.AuthenticatorRepository
import modules._
import com.mohiva.play.silhouette.api.util._
import scala.concurrent.Future
import scala.concurrent.duration.Duration
import scala.reflect.ClassTag
import com.mohiva.play.silhouette.api.{ AuthInfo, LoginInfo }
import com.mohiva.play.silhouette.persistence.exceptions.MongoException
import play.api.Configuration
import play.api.libs.concurrent.Execution.Implicits._
import play.api.libs.json.{ Format, JsObject, Json }
import play.modules.reactivemongo.ReactiveMongoApi
import play.modules.reactivemongo.json._
import reactivemongo.api.commands.WriteResult
import reactivemongo.play.json.collection.JSONCollection
import play.api.libs.json.Reads
/**
* Implementation of the authenticator repository which uses the reactive mongo to persist the authenticator.
*
* #param cacheLayer The cache layer implementation.
* #tparam T The type of the authenticator to store.
*/
class MongoAuthenticatorRepository[JWTAuthenticator] #Inject() (reactiveMongoApi: ReactiveMongoApi)
extends AuthenticatorRepository[JWTAuthenticator] {
def collection: Future[JSONCollection] = reactiveMongoApi.database.map(_.collection("jwt.auth.repo"))
/**
* Finds the authenticator for the given ID.
*
* #param id The authenticator ID.
* #return The found authenticator or None if no authenticator could be found for the given ID.
*/
override def find(id: String): Future[Option[JWTAuthenticator]] = {
//cacheLayer.find[JWTAuthenticator](id)
val query = Json.obj("id" -> id)
collection.flatMap(_.find(query).one[JWTAuthenticator])
}
/**
* Adds a new authenticator.
*
* #param authenticator JWTAuthenticatorhe authenticator to add.
* #return JWTAuthenticatorhe added authenticator.
*/
override def add(authenticator: JWTAuthenticator): Future[JWTAuthenticator] = {
//cacheLayer.save[JWTAuthenticator](authenticator.id, authenticator, Duration.Inf)
val obj = Json.obj("id" -> authenticator.id , "authenticator" -> authenticator, "duration" -> Duration.Inf)
collection.flatMap(_.insert(obj))
}
/**
* Updates an already existing authenticator.
*
* #param authenticator JWTAuthenticatorhe authenticator to update.
* #return JWTAuthenticatorhe updated authenticator.
*/
override def update(authenticator: JWTAuthenticator): Future[JWTAuthenticator] = {
//cacheLayer.save[JWTAuthenticator](authenticator.id, authenticator, Duration.Inf)
val obj = Json.obj("id" -> authenticator.id , "authenticator" -> authenticator, "duration" -> Duration.Inf)
collection.flatMap(_.update(Json.obj("id" -> authenticator.id), obj, upsert = true))
}
/**
* Removes the authenticator for the given ID.
*
* #param id JWTAuthenticatorhe authenticator ID.
* #return An empty future.
*/
override def remove(id: String): Future[Unit] = {
//cacheLayer.remove(id)
val query = Json.obj("id" -> id)
collection.flatMap(_.remove(query))
}
}
I then implemented it by modifying my silhouette.module file like so:
#Provides def provideAuthenticatorService(authenticatorEncoder: AuthenticatorEncoder, idGenerator: IDGenerator, configuration: Configuration, clock: Clock): AuthenticatorService[JWTAuthenticator] = {
val config = new JWTAuthenticatorSettings(
fieldName = configuration.underlying.getString("silhouette.authenticator.fieldName"),
//requestParts = Some(configuration.underlying.getAs[Seq[RequestPart.Value]]("silhouette.authenticator.requestParts").get),
issuerClaim = configuration.underlying.getString("silhouette.authenticator.issuerClaim"),
sharedSecret = configuration.underlying.getString("silhouette.authenticator.sharedSecret"),
authenticatorExpiry = configuration.underlying.getAs[FiniteDuration]("silhouette.authenticator.authenticatorExpiry").get,
authenticatorIdleTimeout = Some(configuration.underlying.getAs[FiniteDuration]("silhouette.authenticator.authenticatorIdleTimeout").get)
)
implicit lazy val format = Json.format[JWTAuthenticator]
val repo = new MongoAuthenticatorRepository[JWTAuthenticator]
new JWTAuthenticatorService(config, repo, authenticatorEncoder, idGenerator, clock)
}
I keep having problems compiling the code and the errors shown are:
"No Json deserializer found for type JWTAuthenticator. Try to implement an implicit Reads or Format for this type."
and
"Imported `MongoAuthenticatorRepository' is permanently hidden by definition of class MongoAuthenticatorRepository in package modules"
I tried to implement a custom JWTAuthenticator class as well and setting implicit formats within the companion object. However the errors still persists.
I am new to Scala as well as Play Framework, would appreciate any pointers or tips on resolving the above issue! Thanks!!

Related

Logging from an sbt plugin

I use the s3 resolver plugin for sbt.
I have changed the credentials provider:
lazy val s3CredentialsProvider = {bucket: String =>
new AWSCredentialsProviderChain(
new EnvironmentVariableCredentialsProvider(),
PropertyFilesCredentialProvider.create(bucket)
)
}
where the PropertyFilesCredentialProvider is a custom provider.
I have done the following where:
we added a ConsoleLogger using sbt.util.internal
we added a System.out.println
instead.
I've published the plugin and have been using it in another plugin. It looks like my plugin is being used because the resolver attempts to use a different access key each time but we cannot see the logs from our class.
How can we add logging when we have code in an AutoPlugin?
The code for the Provider looks like this:
import java.io.{File, FileInputStream, InputStream}
import java.util.Properties
import com.amazonaws.auth.{AWSCredentials, AWSCredentialsProvider, BasicSessionCredentials}
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder
import com.amazonaws.services.securitytoken.model.{AssumeRoleRequest, AssumeRoleResult, Credentials}
/** Creates a credential provider that reads a `roleArn` property from a file
* and assumes the role using STS.
*
* This is based on https://github.com/frugalmechanic/fm-sbt-s3-resolver/blob/master/src/main/scala/fm/sbt/S3URLHandler.scala#L84
*
* #param file Properties file holding the ROLE_ARN for the project.
*/
class PropertyFilesCredentialProvider(file: File)
extends AWSCredentialsProvider {
private val ROLE_ARN_KEY: String = "roleArn"
private val AWS_REGION: String = "<AWS_REGION>"
protected def getRoleArn: String = {
val is: InputStream = new FileInputStream(file)
try {
val props: Properties = new Properties()
props.load(is)
props.getProperty(ROLE_ARN_KEY)
} finally is.close()
}
def createAWSCredentials(credentials: Credentials): AWSCredentials = {
System.out.println("Retrieved AWS Session Token and Credentials for assuming role")
new BasicSessionCredentials(credentials.getAccessKeyId,
credentials.getSecretAccessKey,
credentials.getSessionToken)
}
def assumeRole(roleArn: String): AssumeRoleResult = {
System.out.println(s"Making a request to AWS STS with the roleArn: $roleArn to assume a role")
val stsClient = AWSSecurityTokenServiceClientBuilder
.standard
.withRegion(AWS_REGION)
.build
val assumeRoleRequest = new AssumeRoleRequest
assumeRoleRequest.setRoleArn(roleArn)
stsClient.assumeRole(assumeRoleRequest)
}
override def getCredentials: AWSCredentials = {
val roleArn = getRoleArn
if (roleArn == null || roleArn == "") {
System.out.println(s"Key of name $ROLE_ARN_KEY was not found in file at ${file.getAbsolutePath}")
return null
}
System.out.println(s"$ROLE_ARN_KEY was read from ${file.getAbsolutePath} successfully")
val assumeRoleResult = assumeRole(roleArn)
System.out.println("Request to assume role using AWS STS successful")
createAWSCredentials(assumeRoleResult.getCredentials)
}
override def refresh(): Unit = {}
}
object PropertyFilesCredentialProvider {
private val DOT_SBT_DIR: File =
new File(System.getProperty("user.home"), ".sbt")
/** Uses a bucket specific propertyfile to read AWS `roleArn` from and provides it
* to the PropertyFilesCredentialProvider.
*
* #param bucket Name of the S3 bucket.
* #return a PropertyFileCredentialProvider
*/
def create(bucket: String): PropertyFilesCredentialProvider = {
val fileName = s".${bucket}_s3credentials"
System.out.println("Using the Property Files Credential Provider")
System.out.println(s"Reading $fileName for AWS Credentials ")
val file: File = new File(DOT_SBT_DIR, fileName)
new PropertyFilesCredentialProvider(file)
}
}
UPDATE
Attempts to use streams.value.log failed with the error:
`value` can only be called on a task within a task definition macro,
such as :=, +=, ++=, or Def.task.
[error] val logger = streams.value.log
[error]
Attempts to use ConsoleLogger which is a sub class of Logger but instantiated within the class. The apply method on it was invoked like this:
val logger = sbt.internal.util.ConsoleLogger(System.out)
logger.info("s"Key of name $ROLE_ARN_KEY was not found in file at ${file.getAbsolutePath}"")
inside the class above. That did not output the logs either.
I created two loggers, one for the class and another for the companion object as members and not within the class extending AutoPlugin.
This is a way of adding logging to classes that you defined for your AutoPlugin
import sbt._
import sbt.Keys._
object TestPlugin extends AutoPlugin {
class SomeClassThatNeedsLogger(logger: Logger) {
def doSomeLogging(): Unit = {
logger.info("It logs")
}
}
object autoImport {
val someClassThatNeedsLoggerHolder = taskKey[SomeClassThatNeedsLogger]("Holds instance of SomeClassThatNeedsLogger")
val runSomeClassThatNeedsLogger = taskKey[Unit]("Runs SomeClassThatNeedsLogger")
}
import autoImport._
override def trigger = allRequirements
override def projectSettings: Seq[Def.Setting[_]] = {
Seq(
someClassThatNeedsLoggerHolder := new SomeClassThatNeedsLogger(streams.value.log),
runSomeClassThatNeedsLogger := someClassThatNeedsLoggerHolder.value.doSomeLogging()
)
}
}
And running gives us a log entry:
> runSomeClassThatNeedsLogger
[info] It logs
[success] Total time: 0 s, completed Feb 6, 2019 9:47:15 AM
Some notes on errors you're getting
value can only be called on a task within a task definition macro,
such as :=, +=, ++=, or Def.task.
This tells you that streams.value can be used only in task definitions like someClassThatNeedsLoggerHolder := new SomeClassThatNeedsLogger(streams.value.log)
ConsoleLogger using sbt.util.internal
As the package name suggests, this is an internal package and subject to a change in next versions of sbt. It should not be used in your plugin definitions.

Play Framework Dependency Injection error

This is my code:
package controllers
import javax.inject.Inject
import scala.concurrent.Future
import play.api.Logger
import play.api.mvc.{ Action, Controller }
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.libs.functional.syntax._
import play.api.libs.json._
// Reactive Mongo imports
import reactivemongo.api.Cursor
import reactivemongo.api.ReadPreference
import play.modules.reactivemongo.{ // ReactiveMongo Play2 plugin
MongoController,
ReactiveMongoApi,
ReactiveMongoComponents
}
// BSON-JSON conversions/collection
import reactivemongo.play.json._
import play.modules.reactivemongo.json.collection._
/*
* Example using ReactiveMongo + Play JSON library.
*
* There are two approaches demonstrated in this controller:
* - using JsObjects directly
* - using case classes that can be turned into JSON using Reads and Writes.
*
* This controller uses JsObjects directly.
*
* Instead of using the default Collection implementation (which interacts with
* BSON structures + BSONReader/BSONWriter), we use a specialized
* implementation that works with JsObject + Reads/Writes.
*
* Of course, you can still use the default Collection implementation
* (BSONCollection.) See ReactiveMongo examples to learn how to use it.
*/
class Application #Inject() (val reactiveMongoApi: ReactiveMongoApi)
extends Controller with MongoController with ReactiveMongoComponents {
def collection: Future[JSONCollection] =
database.map(_.collection[JSONCollection]("Persons"))
def index = Action { Ok("works") }
def create(name: String, age: Int) = Action.async {
val json = Json.obj(
"name" -> name,
"age" -> age,
"created" -> new java.util.Date().getTime())
collection.flatMap(_.insert(json)).map(lastError =>
Ok("Mongo LastError: %s".format(lastError)))
}
}
I´ve an error that said can´t find database value (database is a member of ReactiveMongoApi Trait) so I guess there is something wrong with dependency injection. I added also this line to the build sbt:
adding routesGenerator := InjectedRoutesGenerator
Any ideas? thank you!
You need to refer to the injected API. That is, replace database with reactiveMongoApi.database:
class Application #Inject() (val reactiveMongoApi: ReactiveMongoApi)
extends Controller with MongoController with ReactiveMongoComponents {
def collection: Future[JSONCollection] =
reactiveMongoApi.database.map(_.collection[JSONCollection]("Persons"))
...
}

ReactiveMongo and Play framework: error ConnectionNotInitialized

I'm trying to follow the tutorial of "reactiveMongo" with play framework and scala, but I get the following error:
[ConnectionNotInitialized: MongoError['Connection is missing metadata (like protocol version, etc.) The connection pool is probably being initialized.']]
My application.conf haves:
play.modules.enabled += "play.modules.reactivemongo.ReactiveMongoModule"
mongodb.uri ="mongodb://localhost:27017/cambio"
My build.sbt haves:
libraryDependencies += "org.reactivemongo" %% "play2-reactivemongo" % "0.12.5-play26"
My mongo is start.
My controller is:
package controllers
import javax.inject.Inject
import scala.concurrent.Future
import play.api.Logger
import play.api.mvc.{ Action, Controller }
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.libs.functional.syntax._
import play.api.libs.json._
// Reactive Mongo imports
import reactivemongo.api.Cursor
import reactivemongo.api.ReadPreference
import play.modules.reactivemongo.{ // ReactiveMongo Play2 plugin
MongoController,
ReactiveMongoApi,
ReactiveMongoComponents
}
// BSON-JSON conversions/collection
import reactivemongo.play.json._
import play.modules.reactivemongo.json.collection._
/*
* Example using ReactiveMongo + Play JSON library.
*
* There are two approaches demonstrated in this controller:
* - using JsObjects directly
* - using case classes that can be turned into JSON using Reads and Writes.
*
* This controller uses JsObjects directly.
*
* Instead of using the default Collection implementation (which interacts with
* BSON structures + BSONReader/BSONWriter), we use a specialized
* implementation that works with JsObject + Reads/Writes.
*
* Of course, you can still use the default Collection implementation
* (BSONCollection.) See ReactiveMongo examples to learn how to use it.
*/
class UserController #Inject() (val reactiveMongoApi: ReactiveMongoApi)
extends Controller with MongoController with ReactiveMongoComponents {
/*
* Get a JSONCollection (a Collection implementation that is designed to work
* with JsObject, Reads and Writes.)
* Note that the `collection` is not a `val`, but a `def`. We do _not_ store
* the collection reference to avoid potential problems in development with
* Play hot-reloading.
*/
def collection: JSONCollection = db.collection[JSONCollection]("broker")
def index = Action { Ok("works") }
def create(name: String, age: Int) = Action.async {
val json = Json.obj(
"name" -> name,
"age" -> age,
"created" -> new java.util.Date().getTime())
collection.insert(json).map(lastError =>
Ok("Mongo LastError: %s".format(lastError)))
}
def createFromJson = Action.async(parse.json) { request =>
import play.api.libs.json.Reads._
/*
* request.body is a JsValue.
* There is an implicit Writes that turns this JsValue as a JsObject,
* so you can call insert() with this JsValue.
* (insert() takes a JsObject as parameter, or anything that can be
* turned into a JsObject using a Writes.)
*/
val transformer: Reads[JsObject] =
Reads.jsPickBranch[JsString](__ \ "firstName") and
Reads.jsPickBranch[JsString](__ \ "lastName") and
Reads.jsPickBranch[JsNumber](__ \ "age") reduce
request.body.transform(transformer).map { result =>
collection.insert(result).map { lastError =>
Logger.debug(s"Successfully inserted with LastError: $lastError")
Created
}
}.getOrElse(Future.successful(BadRequest("invalid json")))
}
def findByName(name: String) = Action.async {
// let's do our query
val cursor: Cursor[JsObject] = collection.
// find all people with name `name`
find(Json.obj("name" -> name)).
// sort them by creation date
sort(Json.obj("created" -> -1)).
// perform the query and get a cursor of JsObject
cursor[JsObject](ReadPreference.primary)
// gather all the JsObjects in a list
val futurePersonsList: Future[List[JsObject]] = cursor.collect[List]()
// transform the list into a JsArray
val futurePersonsJsonArray: Future[JsArray] =
futurePersonsList.map { persons => Json.arr(persons) }
// everything's ok! Let's reply with the array
futurePersonsJsonArray.map { broker =>
Ok(broker)
}
}
}
My routes is:
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# An example controller showing a sample home page
GET / controllers.HomeController.index
GET /crear/:name/:age controllers.UserController.create(name: String, age: Int)
GET /buscar/:name controllers.UserController.findByName(name: String)
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)

Instantiate a val from a given #inject class

How do I instantiate an object from this class? I have another class where I would like to use the MongoUtils class as defined below. This class is from reactivemongo
package controllers
import javax.inject.Inject
import scala.concurrent.Future
import play.api.Logger
import play.api.mvc.{ Action, Controller }
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.libs.functional.syntax._
import play.api.libs.json._
// Reactive Mongo imports
import reactivemongo.api.Cursor
import play.modules.reactivemongo.{ // ReactiveMongo Play2 plugin
MongoController,
ReactiveMongoApi,
ReactiveMongoComponents
}
// BSON-JSON conversions/collection
import reactivemongo.play.json._
import play.modules.reactivemongo.json.collection._
/*
* Example using ReactiveMongo + Play JSON library.
*
* There are two approaches demonstrated in this controller:
* - using JsObjects directly
* - using case classes that can be turned into JSON using Reads and Writes.
*
* This controller uses JsObjects directly.
*
* Instead of using the default Collection implementation (which interacts with
* BSON structures + BSONReader/BSONWriter), we use a specialized
* implementation that works with JsObject + Reads/Writes.
*
* Of course, you can still use the default Collection implementation
* (BSONCollection.) See ReactiveMongo examples to learn how to use it.
*/
class MongoUtils #Inject() (val reactiveMongoApi: ReactiveMongoApi)
extends Controller with MongoController with ReactiveMongoComponents {
/*
* Get a JSONCollection (a Collection implementation that is designed to work
* with JsObject, Reads and Writes.)
* Note that the `collection` is not a `val`, but a `def`. We do _not_ store
* the collection reference to avoid potential problems in development with
* Play hot-reloading.
*/
def collection: JSONCollection = db.collection[JSONCollection]("persons")
def index = Action { Ok("works") }
def create(name: String, age: Int) = Action.async {
val json = Json.obj(
"name" -> name,
"age" -> age,
"created" -> new java.util.Date().getTime())
collection.insert(json).map(lastError =>
Ok("Mongo LastError: %s".format(lastError)))
}
def createFromJson = Action.async(parse.json) { request =>
import play.api.libs.json.Reads._
/*
* request.body is a JsValue.
* There is an implicit Writes that turns this JsValue as a JsObject,
* so you can call insert() with this JsValue.
* (insert() takes a JsObject as parameter, or anything that can be
* turned into a JsObject using a Writes.)
*/
val transformer: Reads[JsObject] =
Reads.jsPickBranch[JsString](__ \ "firstName") and
Reads.jsPickBranch[JsString](__ \ "lastName") and
Reads.jsPickBranch[JsNumber](__ \ "age") reduce
request.body.transform(transformer).map { result =>
collection.insert(result).map { lastError =>
Logger.debug(s"Successfully inserted with LastError: $lastError")
Created
}
}.getOrElse(Future.successful(BadRequest("invalid json")))
}
def findByName(name: String) = Action.async {
// let's do our query
val cursor: Cursor[JsObject] = collection.
// find all people with name `name`
find(Json.obj("name" -> name)).
// sort them by creation date
sort(Json.obj("created" -> -1)).
// perform the query and get a cursor of JsObject
cursor[JsObject]()
// gather all the JsObjects in a list
val futurePersonsList: Future[List[JsObject]] = cursor.collect[List]()
// transform the list into a JsArray
val futurePersonsJsonArray: Future[JsArray] =
futurePersonsList.map { persons => Json.arr(persons) }
// everything's ok! Let's reply with the array
futurePersonsJsonArray.map { persons =>
Ok(persons)
}
}
}
So far I have tried val mongoutil = new MongoUtils(reactiveMongoApi = play.modules.reactivemongo)
but apparently it does not work.
You can use Play’s dependency injection mechanism to resolve instance of ReactiveMongoApi which is the interface to MongoDB. The steps could be:
Add the lines below to application.conf
play.modules.enabled += "play.modules.reactivemongo.ReactiveMongoModule"
play.modules.enabled += "modules.CommonModule"
modules.CommonModule is the place you define your bindings, so that Guice, a DI framework integrated with Play 2.4, knows which implementation to be used for a particular interface. For example,
package modules
import com.google.inject.AbstractModule
import dao.impl.UserDaoMongo
import dao.UserDao
import play.api.{Configuration, Environment}
import services.{UserService, UserServiceImpl}
case class CommonModule(environment: Environment, configuration: Configuration) extends AbstractModule {
def configure() {
bindDAOs()
bindServices()
}
private def bindDAOs(): Unit = {
bind(classOf[UserDao]).to(classOf[UserDaoMongo])
}
def bindServices(): Unit = {
bind(classOf[UserService]).to(classOf[UserServiceImpl])
}
}
In addition, it is a good practice to make controllers singleton. For example:
import javax.inject.{Singleton, Inject}
#Singleton
class UserController #Inject()(userService: UserService) extends Controller {
Please read this to know how to use Reactivemongo with Play framework. On the other hand, this is a complete example: https://github.com/luongbalinh/play-mongo
If you're using this class from a controller, you should annotate your controller as follows to have play inject an instance into it:
class MyController #Inject() (mongoUtils: MongoUtils) extends Controller {}
If you are not using the injected controllers, you can access it via the injector:
Play.current.injector.instanceOf(classOf[MongoUtils])
Read more about dependency injection in play here: https://www.playframework.com/documentation/2.4.x/ScalaDependencyInjection

How can I add a message body to a Play2.2 Scala WS GET?

I'm aware this is unconventional - I'm accessing an API that requires JSON objects posted as a message body for GET requests. Using Play 2.2.2 and play.api.libs.ws.WS
I'm attempting to add a message body to the WSRequestHolder. The .post() method allows this, but the .get() method doesn't have such a parameter. I'm looking at using the basic WSRequest class instead of the WSRequestHolder but not sure of a better way to do this.
Psuedo-code:
WS.url(url).get(Json.obj("param" -> "value"))
or
WS.url(url).setBody(Json.obj("param" -> "value")).get()
Any ideas? Thanks for your help!
When in doubt, roll your own solution.
import scala.concurrent.{ Future, Promise }
import play.api.libs.concurrent._
import play.api.libs.iteratee._
import play.api.libs.iteratee.Input._
import play.api.libs.json._
import play.api.http.{ Writeable, ContentTypeOf }
import play.api.libs.ws.WS._
import play.api.libs.ws.{SignatureCalculator, Response}
import com.ning.http.client.Realm.AuthScheme
import com.ning.http.client.PerRequestConfig
class MyRequest(
url: String,
headers: Map[String, Seq[String]],
queryString: Map[String, Seq[String]],
calc: Option[SignatureCalculator],
auth: Option[Tuple3[String, String, AuthScheme]],
followRedirects: Option[Boolean],
requestTimeout: Option[Int],
virtualHost: Option[String]
) extends WSRequestHolder(url,headers,queryString,calc,auth,followRedirects,requestTimeout,virtualHost) {
/**
* Perform a GET on the request asynchronously with a body.
*/
def get[T](body: T)(implicit wrt: Writeable[T], ct: ContentTypeOf[T]): Future[Response] = prepare("POST", body).execute
/**
* Prepare a request
* #param method
* #param body
* #param wrt
* #param ct
* #tparam T
* #return
*/
override def prepare[T](method: String, body: T)(implicit wrt: Writeable[T], ct: ContentTypeOf[T]) = {
val request = new WSRequest(method, auth, calc).setUrl(url)
.setHeaders(Map("Content-Type" -> Seq(ct.mimeType.getOrElse("text/plain"))) ++ headers)
.setQueryString(queryString)
.setBody(wrt.transform(body))
followRedirects.map(request.setFollowRedirects)
requestTimeout.map {
t: Int =>
val config = new PerRequestConfig()
config.setRequestTimeoutInMs(t)
request.setPerRequestConfig(config)
}
virtualHost.map {
v =>
request.setVirtualHost(v)
}
request
}
}
object MyWS {
/**
* Prepare a new request. You can then construct it by chaining calls.
*
* #param url the URL to request
*/
def url(url: String): MyRequest = new MyRequest(url, Map(), Map(), None, None, None, None, None)
}