ฉันจะปิดการเชื่อมต่อก่อนกำหนดได้อย่างไร


100

ฉันกำลังพยายามทำการโทร AJAX (ผ่าน JQuery) ซึ่งจะเริ่มกระบวนการที่ค่อนข้างยาว ฉันต้องการให้สคริปต์เพียงแค่ส่งคำตอบที่ระบุว่ากระบวนการได้เริ่มต้นแล้ว แต่ JQuery จะไม่ตอบกลับจนกว่าสคริปต์ PHP จะทำงานเสร็จ

ฉันได้ลองใช้กับส่วนหัว "ปิด" (ด้านล่าง) และยังมีบัฟเฟอร์เอาต์พุต ดูเหมือนจะไม่ได้ผล เดา ๆ ? หรือนี่คือสิ่งที่ฉันต้องทำใน JQuery?

<?php

echo( "We'll email you as soon as this is done." );

header( "Connection: Close" );

// do some stuff that will take a while

mail( 'dude@thatplace.com', "okay I'm done", 'Yup, all done.' );

?>

คุณล้างบัฟเฟอร์เอาต์พุตของคุณด้วย ob_flush () และไม่ได้ผลหรือไม่?
Vinko Vrsalovic

คำตอบ:


89

หน้าคู่มือ PHP ต่อไปนี้ (รวมถึงบันทึกผู้ใช้) แนะนำคำแนะนำหลายประการเกี่ยวกับวิธีปิดการเชื่อมต่อ TCP กับเบราว์เซอร์โดยไม่ต้องสิ้นสุดสคริปต์ PHP:

สมมติว่าต้องใช้มากกว่าการส่งส่วนหัวปิดเล็กน้อย


จากนั้น OP ยืนยัน: ใช่นี่เป็นเคล็ดลับ: ชี้ไปที่ user-note # 71172 (พ.ย. 2549)คัดลอกที่นี่:

การปิดการเชื่อมต่อเบราว์เซอร์ของผู้ใช้ในขณะที่ทำให้สคริปต์ php ของคุณทำงานอยู่เป็นปัญหาตั้งแต่ [PHP] 4.1 เมื่อพฤติกรรมของregister_shutdown_function()ถูกแก้ไขเพื่อไม่ให้ปิดการเชื่อมต่อกับผู้ใช้โดยอัตโนมัติ

sts ที่ mail dot xubion dot hu โพสต์วิธีแก้ปัญหาเดิม:

<?php
header("Connection: close");
ob_start();
phpinfo();
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush();
flush();
sleep(13);
error_log("do something in the background");
?>

ซึ่งทำงานได้ดีจนคุณแทนphpinfo()สำหรับecho('text I want user to see');ในกรณีที่ส่วนหัวจะไม่ส่ง!

วิธีแก้ไขคือปิดการบัฟเฟอร์เอาต์พุตอย่างชัดเจนและล้างบัฟเฟอร์ก่อนที่จะส่งข้อมูลส่วนหัวของคุณ ตัวอย่าง:

<?php
ob_end_clean();
header("Connection: close");
ignore_user_abort(true); // just to be safe
ob_start();
echo('Text the user will see');
$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');
?>

ใช้เวลาเพียง 3 ชั่วโมงในการพยายามคิดออกหวังว่ามันจะช่วยใครสักคน :)

ผ่านการทดสอบใน:

  • IE 7.5730.11
  • Mozilla Firefox 1.81

ต่อมาในเดือนกรกฎาคม 2010 ในคำตอบที่เกี่ยวข้อง Arctic Fireจากนั้นก็เชื่อมโยงบันทึกย่อของผู้ใช้อีกสองบันทึกที่ติดตามผลกับข้อความข้างต้น:


7
ใช่นี่เป็นเคล็ดลับ: php.net/manual/en/features.connection-handling.php#71172
Eric_WVGG

