I'm comunicating my Rails API with my AngularJS application.
Everything is working great and normal up until the point I have to send parameters in a GET request. This is the Rails controller
def cats
if cat_params[:color]
#cats = Cat.where(... #you know
else
//Do something else
end
private
def cat_params
params.require(:cat).permit(:color)
end
Then from Angular
var kitty = {
cat: {
color: "red"
}
}
$http.get('some URL', { params: kitty }).success.....
By this time, the params hash looks like a stringify JSON
Started GET "some URL?cat=%7B%22color%22:%22red%22%7D" for 127.0.0.1 at 2015-01-28 23:10:24 -0300
Processing by Some_controller as JSON
Parameters: {"cat"=>"{\"color\":\"red\"}", "cat_id"=>"19"}
Cat Load (0.5ms) SELECT "cat".* FROM "cats" WHERE "cat"."id" = 19 LIMIT 1
{"cat"=>"{\"color\":\"red\"}", "format"=>"json", "controller"=>"some_controller", "action"=>"some_action", "cat_id"=>"19"}
Completed 500 Internal Server Error in 95ms
NoMethodError - undefined method `permit' for "{\"cat\":\"red\"}":String:
I'm also sending the Content-Type header, set to 'application/json'.
From Angular's $http.get documentation, I read that if the value of params is something other than a string, it will stringify the JSON object, so the issue is not in the front-end.
I don't think the solution begins with starting JSON parsing the params hash, since I've had no need to do it in the past. It seems to me that strong_parameters is playing dirty, or Rails is ignoring the JSON string. Any ideas what to do next?
I just ran into the same issue. Changing the param serializer solved the issue for me:
$http.get('someURL', {
paramSerializer: '$httpParamSerializerJQLike',
params: {cat: {color: 'red}}
})
Also adding the 'Content-Type': 'application/json' header will not help since it applies to the the body the request.
I used to meet a $http.get problem that when call $http.get('some url', {params:SOME_PARAMS}), SOME_PARAMS could be transformd to key-value params when it is a simple key-value data like {color:'red'}, and it would transform params to json string when it is a complex type like {cat:{color:'red'}}.
So to solve your question, I suggest that add params behind url like:
$http.get('some URL?cat[color]=red').success.....
Related
I built an API service that will process REST requests and use them to perform CRUD operations on a MongoDB instance. This application is standalone (by design) and should be a passthrough for anything that calls it. My other application that I built in Angular is calling this API to interact with my MongoDB instance. I have been trying to construct my JSON payload from a form, which works fine. I get something like:
{ "_id":"111111111", "name":"herp", "address":"derp", "city":"foo", "state":"bar", "zip":"11111", "phone":"111-222-3333"}
I am then trying to take that JSON and send it along to the service, but something is getting lost in translation once the service gets a hold of it and my variable name that contains the JSON object is being turned into an actual key in the request, with the JSON as its value. I am calling the service like this:
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json', 'Cache-Control': 'no-cache' })
};
updateStuff(update){
console.log("Sending: " + JSON.stringify(update) + " for update");
return this.http.put('http://localhost:3000/api/test/_update', {dbName:"testDb",collection:"testing",update}, httpOptions);
}
Which logs:
Sending: {"name":"blah","address":"111 Anystreet","city":"MyCity","state":"NY","zip":"11111","phone":"555-111-2222","_id":"5ba914df13236f7a6ea3e233"} for update
So I know that right before the call is made, the data is fine. However, on the other side, it sees the following when it gets the data:
Received request: {"dbName":"testDb","collection":"testing","update":{"name":"blah","address":"111 Anystreet","city":"MyCity","state":"NY","zip":"11111","phone":"555-111-2222","_id":"5ba914df13236f7a6ea3e233"}}
instead of what I intended, which is below:
{"dbName":"testDb","collection":"testing","name":"blah","address":"111 Anystreet","city":"MyCity","state":"NY","zip":"11111","phone":"555-111-2222","_id":"5ba914df13236f7a6ea3e233"}
How do I tell the HTTP request to send the data itself rather than constructing a new key with the name "update" and sticking the payload in there as its value? I tried JSON.stringify, but that ends up sending the same thing, but with a bunch of backslashes in front of all the parenthesis. It still sends it all in a key with the name "update" as well. Any help would be greatly appreciated.
Your problem is here:
{dbName:"testDb",collection:"testing",update}
The statement above is shorthand for this:
{dbName:"testDb",collection:"testing",update:update}
What you're looking to do is this:
{dbName:"testDb",collection:"testing",...update}
Which is shorthand for this:
const data = {dbName:"testDb",collection:"testing"};
for (let key in update) {
if (update.hasOwnProperty(key)) {
data[key] = update[key];
}
}
The task is rather simple, I request the endpoint with POST request (https://banana.com/endpoint/swap.php), give it my form: { banana: ["China's Red", "Sweden's Gray"], apples: [] } and send it.
However, the Request module for NodeJS that I am using does not encode the empty array (in this case "apples") and if the endpoint doesn't receive the "apples" array, it returns an error - "Invalid JSON". I have tried doing this with already encoded strings and it works just fine. I am also unable to stringify my json and then use encodeURI(), as it will then give "bananas" and "apples" quotes around them, which will get encoded - needless to say, the endpoint doesn't like that either.
I'd really appreciate if somebody could at least point me in the right direction. As I am unsure on how to proceed with this, without creating some awful spaghetti code.
data = { banana: ["China's Red", "Sweden's Gray"], apples: [] }
result = JSON.parse(JSON.stringify(data)) .
You wouldn't get double around banana and apple and if you need to access then access it
console.log(result.banana)
console.log(result.apple)
So if you need to feed this result in post request then -
url = 'your url';
const options = {
url: url,
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-Charset': 'utf-8'
},
json: result
};
request.post(options, function (err, response, body) {
// do something with your data
})
Let me know if this works.
I am using Angular4 with TypeScript version 2.2.2
My web app is running fine when I call JSON with Filters but my NativeScript app fails when I call the Filter Values as an Object but works fine when I call filter values as a string.
Error Response with status: 200 for URL: null
THIS WORKS
https://domainname.com/api/v1/searchevents?token=057001a78b8a7e5f38aaf8a682c05c414de4eb20&filter=text&search=upcoming
If the filter value and search value is STRING it works whereas if they are objects as below, it does not work
THIS DOES NOT WORK
https://api.domainname.com/api/v1/searchevents?token=057001a78b8a7e5f38aaf8a682c05c414de4eb20&filter={"limit":"12","skip":"0"}&search={"search":"","latitude":"","longitude":"","categories":"","address":"","type":"upcoming"}
The Code I used is below
getData(serverUrl, type, skip_limit) {
console.log(serverUrl);
let headers = this.createRequestHeader();
let token_value = localStorage.getItem('access_token')
let url;
var filter;
filter = '{"limit":"10","skip":"0"}'
url = this.apiUrl + serverUrl + '?token=' + token_value + '&filter=' + filter
return this.http.get(url, { headers: headers })
.map(res => res.json());
}
The URL as formed above for the API is fine and works fine. Yet the error comes Error Response with status: 200 for URL: null
CAN ANYONE HELP ME SOLVE THIS?
Looks like the issue is the "filter" values are of different type and from what you mentioned as what worked, your service is expecting a string and not an object/array. So it fails to send the proper response when it gets one. With an object in the URL, you may have to rewrite the service to read it as an object (parse the two attributes and get them individually)
To make it simple, you can make these two as two different variables in the URL. like below,
https://api.domainName.in/api/v1/oauth/token?limit=10&skip=0
Be more precise in whats happening in your question,
1) Log the exact URL and post it in the question. No one can guess what goes in "text" in your first URL.
2) Your URL which you mentioned as worked have "token" as part of path, but in the code, its a variable which will have a dynamic value from "token_value".
3) Post your service code. Especially the signature and input parsing part.
Got the solution:
All you have to do is encode the Filter and Search Parameters if it is an Object or Array using Typescript encodeURI()
var filter = '{"limit":"12","skip":"0"}'
var search = '{"search":"","latitude":"","longitude":"","categories":"","address":"","type":"upcoming"}'
var encoded_filter = encodeURI(filter);
var encoded_search = encodeURI(search);
url = this.apiUrl+serverUrl+'?token='+token_value+'&filter='+encoded_filter+'&search='+encoded_search
POST ing json from javascript to server in Play Framework:
var myJson = {"name": "joe", "age":20};
var obj = JSON.parse(myJson);
$.ajax(jsRoutes.controllers.MyController.create(obj));
Now, I have the javascript router configured fine. If i recieve the obj as a string I can print it out to the console just fine.
routes.conf:
POST /person/add controllers.MyController.createFromAjax(ajax: String)
BUT, I want to write the json to MongoDB using an Async promise which Activator gives the compile time error:
scala.concurrent.Future[play.api.mvc.Result][error] cannot be applied to (String)
I have other routes that take no parameters that receive json using Postman and write it to MongoDB just fine
routes.conf
POST /heartrates/bulk controllers.HRController.createFromJson
If I omit the parameter on the route that receives the json from Ajax instead of using Postman I get a HTTP 400 error in the browser.
POST http://localhost:9000/person/add 400 (Bad Request)
SO, my question is, Ajax needs a parameter but String wont work. Play documentation says json is always received as a String. What am I doing wrong here?
Scala Controller Code taken from Lightbend seed Play.Reactive.MongoDB:
def createBulkFromAjax = Action.async(parse.json) { request =>
val documents = for {
heartRate <- request.body.asOpt[JsArray].toStream
maybeHeartRate <- heartRate.value
validHeartRate <- maybeHeartRate.transform(transformer).asOpt.toList
} yield validHeartRate
for {
heartRate <- hrFuture
multiResult <- heartRate.bulkInsert(documents = documents, ordered = true)
} yield {
Logger.debug(s"Successfully inserted with multiResult: $multiResult")
Created(s"Created ${multiResult.n} heartRate")
}
}
I think you're getting mixed up between the parameters you pass to your Action as part of the jsRoutes call, and parameters that get passed to endpoints (i.e. the query string, query parameters etc).
Play will return a 400 Bad Request if you've declared a non-optional parameter (like you did with ajax: String) and you don't then actually supply it in your request.
While conceptually you are passing obj to your action, it's not as a query parameter - you've declared that your endpoint expects an HTTP POST - so the JSON should be in the HTTP request body. Notice your other endpoints don't take any query parameters.
So step 1 is to fix your routes file (I've renamed your method to match your other existing working one):
POST /person/add controllers.MyController.createFromJson
If you look at the Play documentation for the Javascript reverse router, you'll see that you'll need to set the type (aka HTTP method) if you're doing something other than a GET. So, step 2, here's what your Javascript should look like to achieve a POST:
var myJson = {"name": "joe", "age":20};
var obj = JSON.stringify(myJson);
var r = controllers.MyController.createFromJson;
$.ajax({url: r.url, type: r.type, data: obj });
After those changes you should be good; your controller code looks fine. If you still get 400 Bad Request responses, check that jQuery is setting your Content-Type header correctly - you may need to use the contentType option in the jQuery $.ajax call.
Edit after still getting 400 errors:
I've just noticed that you were using JSON.parse in your Javascript - as per this answer you should be using JSON.stringify to convert an object into something jQuery can send - otherwise it may try to URLEncode the data and/or send the fields as query parameters.
The other thing to look at is whether the JSON you are sending actually agrees with what you're trying to parse it as. I'm not sure if you've provided a simplified version for this question but it looks like you're trying to parse:
{"name": "joe", "age":20}
Using:
request.body.asOpt[JsArray]
Which will always result in a None - you didn't give it an array.
The Answer to ajax javascript routes in Play Framework 2.5 for ReativeMongo:
routes.conf:
GET /javascriptRoutes controllers.HRController.javascriptRoutes
HRController:
def javascriptRoutes = Action { implicit request =>
Ok(
JavaScriptReverseRouter("jsRoutes")(
routes.javascript.HRController.createBulkFromAjax
)
).as("text/javascript")
}
routes.conf:
POST /heartrates/add controllers.HRController.createBulkFromAjax
main.scala.html:
<script type="text/javascript" src="#routes.HRController.javascriptRoutes"></script>
javascript:
var r = jsRoutes.controllers.HRController.createBulkFromAjax();
$.ajax({url: r.url, type: r.type, contentType: "application/json", data: JsonString });
HRController:
def createBulkFromAjax = Action.async(parse.json) { request =>
//Transformation silent in case of failures.
val documents = for {
heartRate <- request.body.asOpt[JsArray].toStream
maybeHeartRate <- heartRate.value
validHeartRate <- maybeHeartRate.transform(transformer).asOpt.toList
} yield validHeartRate
for {
heartRate <- hrFuture
multiResult <- heartRate.bulkInsert(documents = documents, ordered = true)
} yield {
Logger.debug(s"Successfully inserted with multiResult: $multiResult")
Created(s"Created ${multiResult.n} heartRate")
}
}
HRController.createBulkFromAjax was built from a Lightbend activator ui seed example called play.ReactiveMogno
In my routes:
POST /forms/FormValidator1/validateForm controllers.FormValidator1.validateForm(jsonForm:String)
There is a controller method defined for that route:
def validateForm(jsonForm:String) = Action { ...
Then I try to send POST request by chrome POSTMAN plugin (see pic above).
I use:
url: http://localhost:9000/forms/FormValidator1/validateForm
headers: Content Type: application/json
json data: {name: "me", surname: "my"}
So, sending this POST request I can not reach controller's method by mentioned route / url. Why?
UPDATE:
Interestly enough: after I got it working on my laptop (see my answer below) then push it on gitHub and pull it to another machine it starts working differently. Now it complains than Bad Request is [Invalid XML] nevertheless I use "application/json" header and did not change any line of code after commit. I wonder maybe it is a bug.
It seems I got it.
Here:
https://groups.google.com/forum/#!topic/play-framework/XH3ulCys_co
And here:
https://groups.google.com/forum/#!msg/play-framework/M97vBcvvL58/216pTqm22HcJ
There is wrong and correct way explained:
Doesn't work: curl -d "name=sam" http://localhost:9000/test
Works: curl -d "" http://localhost:9000/test?name=sam
This is the way how POST params are passing..in play. (second link is explanation WHY):
Sometimes you have to make compromises. In Play 1 you could bind your
action parameters from any parameter extracted from the URL path,
query string or even the request body. It was highly productive but
you had no way to control the way the form was uploaded. I mean, if a
user uploads a big file you needed to load the entire request in
memory to be able to handle it.
In Play 2 you can control the request body submission. You can reject
it early if something is wrong with the user, you can process big
files or streams without filling your memory with more than one HTTP
chunk… You gain a high control of what happens and it can help you to
scale you service. But, the other side of the coin is that when a
request is routed, Play 2 only uses the request header to make its
decision: the request body is not available yet, hence the inability
to directly bind an action parameter from a parameter extracted from
the request body.
UPDATE:
Interestly enough: after I got it working on my laptop then push it on gitHub and pull it to another machine it starts working differently. Now it complains than Bad Request is [Invalid XML] nevertheless I use "application/json" header and did not change any line of code after commit.
UPDATE 2
So I fixed it like this:
On angular side (we even can comment dataType and headers):
var data = $scope.fields
$http({
url: '/forms/FormValidator1/validateForm',
method: "POST",
//dataType: "json",
data: data,
//headers: {'Content-Type': 'application/json'}
}).success(function (data, status, headers, config) {
console.log("good")
}).error(function (data, status, headers, config) {
console.log("something wrong")
});
On playFramework side: (use BodyParser)
def validateForm = Action { request =>
val body: AnyContent = request.body
val jsonBody: Option[JsValue] = body.asJson
// Expecting text body
jsonBody.map { jsValue =>
val name = (jsValue \ "name")
val surname = (jsValue \ "surname")
....
}
Routes (don't define parameters at all !):
POST /forms/FormValidator1/validateForm controllers.FormValidator1.validateForm