Mobile app version of vmapp.org
Login or Join
Rambettina238

: Browser will not cancel the request if routed through PHP I have a site which has a feature that causes Apache to route all requests for files in a particular directory through a PHP script

@Rambettina238

Posted in: #Apache #Download #Php

I have a site which has a feature that causes Apache to route all requests for files in a particular directory through a PHP script via .htaccess. A problem has come up that I'm not sure how to troubleshoot.

There is an online course which has a video on each page, and the pages are loaded via ajax. With the above feature disabled, where Apache directly serves the video, the user can press the Next button while the video is still being downloaded and the next page will load immediately. With the feature enabled, if they press the Next button before the video has finished downloading then the browser will basically hang until the download finishes, and then the next page will load. I'm having a hard time figuring out why this happens, is there something about invoking PHP that would make the browser wait for the entire file to finish?

These are the response headers for a direct request:

HTTP/1.1 206 Partial Content
Date: Fri, 31 Oct 2014 21:28:10 GMT
Server: Apache
Last-Modified: Mon, 20 Oct 2014 15:45:20 GMT
Etag: "1601ee2-c4fcc5-505dc9a1e4fe5"
Accept-Ranges: bytes
Content-Length: 12909765
Vary: User-Agent
Content-Range: bytes 0-12909764/12909765
Keep-Alive: timeout=15, max=142
Connection: Keep-Alive
Content-Type: video/mp4


And for the request that goes through PHP:

HTTP/1.1 206 Partial Content
Date: Fri, 31 Oct 2014 19:11:31 GMT
Server: Apache
P3P: CP="OTI DSP COR CURa ADMa HISa OUR IND STA"
X-Lms: auth
Last-Modified: Mon, 20 Oct 2014 15:45:20 GMT
Etag: "1601ee2-c4fcc5-505dc9a1e4fe5"
Accept-Ranges: bytes
Content-Length: 12909765
Vary: User-Agent
Content-Range: bytes 0-12909764/12909765
Keep-Alive: timeout=15, max=149
Connection: Keep-Alive
Content-Type: video/mp4


Not shown in the list of headers for PHP is that I send blank headers for cache-control, pragma, and expires to override the default set of headers that would disable caching for PHP responses, i.e.:

header('Cache-Control: ');
header('Pragma: ');
header('Expires: ');


I see the same behavior in Firefox and Chrome, I haven't tested with other browsers yet. Chrome is particularly bad, the entire tab becomes unresponsive. If I switch to another tab and back again, it will just be blank white. I cannot close the tab nor the browser unless I use Chrome's task manager to kill the process for that tab. It gets responsive again once the transfer finishes.

Does anyone know what might cause this? The server is running Apache 2.2.26 and PHP 5.4.22. In WHM I have "dso" selected as the PHP 5 handler, and suEXEC is enabled.

Edit:

I've replaced my single-read statement with a loop to check for the client aborting, or the stream timing out, and that hasn't fixed the issue. I'm pasting below the portion of the PHP code that deals with actually sending the file out (as opposed to responses for 304, 403, 404, etc):

(sorry about the formatting, I'm not sure why it added so many extra lines and broke up highlighting)

// file not cached or cache outdated, we respond '200 OK' (or 206) and
// output the file.
if (!isset($headers['Range'])) {
$response_code = 200;
}
else {
$response_code = 206;
}

// send Last-Modified header, and set response code
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $mtime).' GMT',
true, $response_code);
header('Content-Type: ' . get_mime_type($file));
if ($method == 'GET' || $method == 'HEAD') {
header('Accept-Ranges: bytes');
}

$filesize = filesize($file);
$start_read_pos = 0;
$max_read_chunk = 65536;
$total_read_size = $filesize;
$content_length = $filesize;

// adjust content length and read position based on the range
if (isset($headers['Range'])) {
// Range: bytes=1000-1999
// Range: bytes=2000-
$range_chunks = explode('=', $headers['Range']);
// the first element should be "bytes"
$range = $range_chunks[1]; // e.g. 1024-2048, or 1024-
$range_chunks = explode('-', $range);
$start_read_pos = intval($range_chunks[0]);
$end_read_pos = isset($range_chunks[1]) ? intval($range_chunks[1]) : 0;
if ($end_read_pos == 0) {
$end_read_pos = $filesize - 1;
}
$total_read_size = $end_read_pos - $start_read_pos + 1;
$content_length = $total_read_size;
}

header('Content-Length: ' . $content_length);
if (isset($headers['Range'])) {
header('Content-Range: bytes ' . $start_read_pos . '-' .
$end_read_pos . '/' . $filesize);
}

// only output the content if this is not a head request
if ($method != 'HEAD') {

// read the file data

if ($ext != 'php') {
$fh = fopen($file, 'rb');
if ($fh) {

if ($start_read_pos != 0) {
// discard $start_read_pos many bytes
fread($fh, $start_read_pos);
}

$total_sent = 0;
$read_chunk = $max_read_chunk;
$timeout = false;
while(!connection_aborted() &&
!feof($fh) &&
!$timeout &&
$total_sent < $total_read_size) {
// if the next (i.e. last) read requires fewer
// than the max read chunk
if ($total_sent + $max_read_chunk > $total_read_size) {
$read_chunk = $total_read_size - $total_sent;
}
echo fread($fh, $read_chunk);
$total_sent += $read_chunk;
$stream_info = stream_get_meta_data($fh);
if ($stream_info['timed_out']) {
$timeout = true;
}
}

fclose($fh);
}
}
else {
// don't show the PHP code
// pad to match the content length
echo str_pad('PHP code', $content_length);header
}

exit();
}

10.02% popularity Vote Up Vote Down


Login to follow query

More posts by @Rambettina238

2 Comments

Sorted by latest first Latest Oldest Best

 

@Martha676

I believe the problem here is not likely to be related to your Apache configuration but in your PHP script. For example, if your script loads the entire video file in one go, and then sends it all to the browser in one go this can cause this issue:

<?php
header( 'Content-Type: video/mp4' );
$sVideo = @file_get_contents ( '/var/www/www.example.com/videos/video.mp4' );
echo( $sVideo );
exit;


However, if instead you loaded the video file and sent it back to the browser in a series of much smaller chunks of data, then this may well solve your issue:

<?php
header( 'Content-Type: video/mp4' );
$hVideo = fopen( '/var/www/www.example.com/videos/video.mp4', 'rb' );
while( !feof( $hVideo )) {
echo( fread( $hVideo, 4096 ));
$aStreamInfo = stream_get_meta_data( $hVideo );
if( $aStreamInfo['timed_out'] ) break;
}
fclose( $hVideo );
exit;

10% popularity Vote Up Vote Down


 

@Cooney921

The range header handling code is probably where the issue lies, although it's hard to say for sure. Serving videos with PHP tends not to be very memory efficient, particularly if they are large. I'd suggest you use something like X-Sendfile instead. That way, you still do your authentication checks in PHP, but you get your web server to do the heavy lifting for the actual file serving.

10% popularity Vote Up Vote Down


Back to top | Use Dark Theme