1
ผู้เขียนและ @Timbo White เป็นไปได้ไหมที่จะปิดการเชื่อมต่อก่อนกำหนดโดยไม่ทราบขนาดของเนื้อหา IE โดยไม่ต้องจับเนื้อหาก่อนปิด
skibulk

3
แฮกเกอร์และเว็บเบราว์เซอร์เส็งเคร็งยังคงเพิกเฉยต่อส่วนหัว HTTP ที่ปิดการเชื่อมต่อและรับส่วนที่เหลือของผลลัพธ์ .. ตรวจสอบให้แน่ใจว่าสิ่งที่จะเกิดขึ้นต่อไปไม่ใช่เรื่องละเอียดอ่อน บางที ob_start (); เพื่อระงับทุกสิ่ง: p
hanshenrik

3
การเพิ่ม fastcgi_finish_request (); ได้รับการกล่าวว่าปิดการเชื่อมต่อสำเร็จเมื่อข้างต้นไม่ได้ผล อย่างไรก็ตามในกรณีของฉันมันทำให้สคริปต์ของฉันไม่สามารถดำเนินการต่อได้ดังนั้นโปรดใช้ด้วยความระมัดระวัง
Eric Dubé

@RichardSmith เนื่องจากConnection: closeซอฟต์แวร์อื่นในสแต็กสามารถเขียนทับส่วนหัวได้ตัวอย่างเช่น reverse proxy ในกรณีของ CGI (ฉันสังเกตพฤติกรรมนั้นด้วย nginx) ดูคำตอบจาก @hanshenrik เกี่ยวกับเรื่องนั้น โดยทั่วไปConnection: closeจะดำเนินการในฝั่งไคลเอ็นต์และไม่ควรถือเป็นคำตอบสำหรับคำถามนี้ ควรปิดการเชื่อมต่อจากฝั่งเซิร์ฟเวอร์
7heo.tk

56

จำเป็นต้องส่ง 2 ส่วนหัวเหล่านี้:

Connection: close
Content-Length: n (n = size of output in bytes )

เนื่องจากคุณต้องการทราบขนาดของผลลัพธ์ของคุณคุณจะต้องบัฟเฟอร์ผลลัพธ์ของคุณจากนั้นล้างข้อมูลไปที่เบราว์เซอร์:

// buffer all upcoming output
ob_start();
echo "We'll email you as soon as this is done.";

// get the size of the output
$size = ob_get_length();

// send headers to tell the browser to close the connection
header("Content-Length: $size");
header('Connection: close');

// flush all output
ob_end_flush();
ob_flush();
flush();

// if you're using sessions, this prevents subsequent requests
// from hanging while the background process executes
if (session_id()) session_write_close();

/******** background process starts here ********/

นอกจากนี้หากเว็บเซิร์ฟเวอร์ของคุณใช้การบีบอัด gzip อัตโนมัติบนเอาต์พุต (เช่น Apache ที่มี mod_deflate) สิ่งนี้จะไม่ทำงานเนื่องจากขนาดจริงของเอาต์พุตมีการเปลี่ยนแปลงและความยาวของเนื้อหาจะไม่ถูกต้องอีกต่อไป ปิดใช้งานการบีบอัด gzip ของสคริปต์เฉพาะ

ดูรายละเอียดเพิ่มเติมได้ที่http://www.zulius.com/how-to/close-browser-connection-continue-execution


16
หากเซิร์ฟเวอร์ของคุณบีบอัดเอาต์พุตคุณสามารถปิดใช้งานได้ด้วย header("Content-Encoding: none\r\n");วิธีนั้น apache จะไม่บีบอัด
GDmac

1
@GDmac ขอบคุณ! ฉันไม่สามารถทำให้สิ่งนี้ทำงานได้ในขณะที่ แต่การปิดใช้งานการบีบอัดนั้นเป็นเคล็ดลับ
Reactgular

ไม่จำเป็นจริงและทำให้เกิดการแจ้งให้ทราบล่วงหน้าob_flush() failed to flush bufferฉันเอามันออกมาและได้ผลดี
Levi

