Sending multi part form data in post method in play/scala - scala

How can I send multi part form data in post method in play scala
using : scalaVersion:2.11.7
playVersion-2.1.5

You need to do a little code, then you can use it like
val data = MultipartFormData(formFields, "asdasboundarystringas")
WS.url(myUrl).post(data.body)
The code of MultipartFormData you can find on the github: https://gist.github.com/EECOLOR/7649144
UPDATE
Another method, I have been try it with Play 2.4.3
package controllers
import play.api.Play.current
import play.api.libs.ws._
import play.api._
import play.api.mvc._
import com.ning.http.client.AsyncHttpClient
import com.ning.http.client.multipart.FilePart
import com.ning.http.client.multipart.StringPart
import java.io.File
class Application extends Controller {
def index = Action {
val url = "http://symlink.dk/code/php/submit/"
val asyncHttpClient:AsyncHttpClient = WS.client.underlying
val postBuilder = asyncHttpClient.preparePost("http://symlink.dk/code/php/submit/")
val builder = postBuilder.addBodyPart(new StringPart("myField", "abc", "UTF-8"))
.addBodyPart(new StringPart("myField1", "abc1", "UTF-8"))
.addBodyPart(new StringPart("myField2", "abc2", "UTF-8"))
.addBodyPart(new FilePart("myFile", new File("app/controllers/Application.scala")))
val response = asyncHttpClient.executeRequest(builder.build()).get();
Ok(response.getResponseBody)
}
}
Symlink does not verify file part, so I am not sure about it, but fileds definitely was send
UPDATE
With Authentication and file verification
package controllers
import play.api.Play.current
import play.api.libs.ws._
import play.api._
import play.api.mvc._
import com.ning.http.client.AsyncHttpClient
import com.ning.http.client.multipart.FilePart
import com.ning.http.client.multipart.StringPart
import java.io.File
import org.apache.commons.codec.binary.Base64.encodeBase64
class Application extends Controller {
def index = Action {
val url = "http://httpbin.org/post"
val name = "MyUserName"
val password = "MyPassword"
val encodedCredentials =
new String(encodeBase64("%s:%s".format(name, password).getBytes))
val asyncHttpClient:AsyncHttpClient = WS.client.underlying
val postBuilder = asyncHttpClient.preparePost(url)
val builder = postBuilder
.addHeader("Authorization", "Basic " + encodedCredentials)
.addBodyPart(new StringPart("myField", "abc", "UTF-8"))
.addBodyPart(new StringPart("myField1", "abc1", "UTF-8"))
.addBodyPart(new StringPart("myField2", "abc2", "UTF-8"))
.addBodyPart(new FilePart("myFile", new File("app/controllers/Application.scala")))
val response = asyncHttpClient.executeRequest(builder.build()).get();
Ok(response.getResponseBody)
}
}
Echo of posted data:
{
"args": {},
"data": "",
"files": {
"myFile": "package controllers\n\nimport play.api.Play.current\nimport play.api.libs.ws._\nimport play.api._\nimport play.api.mvc._\nimport com.ning.http.client.AsyncHttpClient\nimport com.ning.http.client.multipart.FilePart\nimport com.ning.http.client.multipart.StringPart\nimport java.io.File\nimport org.apache.commons.codec.binary.Base64.encodeBase64\n\nclass Application extends Controller {\n\n def index = Action {\n// val url = \"http://symlink.dk/code/php/submit/\"\n val url = \"http://httpbin.org/post\"\n\n val name = \"MyUserName\"\n val password = \"MyPassword\"\n\n val encodedCredentials =\n new String(encodeBase64(\"%s:%s\".format(name, password).getBytes))\n\n val asyncHttpClient:AsyncHttpClient = WS.client.underlying\n val postBuilder = asyncHttpClient.preparePost(url)\n val builder = postBuilder\n .addHeader(\"Authorization\", \"Basic \" + encodedCredentials)\n .addBodyPart(new StringPart(\"myField\", \"abc\", \"UTF-8\"))\n .addBodyPart(new StringPart(\"myField1\", \"abc1\", \"UTF-8\"))\n .addBodyPart(new StringPart(\"myField2\", \"abc2\", \"UTF-8\"))\n .addBodyPart(new FilePart(\"myFile\", new File(\"app/controllers/Application.scala\")))\n val response = asyncHttpClient.executeRequest(builder.build()).get();\n Ok(response.getResponseBody)\n }\n\n}\n"
},
"form": {
"myField": "abc",
"myField1": "abc1",
"myField2": "abc2"
},
"headers": {
"Accept": "*/*",
"Authorization": "Basic TXlVc2VyTmFtZTpNeVBhc3N3b3Jk",
"Content-Length": "1991",
"Content-Type": "multipart/form-data; boundary=ZUeOacX0k9AyI7O12kXDuV5gucDyh2IcA",
"Host": "httpbin.org",
"User-Agent": "AHC/1.0"
},
"json": null,
"origin": "111.111.111.11",
"url": "http://httpbin.org/post"
}
UPDATE
just for clarification - if you do not need to send file as part of the form then you do not need to access underlying WSClient and can use trivial WS
val url = "http://httpbin.org/post"
val name = "MyUserName"
val password = "MyPassword"
val result = WS.url(url)
.withAuth(name, password, WSAuthScheme.BASIC)
.post(
Map(
"myField" -> Seq("myValue"),
"myField1" -> Seq("myValue1"),
"myField2" -> Seq("myValue2")
)
)
val response = Await.result(result, 10 seconds)
Ok(response.body)
request that this code send:
{
"args": {},
"data": "",
"files": {},
"form": {
"myField": "myValue",
"myField1": "myValue1",
"myField2": "myValue2"
},
"headers": {
"Accept": "*/*",
"Authorization": "Basic TXlVc2VyTmFtZTpNeVBhc3N3b3Jk",
"Content-Length": "51",
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
"Host": "httpbin.org",
"User-Agent": "AHC/1.0"
},
"json": null,
"origin": "111.111.111.11",
"url": "http://httpbin.org/post"
}

