XMLHttpRequest JSON Post going as form-urlencoded - javascript

I have a form in a HTML page which does the following:
form method="POST" id="myForm" onsubmit="callSlates();">
Javascript function is as follows:
function callSlates(){
var form=document.getElementById('myForm');
form.action = "https://dev1-apiservicesweb.dev.jabs.com:8111/api/v1/systems/slates";
// collect the form data while iterating over the inputs
var data = {};
for (var i = 0, ii = form.length; i < ii; ++i) {
var input = form[i];
if (input.name == "ID1") {
data[input.name] = input.value;
}
if (input.name == "SDCode") {
data[input.name] = input.value;
}
}
var xhr = new XMLHttpRequest();
xhr.open('POST', form.action);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
}
xhr.send(JSON.stringify(data));
}
//return false;
}
POST calls are happening but when I check the Request Header, it shows that the application/x-www-form-urlencoded and am ending with error 504-Gateway Timeout Error
The endpoint I am calling expects json header and JSON data. I believe I am doing that but when I used Chrome developer tools, I don't see that. Has it got something to do with the way am making the call?
Edit: When I tried hitting that URL with Postman, with header and body as JSON, am getting 200 response (as expected).
UPDATE: I understood that the form was getting submitted rather than the Ajax call and thus took Barmar advice and put return false; in form onsubmit. Now am not getting 504 anymore and getting 405 instead.
Following is the request header am seeing currently:
Accept:*/*
Accept-Encoding:gzip, deflate, sdch, br
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:content-type
Access-Control-Request-Method:POST
Connection:keep-alive
Host:dev1-apiservicesweb.dev.jabs.com:8111
Origin:http://localhost:3000
And in general information, it says:
Request Method:OPTIONS
Status Code:405
As per le_m, could this be a CORS issue?

Since your request works from your local host via postman but not from within the browser, the culprit is probably the Same Origin Policy:
Your browser attempts to send a Cross Origin Request (CORS). There are two types of CORS requests:
Preflighted requests: The browser first asks the server about supported HTTP methods by sending an HTTP OPTIONS request followed by the actual HTTP request upon approval.
Simple requests: The browser directly sends the actual HTTP request.
Now, which of the above does your browser choose? MDN states that simple requests will be send if the Content-Type is one of the following:
application/x-www-form-urlencoded
multipart/form-data
text/plain
However, Content-Type: application/json is not allowed for simple requests. Thus, you currently send preflighted CORS requests. You can verify that by opening the developer console e.g. in Firefox: You will notice that your code first sends an OPTIONS request.
Now, this OPTIONS request receives a 405 Method not allowed error. This indicates that the server doesn't support preflighted CORS requests. You either need to change the server configuration OR change the Content-Type to one of the compatible ones listed above. If your server can't accept one of those content types, then you are probably out of luck.
See also this answer given by #ArslanTariq in reply to a similar issue.

You're not preventing the normal form submission. Change your form to:
<form method="POST" id="myForm" onsubmit="callSlates();return false;">
return false; prevents the default submit action.
There's also no need to set form.action in your function, since you don't want to submit the form normally.

Related

How to POST json data to the server? (CORS error)

My goal
I am doing .aspx file on Microsoft Visual Studio. What I expect is that the system will scan the QR code and then send the JSON data to the server side.
PS. I am using XMLHttpRequest to send the data.
What I Get
First I tried to pass it directly, it does not work so I check its status and readyStats, what it shows is 1(open) and 0(not initialized) (I am expecting 4(done) and 200(ok)) the responseText is null. I then inspected my browser (Google Chrome), the console shows an error "Access to XMLHttpRequest at 'https://website-A.aspx' from origin 'https://localhost:44371' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource."
What I tried
So I searched for solution on stack overflow, most of them says that I need to put in the header Access-Control-Allow-Origin:* to the server, but the problem is I do not have the control of the server but I do contacted them to change the code to try it out, end up Access-Control-Allow-Origin:* doesn't work so I looked for alternative. I tried to use plugin such as Moesif Origin & CORS Changer but it shows "Access to XMLHttpRequest at 'https://website-A.aspx' from origin 'https://localhost:44371' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.".
Below is my code:
function onScanSuccess(qrCodeMessage) {
...
var url = "https://Website-A.aspx";
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open("POST", url);
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
console.log(xhr.status);
console.log(xhr.responseText);
}
};
//I hardcoded the data for testing
var data = {
"SOME_ID": "Value",
"SOME_TYPE": "Value",
"SOME_ID": "Value",
"SOME_AMT": "Value",
"SOME_AMT_CURRENCY": "Value",
"SOME_DESC": "Value",
...
};
xhr.send(data);
}
Is there anyway to solve this without changing the server side's code or bypassing the CORS?
I wish to solve this by modify the code in the same .aspx only, is it possible?
If my code ain't going to work, is there any other way to do it?
Any helps will be appreciated.
cors must be allowed on server side. your code looks ok.
sorry, i can't use the comment function yet

JSON always returning "client_id not specified"

I'm trying to get a token from the ArcGIS Online service using a Javascript. However, it's always returning an error which indicates the client_id isn't specified.
Am I doing everything right here?
<script type="text/javascript">
var MyJSONText = '{"client_id":"<<MY_CLIENT_ID>>","client_secret":"<<MY_CLIENT_SECRET>>","grant_type":"client_credentials","expiration":"1440","f":"json"}';
var MyJSON = JSON.parse(MyJSONText);
xhr = new XMLHttpRequest();
xhr.open("POST", "https://www.arcgis.com/sharing/rest/oauth2/token/");
xhr.send(MyJSON);
xhr.onreadystatechange = function ()
{
if (xhr.readyState == 4 && xhr.status == 200)
{
alert(xhr.responseText);
}
}
</script>
Edit - full error is:
{"error":{"code":400,"error":"invalid_request","error_description":"client_id not specified","message":"client_id not specified","details":[]}}
I was able to retrieve an access token using application/x-www-form-urlencoded request:
POST https://www.arcgis.com/sharing/rest/oauth2/token HTTP/1.1
User-Agent: Fiddler
Content-Type: application/x-www-form-urlencoded
Host: www.arcgis.com
Content-Length: 126
client_id=<YOUR ID>&client_secret=<YOUR SECRET>&grant_type=client_credentials&expiration=1440&f=json
which means that you might need to specify the Content-Type request header when making the XHR request:
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
and of course properly formatting the body as application/x-www-form-urlencoded instead of JSON. In my tests this endpoint didn't work with JSON payload.
Unfortunately from what it looks, the token endpoint doesn't support setting the Content-Type request header in its CORS policy which means that you might be out of luck in calling it with javascript. Besides their documentation doesn't mention anything about javascript as a supported language.
So basically if you want to make this work you could get the access token on your server side and pass it along to the client.
Access Token Generation Url =>https://www.arcgis.com/sharing/rest/oauth2/token
Headers
enter image description here
For the Case of body in Post man select 'x-www-form-urlencoded'
enter image description here
After that you will get the respective expected result with token
{"access_token":"S-z3mxAqsZBeihx8NgFmNGsZAUGfzKZWCYVV2TiQO422u9XDLTCTZAkpoecyxx_LAc71I_tPkeeOlo2Pzkapv01bBOA03SzMRjRMOm-h3ljTl-pb3XdffoyYjCflE4F8LD377DbpjDWkupkdTaxJpg..","expires_in":7200}

Programmatically setting http request headers in javascript for Content-Type

I'm writing some code to handle sending post data for my application and I wanted to make it so that I can send custom headers from another function if I need them. My question is, can I default something like "Content-Type" the way my code example does below and then overwrite it, or do I need to check the custom headers being sent, and if Content-type is not set, set it to the default. Basically, during the creation of the post request, can you overwrite headers programmatically?
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlhttp.setRequestHeader("Content-length", formData.length);
// check for custom headers
if ((headers !== null) && (headers !== undefined)) {
for(var k in headers) {
if(headers.hasOwnProperty(k) {
xmlhttp.setRequestHeader(k.toString(), headers[k]);
}
}
}
I'm sending a different "Content-Type" like JSON perhaps in the "headers" object. If I do setRequestHeader on Content-Type again does it overwrite or does it send 2 content-type headers in the post request?
edit: I don't know why I asked this on StackOverflow, I just realized I could probably test this by logging my headers with a form handler, which I'm off to do, I'll leave the question up anyway.
According to MDN:
Sets the value of an HTTP request header. You must call
setRequestHeader()after open(), but before send(). If this method is
called several times with the same header, the values are merged into
one single request header.
So calling setRequestHeader() multiple times will yield the following:
Content-Type: application/x-www-form-urlencoded, application/json

"Acecss Denied to content document" error in IE9 with goog.net.IFrameIO

I am trying to use goog.net.IFrameIO to upload a file and get a JSON response from server after uploaded file is processed. It works fine on Chrome and Safari, but does not with work with IE9 where it errors out with "Acecss Denied to content document", then prompts up to save/open the json response. Here is the html code and js code that I have -
HTML Code:
<form action="/" enctype="multipart/form-data" method="POST" id="upload-form">
<input type="file" name="selected-file" id="selected-file">
<div role="button" class="test-button>Upload</div>
</form>
JS Code:
test.prototype.uploadFile = function() {
// Send the form submission to the server using IframeIo to make it feel like
// AJAX.
var form = /** #type {HTMLFormElement} */ (goog.dom.getRequiredElement(
'upload-form'));
var io = new goog.net.IframeIo();
goog.events.listen(io, goog.net.EventType.COMPLETE, goog.bind(this.processResponse_, this, io));
io.sendFromForm(form, '/uploadfile');
}
test.prototype.processResponse = function(io) {
if (!io.isSuccess()) {
alert('iframeio error encountered:' + io.getLastError());
return;
}
var response = null;
try {
response = io.getResponseJson();
..
} catch (error) {
....
}
I am trying to use closure solution only and not other libraries. I have also went through the question at Fallback AJAX file upload for Internet Explorer but was not able to comment on accepted answer for it.
[Troubleshooting]: I see that method goog.net.IframeIo.prototype.onIeReadyStateChange_() is called with ready states like - loading, interactive and then complete, but is not able to get the content document from iframe created for IE.
HTTP Request Headers:
Accept: text/html, application/xhtml+xml, /
Content-Type: multipart/form-data
Accept-Encoding: gzip, deflate
HTTP Response Headers:
X-Frame-Options: SAMEORIGIN
Content-Type: application/json; charset=utf-8
The IframeIO library, for IE(version less than 11) creates an iframe to which the HTTP response is posted after file upload.
But iframe in IE9/10(not sure about 8 or 11) does not accept HTTP response with content-type JSON. The response content-type need to be changed to "text/plain" for IE9/10, to make it work.

Prototype AJAX request being sent as OPTIONS rather than GET; results in 501 error

I'm attempting to access a web service with Prototype/AJAX and am running into an error I can't figure out: it seems that when I make a request to a server my request is interpreted as an OPTIONS rather than a GET request (and in turn throws a 501 - not implemented error since the server only allows GET requests, based on what I understand from Access-Control-Request-Method:). Am I missing something in my AJAX/request formulation that may be causing this error? I've read a bit into CORS/preflighted requests here but I'm unsure how it could apply when my code looks compliant...
Here's the relevant AJAX request:
function fetchMetar() {
var station_id = $("station_input").value;
new Ajax.Request(REQUEST_ADDRESS, {
method: "get",
parameters: {stationString: station_id},
onSuccess: displayMetar,
onFailure: function() {
$("errors").update("an error occurred");
}
});
}
and here's the error and relevant request info I get from Chrome:
Request URL:http://weather.aero/dataserver_current/httpparam?
dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=3
&mostRecent=true&stationString=&stationString=KSBA
Request Method:OPTIONS
Status Code:501 Not Implemented
Request Headers
Accept:*/*
Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:origin, x-prototype-version, x-requested-with, accept
Access-Control-Request-Method:GET
Connection:keep-alive
Host:weather.aero
Origin:http://domain.com
Referer:http://domain.com/.../...html
What could I be overlooking here? Why does Chrome say the request is being sent as OPTIONS rather than GET? When Chrome spits out the Access-Control-Request-Headers: information, are these exclusively the only headers allowed in the request?
Thanks!
Too many hours looking for a correct fix on prototypejs... finally, we have a non-intrusive solution on great kourge (Wilson Lee) article!. Here is an excerpt:
Most major Ajax frameworks like to set custom HTTP headers on the Ajax requests you instantiate; the most popular header is X-Requested-With: XMLHttpRequest. Consequently your request is promoted to a preflighted one and fails. The fix is to prevent your JavaScript framework from setting these custom headers if your request is a cross-domain one. jQuery already cleverly avoids unintentionally preflighting requests by not setting custom headers if your URL is considered to be remote. You'd have to manually prevent this if you're using other frameworks.
It can be so simple as:
new Ajax.Request('http://www.external-domain.net/my_api.php?getParameterKey=getParameterValue', {
method:'post',
contentType:"application/x-www-form-urlencoded",
postBody:'key=' + value,
onSuccess: function(response) {
// process response
},
onCreate: function(response) { // here comes the fix
var t = response.transport;
t.setRequestHeader = t.setRequestHeader.wrap(function(original, k, v) {
if (/^(accept|accept-language|content-language)$/i.test(k))
return original(k, v);
if (/^content-type$/i.test(k) &&
/^(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)(;.+)?$/i.test(v))
return original(k, v);
return;
});
}
});
If you see any disadvantage/improvement to this solution, we welcome you to share :)
In fact it is preflight request, because Prototype adds custom headers X-Requested-With, X-Prototype-Version to the request. Because of these headers browser sends first OPTIONS request. XHR spec says:
For non same origin requests using the HTTP GET method a preflight request is made when headers other than Accept and Accept-Language are set.
How to solve this problem? I can see only one possibility to solve this ASAP: completely overwrite method Ajax.Request#setRequestHeaders(), e.g. insert this script right after Prototype.js:
Ajax.Request.prototype.setRequestHeaders = function() {
var headers = {
// These two custom headers cause preflight request:
//'X-Requested-With': 'XMLHttpRequest',
//'X-Prototype-Version': Prototype.Version,
'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
};
if (this.method == 'post') {
headers['Content-Type'] = this.options.contentType +
(this.options.encoding ? '; charset=' + this.options.encoding : '');
/* Force "Connection: close" for older Mozilla browsers to work
* around a bug where XMLHttpRequest sends an incorrect
* Content-length header. See Mozilla Bugzilla #246651.
*/
if (this.transport.overrideMimeType &&
(navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
headers['Connection'] = 'close';
}
if (typeof this.options.requestHeaders == 'object') {
var extras = this.options.requestHeaders;
if (Object.isFunction(extras.push))
for (var i = 0, length = extras.length; i < length; i += 2)
headers[extras[i]] = extras[i+1];
else
$H(extras).each(function(pair) { headers[pair.key] = pair.value; });
}
for (var name in headers)
this.transport.setRequestHeader(name, headers[name]);
}
This patch removes custom headers from any AJAX request. In case when you still need these headers for non-CORS requests, more logic may be added which will give possibility to disable these headers in options for new Ajax.Request() (I'll skip this variant here to make answer shorter).
Actually, it's much easier with Prototype.js V1.7:
Ajax.Responders.register({
onCreate:function(r){
r.options.requestHeaders={
'X-Prototype-Version':null,
'X-Requested-With':null
};
}
});
Prototype.js drops any pre-defined header if its value is null.
I've never used Prototype and i'm not sure how much use i'll be. But I had a quick look at the docs and I didn't see any support for method and parameters.
So try:
new Ajax.Request(REQUEST_ADDRESS+"?stationString="+station_id, {
onSuccess: displayMetar,
onFailure: function() {
$("errors").update("an error occurred");
}
});
Also I just noticed that stationString in your example should be in quotes assuming it isn't a variable.

Categories

Resources