2
ฉันพบว่าob_flush()เส้นมีความจำเป็น
Deebster

21

คุณสามารถใช้ Fast-CGI กับ PHP-FPM ที่จะใช้ฟังก์ชั่นfastcgi_end_request() ด้วยวิธีนี้คุณสามารถดำเนินการต่อไปได้ในขณะที่การตอบกลับถูกส่งไปยังไคลเอนต์แล้ว

คุณพบสิ่งนี้ในคู่มือ PHP ที่นี่: FastCGI Process Manager (FPM) ; แต่ฟังก์ชั่นนั้นไม่ได้รับการบันทึกไว้ในคู่มือ นี่คือข้อความที่ตัดตอนมาจากPHP-FPM: PHP FastCGI Process Manager Wiki :


fastcgi_finish_request ()

ขอบเขต: ฟังก์ชัน php

หมวดหมู่: การเพิ่มประสิทธิภาพ

คุณลักษณะนี้ช่วยให้คุณสามารถดำเนินการตามคำค้นหา php ได้เร็วขึ้น การเร่งความเร็วเป็นไปได้เมื่อมีการดำเนินการในกระบวนการเรียกใช้สคริปต์ที่ไม่มีผลต่อการตอบสนองของเซิร์ฟเวอร์ ตัวอย่างเช่นการบันทึกเซสชันใน memcached สามารถเกิดขึ้นได้หลังจากสร้างเพจแล้วและส่งต่อไปยังเว็บเซิร์ฟเวอร์ fastcgi_finish_request()เป็นคุณสมบัติ php ที่หยุดเอาต์พุตการตอบสนอง เว็บเซิร์ฟเวอร์เริ่มถ่ายโอนการตอบกลับ "ช้าและเศร้า" ไปยังไคลเอนต์ทันทีและ php ในเวลาเดียวกันสามารถทำสิ่งที่มีประโยชน์มากมายในบริบทของการสืบค้นเช่นการบันทึกเซสชันการแปลงวิดีโอที่ดาวน์โหลดมาการจัดการทุกชนิด สถิติ ฯลฯ

fastcgi_finish_request() สามารถเรียกใช้ฟังก์ชันปิดการทำงาน


หมายเหตุ: fastcgi_finish_request()มีมุมแหลมที่โทรไปflush, printหรือechoจะยุติสคริปต์ต้น

เพื่อหลีกเลี่ยงปัญหานั้นคุณสามารถโทรignore_user_abort(true)ก่อนหรือหลังการfastcgi_finish_requestโทรได้:

ignore_user_abort(true);
fastcgi_finish_request();

3
นี่คือคำตอบที่แท้จริง!
Kirill Titov

2
หากคุณใช้ php-fpm - เพียงแค่ใช้ฟังก์ชันนี้ - อย่าลืมเกี่ยวกับส่วนหัวและสิ่งอื่น ๆ ช่วยฉันประหยัดเวลาได้มาก!
รอส

17

เวอร์ชันสมบูรณ์:

ignore_user_abort(true);//avoid apache to kill the php running
ob_start();//start buffer output

echo "show something to user";
session_write_close();//close session file on server side to avoid blocking other requests

header("Content-Encoding: none");//send header to avoid the browser side to take content as gzip format
header("Content-Length: ".ob_get_length());//send length header
header("Connection: close");//or redirect to some url: header('Location: http://www.google.com');
ob_end_flush();flush();//really send content, can't change the order:1.ob buffer to normal buffer, 2.normal buffer to output

//continue do something on server side
ob_start();
sleep(5);//the user won't wait for the 5 seconds
echo 'for diyism';//user can't see this
file_put_contents('/tmp/process.log', ob_get_contents());
ob_end_clean();

สมบูรณ์ในแง่ไหน? ปัญหาใดที่ทำให้คุณต้องกรอกสคริปต์คำตอบที่ยอมรับ (ข้อใด) และข้อแตกต่างของการกำหนดค่าใดที่ทำให้สิ่งนี้จำเป็น
hakre

