How do I close a connection early?

前端 未结 19 1422
情书的邮戳
情书的邮戳 2020-11-22 04:24

I\'m attempting to do an AJAX call (via JQuery) that will initiate a fairly long process. I\'d like the script to simply send a response indicating that the process has star

相关标签:
19条回答
  • 2020-11-22 04:55

    Note for mod_fcgid users (please, use at your own risk).

    Quick Solution

    The accepted answer of Joeri Sebrechts is indeed functional. However, if you use mod_fcgid you may find that this solution does not work on its own. In other words, when the flush function is called the connection to the client does not get closed.

    The FcgidOutputBufferSize configuration parameter of mod_fcgid may be to blame. I have found this tip in:

    1. this reply of Travers Carter and
    2. this blog post of Seumas Mackinnon.

    After reading the above, you may come to the conclusion that a quick solution would be to add the line (see "Example Virtual Host" at the end):

    FcgidOutputBufferSize 0
    

    in either your Apache configuration file (e.g, httpd.conf), your FCGI configuration file (e.g, fcgid.conf) or in your virtual hosts file (e.g., httpd-vhosts.conf).

    In (1) above, a variable named "OutputBufferSize" is mentioned. This is the old name of the FcgidOutputBufferSize mentioned in (2) (see the upgrade notes in the Apache web page for mod_fcgid).

    Details & A Second Solution

    The above solution disables the buffering performed by mod_fcgid either for the whole server or for a specific virtual host. This might lead to a performance penalty for your web site. On the other hand, this may well not be the case since PHP performs buffering on its own.

    In case you do not wish to disable mod_fcgid's buffering there is another solution... you can force this buffer to flush.

    The code below does just that by building on the solution proposed by Joeri Sebrechts:

    <?php
        ob_end_clean();
        header("Connection: close");
        ignore_user_abort(true); // just to be safe
        ob_start();
        echo('Text the user will see');
    
        echo(str_repeat(' ', 65537)); // [+] Line added: Fill up mod_fcgi's buffer.
    
        $size = ob_get_length();
        header("Content-Length: $size");
        ob_end_flush(); // Strange behaviour, will not work
        flush(); // Unless both are called !
        // Do processing here 
        sleep(30);
        echo('Text user will never see');
    ?>
    

    What the added line of code essentially does is fill up mod_fcgi's buffer, thus forcing it to flush. The number "65537" was chosen because the default value of the FcgidOutputBufferSize variable is "65536", as mentioned in the Apache web page for the corresponding directive. Hence, you may need to adjust this value accordingly if another value is set in your environment.

    My Environment

    • WampServer 2.5
    • Apache 2.4.9
    • PHP 5.5.19 VC11, x86, Non Thread Safe
    • mod_fcgid/2.3.9
    • Windows 7 Professional x64

    Example Virtual Host

    <VirtualHost *:80>
        DocumentRoot "d:/wamp/www/example"
        ServerName example.local
    
        FcgidOutputBufferSize 0
    
        <Directory "d:/wamp/www/example">
            Require all granted
        </Directory>
    </VirtualHost>
    
    0 讨论(0)
  • 2020-11-22 04:55

    Ok, so basically the way jQuery does the XHR request, even the ob_flush method will not work because you are unable to run a function on each onreadystatechange. jQuery checks the state, then chooses the proper actions to take (complete,error,success,timeout). And although I was unable to find a reference, I recall hearing that this does not work with all XHR implementations. A method that I believe should work for you is a cross between the ob_flush and forever-frame polling.

    <?php
     function wrap($str)
     {
      return "<script>{$str}</script>";
     };
    
     ob_start(); // begin buffering output
     echo wrap("console.log('test1');");
     ob_flush(); // push current buffer
     flush(); // this flush actually pushed to the browser
     $t = time();
     while($t > (time() - 3)) {} // wait 3 seconds
     echo wrap("console.log('test2');");
    ?>
    
    <html>
     <body>
      <iframe src="ob.php"></iframe>
     </body>
    </html>
    

    And because the scripts are executed inline, as the buffers are flushed, you get execution. To make this useful, change the console.log to a callback method defined in you main script setup to receive data and act on it. Hope this helps. Cheers, Morgan.

    0 讨论(0)
  • 2020-11-22 04:56

    Joeri Sebrechts' answer is close, but it destroys any existing content that may be buffered before you wish to disconnect. It doesn't call ignore_user_abort properly, allowing the script to terminate prematurely. diyism's answer is good but is not generically applicable. E.g. a person may have greater or fewer output buffers that that answer does not handle, so it may simply not work in your situation and you won't know why.

    This function allows you to disconnect any time (as long as headers have not been sent yet) and retains the content you've generated so far. The extra processing time is unlimited by default.

    function disconnect_continue_processing($time_limit = null) {
        ignore_user_abort(true);
        session_write_close();
        set_time_limit((int) $time_limit);//defaults to no limit
        while (ob_get_level() > 1) {//only keep the last buffer if nested
            ob_end_flush();
        }
        $last_buffer = ob_get_level();
        $length = $last_buffer ? ob_get_length() : 0;
        header("Content-Length: $length");
        header('Connection: close');
        if ($last_buffer) {
            ob_end_flush();
        }
        flush();
    }
    

    If you need extra memory, too, allocate it before calling this function.

    0 讨论(0)
  • 2020-11-22 04:58

    A better solution is to fork a background process. It is fairly straight forward on unix/linux:

    <?php
    echo "We'll email you as soon as this is done.";
    system("php somestuff.php dude@thatplace.com >/dev/null &");
    ?>
    

    You should look at this question for better examples:

    PHP execute a background process

    0 讨论(0)
  • 2020-11-22 04:58

    If flush() function does not work. You must set next options in php.ini like:

    output_buffering = Off  
    zlib.output_compression = Off  
    
    0 讨论(0)
  • 2020-11-22 04:58

    After trying many different solutions from this thread (after none of them worked for me), I've found solution on official PHP.net page:

    function sendResponse($response) {
        ob_end_clean();
        header("Connection: close\r\n");
        header("Content-Encoding: none\r\n");
        ignore_user_abort(true);
        ob_start();
    
        echo $response; // Actual response that will be sent to the user
    
        $size = ob_get_length();
        header("Content-Length: $size");
        ob_end_flush();
        flush();
        if (ob_get_contents()) {
            ob_end_clean();
        }
    }
    
    0 讨论(0)
提交回复
热议问题