Thanks Andriy Kuba!
I've made some changes (WS.client.underlying didn't work for me)
I m running with play 2.4.
package utils
import com.ning.http.client.{AsyncHttpClient, ListenableFuture, Request, Response}
import com.ning.http.client.cookie.Cookie
import com.ning.http.client.multipart.{FilePart, StringPart}
/**
* Created by ozma on 27/05/16.
*/
object WsExtend {
def postFile(url: String,
files: List[(String, String)] = List(),
bodyParams: List[(String, String)] = List(),
cookies: List[Cookie] = List(),
headers: Map[String, String] = Map(),
encoding: String = "UTF-8"): Response = {
postFileAsync(url, files, bodyParams, cookies, headers, encoding).get()
}
def postFileAsync(url: String,
files: List[(String, String)] = List(),
bodyParams: List[(String, String)] = List(),
cookies: List[Cookie] = List(),
headers: Map[String, String] = Map(),
encoding: String = "UTF-8"): ListenableFuture[Response] = {
val asyncHttpClient:AsyncHttpClient = new AsyncHttpClient()
val postBuilder: AsyncHttpClient#BoundRequestBuilder = asyncHttpClient.preparePost(url)
files.foreach(e => postBuilder.addBodyPart(new FilePart(e._1, new java.io.File(e._2))))
bodyParams.foreach(e => postBuilder.addBodyPart(new StringPart(e._1, e._2, encoding)))
cookies.foreach(c => postBuilder.addCookie(c))
headers.foreach(h => postBuilder.addHeader(h._1, h._2))
val request: Request = postBuilder.build()
asyncHttpClient.executeRequest(request)
}
}
This is how I used it with Silhouette CookieAuthenticator
"cookie" is a header called "Set-Cookie" from login response.
WsExtend.postFile(
url=url,
files = List("fileparamname" -> filepath),
bodyParams = List("albumName" -> albumName),
headers = Map("Cookie" -> cookie)

Related

Inserting a document into Elastic using Alpakka

I'm trying to learn how to use Alpakka and have setup a test to write a document to Elastic. From reading docs, including https://doc.akka.io/docs/alpakka/current/elasticsearch.html have written the following :
import akka.actor.ActorSystem
import akka.stream.alpakka.elasticsearch.scaladsl.ElasticsearchSink
import akka.stream.alpakka.elasticsearch._
import akka.stream.scaladsl.Source
import spray.json.DefaultJsonProtocol._
import spray.json.{JsonFormat, _}
object AlpakkaWrite extends App{
case class VolResult(symbol : String, vol : Double, timestamp : Long)
implicit val actorSystem = ActorSystem()
val connectionString = "****";
val userName = "****"
val password = "****"
def constructElasticsearchParams(indexName: String, typeName: String, apiVersion: ApiVersion) =
if (apiVersion eq ApiVersion.V5)
ElasticsearchParams.V5(indexName, typeName)
else if (apiVersion eq ApiVersion.V7)
ElasticsearchParams.V7(indexName)
else
throw new IllegalArgumentException("API version " + apiVersion + " is not supported")
val connectionSettings = ElasticsearchConnectionSettings
.create(connectionString).withCredentials(userName, password)
val sinkSettings =
ElasticsearchWriteSettings.create(connectionSettings).withApiVersion(ApiVersion.V7);
implicit val formatVersionTestDoc: JsonFormat[VolResult] = jsonFormat3(VolResult)
Source(List(VolResult("test" , 1 , System.currentTimeMillis())))
.map { message: VolResult =>
WriteMessage.createIndexMessage("00002", message )
}
.log(("Error"))
.runWith(
ElasticsearchSink.create[VolResult](
constructElasticsearchParams("ccy_vol_normalized", "_doc", ApiVersion.V7),
settings = sinkSettings
)
)
}
Outputs :
19:15:51.815 [default-akka.actor.default-dispatcher-5] INFO akka.event.slf4j.Slf4jLogger - Slf4jLogger started
19:15:52.547 [default-akka.actor.default-dispatcher-5] ERROR akka.stream.alpakka.elasticsearch.impl.ElasticsearchSimpleFlowStage$StageLogic - Received error from elastic after having already processed 0 documents. Error: java.lang.RuntimeException: Request failed for POST /_bulk
Have I defined the case class DataPayload correctly ? It does match the expected payload defined in the index mapping ? :
"properties": {
"timestamp": { "type": "date",
"format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
},
"vol": { "type": "float" },
"symbol": { "type": "text" }
}
Using Elastic dev tools the following command will insert a document successfully :
POST ccy_vol_normalized/_doc/
{
"timestamp": "2022-10-21T00:00:00.000Z",
"vol": 1.221,
"symbol" : "SYM"
}
This works :
import akka.actor.ActorSystem
import akka.stream.alpakka.elasticsearch._
import akka.stream.alpakka.elasticsearch.scaladsl.ElasticsearchSink
import akka.stream.scaladsl.Source
import spray.json.DefaultJsonProtocol._
import spray.json.JsonFormat
import java.text.SimpleDateFormat
import java.util.Date
object AlpakkaWrite extends App {
val connectionString = "";
implicit val actorSystem = ActorSystem()
val userName = ""
val password = ""
val connectionSettings = ElasticsearchConnectionSettings
.create(connectionString).withCredentials(userName, password)
val sinkSettings =
ElasticsearchWriteSettings.create(connectionSettings).withApiVersion(ApiVersion.V7);
val HOUR = 1000 * 60 * 60
val utcDate = new Date(System.currentTimeMillis() - HOUR)
val ts = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").format(utcDate) + "Z"
implicit val formatVersionTestDoc: JsonFormat[VolResult] = jsonFormat3(VolResult)
def constructElasticsearchParams(indexName: String, typeName: String, apiVersion: ApiVersion) =
if (apiVersion eq ApiVersion.V5)
ElasticsearchParams.V5(indexName, typeName)
else if (apiVersion eq ApiVersion.V7)
ElasticsearchParams.V7(indexName)
else
throw new IllegalArgumentException("API version " + apiVersion + " is not supported")
case class VolResult(symbol: String, vol: Double, timestamp: String)
println("ts : " + ts)
Source(List(VolResult("test1", 1, ts)))
.map { message: VolResult =>
WriteMessage.createIndexMessage(System.currentTimeMillis().toString, message)
}
.log(("Error"))
.runWith(
ElasticsearchSink.create[VolResult](
constructElasticsearchParams("ccy_vol_normalized", "_doc", ApiVersion.V7),
settings = sinkSettings
)
)
}
My date format was incorrect, using :
val HOUR = 1000 * 60 * 60
val utcDate = new Date(System.currentTimeMillis() - HOUR)
val ts = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").format(utcDate) + "Z"
fixed the issue.

org.http4s.client Post with header and UriForm

With using org.http4s.client can't find how I can send headers and UriForm together with Post request.
import org.http4s.client.dsl.io._
import org.http4s.Method._
val lstHeader: List[Header] = List(
Header("Accept", "application/json")
, Header("Accept-Charset", "utf-8")
, Header("Accept-Encoding", "gzip")
)
val formData :UrlForm = UrlForm(
"username" -> "user",
"enc_password" -> "password",
"queryParams" -> "{}",
"optIntoOneTap" -> "false"
)
val req1 = POST(
formData,
uri"https://www.instagram.com/accounts/login/ajax/"
)
val req2: Request[IO] = Request[IO](
Method.POST,
uri"https://www.instagram.com/accounts/login/ajax/",
HttpVersion.`HTTP/2.0`,
Headers(lstHeader)
)
req1 without my headers
req2 without form data
Thanks
I found
val req2: Request[IO] = Request[IO](
Method.POST,
uri"https://www.instagram.com/accounts/login/ajax/",
HttpVersion.`HTTP/2.0`,
Headers(lstHeader)
).withEntity(formData)

Sending a document to Stripe using Play! Scala 2.5

I want to send a file to Stripe using Play! Scala 2.5.
The documentation says that the request should be (curl equivalent) like this:
curl https://uploads.stripe.com/v1/files \
-u sk_test_BQokikJOvBiI2HlWgH4olfQ2: \
-F purpose=dispute_evidence \
-F file="#/path/to/a/file.jpg"
Mine is:
def test: Action[MultipartFormData[TemporaryFile]] = Action(parse.multipartFormData) { request =>
val image = request.body.file("picture").get
val info = Map("purpose" -> Seq("identity_document"))
val attachment =
FilePart[TemporaryFile](
key = "file",
filename = "file",
contentType = image.contentType,
ref = image.ref)
val formData: MultipartFormData[TemporaryFile] =
MultipartFormData(
dataParts = info,
files = Seq(attachment),
badParts = Seq.empty)
import services.MultipartFormDataWritable.anyContentAsMultipartFormWritable
wSClient
.url("https://uploads.stripe.com/v1/files")
.withAuth("secret_key", "", WSAuthScheme.BASIC)
.post(formData)
.map(response => println(response.body))
Ok
}
But Stripe returns me this Bad Request error:
"error": {
"type": "invalid_request_error",
"message": "Missing required param: file.",
"param": "file"
}
What am I doing wrong?
This works:
val file = request.body.file("picture").get
val filename = file.filename
val contentType = file.contentType.getOrElse(logAndThrowException("Id card without content type"))
if (contentType != "image/png" && contentType != "image/jpeg" && contentType != "image/jpg") {
logAndThrowException("Wrong content type (jp[e]g or png required)")
}
val tmpFile = file.ref.file
wSClient
.url("https://uploads.stripe.com/v1/files")
.withAuth(stripeTestAPIKey, "", WSAuthScheme.BASIC)
.withHeaders("Stripe-Account" -> stripeAccount)
.post(
Source(
iterable =
FilePart("file", filename, Option("text/plain"),
FileIO.fromPath(tmpFile.toPath)) ::
DataPart("purpose", "identity_document") ::
List()))

Composing with Play's EssentialAction

I'm using Play 2.2.1 and I'm trying to write my own Action to deal with CORS requests. I found this but unfortunately it doesn't compile.
Just for reference here's the (slightly modified) code:
import play.api.mvc._
import scala.concurrent.ExecutionContext
case class CorsAction(action: EssentialAction) extends EssentialAction {
def apply(request: RequestHeader) = {
implicit val executionContext: ExecutionContext = play.api.libs.concurrent.Execution.defaultContext
val origin = request.headers.get("Origin").getOrElse("*")
if (request.method == "OPTIONS") {
val cors = Action { request =>
Ok("").withHeaders(
"Access-Control-Allow-Origin" -> origin,
"Access-Control-Allow-Methods" -> "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers" -> "Accept, Origin, Content-type, Authorization, X-Auth-Token, " +
"X-HTTP-Method-Override, X-Json, X-Prototype-Version, X-Requested-With",
"Access-Control-Allow-Credentials" -> "true",
"Access-Control-Max-Age" -> (60 * 60 * 24 * 30).toString)
}
cors(request)
} else {
action(request).map(res =>
res.withHeaders(
"Access-Control-Allow-Origin" -> origin,
"Access-Control-Allow-Credentials" -> "true"
))
}
}
}
The error is:
Cors.scala:13: not found: value Ok
I'm very new to Scala and even more so to Play! and can't figure out what's going on. As far as I know I have to use EssentialAction and not just Action b/c I want to get to the header of the request. All the examples I found so far involve only Action.
As Will said you are missing the the Results trait.
A probably cleaner way to implement the CorsAction would be to use ActionBuildersas described in
http://www.playframework.com/documentation/2.2.x/ScalaActionsComposition
The implementation would look like this:
import play.api.mvc._
import scala.concurrent.{ExecutionContext, Future}
object CorsAction extends ActionBuilder[Request] with Results{
val MaxAge = 60 * 60 * 24 * 30
val AllowHeaders = List(
"Accept", "Origin", "Content-type", "Authorization", "X-Auth-Token",
"X-HTTP-Method-Override", "X-Json", "X-Prototype-Version", "X-Requested-With")
val AllowMethods = List("GET", "POST", "PUT", "DELETE", "OPTIONS")
val AllowCredentials = true
def cors[A](origin: String) =
Ok.withHeaders(
"Access-Control-Allow-Origin" -> origin,
"Access-Control-Allow-Methods" -> AllowMethods.mkString(", "),
"Access-Control-Allow-Headers" -> AllowHeaders.mkString(", "),
"Access-Control-Allow-Credentials" -> AllowCredentials.toString,
"Access-Control-Max-Age" -> MaxAge.toString)
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[SimpleResult]) = {
implicit val executionContext = play.api.libs.concurrent.Execution.defaultContext
val origin = request.headers.get("Origin").getOrElse("*")
if (request.method == "OPTIONS") {
Future.successful(cors(origin))
} else {
block(request).map(res =>
res.withHeaders(
"Access-Control-Allow-Origin" -> origin,
"Access-Control-Allow-Credentials" -> AllowCredentials.toString
))
}
}
}
You need to import the Results trait -- the Ok val is defined there.
http://www.playframework.com/documentation/2.2.x/api/scala/index.html#play.api.mvc.Results