4
บรรทัดนี้: header ("Content-Encoding: none"); -> สำคัญมาก
Bobby Tables

2
ขอบคุณนี่เป็นวิธีเดียวที่ใช้ได้ผลในหน้านี้ สิ่งนี้ควรได้รับการอนุมัติเป็นคำตอบ

6

ทางออกที่ดีกว่าคือแยกกระบวนการพื้นหลัง มันค่อนข้างตรงไปตรงมาใน unix / linux:

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

คุณควรดูคำถามนี้เพื่อดูตัวอย่างที่ดีกว่า:

PHP ดำเนินการกระบวนการพื้นหลัง


ขึ้นอยู่กับสิ่งที่คุณต้องการจะทำ. หากมีการเขียนข้อมูลบางอย่างซึ่งถูกกำหนดในระหว่างการร้องขอปัจจุบันไปยังแคชการฟอร์กนั้นแทบจะไม่ใช่วิธีแก้ปัญหาที่ดีกว่า แม้ในกรณีของ OP หากโซลูชันอื่นเชื่อถือได้ก็อาจช่วยประหยัดรอบ CPU ของเซิร์ฟเวอร์ได้ดีเมื่อเทียบกับการส่งข้อมูลจำนวนมากไปยังกระบวนการแยก
Jake

4

สมมติว่าคุณมีเซิร์ฟเวอร์ Linux และการเข้าถึงรูทให้ลองทำเช่นนี้ เป็นวิธีแก้ปัญหาที่ง่ายที่สุดที่ฉันพบ

สร้างไดเร็กทอรีใหม่สำหรับไฟล์ต่อไปนี้และให้สิทธิ์แบบเต็ม (เราสามารถทำให้ปลอดภัยมากขึ้นในภายหลัง)

mkdir test
chmod -R 777 test
cd test

ใส่สิ่งนี้ในไฟล์ที่เรียกว่าbgping.

echo starting bgping
ping -c 15 www.google.com > dump.txt &
echo ending bgping

หมายเหตุ&. คำสั่ง ping จะทำงานอยู่เบื้องหลังในขณะที่กระบวนการปัจจุบันย้ายไปยังคำสั่ง echo มันจะ ping www.google.com 15 ครั้งซึ่งจะใช้เวลาประมาณ 15 วินาที

ทำให้ปฏิบัติการได้

chmod 777 bgping

ใส่สิ่งนี้ในไฟล์ที่เรียกว่าbgtest.php.

<?php

echo "start bgtest.php\n";
exec('./bgping', $output, $result)."\n";
echo "output:".print_r($output,true)."\n";
echo "result:".print_r($result,true)."\n";
echo "end bgtest.php\n";

?>

เมื่อคุณขอ bgtest.php ในเบราว์เซอร์ของคุณคุณควรได้รับคำตอบต่อไปนี้อย่างรวดเร็วโดยไม่ต้องรอประมาณ 15 วินาทีเพื่อให้คำสั่ง ping เสร็จสมบูรณ์

start bgtest.php
output:Array
(
    [0] => starting bgping
    [1] => ending bgping
)

result:0
end bgtest.php

ตอนนี้คำสั่ง ping ควรทำงานบนเซิร์ฟเวอร์ แทนที่จะใช้คำสั่ง ping คุณสามารถเรียกใช้สคริปต์ PHP:

php -n -f largejob.php > dump.txt &

หวังว่านี่จะช่วยได้!


4

นี่คือการแก้ไขโค้ดของ Timbo ที่ทำงานร่วมกับการบีบอัด gzip

// buffer all upcoming output
if(!ob_start("ob_gzhandler")){
    define('NO_GZ_BUFFER', true);
    ob_start();
}
echo "We'll email you as soon as this is done.";

//Flush here before getting content length if ob_gzhandler was used.
if(!defined('NO_GZ_BUFFER')){
    ob_end_flush();
}

