26

I have a noticed a strange phenomenon in my LAMP environment.
Over the frontend I execute an AJAX post request with jQuery like this:

$.post('save.php', {data1: d1, data2: d2, [...],  dataN: dN})

The variables d1 to dN are collected from the website (e.g. from text inputs, textareas, checkboxes, etc.) with jQuery beforehand.

The file save.php takes the post parameters data1 to dataN and saves them in the database in one query.

The request takes about 500ms and works without problems unless I change pages (e.g. by clicking a link) during the request.

Normally, I would expect the request to be aborted and ignored (which would be fine) but (and this is the strange behaviour) the request seems to be completed but only with part of the data transmitted and thus saved.

That means for example, that the php script saves only data1 to data5 and sets data6 to dataN to empty.
The problem seems to be caused by the AJAX request already (not the php script) since fields $_POST['data6'] to $_POST['dataN'] are not set in php in this scenario.

So my questions:
Why does this happen (is this expected behaviour)?
How can I avoid it?

Update
The problem is neither jQuery nor php solely. jQuery collects the values correctly and tries to post them to php. I just validated it - it works. The php script on the other hand handles everything it gets as expected - it just does not receive the whole request.
So the problem must be the interrupted request itself. Unlike I'd expect it does not abort or fail, it still transmits all the data until the cut off.
Then php gets this post data and starts handling it - obviously missing some information.

Update 2
I fixed the problem by adding a parameter eof after dataN and checking if it was set in php. This way I can be sure the whole request was transmitted.
Nevertheless this does not fix the source of the problem which I still don't understand.
Any help anyone?

