Playframework Scala - Delete Route problems - scala

I'm working with playframework for final project at university and I'm getting a problem when routing a delete or put method.
When I'm requesting a DELETE or PUT methods I'm getting:
[info] play.api.Play - Application started (Dev)
[debug] a.ErrorHandler - onClientError: statusCode = 404, uri = /Rest/deleteCity, message ="
My JQuery ajax call is:
$("#scalaDelete").click(function(){
$("#result").empty();
$.ajax({
url: "http://localhost:9000/Rest/deleteCity",
method: "DELETE",
data: {city: "Alvorada"},
dataType: "json",
success: function(result){
$("#result").append("Result: "+result.Result);
},
error: function (request, status, error) {
alert(status);
}
});
});
My Route Play Route:
DELETE /Rest/deleteCity controllers.RestController.deleteCity()
My Controller Method:
case class UserDelete(city:String)
class RestController #Inject()(db: Database, cc: ControllerComponents) extends AbstractController(cc) {
val userDeleteForm = Form(
mapping(
"city" -> text
)(UserDelete.apply)(UserDelete.unapply)
)
def deleteCity = Action{ implicit request=>
val userPar = userDeleteForm.bindFromRequest.get
//DatabaseDelete
Ok(jsonResult)
}
}
I've already activated cross domain in chrome, I've used a CORS extension for it.
Thanks for helping

This seems related to Restful http delete in play, i.e. DELETE with data can be sketchy.
Instead of passing data, I would just move this to the url:
DELETE /Rest/deleteCity/:city controllers.RestController.deleteCity(city: String)
# or with a query string
DELETE /Rest/deleteCity controllers.RestController.deleteCity(city: String)
and then do
http://localhost:9000/Rest/deleteCity/Alvorada
# or with a query string
http://localhost:9000/Rest/deleteCity?city=Alvorada
Personally I prefer the latter.