// get the size of the output
$size = ob_get_length();

// send headers to tell the browser to close the connection
header("Content-Length: $size");
header('Connection: close');

// flush all output
ob_end_flush();
ob_flush();
flush();

// if you're using sessions, this prevents subsequent requests
// from hanging while the background process executes
if (session_id()) session_write_close();

/******** background process starts here ********/

คุณคือพระเจ้า ฉันทำงานมา 2 วันแล้วเพื่อลองคิดหาสิ่งนี้ มันใช้งานได้กับ dev ในพื้นที่ของฉัน แต่ไม่ใช่บนโฮสต์ ฉันถูกหลอก คุณช่วยฉันไว้. ขอบคุณ!!!!
Chad Caldwell

3

ฉันใช้โฮสต์ที่ใช้ร่วมกันและได้fastcgi_finish_requestรับการตั้งค่าให้ออกจากสคริปต์โดยสมบูรณ์ ฉันไม่ชอบconnection: closeวิธีแก้ปัญหาเช่นกัน การใช้มันบังคับให้มีการเชื่อมต่อแยกต่างหากสำหรับการร้องขอในภายหลังทำให้เสียค่าใช้จ่ายทรัพยากรเซิร์ฟเวอร์เพิ่มเติม ฉันอ่านTransfer-Encoding: cunked บทความ Wikipediaและเรียนรู้ว่า0\r\n\r\nยุติการตอบกลับ ฉันยังไม่ได้ทดสอบสิ่งนี้อย่างละเอียดในเวอร์ชันเบราว์เซอร์และอุปกรณ์ แต่มันใช้ได้กับเบราว์เซอร์ปัจจุบันทั้ง 4 ตัว

// Disable automatic compression
// @ini_set('zlib.output_compression', 'Off');
// @ini_set('output_buffering', 'Off');
// @ini_set('output_handler', '');
// @apache_setenv('no-gzip', 1);

// Chunked Transfer-Encoding & Gzip Content-Encoding
function ob_chunked_gzhandler($buffer, $phase) {
    if (!headers_sent()) header('Transfer-Encoding: chunked');
    $buffer = ob_gzhandler($buffer, $phase);
    return dechex(strlen($buffer))."\r\n$buffer\r\n";
}

ob_start('ob_chunked_gzhandler');

// First Chunk
echo "Hello World";
ob_flush();

// Second Chunk
echo ", Grand World";
ob_flush();

ob_end_clean();

// Terminating Chunk
echo "\x30\r\n\r\n";
ob_flush();
flush();

// Post Processing should not be displayed
for($i=0; $i<10; $i++) {
    print("Post-Processing");
    sleep(1);
}

ขอบคุณสำหรับคำตอบที่ดีของคุณฉันตระหนักดีว่าการใช้การเชื่อมต่อนั้นโง่ (และไม่จำเป็น) แค่ไหน: ปิด ฉันเดาว่าบางคนไม่คุ้นเคยกับน็อตและสลักเกลียวของเซิร์ฟเวอร์
Justin

@ จัสตินฉันเขียนไว้เมื่อนานมาแล้ว เมื่อมองดูอีกครั้งฉันควรทราบว่าอาจจำเป็นต้องเพิ่มชิ้นส่วนออกเป็น 4KB ฉันดูเหมือนจะจำได้ว่าเซิร์ฟเวอร์บางตัวจะไม่ล้างจนกว่าจะถึงขั้นต่ำนั้น
skibulk

@skibulk บางตัวต้องการการขยาย 64K ของ (ไม่บีบอัด ??) เช่นนี่เป็นค่าเริ่มต้นสำหรับFcgidOutputBufferSizeและไม่สามารถแทนที่บนเซิร์ฟเวอร์ที่ใช้ร่วมกันบางเครื่องได้
Jake

2

คุณสามารถลองทำมัลติเธรด

