I'm trying to post json data to my Django Rest Framework APIView.
All works well when I use the DRF api post form, but when I try with an external app (Angular2 in my case), the request.data variable is empty.
My APIView:
class CreatePaymentView(views.APIView):
def post(self, request, *args, **kwargs):
print(request.data)
return None
My urls:
url(r'payment/create', views.CreatePaymentView.as_view(), name='CreatePayment'),
My Angular2 post:
createPayment(): Observable<any> {
let body = JSON.stringify({test_data: 'whatever'});
let headers = new Headers({
'Content-Type': 'application/json',
'X-CSRFToken': this.cookiesService.csrftoken,
'Authorization': `Token ${this.logged.user.token}`
});
let options = new RequestOptions({ headers: headers });
return this.http.post(environment.server_url_api + 'payment/create/', body, options)
.map(response => {
return response;
});
}
All the other posts of my app works well, but all my other views are DRF ModelViewSet, that's why I tink that my bug doesn't come from the CRFs. I suspect something is bad with my django view.
When I read the request variable in my CreatePaymentView, the data is an empty JSON instead of {test_data: 'whatever'}.
What is the good practice to send a simple POST with DRF ?
As explained in comments, it appears that the problem comes from the Visual Studio watch feature with breakpoint. Without watching the code with breakpoint, all works fine.
Related
Before going into more detail about the title, I just want to describe the problem at a basic level. I'm getting the dreaded 422, Unprocessable Entity error (ActionController::InvalidAuthenticityToken) after asynchronous requests in my app. What's the best way to handle this?
Loads of answers will say do:
skip_before_action :verify_authenticity_token
(which is now skip_forgery_protection in Rails 6), and then usually in the comments people get way more upvotes asking about whether or not that's a security risk. There are probably 5 threads like that.
The alternatives to doing this though, is sending the csrf token along with the POST request. So, most answers say make sure to include these headers
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
and also a csrf token like:
'X-CSRF-Token': csrfToken
The issue is getting the csrf token itself. I've tried displaying the contents of the header object with
const getHeaders = () => {
let headers = new window.Headers({
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
})
const csrfToken = document.head.querySelector("[name='csrf-token']")
if (csrfToken) { headers.append('X-CSRF-Token', csrfToken) }
return headers
}
and
let headers = getHeaders();
headers.forEach(function(value, name) {
console.log(name + ": " + value);
});
and that doesn't work. I'm guessing however I'm actually getting the token isn't working. Namely:
document.head.querySelector("[name='csrf-token']")
What's the best way to do this with plain ol' JavaScript, no other libraries? I've tried other suggestions and methods to attempt to get the token but so far all have failed. I'm assuming this lives somewhere on my browser but where? I can see all the data in Redux dev tools so the issue is definitely just this token as opposed to sending the correct data correctly (not to mention skip_forgery_protection completely solves it).
In the mean time sadly skip_forgery_protection works perfectly fine added to my Rails controllers, as security isn't the biggest concern in this stage, but I would rather fix the token issue. Thanks!
Currently we are using client-side javascript fetch to connect to our ODATA V4 ERP server:
const BaseURL = 'https://pwsepicorapp.com/ERP10.2/api/v1/Erp.BO.JobEntrySvc/'
const fetchJobNum = (async () => {
let url = BaseURL + 'GetNextJobNum'
const reply = await fetch(url,{
method: 'POST',
mode: 'cors',
headers: {
'Accept': 'application/json',
'Authorization': 'Basic xxxx',
'x-api-key' : '0HXJZgldKZjKIXNgIycD4c4DPqSrzn2UFCPHbiR1aY7IW',
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json'
},
body: JSON.stringify({})
})
let rsp = await reply.json()
let job = rsp.parameters.opNextJobNum
return job
})
And this works fine for us. We recently started looking at javascript ODATA libraries (Apache OLINGO, O.js, JayData (or other ones suggested at: https://www.odata.org/libraries/)
But what I don't see is an objective guide for a developer understand why and what these libraries provide.
I.e. I think they read the meta-data for the particular ODATA service. Fine but what does power does that add?
Perhaps my mental block is that we are only:
searching only JSON data
Not doing any nested queries (only simple $filter, $select)
Just doing simple GET, POST, PATCH
Or perhaps these libraries were needed for functionality that was missing before ODATA V4
Can anyone give a succinct description of the features for these libraries and their UNIQUE VALUE PROPOSITIONS (to borrow a Venture Capital Term) to developers? I bet others would find this useful.
Short Answer
You are right. If all you are doing just the simple operations you don't need any of these libraries as at the end of the day they are just REST calls that follow some specific conventions(i.e. OData specification).
Long Answer
The reason we have all these client side APIs is that OData offers/defines a lot more stuff.
Lets try to go though it with an example. The example I am using is Batch Requests in OData. I simplest of the terms Olingo defines a way to club multiple HTTP requests in one. It has a well defined syntax for it. That looks something like this
POST /service/$batch HTTP/1.1
Host: host
OData-Version: 4.0
Content-Type: multipart/mixed; boundary=batch_36522ad7-fc75-4b56-8c71-56071383e77b
Content-Length: ###
--batch_36522ad7-fc75-4b56-8c71-56071383e77b
Content-Type: application/http
GET /service/Customers('ALFKI')
Host: host
--batch_36522ad7-fc75-4b56-8c71-56071383e77b
Content-Type: application/http
GET /service/Products HTTP/1.1
Host: host
--batch_36522ad7-fc75-4b56-8c71-56071383e77b--
Now there are quite a few things here.
You have to start the batch request with batch_<Unique identifier> and separate individual HTTP requests with the batch boundary, and when you are done you end it with batch__<Unique identifier>--
You set the batch identifier in as the header and send additional headers(like content-type, content-length) properly that you can see in the .
Now coming back to your original question sure you can use a lot of string concatenation in your JavaScript code and generate the right payload and make an ajax call then parse back a similar kind of response, but as an application developer all you care about is batching your GET, POST, PUT and DELETE request and operation the operation you desire.
Now if you use a client library(the example is generic and might differ from library to library) the code should look something like
OData.request( {
requestUri: "http://ODataServer/Myservice.svc/$batch",
method: "POST",
data: { __batchRequests: [
{ requestUri: "Customers('ALFKI')", method: "GET" },
{ requestUri: "Products", method: "GET" }
]}
},
function (data, response) {
//success handler
}, undefined, OData.batchHandler);
So on a purely business proposition terms libraries like these can save you quite a few man hours based on your application size that will be consumed on generating the right payload strings or right URL string(in case of filters, navigation properties etc.) and debugging thought the code in case you missed a bracket or misspelled a header name, which can be used on building the core logic for the application/product and let the standardized, repetitive and boring(opinionated thought) work for you.
I have a Jetty http server with some Jersey rest services. Those services are called from a React website that runs on a Node server.
Due to the cross origin nature of this setup, I had to add some HTTP headers. Basically, all my webservices return a createOkResult() which is created as follows.
#POST
#Path("orders/quickfilter")
#Consumes(MediaType.APPLICATION_JSON)
public Response getQuickFilterProductionOrders(String data)
{
...
return createOkResult(json.toString());
}
protected Response createOkResult(Object result)
{
return buildCrossOrigin(Response.ok().entity(result));
}
protected static Response buildCrossOrigin(Response.ResponseBuilder responseBuilder)
{
return responseBuilder.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
.allow("OPTIONS")
.build();
}
For the #GET webservices that works fine. But when I create an #POST service, I just can't get it working.
Webbrowsers (chrome and firefox) return these kind of errors:
Access to XMLHttpRequest at 'http://localhost:59187/rs/production/orders/quickfilter' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
So, at first sight I would be tempted to think that the headers are still missing. The thing is, when I test this service with a tool like Postman, then all headers turn out to be in place, and the service even returns the requested data.
This is a screenshot of a POST request.
From my front-end (which runs on the node server), I use the axios API, which uses promises, and my request looks like this:
const url = "http://localhost:59187/rs/production/orders/quickfilter";
const data = JSON.stringify(request);
const headers = { headers: { "Content-Type": "application/json" } };
const promise = axios.post(url, data, headers);
Right now I have a HTTP error 500, If I remove the content type header, I get an unsupported media exception. So, I have reasons to believe that the content type is ok.
Paul Samsotha pointed me in the right direction.
I ended up adding a filter to the ServletContextHandler. Unlike the linked article, I didn't really have to create that filter from scratch. There was an existing filter class that I could use: i.e. org.eclipse.jetty.servlets.CrossOriginFilter.
FilterHolder filterHolder = context.addFilter(CrossOriginFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,PUT,POST,DELETE,OPTIONS");
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "Content-Type,Authorization,X-Requested-With,Content-Length,Accept,Origin");
filterHolder.setInitParameter(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM, "true");
filterHolder.setInitParameter(CrossOriginFilter.CHAIN_PREFLIGHT_PARAM, "false");
Some of the above parameters can probably be left out, as they are default values. But what appeared to be crucial for me, was to set the CHAIN_PREFLIGHT_PARAM to false.
One nice side-effect, is that I can simplify the code of the actual services. They do not longer need to add special headers, by contrast they can now just return Response.ok().entity(result).build();.
Note: I'm using Grails 2.5.5.
This is my method in the controller (I know save() shouldn't be a GET, but I'm just testing things out):
def save(Test cmd) {
println cmd.duration
println params.duration
}
This is my client code:
let data = JSON.parse($('#req').val());
$.ajax({
url: url,
data: data,
method: 'GET',
contentType: 'application/json'
});
When this flow is executed, on the controller side, cmd.duration does not print what was sent from the client side (instead it's the default value of zero since duration is typed as an int). On the other hand, params.duration does print what was sent from the client side.
So this indicates that it's not a problem with how the data is getting sent, but instead has to do with some data binding issue?
Also, just for reference, POST works perfectly fine with the above server-side code. The command object gets populated appropriately as long as I change the client code accordingly (changing method type and stringifying the JSON):
let data = JSON.parse($('#req').val());
$.ajax({
url: url,
data: JSON.stringify(data),
method: 'POST',
contentType: 'application/json'
});
I know there are similar questions out there, but it seems like most of them deal with issues with POST requests. So this is a bit different.
Any help is appreciated!
It looks like I just needed to remove the contentType in the ajax call on the client side for the GET request. Once I did that, everything worked as expected.
Not sure if that is expected behavior, but it works for me for now.
I have an Web API written in Core 2 that returns a FileContentResult.
You need to do a GET with an Auth Token in the header for it to work.
If you try it with postman, the file renders fine and it also renders well in an MVC View.
Now we have an external Salesforce system trying to consume the API but due to the limitations of the APEX language they have to use Javascript to inject the token into the GET method.
Example code:
$(document).ready(function() {
$.ajax({
type: 'GET',
url: 'https://file-store.com/document/{! docId}',
headers: {
'Authorization': 'Bearer {! oauthToken}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}).done(function (data) {
console.log(data);
});
});
This seems to work as the "data" that is returns contains a file - or at least an array of squiggles that look like the file serialized.
However, there seems to be no way to get the web view to render this.
I tried using embed
var object = '<embed scr="' + data + '" />';
or using an iframe
$('#iFrame').html(data);
or
$('#iFrame').html(object);
and lots of other things but so far had no success.
I do understand that in MVC this is simple, but is it at all feasible using javascript?
We do successfully receive the file, and we do have the data in memory, I just need way to render it on the browser.
Any suggestion is very welcome