Horen
  • 11,184
  • 11
  • 71
  • 113
  • Never seen a jquery post with data1 [...], dataN.. as parameters. Does it make multiple xhr, or only one xhr ? – mguimard Jun 05 '13 at 07:55
  • It's just one xhr and this is how you post several parameters: http://api.jquery.com/jQuery.post/ – Horen Jun 05 '13 at 07:58
  • Are you sure of your jQuery syntax of $.post ? – netvision73 Jun 05 '13 at 07:58
  • ok you've just edited your post and wrapped your datas between { } – mguimard Jun 05 '13 at 07:58
  • @mguimard Yes, sorry for the confusion – Horen Jun 05 '13 at 08:01
  • 3
    This is the expected behavior, if your web client abort the request, your php thread is killed, no rollback on queries you've made before the kill. See http://php.net/manual/en/function.ignore-user-abort.php to ignore user aborts. – mguimard Jun 05 '13 at 08:02
  • To answer your question about how to avoid it, just check if `isset ($_POST['dataN'])` returns true. – Jerska Jun 05 '13 at 08:07
  • I don't think interrupting the page load on the navigator may impact on the server-side script in this fashion. Sending POST data is the very first thing that happens, so I do not believe partial data may be sent. Even if it were the case, I suppose the server would notice the request is incomplete, and wouldn't even start the script. Something else must be taking place here. Are you positive the expected data is really sent by the navigator (check the JS debugger on your navigator)? – RandomSeed Jun 05 '13 at 08:09
  • @mguimard This would be the right answer if the database writes were not atomic. However the OP states that the parameters are "saved in one query". – RandomSeed Jun 05 '13 at 08:10
  • @YaK indeed, something must goes wrong elsewhere. I found that link, maybe this could help http://stackoverflow.com/questions/3945683/can-a-php-script-start-before-all-post-data-is-received – mguimard Jun 05 '13 at 08:15
  • Can you show the PHP code handling the AJAX-request? Most likely the code handles the data incorrectly. – MarcDefiant Jun 07 '13 at 08:36
  • On link click try to abort all remaining xhr requests like explained here: http://stackoverflow.com/a/7566169/550618 If this solves the problem it will be a first step to a solution. – regilero Jun 10 '13 at 12:52
  • 1
    To clarify: Are you saying that there are cases (e.g., when you "change pages"), when you see in your browser that the ajax request is populated with all data, and this exact request arrives only partially at the server? Or is it maybe so that when you "change pages" that (for whatever reason) NOT the whole data is populated, but snipped off and sent to the server incomplete? I'd wireshark the traffic sent to your server to make sure that there really, really is a difference between what is sent from the client and what is populated into PHP's $_POST, which I find hard to believe. – stef77 Jun 11 '13 at 19:39
  • Sniff the network on the server to see what data reaches it. – Carlos Campderrós Jun 12 '13 at 07:42
  • I know that jQuery collects all data correctly and that jQuery posts the AJAX request. And I know that in PHP only a part of it arrives. So the problem seems to be the interrupted request itself. – Horen Jun 12 '13 at 07:55
  • 1
    @Horen Sorry to ask, but how do you know that jQuery posts the complete data? What I'd do next (if you didn't do it already) is really to tcpdump (i.e. sniff) the data being **SENT** from your client and the data that **ARRIVES** at your server. **IF** jQuery really chokes on the "interrupted request", you'd see that only part of the data is being transferred in the first place. However, I expect the data being sent exactly the same as arrives on the server, but the tcpdump will tell for sure. Then, you would know at least that the server side is fine and you could concentrate on the client. – stef77 Jun 12 '13 at 08:07
  • What about a firs parameter indicating the numbers of parameters behind? in that case you could do a rollback or something similar if there is one or more params missing. – Wallack Jun 12 '13 at 10:28
  • @Wallack This is what I am doing to solve the problem (see Update 2 in my question). However I don't know the source of problem yet – Horen Jun 12 '13 at 20:19
  • @stef77 Here is what I did to find out the cause: 1) With console.log() and Firebug I output the post parameters right before the request - they were complete 2) in php I sent a mail with a var_dump of all $_POST parameters - they were incomplete as stated in my question. So only the first couple of post parameters arrived, sometimes the value of one parameter was even interrupted in the middle. – Horen Jun 12 '13 at 20:22
  • @Horen OK, so it seems that the jQuery's ajax post gets cut off if you navigate to another page, and this cut off data is being sent to the server... Not sure how the server detects END of POST, but according to this: http://stackoverflow.com/questions/12600199/how-to-detect-end-of-http-request or this: http://stackoverflow.com/questions/4824451/detect-end-of-http-request-body such an effect should hardly happen... A network sniff to display the headers and data being sent might still help. Does this happen if you use a different browser, too? Could you post an example of data you're sending? – stef77 Jun 13 '13 at 10:19
  • Just gave it another thought. It should be virtually impossible that the server accepts an uncomplete POST. If that could happen, all kind of nasty things would have been encountered by a legion of coders. Firebug has a "Network" tab where you can see the POST and its headers, if you don't want to sniff the traffic. I suppose there is a header "Content-Length" being sent with the request with a sane value? What server are you using? Standard Apache? Have you triple checked that this is the one and only POST being sent, or is ANOTHER jQuery POST being made AFTER you navigate to another page? – stef77 Jun 13 '13 at 10:30
  • Please have a look at the answer I suggested. – stef77 Jun 13 '13 at 11:23
  • You can see with **Wireshark** what data has been sent to the server – Dor Jun 13 '13 at 20:00
  • I have the exact same problem. POST size is about 650KB, and gets corrupted if closing browser window or refreshing page in case of ajax, before post is completed. ajax.success() is not fired, but partial data is posted to "save.php" with 200 OK status. Firebug/chrome Network debugger does not log HTTP request, but apache does log it, with the partial byte count. This must be a bug in apache/php. – sivann Jan 06 '14 at 13:53

13 Answers13

6

Try the following actions to debug the problem:

  1. Check post_max_size in your php settings and compare it with the data size you are posting.

  2. User HTTP request builder, i.e. Use Fiddler to make an http request and check what it returns.

  3. Use print_r($_POST); on the top of the save.php, to check what you are getting in it.

  4. Use tool like Firebug to check what jQuery has posted.

  5. You should also verify the json object on client side that you are posting. i.e. JSON.stringify(some_object);

  6. Try posting some basic sample data { "data1":1, "data2":2, "data3":3, "data4":4, "data5":5 , "data6":6 }

Most probably you are sending to much data or likely data is invalid!

Edits: Very Foolish act but lets say you posted count as well. so directly check isset($_POST['data'.$_POST['count']] )