I agree with #AndyHayden.
Play ignores the body of the DELETE request, that is the correct behavior to my mind, but you can work around by explicitly passing a body parser:
def delete = Action(parse.json) { implicit request =>
val json = request.body
val someProp = (json \ "someprop").as[String]
Ok(s"Prop is: $someProp")
}
(this example was given by one of the developers of the Play itself:
https://github.com/playframework/playframework/issues/4606#issuecomment-109192802.)
About the doubts in comments:
I've seen another post here where a guy said some browsers just support get and post method.
POST and GET are only valid for the method attribute of the form tag.
You are using javascript request, so you can use any method that server supports. i.e. DELETE is completely fine there.
But something interesting for you to know is that playframework uses akka and this framework does not support DELETE request for security reasons, in fact it wasn't well explained on post. Then if you wanna make a DELETE method you gotta make a post method for complete your code.
Akka HTTP supports the DELETE request (as well as Play Framework): https://doc.akka.io/docs/akka-http/current/scala/http/routing-dsl/directives/method-directives/delete.html

Related

Trouble using case class for multiple fields in POST body Finatra

I am working on a simple Finatra API example, but having trouble using a case class for the POST request when using more than one field in the request body.
Here is an example of my case class and controller:
class SaleResource extends Controller {
post("/sales") { sale: Sale =>
s"$sale"
}
}
case class Sale(
user: Option[String],
streetAddress: Option[String]
)
I send a post request to that route with the following request body JSON:
{
"user": "Foo Barrington",
"streetAddress":"Baz Street"
}
The response:
Sale(Some(Foo Barrington),None)
The response shows that the user is being properly deserialized, but for some reason I cannot get the streetAddress field to populate.
Also, I noticed when I set either of these fields to String instead of Option[String] I only get unsuccessful 500 responses.
Things I have tried:
case statements matching Some(streetAddress) to that fields string value or "none found" when it is None. In these cases it still is saying streetAddress is None when it is not.
Making the request with both curl and Postman.
I can always access the user field from the Sales object, but never the streetAddress (or any other field from the request body for that matter if I add test elements to the case class.
I would expect both fields to be recognized since they are both provided in the request. I am newer to Scala/Finatra in general, so it is possible I am just using the Finatra library or Case classes incorrectly.
EDIT:
It seems as if changing the field names to not be mixed/camelcase fixes all issues, but this seems like odd behavior.
Finatra uses Jackson library behind the scenes. The default configuration uses PropertyNamingStrategy.SNAKE_CASE which seems like:
{
"user": "Foo Barrington",
"street_address":"Baz Street"
}
You need to change it to PropertyNamingStrategy.LOWER_CAMEL_CASE to parse that JSON.
In order to do that, you need to define a custom FinatraJacksonModule and tell the app to
use it.
object CustomFinatraJacksonModule extends FinatraJacksonModule {
override val propertyNamingStrategy = PropertyNamingStrategy.LOWER_CAMEL_CASE
}
class MyFinatraHttpServer extends HttpServer {
override protected def jacksonModule: Module = CustomFinatraJacksonModule
}
Jackson Integration provides more information about the topic.

Swagger-generated Spring controller recievs NULLs in the body parameter of POST

My API-first swagger client/server transfers POST request body parameter object incorrectly. Spring (4.3.14.RELEASE) server receives nulls in the #RequestBody parameter of the POST request.
The key difference between requests passed over SwaggerUI and over generated
typescript client is that SwaggerUI passes object fields as query parameters (Spring handles well), but the generated typescript client over json body (Spring handles nulls).
It seems like autogenerated client sends parameter over body, but the autogenerated server expects parameters in the query.
The swagger fragment:
/project:
post:
security:
- Bearer: []
tags:
- Project
summary: Create new project
operationId: createProject
consumes:
- "application/json"
- "text/json"
parameters:
- name: project
in: body
description: project value
schema:
$ref: '#/definitions/ProjectRequestDTO'
responses:
'200':
description: Successful response
schema:
$ref: '#/definitions/ProjectDTO'
default:
description: Bad requst
The resulting Spring MVC method:
ApiOperation(value = "Create new project", nickname = "createHachathon", notes = "", response = ProjectDTO.class, authorizations = {
#Authorization(value = "Bearer")
}, tags={ "Project", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Successful response", response = ProjectDTO.class),
#ApiResponse(code = 200, message = "Bad requst") })
#RequestMapping(value = "/project",
consumes = { "application/json", "text/json" },
method = RequestMethod.POST)
default ResponseEntity<ProjectDTO> createHachathon(#ApiParam(value = "project value" ) #Valid #RequestBody ProjectRequestDTO project) {
I'm sending request from typescript over manually hacked client (title parameter added as demonstration that this parameter will be unmarshalled well):
return this.httpClient.post<ProjectDTO>(`${this.basePath}/project?title=hello`,
The resulting request in the Chrome console:
Unfortunatelly Spring receives only the value which were passed over query parameter but not over request body:
Plese help me to make my swagger-codegen-maven-plugin 2.3.1 produce client/server swagger API which will transfer data transparently.
The root cause is that Spring ignores method parameter annotations of the implemented Interface, so the #RequestBody was ignored in my Controller implementation.
The <delegatePattern>true</delegatePattern> parameter inside <configOptions> section of swagger-codegen-maven-plugin resolved this problem.
This parameter makes Swagger generate invocation of delegate method inside default implementation of API Interface methods. My Controller implements this method and overrides this delegate mathods, so the source method annotations are in safe.
Sorry, may be late answer. maybe helps someone.
I was hitting same issue while using spring boot+Swagger2.
My REST API with POST method :
public void getAllNames(#ApiParam(name = "Get All names Request",
value = "Request to get all names",
required = true) #RequestBody BasicCredentialsJsonObject requestJson)
I had to introduce Json annotations(JsonPropertyOrder, JsonProperty) on below class to get going. I was hitting issue till I made this changes.
BasicCredentialsJsonObject :
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"password",
"username"
})
public class BasicCredentialsJsonObject {
#JsonProperty("username")
private String username;
#JsonProperty("password")
private String password;
}
Sometimes, it seems stupid but I had issue with my request body, it was null into the controller. What I changed, the import, I was using the swagger.requestBody package instead of the springframeWork.request import.

How to get list of all Route URL strings in play framework?

I have many controllers in my play 2.4.x application.
I want to get a list of all route URLs pointing their respective controllers. I know how to get the URL from the current request. But I need a list of all the URLs available inside a play application.I want to generate this list dynamically because URLs can be changed/added/deleted in future.
So,is there some way I can generate this URL list dynamically ? Or do I have the obligation to store all the URLs statically somewhere in cache or dictionary ?
I obtained the desired list by using the documentation method provided by Router trait. The documentation method returns Seq[(String, String, String)] .Here each tuple has the format:
( {http-method} , {url} , {controller method} )
The Router trait is extended by all the autogenerated Routes.scala classes. Scala-compiler generated a separate Routes.scala for each routes file in the application. These auto-generated Routes.scala files implement all the methods of Router trait including the documentation method that we discussed above.
So,to get list of all URLs, I simply had to inject the Router trait and then access the documentation method:
import play.api.routing.Router
class MyClass #Inject()(router: Router) {
def getAllURLs:Seq[String] = router.documentation.map(k => k._2)
}
Update for Play 2.7, Scala:
class MyController #Inject()(routesProvider: Provider[play.api.routing.Router]) {
lazy val routes: Seq[(String, String, String)] = routesProvider.get.documentation
}
from discussion for play 2.6
In response to the accepted answer: Note that if you're working in a Controller and you try to inject the Router, you'll get a Circular Dependency error (because the Router depends on your Controller). You can fix this by doing the following:
private final Provider<Router> routerProvider;
#Inject
public MainController(Provider<Router> routerProvider) {
this.routerProvider = routerProvider;
}
And then
// later in execution
Router router = routerProvider.get();
if (!router.documentation().isEmpty()) {
html.append("<ul>");
router.documentation().forEach(doc ->
html.append("<li>")
.append("<b>Method</b>: ")
.append(doc.getHttpMethod())
.append(" <b>Path</b>: ")
.append(doc.getPathPattern())
.append(" <b>Controller</b>: ")
.append(doc.getControllerMethodInvocation())
.append("</li>"));
html.append("</ul>");
}

Session doesn't work after upgrade to Play Framework 2.5.4, Scala

After upgrading my project to Play Framework 2.5.4 from 2.4.8, I'm having issue while working with session. There are to methods in my controller to test the issue:
def test = Action { implicit request =>
Redirect(controllers.routes.Carts.test2()).withSession(new Session(Map("user" -> "customer")))
}
def test2 = Action { implicit request =>
Ok(request.session.data.toString())
}
Basically test method adds session and redirects to test2 method. When I open page with test method url after redirect I'm getting what I need in the browser:
Map(user -> customer)
But if I refresh page after that I'm getting:
Map(csrfToken ->
ce1a6222484f378d38ab3534c2b400191270395d-1470238791014-c988ce3fe47259173166949a)
So, seems like session works for one request only and then overwrites with csrfToken. I have disabled all the filters. My class with filters looks like this:
class Filters #Inject() () extends HttpFilters {
val filters = Seq.empty
}
Can't understand what is wrong with my code. The same code was working fine before upgrade.
Check in ur application.conf if session.secure is true bring it to
play.http.session.secure=false