play scala json of map with list

I've two classes, User & Address.
case class User(
id: Pk[Long] = NotAssigned,
name: String = "",
email: String = "",
addresses: Seq[Address])
case class Address(
id: Pk[Long] = NotAssigned,
userId: Long,
city: String)
From my controller I've to send all users along with their addresses, like Map[User, List[Address]]. I could able to extract them using anorm (mysql) but then I need to send them as json. Could you please help on how to implement the writes & reads for the above Map[User, List[Address]], Thanks.
That should help
import anorm._
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class User(
id: Pk[Long] = NotAssigned,
name: String = "",
email: String = "",
addresses: Seq[Address])
case class Address(
id: Pk[Long] = NotAssigned,
userId: Long,
city: String)
// Play does not provide Format[Pk[A]], so you need to define it
implicit def pkReads[A](implicit r: Reads[Option[A]]): Reads[Pk[A]] = r.map { _.map(Id(_)).getOrElse(NotAssigned) }
implicit def pkWrites[A](implicit w: Writes[Option[A]]): Writes[Pk[A]] = Writes(id => w.writes(id.toOption))
implicit val addrFormat = Json.format[Address]
implicit val userFormat = Json.format[User]
Now you can easily serialize a user:
val as = Seq(Address(Id(2), 1, "biim"))
val u = User(Id(1), "jto", "jto#foo.bar", as)
scala> Json.toJson(u)
res6: play.api.libs.json.JsValue = {"id":1,"name":"jto","email":"jto#foo.bar","addresses":[{"id":2,"userId":1,"city":"biim"}]}
As Julien says, you can't just serialize a Map[User, Seq[Address]]. It just does not make sense since User can't be a key in a Json Object.
You can solve this problem by transforming your Map[User, List[Address]] to a List[User], and the JsonWriter will became easy to write.
Something like:
list.map {
case (user, address) => user.copy(addresses = address.toSeq)
}
The "addresses" in User contains the address so you don't really need to send Map[User, List[Address]] back to client. The Json would be an array of serialized user objects and addresses is part of that. If you do want to send back a Map then the type Map[String, List[Address]] makes more sense in Json serialization context. Here is the code to generate Json for List[User]. The output looks like this
[
{
"id": 1,
"name": "John Doe",
"email": "john#email.com",
"addresses": [
{
"id": 1001,
"userId": 1,
"city": "Chicago"
},
{
"id": 1002,
"userId": 1,
"city": "New York"
}
]
},
{
"id": 2,
"name": "Jane Doe",
"email": "jane#email.com",
"addresses": [
{
"id": 1012,
"userId": 1,
"city": "Dallas"
}
]
}
]
Here is the code the would be in you controller. It has implicit Json formatters that are used by Json.toJson.
implicit object PkWrites extends Writes[Pk[Long]] {
def writes(key: Pk[Long]) = Json.toJson(key.toOption)
}
implicit object PkReads extends Reads[Pk[Long]] {
def reads(json: JsValue) = json match {
case l: JsNumber => JsSuccess(Id(l.value.toLong))
case _ => JsSuccess(NotAssigned)
}
}
implicit val AddressWrites: Writes[Address] = (
(JsPath \ "id").write[Pk[Long]] and
(JsPath \ "userId").write[Long] and
(JsPath \ "city").write[String]
)(unlift(Address.unapply))
implicit val AddressReads: Reads[Address] = (
(JsPath \ "id").read[Pk[Long]] and
(JsPath \ "userId").read[Long] and
(JsPath \ "city").read[String]
)(Address.apply _)
implicit val UserWrites: Writes[User] = (
(JsPath \ "id").write[Pk[Long]] and
(JsPath \ "name").write[String] and
(JsPath \ "email").write[String] and
(JsPath \ "addresses").write[List[Address]]
)(unlift(User.unapply))
def makeJson() = Action {
val johnAddr1 = Address(Id(1001), 1, "Chicago")
val johnAddr2 = Address(Id(1002), 1, "New York")
val janeAddr1 = Address(Id(1012), 1, "Dallas")
val john = User(Id(1), "John Doe", "john#email.com", List(johnAddr1, johnAddr2))
val jane = User(Id(2), "Jane Doe", "jane#email.com", List(janeAddr1))
Ok(Json.toJson(List(john, jane)))
// Ok(Json.toJson(map))
}