php次日凌晨恢复,当使用PHP发送文件时,可恢复下载吗?

一种经过测试的工作解决方案(主要基于Theo上面的答案),它在一组独立的工具中处理可恢复的下载。此代码需要PHP5.4或更高版本。

这个解决方案仍然只能处理每个请求的一个范围,但是在任何情况下,使用我能想到的标准浏览器,这都不会导致问题。<?php /**

* Get the value of a header in the current request context

*

* @param string $name Name of the header

* @return string|null Returns null when the header was not sent or cannot be retrieved

*/function get_request_header($name){

$name = strtoupper($name);

// IIS/Some Apache versions and configurations

if (isset($_SERVER['HTTP_' . $name])) {

return trim($_SERVER['HTTP_' . $name]);

}

// Various other SAPIs

foreach (apache_request_headers() as $header_name => $value) {

if (strtoupper($header_name) === $name) {

return trim($value);

}

}

return null;}class NonExistentFileException extends \RuntimeException {}class UnreadableFileException extends \RuntimeException

{}class UnsatisfiableRangeException extends \RuntimeException {}class InvalidRangeHeaderException extends \RuntimeException

{}class RangeHeader{

/**

* The first byte in the file to send (0-indexed), a null value indicates the last

* $end bytes

*

* @var int|null

*/

private $firstByte;

/**

* The last byte in the file to send (0-indexed), a null value indicates $start to

* EOF

*

* @var int|null

*/

private $lastByte;

/**

* Create a new instance from a Range header string

*

* @param string $header

* @return RangeHeader

*/

public static function createFromHeaderString($header)

{

if ($header === null) {

return null;

}

if (!preg_match('/^\s*(\S+)\s*(\d*)\s*-\s*(\d*)\s*(?:,|$)/', $header, $info)) {

throw new InvalidRangeHeaderException('Invalid header format');

} else if (strtolower($info[1]) !== 'bytes') {

throw new InvalidRangeHeaderException('Unknown range unit: ' . $info[1]);

}

return new self(

$info[2] === '' ? null : $info[2],

$info[3] === '' ? null : $info[3]

);

}

/**

* @param int|null $firstByte

* @param int|null $lastByte

* @throws InvalidRangeHeaderException

*/

public function __construct($firstByte, $lastByte)

{

$this->firstByte = $firstByte === null ? $firstByte : (int)$firstByte;

$this->lastByte = $lastByte === null ? $lastByte : (int)$lastByte;

if ($this->firstByte === null && $this->lastByte === null) {

throw new InvalidRangeHeaderException(

'Both start and end position specifiers empty'

);

} else if ($this->firstByte lastByte 

throw new InvalidRangeHeaderException(

'Position specifiers cannot be negative'

);

} else if ($this->lastByte !== null && $this->lastByte firstByte) {

throw new InvalidRangeHeaderException(

'Last byte cannot be less than first byte'

);

}

}

/**

* Get the start position when this range is applied to a file of the specified size

*

* @param int $fileSize

* @return int

* @throws UnsatisfiableRangeException

*/

public function getStartPosition($fileSize)

{

$size = (int)$fileSize;

if ($this->firstByte === null) {

return ($size - 1) - $this->lastByte;

}

if ($size <= $this->firstByte) {

throw new UnsatisfiableRangeException(

'Start position is after the end of the file'

);

}

return $this->firstByte;

}

/**

* Get the end position when this range is applied to a file of the specified size

*

* @param int $fileSize

* @return int

* @throws UnsatisfiableRangeException

*/

public function getEndPosition($fileSize)

{

$size = (int)$fileSize;

if ($this->lastByte === null) {

return $size - 1;

}

if ($size <= $this->lastByte) {

throw new UnsatisfiableRangeException(

'End position is after the end of the file'

);

}

return $this->lastByte;

}

/**

* Get the length when this range is applied to a file of the specified size

*

* @param int $fileSize

* @return int

* @throws UnsatisfiableRangeException

*/

public function getLength($fileSize)

{

$size = (int)$fileSize;

return $this->getEndPosition($size) - $this->getStartPosition($size) + 1;

}

/**

* Get a Content-Range header corresponding to this Range and the specified file

* size

*

* @param int $fileSize

* @return string

*/

public function getContentRangeHeader($fileSize)

{

return 'bytes ' . $this->getStartPosition($fileSize) . '-'

. $this->getEndPosition($fileSize) . '/' . $fileSize;

}}class PartialFileServlet{

/**

* The range header on which the data transmission will be based

*

* @var RangeHeader|null

*/

private $range;

/**

* @param RangeHeader $range Range header on which the transmission will be based

*/

public function __construct(RangeHeader $range = null)

{

$this->range = $range;

}

/**

* Send part of the data in a seekable stream resource to the output buffer

*

* @param resource $fp Stream resource to read data from

* @param int $start Position in the stream to start reading

* @param int $length Number of bytes to read

* @param int $chunkSize Maximum bytes to read from the file in a single operation

*/

private function sendDataRange($fp, $start, $length, $chunkSize = 8192)

{

if ($start > 0) {

fseek($fp, $start, SEEK_SET);

}

while ($length) {

$read = ($length > $chunkSize) ? $chunkSize : $length;

$length -= $read;

echo fread($fp, $read);

}

}

/**

* Send the headers that are included regardless of whether a range was requested

*

* @param string $fileName

* @param int $contentLength

* @param string $contentType

*/

private function sendDownloadHeaders($fileName, $contentLength, $contentType)

{

header('Content-Type: ' . $contentType);

header('Content-Length: ' . $contentLength);

header('Content-Disposition: attachment; filename="' . $fileName . '"');

header('Accept-Ranges: bytes');

}

/**

* Send data from a file based on the current Range header

*

* @param string $path Local file system path to serve

* @param string $contentType MIME type of the data stream

*/

public function sendFile($path, $contentType = 'application/octet-stream')

{

// Make sure the file exists and is a file, otherwise we are wasting our time

$localPath = realpath($path);

if ($localPath === false || !is_file($localPath)) {

throw new NonExistentFileException(

$path . ' does not exist or is not a file'

);

}

// Make sure we can open the file for reading

if (!$fp = fopen($localPath, 'r')) {

throw new UnreadableFileException(

'Failed to open ' . $localPath . ' for reading'

);

}

$fileSize = filesize($localPath);

if ($this->range == null) {

// No range requested, just send the whole file

header('HTTP/1.1 200 OK');

$this->sendDownloadHeaders(basename($localPath), $fileSize, $contentType);

fpassthru($fp);

} else {

// Send the request range

header('HTTP/1.1 206 Partial Content');

header('Content-Range: ' . $this->range->getContentRangeHeader($fileSize));

$this->sendDownloadHeaders(

basename($localPath),

$this->range->getLength($fileSize),

$contentType            );

$this->sendDataRange(

$fp,

$this->range->getStartPosition($fileSize),

$this->range->getLength($fileSize)

);

}

fclose($fp);

}}

示例用法:<?php

$path = '/local/path/to/file.ext';$contentType = 'application/octet-stream';

// Avoid sending unexpected errors to the client - we should be serving a file,

// we don't want to corrupt the data we sendini_set('display_errors', '0');try {

$rangeHeader = RangeHeader::createFromHeaderString(get_request_header('Range'));

(new PartialFileServlet($rangeHeader))->sendFile($path, $contentType);} catch (InvalidRangeHeaderException $e) {

header("HTTP/1.1 400 Bad Request");} catch (UnsatisfiableRangeException $e) {

header("HTTP/1.1 416 Range Not Satisfiable");} catch (NonExistentFileException $e) {

header("HTTP/1.1 404 Not Found");} catch (UnreadableFileException $e) {

header("HTTP/1.1 500 Internal Server Error");}// It's usually a good idea to explicitly exit after sending a file to avoid sending any

// extra data on the end that might corrupt the fileexit;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值