คุณสามารถแส้สคริปต์ที่ทำให้ระบบเรียก (โดยใช้shell_exec ) ที่เรียก php ไบนารีพร้อมกับสคริปต์เพื่อทำงานของคุณเป็นพารามิเตอร์ แต่ฉันไม่คิดว่านั่นเป็นวิธีที่ปลอดภัยที่สุด บางทีคุณอาจเพิ่มพูนสิ่งต่างๆได้โดยการ chrooting กระบวนการ php และสิ่งอื่น ๆ

นอกจากนี้ยังเป็นระดับที่ phpclasses ว่าทำอย่างนั้นhttp://www.phpclasses.org/browse/package/3953.html แต่ฉันไม่รู้รายละเอียดของการนำไปใช้


และหากคุณไม่ต้องการรอให้กระบวนการเสร็จสิ้นให้ใช้&อักขระเพื่อเรียกใช้กระบวนการในพื้นหลัง
Liam

2

TL; DR คำตอบ:

ignore_user_abort(true); //Safety measure so that the user doesn't stop the script too early.

$content = 'Hello World!'; //The content that will be sent to the browser.

header('Content-Length: ' . strlen($content)); //The browser will close the connection when the size of the content reaches "Content-Length", in this case, immediately.

ob_start(); //Content past this point...

echo $content;

//...will be sent to the browser (the output buffer gets flushed) when this code executes.
ob_end_flush();
ob_flush();
flush();

if(session_id())
{
    session_write_close(); //Closes writing to the output buffer.
}

//Anything past this point will be ran without involving the browser.

คำตอบของฟังก์ชัน:

ignore_user_abort(true);

function sendAndAbort($content)
{
    header('Content-Length: ' . strlen($content));

    ob_start();

    echo $content;

    ob_end_flush();
    ob_flush();
    flush();
}

sendAndAbort('Hello World!');

//Anything past this point will be ran without involving the browser.

1

ปัญหาของคุณสามารถแก้ไขได้โดยการเขียนโปรแกรมคู่ขนานใน php ฉันถามคำถามเมื่อสองสามสัปดาห์ก่อนที่นี่: เราจะใช้มัลติเธรดในแอปพลิเคชัน PHP ได้อย่างไร

และได้รับคำตอบที่ดี ฉันชอบคนหนึ่งมากเป็นพิเศษ ผู้เขียนได้อ้างอิงถึงบทช่วยสอนEasy Parallel Processing ใน PHP (ก.ย. 2008 โดย johnlim)ซึ่งสามารถแก้ปัญหาของคุณได้ดีมากเพราะฉันได้ใช้มันเพื่อจัดการกับปัญหาที่คล้ายกันที่เกิดขึ้นเมื่อสองสามวันก่อน


1

คำตอบของ Joeri Sebrechtsนั้นใกล้เคียง แต่จะทำลายเนื้อหาที่มีอยู่ซึ่งอาจถูกบัฟเฟอร์ก่อนที่คุณจะต้องการยกเลิกการเชื่อมต่อ มันเรียกไม่ignore_user_abortถูกต้องทำให้สคริปต์ยุติก่อนกำหนด คำตอบของ diyismนั้นดี แต่ใช้ไม่ได้โดยทั่วไป เช่นคน ๆ หนึ่งอาจมีบัฟเฟอร์เอาต์พุตมากขึ้นหรือน้อยลงซึ่งคำตอบนั้นไม่สามารถจัดการได้ดังนั้นจึงอาจไม่ได้ผลในสถานการณ์ของคุณและคุณจะไม่รู้ว่าทำไม

ฟังก์ชันนี้ช่วยให้คุณตัดการเชื่อมต่อได้ตลอดเวลา (ตราบใดที่ยังไม่ได้ส่งส่วนหัว) และยังคงรักษาเนื้อหาที่คุณสร้างไว้ เวลาในการประมวลผลเพิ่มเติมไม่ จำกัด โดยค่าเริ่มต้น

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();
}