Waqar Alamgir
  • 9,828
  • 4
  • 30
  • 36
  • 1. post_max_size is not the problem. The request always works except when interrupted, 3. save.php returns only the first part of parameters. There are parameters missing for sure, 4. jQuery posts the complete data, so all parameters – Horen Jun 07 '13 at 13:58
  • Could you elaborate on what you mean with "interrupted"? Is it 100% reproducable behaviour that if you trigger your ajax POST, then click another link, and then the "half data" phenomenon occurs? Always? That sounds rather impossible, since the ajax request would be quite fast, so there should be occasions where it works even if you interrupt. And the Server doesn't care what you do on the client's end - either it receives a POST which will be complete since jQuery won't fire the request before it hasn't got all data, or it receives no POST at all. See my other comment => wireshark, tcpdump. – stef77 Jun 11 '13 at 19:51
  • @stef77 he may be sending huge data lets say 1MB or more the jquery post will take a little time to upload, but the mean while no code will be executed and I believe $_POST either be full with data or rather empty array. This question is pointless in that terms! – Waqar Alamgir Jun 12 '13 at 06:55
  • 1
    @WaqarAlamgir I too find it hard to believe that the POST is only "half-populated", I've never seen such (what doesn't mean that it could be, of course). Feedback on what happend when trying your point number 6 might be interesting. And, perhaps there's an error thrown from the jQuery ajax request? => http://stackoverflow.com/questions/9229005/how-to-handle-jquery-ajax-post-error-when-navigating-away-from-a-page – stef77 Jun 12 '13 at 08:15
5

