2

So I'm having an issue getting Server Side Events working with PHP. I've tried various different tutorials online that utilise an infinite while loop but all of my attempts end up in a 500 error after a couple of minutes, instead of the data being sent.

For my application I need the script to keep running in an infinite loop as I'm getting updates from a hardware device via TCP sockets, and for the device to send me updates I have to keep the socket connection open.

My setup is as follows:
PHP Version 7.3.33
Windows Server 2022 with IIS 10 (Also tried on Windows Server 2016 with IIS 10)
output_buffering and zlib.output_compression are both set to "Off"
responseBufferLimit is set to 0 in the FastCGI Handler settings for PHP.

The basic test code I'm currently using is as follows:

sse.php

<?PHP
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
while(true) {
   $now = date('r');
   echo "data: The time is : {$now}\n\n";
   @ob_flush();
   flush();
   sleep(2);
}
?>

test.html

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Untitled Document</title>
</head>

<body>
    <script>
    var source = new EventSource("sse.php");
    source.onopen = function(e) {
   console.log("The connection to your server has been opened");
}
    source.onmessage = function(e) {
   

   console.log ("message: " + e.data);
}
    source.onerror = function(e) {
   console.log("The server connection has been closed due to some errors");
}
    </script>
</body>
</html>

This should (in theory) send a data string to the client with the current time every 2 seconds, but instead is 500 erroring. I must add it is only when a while loop is implemented. If I remove the while loop then it works with the client reconnecting every 3 seconds (which won't work for my application).

The PHP logs only show the following errors which don't appear to be linked to my issue, unless anyone can suggest otherwise:
[17-Nov-2022 21:26:28 UTC] PHP Warning: Module 'mysqli' already loaded in Unknown on line 0
[17-Nov-2022 21:26:28 UTC] PHP Warning: Module 'mbstring' already loaded in Unknown on line 0
[17-Nov-2022 21:26:28 UTC] PHP Deprecated: Directive 'track_errors' is deprecated in Unknown on line 0

I've searched for days for a solution with nothing working so far. Does anyone have any suggestions on potential settings that need altering with the IIS/PHP setup to get this to work?

Some tutorials have suggested making these config changes: php.ini > output_buffering and zlib.output_compression set to "Off" responseBufferLimit is set to 0 in the FastCGI Handler settings for PHP.

But neither of these have had any effect on the script.

forde52
  • 51
  • 6
  • Why are you silencing the error on ob_flush? What is it generating? – Alex Howansky Nov 17 '22 at 22:18
  • Anything in the IIS logs / event viewer in windows? – ADyson Nov 17 '22 at 22:20
  • You use `while(true)` but don't have anything to stop the loops then it will be execution timeout error. Display all error while writing your code to let it help you what's wrong with your code. – vee Nov 18 '22 at 01:00
  • 1
    @AlexHowansky it is because of output_buffering being set to ”Off”, otherwise it throws an error on every loop about there being no buffer to flush (the tutorial about turning output buffering off said to silence the errors on ob_flush). Even if I completely remove ob_flush it still doesn’t work. – forde52 Nov 18 '22 at 07:30
  • @vee there should be an if statement so if the connection with the client is dropped it will exit, I missed that with this example. I have Error reporting on within my php.ini file and adding it to the top of the script makes no difference in terms of errors. My understanding is that it should still be outputting while it is looping, and that the sleep should take care of the execution timeout but if I’ve misinterpreted that then i’ll hold my hands up there! – forde52 Nov 18 '22 at 08:13
  • @forde52 https://stackoverflow.com/questions/740954/does-sleep-time-count-for-execution-time-limit They said: _Under Linux, sleeping time is ignored, but under Windows, it counts as execution time._ – vee Nov 18 '22 at 14:56
  • 1
    I'm pretty sure that is because execution timeout. It is bad idea to make program work until user disconnected. Try something else like make interval from client and server process & response and end at each request. To make sure that it will not execution timeout, set the loop number and increase on every loop, set if condition to check that if loop over 1000 times then break the loop. – vee Nov 18 '22 at 15:15
  • 1
    @vee The infinite loop is normal for SSE back-ends, and "make program work until user disconnected" is basically what SSE was designed for. I.e. it deliberately trades higher resource usage for lower latency. – Darren Cook Nov 18 '22 at 17:47
  • I have tested the same code as OP except without `ob_flush();` line. Tested on my real hosting using CentOS Apache PHP-fpm, it just keep executing without response and never stop. Tested on Docker on my PC based on Debian Apache PHP, it work very well. Tested on my PC based on Windows Apache or IIS PHP fast CGI, both are response only when execution timeout and wait until timeout again to get response and again. No error message and no HTTP 500. – vee Nov 19 '22 at 14:48
  • @vee that’s interesting… on mine I never get a response. It does look like it must be some configuration within IIS or FastCGI that needs adjusting. Only issue is what those changes are. – forde52 Nov 19 '22 at 14:59
  • @forde52 I use the same settings as yours. That's strange. – vee Nov 19 '22 at 15:09
  • @vee what version of Windows and IIS did you try? – forde52 Nov 19 '22 at 15:15
  • Windows 11 64 bit, IIS 10.0.22621.1 – vee Nov 19 '22 at 15:37

2 Answers2

3

The HTTP 500 error is server error. It is usually caused by using infinite loop that exceeds PHP's memory limit or exceeds timeout limit in PHP. It may cause 500 error and will not be written to logs.

You can increase memory limit via PHP. This sets the maximum amount of memory in bytes that a script is allowed to allocate. For more information about “memory_limit” you can refer to this.

TengFeiXie
  • 176
  • 5
  • 1
    It will be the timeout, not the memory. Use https://www.php.net/manual/en/function.set-time-limit.php Either set it to 0 at top of script, or set it to e.g. 5 seconds inside the loop. I thought it better to post a comment, rather than an almost-the-same answer :-) – Darren Cook Nov 18 '22 at 09:41
  • BTW, notice the comment in the manual page, saying that on Windows it is the wall-clock time that is measured, not execution time. I.e. on Windows it includes the time spent sleeping. The OP is using IIS. – Darren Cook Nov 18 '22 at 09:42
  • @DarrenCook I’ve just tried the set time limit 0 and that hasn’t helped either unfortunately. Could there be any other IIS settings that need adjusting? – forde52 Nov 18 '22 at 13:08
  • @forde52 I've never used IIS for SSE, so maybe... This page describes how to find the timeouts https://www.ibm.com/support/pages/how-modify-time-out-settings-microsoft-iis-when-required-cognos-products (I don't know if Cognos uses SSE, or if it needs timeouts changing for some other reason!) – Darren Cook Nov 18 '22 at 17:43
  • @DarrenCook It’s certainly a strange one. If it was some sort of timeout I’d still expect the script to output the data when it is flushed in the loop. – forde52 Nov 19 '22 at 14:26
3

So after a lot of trial and error I have figured out the issue. If anyone else has a similar issue here are the steps I took.

  1. It would appear PHP is referenced 2 times in my IIS installation (the second time just as Fast-CGI which is how I missed it the first time round, so I had to add the "responseBufferLimit = 0" to both handlers. This enabled the infinite loop to work without a 500 error.

  2. On the Server configuration page within IIS there are some options for FastCGISettings. Within there is a setting for the "Request Timeout". By default this is set to 90 seconds. I've set this to 600 seconds (or 10 minutes) and now I get a constant stream of data for 10 minutes before the script times out and starts again. You can only choose a value between 10 and 2592000 seconds, but 10 minutes should be plenty for my application.

Big thanks to everyone who offered suggestions!

forde52
  • 51
  • 6