Play: add a global optional GET parameter to all routes - scala

I have a Play 2.0 framework that is working well and I want to be able to add a specific get parameter (known only by be) to all routes. That parameters should be ignore by routes.
I explain.
Suppose I have routes like :
GET /add/:id controllers.MyController.add(id : Int)
GET /remove/:id controllers.MyController.remove(id : Int)
What I want is, for example, that http://mydomain.com/add/77?mySecretParam=ok still goes to controllers.MyController.add(id : Int) and then I could get mySecretParam in request object. And the same for all my routes.
Do you have any idea how can I do ?
Thanks.
Greg

package controllers
import play.api._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
object Application extends Controller {
def mySecretParam(implicit request: Request[_]): Option[String] = {
val theForm = Form(of("mySecretParam" -> nonEmptyText))
val boundForm = theForm.bindFromRequest
if(!boundForm.hasErrors)
Option(boundForm.get)
else
None
}
def index = Action { implicit request=>
Ok(views.html.index(mySecretParam.getOrElse("the default")))
}
}

Here's Java:
Your route
GET /hello/:id controllers.Application.hello(id: Int)
in Application controller
public static Result hello(int id){
//Retrieves the current HTTP context, for the current thread.
Context ctx = Context.current();
//Returns the current request.
Request req = ctx.request();
//you can get this specific key or e.g. Collection<String[]>
String[] param = req.queryString().get("mySecretParam");
System.out.println("[mySecretParam] " + param[0]);
//[req uri] /hello/123?mySecretParam=ok
System.out.println("[Request URI] "+req.uri().toString());
System.out.println("[Hello-ID]: " + id); //the function parameter in controller
return ok("[Hello-ID]: " + id + "\n[mySecretParam] " + param[0]);
}
Your console output
[info] play - Application started (Dev)
[Request] GET /hello/123?mySecretParam=imhereyee
[mySecretParam] imhereyee
[Request URI] /hello/123?mySecretParam=imhereyee
[Hello-ID]: 123
The key to your question is Context object and Request object from that

Related

Play Router: How to add a language-sensitive URL redirect rule?