I think we can rule out problems at the server site (unless it's some exotic or self-crafted server daemon), because nobody ever sends "end-of-data"-parameters with a HTTP POST request to make sure all data is really sent. This is handled by HTTP itself (see e.g. Detect end of HTTP request body). Moreover, I don't think that you have to check the Content-Length header when POSTing data to your server, simply because of the fact that nobody does this, ever. At least not in totally common circumstances like you describe them (sending Ajax POST through jQuery).

So I suppose that jQuery sends a syntactically correct POST, but it's cut off. My guess is that if you interrupt this data collecting by navigating to another page, jQuery builds an Ajax request out of the data which it was able to gather and sends a syntactically correct POST to your server, but with cut off data.

Since you're using Firebug, please go to its net tab and activate persist, so traffic data is not lost when navigating to another page. Then trigger your Ajax POST, navigate to another page (and thereby "interrupt" the Ajax call) and check in Firebug's net tab what data has actually been sent to the server by opening ALL the POST requests and checking the Headers tab (and inside this, the Request Headers tab).

My guess is that one of two things might happen:

  1. You will see that the data sent to the server is cut off already in the headers being presented to you in Firebug's net tab and the Content-Length is calculated correctly according to the actual (cut off) length of the POST data. Otherwise, I'm sure the server would reject the request as Bad Request as a whole.
  2. You will see that there are multiple POST requests, some of them (perhaps with the full, non-cut off data) actually interrupted and therefore never reaching the server, but at least one POST request (again, with the cut off data) that ist triggered by some other mechanism in your Javascript, i.e. not the trigger you thought, but by navigating to another page, more and other Ajax requests might be triggered (just a guess since I don't know your source code).

In either case, I think you'll find out that this problem ist client related and the server just processes the (incomplete, but (in terms of HTTP) syntactically valid) data the client sent to it.

From that point on, you could debug your Javascript and implement some mechanism that prevents sending incomplete data to your server. Again, it's hard to tell what to do exactly since I don't know the rest of your source code, but maybe there's some heavy action going on in collecting the data, and you could possibly make sure that the POST only happens if all the data is really collected. Or, perhaps you could prevent navigation until the Ajax request is completed or such things.

What might be interesting, if all of this doesn't make sense, would be to have a look at more of your source code, especially how the Ajax POST is triggered and if there are any other events and such if you navigate to another page. Sample data you're sending could also be interesting.

EDIT: I'd also like to point out that outputting data with console.log() might be misleading, since it's in no way guaranteed that this is the data actually being sent, it's just a logline which evaluates to the given output at the exact time when console.log() is called. That's why I suggested sniffing the network traffic, because then (and only then) you can be sure what is really being sent (and received). Nonetheless, this is a little tricky if you're not used to it (and impossible if you use encrypted traffic e.g. by using HTTPS), so the Firebug net tab might be a good compromise.

Community
  • 1
  • 1
stef77
  • 1,000
  • 5
  • 19
4

You can verify the value of the Content-Length header being received by the PHP.

This value ought to have been calculated client side when running the POST query. If it does not match, that's your error then and there. And that's all the diagnostics you need - if the Content-Length does not match the POST data, reject the POST as invalid; no need of extra parameters (computing the POST data length might be a hassle, though). Also, you might want to investigate why does PHP, while decoding the POST and therefore being able to verify its length, nonetheless seems to accept a wrong length (maybe the information needed to detect the error is somewhere among the $_SERVER variables?).

If it does match though, and still data isn't arriving (i.e., the Content-Length is smaller, and correctly describes the cut-off POST), then it is proof that the POST was inspected after the cut-off, and therefore either the error is in the browser (or, unlikely, in jQuery) or there is something between the browser and the server (a proxy?) that is receiving an incomplete query (with Content-Length > Actual length) and is incorrectly rewriting it, making it appear "correct" to the server, instead of rejecting it out of hand.

Some testing of both the theory and the workaround

Executive summary: I got the former wrong, but the latter apparently right. See code below for a sample that works on my test system (Linux OpenSuSE 12.3, Apache).

I believed that a request with wrong Content-Length would be refused with a 400 Bad Request. I was wrong. It seems that at least my Apache is much more lenient.

I used this simple PHP code to access the key variables of interest to me

<?php
    $f = file_get_contents("php://input");
    print $_SERVER['CONTENT_LENGTH'];
    print "\nLen: " . strlen($f) . "\n";
?>

and then I prepared a request with a wrong Content-Length sending it out using nc:

POST /p.php HTTP/1.0
Host: localhost
Content-Length: 666

answer=42

...and lo and behold, nc localhost 80 < request yields no 400 error:

HTTP/1.1 200 OK
Date: Fri, 14 Jun 2013 20:56:07 GMT
Server: Apache/2.2.22 (Linux/SUSE)
X-Powered-By: PHP/5.3.17
Vary: Accept-Encoding
Content-Length: 12
Content-Type: text/html

666
Len: 10

It occurred to me then that content length might well be off by one or two in case the request ended with carriage return, and what carriage return - LF? CRLF?. However, when I added simple HTML to be able to POST it from a browser

<form method="post" action="?"><input type="text" name="key" /><input type="submit" value="go" /></form>

I was able to verify that in Firefox (latest), IE8, Chrome (latest), all running on XP Pro SP3, the value of the Content-Length is the same as the strlen of php://input.

Except when the request is cut off, that is.

The only problem is that php://input is not always available even for POST data.

This leaves us still in a quandary:

IF THE ERROR IS AT THE NETWORK LEVEL, i.e., the POST is prepared and supplied with a correct Content-Length, but the interruption makes it so that the whole data is cut off as this comment by Horen seems to indicate:

So only the first couple of post parameters arrived, sometimes the value of one parameter was even interrupted in the middle

then really checking Content-Length will prevent PHP from handling an incomplete request:

<?php
    if ('POST' == $_SERVER['SERVER_PROTOCOL'])
    {
            if (!isset($_SERVER['Content-Length']))
            {
                header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request', True, 400);
                die();
            }
            if (strlen(file_get_contents('php://input'))!=(int)($_SERVER['Content-Length']))
            {
                header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request', True, 400);
                die();
            }
    }
    // ... go on
?>

ON THE OTHER HAND if the problem is in jQuery, i.e. somehow the interruption prevents jQuery from assembling the full POST, and yet the POST is made up, the Content-Length calculated of the incomplete data, and the packet sent off -- then my workaround can't possibly work, and the "telltale" extra field must be used, or... perhaps the .post function in jQuery might be extended to include a CRC field?

LSerni
  • 55,617
  • 10
  • 65
  • 107
  • I think that even if the Content-Length header differs from the actual POST data size, this would be a really rare condition. Just think, I've never ever seen checks like you posted last being done. If these were necessary, most sites using POSTs to PHP simply wouldn't work. Besides, what happens if you send a Content-Length **smaller** than the actual data? Moreover, Horen's last comment here http://stackoverflow.com/a/17086985/2406389 suggests that there is no data being posted at all. Without having a look at the actual jQuery sources, I fear we're pretty much lost in guessing. – stef77 Jun 16 '13 at 06:58
  • I can confirm what you wrote, our Apache (2.2.3 on CentOS 5.9) also responds `200 OK`. Moreover, if I add `Content-Type: application/x-www-form-urlencoded` to the request, I can `var_dump` `$_POST['answer']` (without the additional header this doesn't seem to work), and really, when the `Content-Length` is **8**, $_POST['answer'] is **4**, not **42**. While I would also have expected `400 Bad Request`, I still doubt that you would have to check `Content-Length` and `php://input` manually. What we would have to know is the `Content-Length` header of the jQuery post Horen sends to the server. – stef77 Jun 16 '13 at 07:38
  • 1
    @stef77, I believe that most browsers (I'm now experimenting with Firefox) have a buffer of about 16-32 Kb between the POST body and the socket. If the POST body is below that limit, it is either sent, or not sent. No need to check anything. Also, short bodies have little likelihood of getting it in the neck from a page change. Very large bodies, though, are at risk; but they're very rare situations, and that would explain why nobody seems to check for that. Most simply *never need to*. – LSerni Jun 16 '13 at 07:50
  • @Iserni But short and large are relative; just think about slow network connections, especially when it comes to cell phones. The effect would be that mobile users experience cut off POSTs more often than users with a regular network connection. Hard to believe. The solution would be that you **always** have to perform sanity checks like the one you provided, no matter how small the amount of data is you transfer, because you never could be sure. Might of course be a broken jQuery version or very special circumstances Horen faces here. – stef77 Jun 16 '13 at 08:32
  • unfortunately content-length in my case matches the corrupt data length, not the full. – sivann Jan 06 '14 at 14:58
  • You can open a new question detailing your specific situation. I'd be glad to see whether we can work something out. Only, I'd have to get back to you in a day or so. – LSerni Jan 07 '14 at 14:43
3

Post data looks just like GET:

Header1: somedata1\r\n
Header2: somedata2\r\n
...
HeaderN: somedataN\r\n
\r\n
data1=1&data2=2&...&dataN=N

When request is aborted, in some cases, last line may be passed only partially. So, here are some possible solutions:

  1. Compare Content-Length and strlen($HTTP_RAW_POST_DATA)
  2. Validate input data
  3. Pass not so much data at one time
Timur
  • 6,668
  • 1
  • 28
  • 37
3

I have tried to recreate this problem using triggers, manually, changing server settings, doing my very best to #$%& things up, using different data sizes but I never ever got only half a request in PHP. Simply because apache will not invoke PHP untill the request is completely done. See this question about Reading “chunked” POST data in PHP

So the only thing that can go wrong is that Jquery only gathers part of the data and then makes a POST request. Using just $.post('save.php', data) as you mentioned, that wont happen. Its either working to gather the data or its waiting for a response from the server.

If you switch sites during the gathering, there wont be a request. And if you switch after the request has been made and you move away quickly, before all data has been transmitted, the apache server will see it as half a request and wont invoke PHP.

Some suggestions:

Is it possible that you are using seperate pages for succesfull and partial requests? Because PHP does only add the first 1000 elements to $_POST and perhaps the failed requests have more then data1000=data elements? So there wont be an EOF param.

Is it possible that you are gathering the data in a global var in javascript and have an onbeforeunload method that sends data as well? Because then there might only be half the data in the POST.

Can you share some information on the data you are seding? Are there a lot of small elements (like data1 till data10000) or a few large once?

Is it always the same element that you receive last? Like always data6 as you mention? Because if it is, the chances of a failed attempt always at the exact same dataN field would be very slim.

Community
  • 1
  • 1
Hugo Delsing
  • 13,803
  • 5
  • 45
  • 72
  • in my case it only goes wrong when one of the data parameters is very long (100kb e.g.) – Horen Jun 13 '13 at 12:27
  • @Horen Please check the net tab in Firebug as I suggested in my answer. The longer I think about it, the more I'm convinced that this will lead you to the root of the problem. – stef77 Jun 13 '13 at 12:59
  • @stef77 The net tab doesn't give a lot of information. It says the request was aborted and displays the ajax post request in red. – Horen Jun 13 '13 at 13:02
  • @Horen well, so there is no data transferred to the server: http://getfirebug.com/wiki/index.php/Net_Panel#Request_List As I said in my answer: are there multiple POSTs, or just that single, red one? What is the HTTP state? Aborted? 400 Bad request? Ah, and in case you didn't see: you can toggle open the request by clicking on the plus icon left of the request. – stef77 Jun 13 '13 at 13:05
3

My problem was there were too many variables in one of my post objects. PHP has a max_input_vars variable which is set to 1000 by default.

I added this line to my .htaccess file (since I don't have access to the php.ini file):

php_value max_input_vars 5000

Problem solved!

brz
  • 1,846
  • 21
  • 21
2

Can you check your host log at

/var/log/messages

Last time i had "missing"post variables at php i found that i was sending null ASCII chars and the server(CentOS) was considering it an attack, then dropping those specific variables... Took me a week to figure it out! This was the server log response:

suhosin[1173]: ALERT - ASCII-NUL chars not allowed within request variables - dropped variable 'data3' (attacker '192.168.0.37', file '/var/www/upload_reader.php')

If that is your problem, tyr to, with js, compress your variables, encode them with base64. Post them with ajax, then receive then at php, decode64 then uncompress! That solved for me ;)

Hiro Ferreira
  • 376
  • 2
  • 5
2

Solved the problem by increasing max_input_vars in my server's php.ini file

Since I had more than 1000 variables in the array, only part of them was received by the server!

Hope this helps someone!

Viral Solani
  • 840
  • 1
  • 9
  • 31
1

In order to find our more about what is happening, why not properly code you ajax request using jQuery's ajax function. Use all the callback functions to track what happened to your call or what came back? The element type is set to POST and the element data carries whatever object structure { ... } you like.

$.ajax({
    url : "save.php",
    type : "POST",
    data : {
        "ajax_call" : "SOME_CUSTOM_AJAX_REQUEST_REFERENCE",
        "data1" : data1,
        "data2" : data2,
        "data2" : data2,
        "dataN" : dataN
    },
    //dataType : "html", contentType: "text/html; charset=utf-8",
    dataType : "json", contentType: "application/json; charset=utf-8",
    beforeSend: function () {
        //alert('before send...');
    },
    dataFilter: function () {
        //alert('data filter...');
    },
    success: function(data, textStatus, jqXHR) {
        //alert('success...');
        var response = JSON.parse(jqXHR.responseText, true);
        if (undefined != response.data) {
            my_error_function();
        }
        my_response_function(response.data);
    },
    error: function(jqXHR, textStatus, errorThrown) {
        //alert('error...');            
    },
    complete: function (xhr, status) {
        //alert('end of call...');          
        my_continuation_function();
    }
});
LenArt
  • 333
  • 3
  • 6
  • While this could help to find out that the request was aborted or not finished correctly, it will not solve the problem that php is handling the incomplete data and does not prevent the root of the problem nor does it explain it. – Horen Jun 05 '13 at 14:52
1

Before send a request, set "onbeforepageunload" handler for document(to prohibit the transition to another page), and unbind after success.

To example:

$(document).on('unload', function(e){
    e.preventDefault();
    // Here you can display a message, you need to wait a bit
    return false;
});
maximkou
  • 5,252
  • 1
  • 20
  • 41
  • you are looking for onbeforeunload instead: on('beforeunload',...) but i don't think it's can help OP with his issue – A. Wolff Jun 07 '13 at 09:22
1

A guess - the suhosin function on your Ubuntu/Debian server causes the field to be cut off?

jdog
  • 2,465
  • 6
  • 40
  • 74
0

I have the same problem. POST size is about 650KB, and gets corrupted if closing browser window or refreshing page in case of ajax, before post is completed. ajax.success() is not fired, but partial data is posted to "save.php" with 200 OK status. $_SERVER's Content-length is of no use as suggested elswhere since it matches the actual content-length of the partial data.

I figured out 2 ways of overcoming this:

  1. as you propose, append a post variable at the end. Seems to work, although it seems a bit risky.
  2. make your "save.php" script save to a temporary db column, and then use the ajax.success() to call another php script, say savefinal.php, without passing any data, which transfers data from the temp column to the final column (or just flags this data as valid). That way if post is interrupted data will only reside on the temp column on the database (or will not be flagged as valid).

The .success() is not called if post is interrupted, so this should work.

I presume this is a jquery bug sending a wrong (very small) content-length to apache, and apache is forced to assume that the post request has completed, but I'm not really sure.

sivann
  • 2,083
  • 4
  • 29
  • 44
0

Why does this happen (is this expected behaviour)?

Was also looking for the cause of this strange behaviour when some variables from post were missing and came to this question where the very similar behaviour is explained with the slow connection of the web client sending a POST request with multipart/form-data.

Here goes the mentioned question:

I am facing a problem when a remote web client with slow connection fails to send complete POST request with multipart/form-data content but PHP still uses partially received data to populate $_POST array. As a result one value in $_POST array can be incomplete and more values can be missing.

See How to check for incomplete POST request in PHP

How can I avoid it?

There you can find the recommended solution, as well. Nevertheless, the same solution was already proposed by you.

You can add field <input type="hidden" name="complete"> (for example) as the last parameter. in PHP check firstly whether this parameter was sent from client. if this parameter sent - you can be sure that you got the entire data.