หากคุณต้องการหน่วยความจำเพิ่มเติมให้จัดสรรหน่วยความจำก่อนเรียกใช้ฟังก์ชันนี้


1

หมายเหตุสำหรับผู้ใช้ mod_fcgid (โปรดใช้ด้วยความเสี่ยงของคุณเอง)

โซลูชันด่วน

คำตอบที่ยอมรับของJoeri Sebrechtsนั้นใช้งานได้จริง อย่างไรก็ตามหากคุณใช้mod_fcgidคุณอาจพบว่าโซลูชันนี้ใช้งานไม่ได้ด้วยตัวเอง กล่าวอีกนัยหนึ่งคือเมื่อฟังก์ชันflushถูกเรียกว่าการเชื่อมต่อกับไคลเอนต์จะไม่ถูกปิด

FcgidOutputBufferSizeกำหนดค่าพารามิเตอร์ของmod_fcgidอาจจะมีการตำหนิ ฉันพบเคล็ดลับนี้ใน:

  1. คำตอบของ Travers Carterและ
  2. โพสต์บล็อกของ Seumas Mackinnon

หลังจากอ่านข้างต้นคุณอาจได้ข้อสรุปว่าวิธีแก้ปัญหาอย่างรวดเร็วคือการเพิ่มบรรทัด (ดู "ตัวอย่างโฮสต์เสมือน" ในตอนท้าย):

FcgidOutputBufferSize 0

ในไฟล์คอนฟิกูเรชัน Apache ของคุณ (เช่น httpd.conf) ไฟล์คอนฟิกูเรชัน FCGI ของคุณ (เช่น fcgid.conf) หรือในไฟล์โฮสต์เสมือนของคุณ (เช่น httpd-vhosts.conf)

ใน (1) ข้างต้นมีการกล่าวถึงตัวแปรชื่อ "OutputBufferSize" นี่เป็นชื่อเก่าของชื่อที่FcgidOutputBufferSizeกล่าวถึงใน (2) (ดูบันทึกการอัพเกรดในหน้าเว็บ Apache สำหรับ mod_fcgid )

รายละเอียดและแนวทางที่สอง

โซลูชันข้างต้นปิดใช้งานการบัฟเฟอร์ที่ดำเนินการโดยmod_fcgidสำหรับเซิร์ฟเวอร์ทั้งหมดหรือสำหรับโฮสต์เสมือนเฉพาะ ซึ่งอาจนำไปสู่การลงโทษด้านประสิทธิภาพสำหรับเว็บไซต์ของคุณ ในทางกลับกันอาจไม่เป็นเช่นนั้นเนื่องจาก PHP ทำการบัฟเฟอร์ด้วยตัวเอง

ในกรณีที่คุณไม่ต้องการปิดการใช้งานบัฟเฟอร์ของmod_fcgidมีวิธีอื่น ... คุณสามารถบังคับให้บัฟเฟอร์นี้ล้างได้

โค้ดด้านล่างนี้ทำได้โดยสร้างจากโซลูชันที่ 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');
?>

สิ่งที่บรรทัดของโค้ดที่เพิ่มขึ้นโดยพื้นฐานแล้วคือการเติมบัฟเฟอร์ของmod_fcgiดังนั้นจึงบังคับให้ล้าง หมายเลข "65537" ถูกเลือกเนื่องจากค่าเริ่มต้นของFcgidOutputBufferSizeตัวแปรคือ "65536" ตามที่กล่าวไว้ในหน้าเว็บ Apache สำหรับคำสั่งที่เกี่ยวข้อง ดังนั้นคุณอาจต้องปรับค่านี้ให้เหมาะสมหากมีการตั้งค่าอื่นในสภาพแวดล้อมของคุณ

สภาพแวดล้อมของฉัน

  • 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

ตัวอย่าง Virtual Host

<VirtualHost *:80>
    DocumentRoot "d:/wamp/www/example"
    ServerName example.local

    FcgidOutputBufferSize 0

    <Directory "d:/wamp/www/example">
        Require all granted
    </Directory>