I have an internationalized Scala Play 2.7.x WebApp and have the usual routes e.g.
GET / controllers.ApplicationController.index
GET /page/somePage/ controllers.SomeController.somePage
GET /contact controllers.ContactController.view
Now I'd like to add a new route that will basically change-language-redirect to any target route. I implement this use-case by adding an additional route on top of routes like this:
GET /$lang<(en|es)> controllers.ApplicationController.langRedirect(lang: String, target: String = "")
The idea is that every time you do e.g.
http://localhost:9000/en => will go to home page in english
http://localhost:9000/en/contact => will go to contact page in english
http://localhost:9000/es => will go to home page in spanish
http://localhost:9000/es/contact => will go to contact page in spanish
and so on. Unfortunately it doesn't always work e.g. the one included before /en/page/somePage/ it will not match it correctly to the first rule:
GET /$lang<(en|es)> controllers.ApplicationController.langRedirect(lang: String, target: String = "")
presumably because of the intermediate / ... how can I fix that?
For completeness here is my ApplicationController.langRedirect(...) implementation:
def langRedirect(lang: String, target: String = "") = silhouette.UserAwareAction.async { implicit request =>
Future.successful(Redirect("/" + target).withLang(Lang(lang)))
}
Using Router.withPrefix, you can add langage code prefix to all your routes.
Here is an example.
package handlers
import javax.inject.Inject
import play.api.http._
import play.api.i18n.{ Langs, Lang }
import play.api.mvc.{ Handler, RequestHeader }
class I18nRequestHandler #Inject()(
webCommands: play.core.WebCommands,
optDevContext: play.api.OptionalDevContext,
router: play.api.routing.Router,
errorHandler: HttpErrorHandler,
configuration: HttpConfiguration,
filters: HttpFilters,
langs: Langs)
extends DefaultHttpRequestHandler(
webCommands, optDevContext, router, errorHandler, configuration, filters) {
def getLang(request: RequestHeader): Lang = {
// Get the first path
request.path.tail.split('/').headOption
.flatMap(path => Lang.get(path))
// language from the fist path, if it is in "play.i18n.langs (application.conf)"
.filter(lang => langs.availables.exists(_ == lang))
// Or preferred language, refereeing "Accept-Languages"
.getOrElse(langs.preferred(request.acceptLanguages))
}
override def handlerForRequest(request: RequestHeader): (RequestHeader, Handler) = {
// To use the language code from the path with MessagesApi,
// Replace "Accept-Languages" to the language from the path.
val requestWithLang = request.withHeaders(
request.headers.replace(HeaderNames.ACCEPT_LANGUAGE -> getLang(request).code))
super.handlerForRequest(requestWithLang)
}
override def routeRequest(request: RequestHeader): Option[Handler] = {
val lang = getLang(request)
request.path.tail.split('/').headOption
// If the first path is right language code (if not, Not Found)
.filter(_ == lang.code)
// Route this request with language code prefix
.flatMap(_ => router.withPrefix("/" + lang.code).handlerFor(request))
}
}
To enable I18nRequestHandler, you have to add it to "application.conf".
play.http.requestHandler = "handlers.I18nRequestHandler"
Also add supported languages to "application.conf".
play.i18n.langs = [ "en", "es" ]
This code forces all routes to have the language code prefix. If you need a exceptional routes such as "/" to let users choose its language, create custom routes and add it on routeRequest method.
Hope this is what you want ;)
OK found a possible solution that's to add a second top route that will take any possible target including /, the top of my routes file now look like this:
GET /$lang<(en|es)> controllers.ApplicationController.langRedirect(lang: String, target: String = "")
GET /$lang<(en|es)>/*target controllers.ApplicationController.langRedirect(lang: String, target: String = "")
GET / controllers.ApplicationController.index
GET /page/somePage/ controllers.SomeController.somePage
GET /contact controllers.ContactController.view
Why I need two? because of the home page can only be http://localhost:9000/en and can't be http://localhost:9000/en/
However, I will be happy to learn (and accept) a better/simpler solution.

Retrofit2 + SimpleXML + SOAP request in Kotlin

I'm trying to create a service in the Android App that consumes a SOAP API. Sent values and returned values are XML.
Previously i used FormUrlEncoded + JSON in another API and worked, but with XML i'm struggling as the API seems that is not being called (HttpLoggingInterceptor don't show and also the Mockup service don't show any petition).
If i change to FormUrlEncoded my service i can see that the request is done (i checked it with HttpLoggingInterceptor, but if i remove the FormUrlEncoded seems like service is not called never.
My NetModule where is create the retrofir, parser, etc:
#Module
class NetModule {
#Provides
#Singleton
fun provideRetrofit(): Retrofit {
val client =
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
val strategy = AnnotationStrategy()
val serializer = Persister(strategy)
return Retrofit.Builder()
.baseUrl(BuildConfig.API_URL)
.client(client)
.addConverterFactory(SimpleXmlConverterFactory.create(serializer))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
#Provides
#Singleton
fun provideFilesService(retrofit: Retrofit): FilesService =
retrofit.create(FilesService::class.java)
}
My FilesService.kt where the interface is defined is:
import com.liderasoluciones.enviotest.data.model.FileSendResponse
import com.liderasoluciones.enviotest.data.model.FileSendEnvelope
import io.reactivex.Flowable
import retrofit2.http.*
interface FilesService {
#Headers(
"Content-Type: application/soap+xml",
"Accept-Charset: utf-8"
)
#POST("mockWSSMTSoap")
fun sendFile(#Body body: FileSendEnvelope): Flowable<FileSendResponse>
}
My model for the Body, Request and data is FileSendEnvelope.kt and is:
import org.simpleframework.xml.Element
import org.simpleframework.xml.Root
import org.simpleframework.xml.Namespace;
import org.simpleframework.xml.NamespaceList;
#Root(name = "GetInfoByState", strict = false)
#Namespace(reference = "http://www.webservicetest.net")
class FileSendData {
#Element(name = "FileName", required = false)
var name: String? = null
}
#Root(name = "soap12:Body", strict = false)
class FileSendBody {
#Element(name = "GetInfoByFile", required = false)
var fileSendData: FileSendData? = null
}
#Root(name = "soap12:Envelope")
#NamespaceList(
Namespace(prefix = "xsi", reference = "http://www.w3.org/2001/XMLSchema-instance"),
Namespace(prefix = "xsd", reference = "http://www.w3.org/2001/XMLSchema"),
Namespace(prefix = "soap12", reference = "http://www.w3.org/2003/05/soap-envelope")
)
class FileSendEnvelope {
#Element(name = "soap12:Body", required = false)
var body: FileSendBody? = null
}
From the RemoteDataSource class is where i call the api:
class RemoteFilesDataSource(private val filesService: FilesService,
private val genericResponseEntityMapper: GenericResponseEntityMapper):
FilesDataSource {
override fun sendFile(userToken: String): Flowable<GenericResponseEntity> {
var petitionEnvelope = FileSendEnvelope()
var petitionBody = FileSendBody()
var petitionData = FileSendData()
petitionData.name = "test.png"
petitionBody.fileSendData = petitionData
petitionEnvelope.body =
return filesService.sendFile(petitionEnvelope)
.map { it.result }
.map { genericResponseEntityMapper.transform(it) }
}
}
At this moment i'm not taking so much care about the XML sent or parse the response, i just "want to check" that the API is called.
I tried to follow this info:
https://github.com/asanchezyu/RetrofitSoapSample
http://geekcalledk.blogspot.com/2014/08/use-simple-xml-with-retrofit-for-making.html
Even are java examples and i'm using Kotlin but no luck.
Any help is appreciated.
Thanks in advance.
Have you tried to use text/xml for your Content-type in your header? (and try it without the Accept-Charset header as well)
#Headers(
"Content-type: text/xml"
)

Redirect in Scala play framework

I have a problem with Redirect in Scala play framework.
How can I redirect to view BooksController.index() ? In documentation they suggest to use Redirect but I don't know how.
def edit(Id: Int) = Action {
val book: Book = Book.findById(Id)
Ok(views.html.edit())
}
def update = Action {
implicit request =>
val (id, title, price, author) = bookForm.bindFromRequest.get
val book: Book = Book.findById(id)
book.id = id
book.title = title
book.price = price
book.author = author
Redirect(routes.BooksController.index())
}
Now can recognize --> import play.api.mvc.Results._
But i have an error --> "object java.lang.ProcessBuilder.Redirect is not a value"
If you would really like to continue using reverse routing in your code instead of having string uri values all over the place, see this:
Redirect with Bad Request Status.
The Redirect function accepts only a String or Call.
Try the following steps:
0) Add in the BookController
import play.api.mvc._
1) Add the following string in your route config file(hard disk location: controllers/BooksController)
GET /redirectedPage controllers.BooksController.index
2) Define a variable in the BookController
val Home = Redirect(routes.BookController.index())
3) Describe in the BookController
def update = Action {
implicit request => Home
}
Also do "sbt clean; sbt compile" to recompile auto-calls in ReverseRoutes.scala.
Well done.
The last line in the update action is the Redirect call which redirects to BooksController index route
import play.api.mvc.Results._
Redirect(routes.BooksController.index())

How to PUT XmlSlurper back to REST with HttpBuilder

I'm trying to make GET and then PUT call on XML REST web service.
I do it this way:
#Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7')
import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.ContentType.*
import static groovyx.net.http.Method.*
import groovy.xml.XmlUtil
def url = "http://localhost:81"
def pathPrefix = "/api/v1"
def http = new HTTPBuilder(url)
def profile = http.request(GET, XML) { req ->
uri.path = "$pathPrefix/profiles/55"
response.success = {resp, xml ->
xml
}
}
println XmlUtil.serialize(profile) // this is fine!
Now i'm going to change and save
profile.name = "New Name"
// this is not fine (i have 400 Bad Request)
// because it sends body not in XML
def savedProfile = http.request(PUT, XML) { req ->
uri.path = "$pathPrefix/profiles/55"
body = profile
response.success = {resp, xml ->
xml
}
}
println XmlUtil.serialize(savedProfile)
When i make PUT request HTTPBuilder do not send XML. It sends string, made of profile.toString().
It it not what i'm expecting.
How to send XmlSlurper object (that i obtained earlier) in PUT request?
Thank you.
I think i found the solution.
When i define body configuration value, i have to write
body = {
mkp.yield profile
}

Controller action returns "Invalid Json" when using a Fakerequest from a spec2 test

I am using playframework 2.6 and play-slick 0.8.0.
Action code:
def addCompany = Authenticated {
DBAction(parse.json) {
implicit rs => {
val newCompany = rs.request.body
val result = CompanyTable.insert(newCompany.as[Company])(rs.dbSession)
if(result > 0)
Ok("{\"id\":"+result+"}")
else
Ok("New company was not created.")
}
}
}
The Action is a composition of an Action that just checks for a valid session and the DBAction, which requires the request body to have a valid JSON object.
Test code:
"should create a Company from a Json request" in new InMemoryDB {
val newCompany = Company(name = "New Company1")
val fr = FakeRequest(POST, "/company")
.withSession(("email", "bob#villa.com"))
.withHeaders(CONTENT_TYPE -> "application/json")
.withJsonBody(Json.toJson(newCompany))
val action = controllers.CompanyController.addCompany
val result = action(fr).run
status(result) should be_==(OK)
(contentAsJson(result) \ "id").as[Long] should be_>(1L)
}
The InMemoryDB class is just a FakeApplication with a pre-populated in memory database.
The issue that I am having is that when the test runs the result is always a 400 with body content containing a message saying [Invalid Json]. When I call the service using curl with the same JSON body content, it works and the id is returned.
I decided to build a separate test project, and I used the activator to create a seed for the new project. I noticed that in the generated test that a different method of calling the action was used, so I switched my project to use this method. It worked, but I don't know why.
New code:
"should create a Company from a Json request" in new InMemoryDB {
val newCompany = Company(name = "New Company1")
val action = route(
FakeRequest(POST, "/company")
.withSession(("email", "bob#villa.com"))
.withHeaders(CONTENT_TYPE -> "application/json")
.withJsonBody(Json.toJson(newCompany))
).get
status(action) should be_==(OK)
(contentAsJson(action) \ "id").as[Long] should be_>(1L)
}
As you can see it uses a call to route instead of calling the controller.