1

I am trying to post JSON data from JavaScript to PHP. You can do that with either

Content-Type: application/json

or

Content-Type: application/x-www-form-urlencoded

Both works, but I had a hard time getting the first one to work. The reason is that I missed that the PHP script seems to be running 2 times when the content type is application/json.

I am quite surprised and wonder what is going on. Can anyone explain what is going on and how to handle it? (There are some related questions, but none of the answers seems to observe this behaviour.)

Here is my test code. First start PHP builtin server:

php.exe -S localhost:7000

Then in that directory create the file test1.php with the following code:

<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Content-Type");
header("Content-Type: application/json");

function writeStdErr($msg) { $fh = fopen('php://stderr','a'); fwrite($fh, $msg); fclose($fh); }
writeStdErr("\n\nI am here!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");

$e = stripslashes(file_get_contents("php://input"));
writeStdErr("e=".$e."\n");
if (strlen($e) > 0) echo $e;

Now from a web browser console (I am using Google Chrome) make an ajax call:

function test1(j) {
    url = "http://localhost:7000/test1.php";

    type = "application/x-www-form-urlencoded";
    if (j) type = "application/json";

    xhr = new XMLHttpRequest();
    xhr.open("POST", url, true);
    xhr.setRequestHeader("Content-type", type);
    xhr.addEventListener("readystatechange", function () { 
        if (xhr.readyState == 4 && xhr.status == 200) {
            var json = JSON.parse(xhr.responseText);
            console.log(json.email + ", " + json.password)
        }
    });
    var data = JSON.stringify({"email":"hey@mail.com","type":type});
    xhr.send(data);
    console.log("now sending >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", url, type);
}
test1(false) 

The output in the console is just what I expect:

I am here!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
e={"email":"hey@mail.com","type":"application/x-www-form-urlencoded"}

Now instead do the ajax call with applicaion/json, i.e. test1(true). The output is now:

I am here!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
e=

I am here!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
e={"email":"hey@mail.com","type":"application/json"}

As you can see the PHP code has been run two times. And the first time there is no input on php://input.

Why??? And how is this supposed to be handled in PHP?


Borrowed some of the code here: Sending a JSON to server and retrieving a JSON in return, without JQuery

Here are some related questions: Code to be run twice in Ajax request, Running curl twice in php, https://stackoverflow.com/questions/24373623/ajax-request-help-code-gets-created-twice, Ajax form submitting twice with Yii 2

And this question (one of the highest voted here) is of course relevant, but has nothing to say about why the PHP code is run twice:

What is the correct JSON content type?

Community
  • 1
  • 1
Leo
  • 4,136
  • 6
  • 48
  • 72

2 Answers2

0

You had some syntax errors in your javascript code (like missing brackets). Also you should use "var" for declaring your variables to limit the scope to the function. Maybe that causes that strange behavior. After correcting the javascript code I couldn't reproduce the error you mentioned => only one error log entry no matter if the parameter for test1 is set to true or false.

Here is the corrected code - give it a try:

function test1(j) {
    var url = "http://localhost:7000/test1.php";
    var type = "application/x-www-form-urlencoded";

    if (j) {
        type = "application/json";
    }

    var xhr = new XMLHttpRequest();
    xhr.open("POST", url, true);
    xhr.setRequestHeader("Content-type", type);
    xhr.addEventListener("readystatechange", function () { 
            if (xhr.readyState == 4 && xhr.status == 200) {
                    var json = JSON.parse(xhr.responseText);
                    console.log(json.email + ", " + json.type)
            }
    });
    var data = JSON.stringify({"email":"hey@mail.com","type":type});
    xhr.send(data);
    console.log("now sending >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", url, type);
}
test1(false);

Edit:

Ah, sorry my bad. I didn't read that you run the script from the javascript console. I embedded it in an php file that I called via the browser. So it wasn't a cross domain request and that's the reason why it worked for me.

I was able to reproduce your problem now. As stated in the link provided by you, you should check if the HTTP Method of the request is an "OPTIONS" request => if so, just return the headers.

Concerning the caching issue: You can set the preflight caching age to zero via the "Access-Control-Max-Age" header.

Here is the whole php code that should work:

<?php 
    header("Access-Control-Allow-Origin: *");
    header("Access-Control-Allow-Headers: Content-Type");
    header("Content-Type: application/json");
    header("Access-Control-Max-Age: 0");

    if ($_SERVER['REQUEST_METHOD'] != 'OPTIONS') {
        function writeStdErr($msg) {
            $fh = fopen('php://stderr','a'); 
            fwrite($fh, $msg); 
            fclose($fh); 
        }

        writeStdErr("\n\nI am here!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");

        $e = stripslashes(file_get_contents("php://input"));
        writeStdErr("e=".$e."\n");
        if (strlen($e) > 0) echo $e;
    }
?>
JanTheGun
  • 2,165
  • 17
  • 25
  • Thanks, but that works exactly the same for me. What PHP are you using? And what browser? (And did my code actually work differently for you?) – Leo Jan 13 '15 at 16:59
  • PHP 5.4.35, Firefox 33.1.1 and LAMP. I also tried it with the built-in Server as you did - still not reproducable. Do you get the same behavior with Firefox? – JanTheGun Jan 13 '15 at 17:49
  • Yes, I got the same behavior with FF. On Windows 7. – Leo Jan 13 '15 at 17:57
  • But I think the problem is solved. Please see my answer. – Leo Jan 13 '15 at 17:58
  • See my updated answer. Do you really need to have cross browser requests or was it just a side effect as you tested your script via the javascript console? Who will consume your service in the end? CORS makes sense for instance if you want to give someone your javascript to be integrated in another web site (so they can consume your service). – JanTheGun Jan 14 '15 at 09:16
  • You should exit when you find "OPTIONS". After sending the needed headers, of course. – Leo Jan 14 '15 at 13:54
0

I am posting this answer, but the person who solved this was actually Roman Shtylman, please see his comments about CORS preflight here today (2015-01-13).

The rules seems to be that there is no CORS preflight if you use application/x-www-form-urlencode, but there is if you use application/json:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

So, a round-trip might be avoided if using the "wrong" header (i.e. application/x-www-form-urlencode) which some people then suggest:

https://remysharp.com/2011/04/21/getting-cors-working

But... then there is caching:

http://www.html5rocks.com/en/tutorials/cors/

If that caching actually occurs is another question. And I have absolutely no idea of the answer to that question. :-(

Leo
  • 4,136
  • 6
  • 48
  • 72