</VirtualHost>

ฉันลองวิธีแก้ปัญหามากมาย และนี่เป็นทางออกเดียวที่ใช้ได้กับ mod_fcgid สำหรับฉัน
ซึนาเบะ

1

สิ่งนี้ได้ผลสำหรับฉัน

//avoid apache to kill the php running
ignore_user_abort(true);
//start buffer output
ob_start();

echo "show something to user1";
//close session file on server side to avoid blocking other requests
session_write_close();

//send length header
header("Content-Length: ".ob_get_length());
header("Connection: close");
//really send content, can't change the order:
//1.ob buffer to normal buffer,
//2.normal buffer to output
ob_end_flush();
flush();
//continue do something on server side
ob_start();
//replace it with the background task
sleep(20);

0

ตกลงโดยทั่วไปแล้ววิธีที่ jQuery ทำการร้องขอ XHR แม้วิธี ob_flush จะไม่ทำงานเนื่องจากคุณไม่สามารถเรียกใช้ฟังก์ชันบน onreadystatechange แต่ละรายการได้ jQuery ตรวจสอบสถานะจากนั้นเลือกการดำเนินการที่เหมาะสมเพื่อดำเนินการ (เสร็จสมบูรณ์ข้อผิดพลาดสำเร็จหมดเวลา) และแม้ว่าฉันจะไม่พบข้อมูลอ้างอิง แต่ฉันจำได้ว่าสิ่งนี้ใช้ไม่ได้กับการใช้งาน XHR ทั้งหมด วิธีที่ฉันเชื่อว่าน่าจะใช้ได้กับคุณคือการผสมข้ามระหว่างการสำรวจแบบ ob_flush และแบบถาวร

<?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>

และเนื่องจากสคริปต์ถูกเรียกใช้แบบอินไลน์เนื่องจากบัฟเฟอร์ถูกล้างคุณจึงได้รับการดำเนินการ เพื่อให้เป็นประโยชน์เปลี่ยน console.log เป็นวิธีการโทรกลับที่กำหนดไว้ในการตั้งค่าสคริปต์หลักของคุณเพื่อรับข้อมูลและดำเนินการกับมัน หวังว่านี่จะช่วยได้ ไชโยมอร์แกน


0

อีกทางเลือกหนึ่งคือการเพิ่มงานลงในคิวและสร้างสคริปต์ cron ซึ่งจะตรวจสอบงานใหม่และเรียกใช้งาน

ฉันต้องทำแบบนั้นเมื่อเร็ว ๆ นี้เพื่อหลีกเลี่ยงขีด จำกัด ที่กำหนดโดยโฮสต์ที่ใช้ร่วมกัน - exec () et al ถูกปิดใช้งานสำหรับ PHP ที่รันโดยเว็บเซิร์ฟเวอร์ แต่สามารถทำงานในเชลล์สคริปต์ได้


0

หากflush()ฟังก์ชันไม่ทำงาน คุณต้องตั้งค่าตัวเลือกถัดไปในphp.iniเช่น:

output_buffering = Off  
zlib.output_compression = Off  

0

โซลูชันการทำงานล่าสุด

    // client can see outputs if any
    ignore_user_abort(true);
    ob_start();
    echo "success";
    $buffer_size = ob_get_length();
    session_write_close();
    header("Content-Encoding: none");
    header("Content-Length: $buffer_size");
    header("Connection: close");
    ob_end_flush();
    ob_flush();
    flush();

    sleep(2);
    ob_start();
    // client cannot see the result of code below

0

หลังจากลองใช้วิธีแก้ปัญหาต่างๆมากมายจากหัวข้อนี้ (หลังจากไม่มีวิธีใดที่ใช้ได้ผลสำหรับฉัน) ฉันพบวิธีแก้ปัญหาในหน้า PHP.net อย่างเป็นทางการ:

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();
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.