I am trying to make a POST Rest API call to the following custom method using a web browsing session which was logged in manually through the standard login page:
@frappe.whitelist()
def download_test():
frappe.local.response.filename = "test.txt"
with open("/tmp/test.txt", "rb") as fileobj:
filedata = fileobj.read()
frappe.local.response.filecontent = filedata
frappe.local.response.type = "download"
If I use the GET method it works. But If I use the POST method it prompts with “Server Error”.
I’ve done some deeper investigation and found that in frappe/auth.py under validate_csrf_token, the following is blocking the request:
if frappe.local.session.data.csrf_token != csrf_token:
frappe.local.flags.disable_traceback = True
frappe.throw(_("Invalid Request"), frappe.CSRFTokenError)
I’m not very knowledgeable with regards to CSRF but I’m just thinking since it has been authenticated and we permit GET API requests, should we not allow POST API requests as well?
I have changed the API to the following that accepts 1 parameter:
@frappe.whitelist()
def download_test(test):
frappe.local.response.filename = test + ".txt"
with open("/tmp/test.txt", "rb") as fileobj:
filedata = fileobj.read()
frappe.local.response.filecontent = filedata
frappe.local.response.type = "download"
I cannot seem to pass the parameter via an XHR call
var data0 = {"test": "1"};
var json = JSON.stringify(data0);
var xhr = new XMLHttpRequest();
xhr.open("POST", '/api/method/frappe.templates.pages.print.download_test');
xhr.setRequestHeader("X-Frappe-CSRF-Token", frappe.csrf_token);
xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
xhr.onload = function(success) {
do something.....
};
xhr.send(json);
I have tried to send the params via a simple string test=1, I have tried a json format, I have tried a stringyfied version of json (as per the example above), but the querystring doesn’t get passed through. Is the API expecting the params to be in another format? I know using Ajax and specifying a data object works but I really need to use XHR.
@anand actually it is possible to pass parameters via POST which gets converted to a query string if I am not wrong. But it is quite strange since my previous example didn’t work, I tried investigating and only got as far as to seeing that the params don’t get passed to request.form. I abandoned this idea and went on to looking at other possibilities.
And yes the post you provided is right. I actually got it working with the same solution. Although I had to blob it out as an xml else it would reject the form. Thanks very much.
I agree with @anand though, $ajax is much simpler but certain things cannot be done via $ajax as far as my research goes, e.g. spitting the output as an arraybuffer.
So for anyone who wants to achieve the same thing you can use the following as an example:
var formData = new FormData();
formData.append("test", "1234");
var blob = new Blob([], { type: "text/xml"});
formData.append("webmasterfile", blob);
var xhr = new XMLHttpRequest();
xhr.open("POST", '/api/method/frappe.templates.pages.print.download_test');
xhr.setRequestHeader("X-Frappe-CSRF-Token", frappe.csrf_token);
xhr.onload = function(success) {
if (this.status === 200) {
do something....
}
};
xhr.send(formData);