Play Framework redirect not working in Safari

I'm attempting to query Solr from Angular and routing the request through a Play Controller for security and using Play redirect to forward the request to Solr.
This seems to be working on Chrome but not on Safari/Firefox.
Angular ajax request
var solrUrl = '/solr';
storesFactory.getAdvancedMessages = function (searchCriteria, searchType) {
var filterQuery = solrQueryComposer(searchCriteria);
$log.warn(filterQuery);
return $http({
method: 'GET',
url: solrUrl,
params: { 'q': '*',
'fq': filterQuery,
'rows': 30,
'wt': 'json'}
}).
then(function(data, status, headers, config) {
$log.debug(data.data.response.docs);
return data.data.response.docs;
},
function(error){
$log.error(error.message);
});
Play Controller
import play.mvc.Controller;
import play.mvc.Result;
import play.mvc.Security;
#Security.Authenticated(Secured.class)
public class SolrController extends Controller {
private static String solrUrl = "http://whatever.com:5185/solr/select/";
private static String queryPart = "";
public static Result forward(){
queryPart = request().uri().substring(5);
System.out.println(queryPart);
return seeOther(solrUrl+queryPart);
}
}
Play Route
GET /solr controllers.SolrController.forward()
First of all, I'd like to clarify what you're doing.
Play is not forwarding anything here, it's sending a redirect to the client, asking to fetch another URL. The client will send a request, receive a redirect, and send another request.
Which means:
this controller is not "forwarding" anything. It's just tells the client to go somewhere else. ("seeOther", the name speaks for itself).
It's not secure at all. Anyone knowing solr's URL could just query it directly.
since the query is performed by the client, it may be stopped by the cross-domain security policy.
Moreover, There's a HUGE race condition waiting to happen in your code. solrUrl and queryPart are static, therefore shared by all threads, therefore shared by all clients!!
There's absolutely no reason for queryPart to be static, and actually, there's absolutely no reason for it to be in this scope. This variable should be defined in the method body.
I'd also like to point out that request().uri().substring(5) is very brittle and is going to break if you change the URL in the route file.
In return seeOther(solrUrl+queryPart), queryPart arguments keys and values should also be URLencoded.