php - desde - formulario para enviar correo html
¿Descargas reanudables al usar PHP para enviar el archivo? (13)
Estamos utilizando una secuencia de comandos PHP para las descargas de archivos de túnel, ya que no queremos exponer la ruta absoluta del archivo descargable:
header("Content-Type: $ctype");
header("Content-Length: " . filesize($file));
header("Content-Disposition: attachment; filename=/"$fileName/"");
readfile($file);
Desafortunadamente notamos que las descargas pasadas a través de este script no pueden ser reanudadas por el usuario final.
¿Hay alguna forma de admitir descargas reanudables con una solución basada en PHP?
Clase habilitada para pequeños compositores que funciona de la misma manera que pecl http_send_file. Esto significa soporte para descargas reanudables y acelerador. https://github.com/diversen/http-send-file
Esto funcionó muy bien para mí: https://github.com/pomle/php-serveFilePartial
Esto funciona 100% super check it. Lo estoy usando y ya no tengo problemas.
/* Function: download with resume/speed/stream options */
/* List of File Types */
function fileTypes($extension){
$fileTypes[''swf''] = ''application/x-shockwave-flash'';
$fileTypes[''pdf''] = ''application/pdf'';
$fileTypes[''exe''] = ''application/octet-stream'';
$fileTypes[''zip''] = ''application/zip'';
$fileTypes[''doc''] = ''application/msword'';
$fileTypes[''xls''] = ''application/vnd.ms-excel'';
$fileTypes[''ppt''] = ''application/vnd.ms-powerpoint'';
$fileTypes[''gif''] = ''image/gif'';
$fileTypes[''png''] = ''image/png'';
$fileTypes[''jpeg''] = ''image/jpg'';
$fileTypes[''jpg''] = ''image/jpg'';
$fileTypes[''rar''] = ''application/rar'';
$fileTypes[''ra''] = ''audio/x-pn-realaudio'';
$fileTypes[''ram''] = ''audio/x-pn-realaudio'';
$fileTypes[''ogg''] = ''audio/x-pn-realaudio'';
$fileTypes[''wav''] = ''video/x-msvideo'';
$fileTypes[''wmv''] = ''video/x-msvideo'';
$fileTypes[''avi''] = ''video/x-msvideo'';
$fileTypes[''asf''] = ''video/x-msvideo'';
$fileTypes[''divx''] = ''video/x-msvideo'';
$fileTypes[''mp3''] = ''audio/mpeg'';
$fileTypes[''mp4''] = ''audio/mpeg'';
$fileTypes[''mpeg''] = ''video/mpeg'';
$fileTypes[''mpg''] = ''video/mpeg'';
$fileTypes[''mpe''] = ''video/mpeg'';
$fileTypes[''mov''] = ''video/quicktime'';
$fileTypes[''swf''] = ''video/quicktime'';
$fileTypes[''3gp''] = ''video/quicktime'';
$fileTypes[''m4a''] = ''video/quicktime'';
$fileTypes[''aac''] = ''video/quicktime'';
$fileTypes[''m3u''] = ''video/quicktime'';
return $fileTypes[$extention];
};
/*
Parameters: downloadFile(File Location, File Name,
max speed, is streaming
If streaming - videos will show as videos, images as images
instead of download prompt
*/
function downloadFile($fileLocation, $fileName, $maxSpeed = 100, $doStream = false) {
if (connection_status() != 0)
return(false);
// in some old versions this can be pereferable to get extention
// $extension = strtolower(end(explode(''.'', $fileName)));
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
$contentType = fileTypes($extension);
header("Cache-Control: public");
header("Content-Transfer-Encoding: binary/n");
header(''Content-Type: $contentType'');
$contentDisposition = ''attachment'';
if ($doStream == true) {
/* extensions to stream */
$array_listen = array(''mp3'', ''m3u'', ''m4a'', ''mid'', ''ogg'', ''ra'', ''ram'', ''wm'',
''wav'', ''wma'', ''aac'', ''3gp'', ''avi'', ''mov'', ''mp4'', ''mpeg'', ''mpg'', ''swf'', ''wmv'', ''divx'', ''asf'');
if (in_array($extension, $array_listen)) {
$contentDisposition = ''inline'';
}
}
if (strstr($_SERVER[''HTTP_USER_AGENT''], "MSIE")) {
$fileName = preg_replace(''//./'', ''%2e'', $fileName, substr_count($fileName, ''.'') - 1);
header("Content-Disposition: $contentDisposition;
filename=/"$fileName/"");
} else {
header("Content-Disposition: $contentDisposition;
filename=/"$fileName/"");
}
header("Accept-Ranges: bytes");
$range = 0;
$size = filesize($fileLocation);
if (isset($_SERVER[''HTTP_RANGE''])) {
list($a, $range) = explode("=", $_SERVER[''HTTP_RANGE'']);
str_replace($range, "-", $range);
$size2 = $size - 1;
$new_length = $size - $range;
header("HTTP/1.1 206 Partial Content");
header("Content-Length: $new_length");
header("Content-Range: bytes $range$size2/$size");
} else {
$size2 = $size - 1;
header("Content-Range: bytes 0-$size2/$size");
header("Content-Length: " . $size);
}
if ($size == 0) {
die(''Zero byte file! Aborting download'');
}
set_magic_quotes_runtime(0);
$fp = fopen("$fileLocation", "rb");
fseek($fp, $range);
while (!feof($fp) and ( connection_status() == 0)) {
set_time_limit(0);
print(fread($fp, 1024 * $maxSpeed));
flush();
ob_flush();
sleep(1);
}
fclose($fp);
return((connection_status() == 0) and ! connection_aborted());
}
/* Implementation */
// downloadFile(''path_to_file/1.mp3'', ''1.mp3'', 1024, false);
Gracias Theo! su método no funcionaba directamente para la transmisión de divx porque descubrí que el reproductor divx enviaba rangos como bytes = 9932800-
pero me mostró cómo hacerlo, así que gracias: D
if(isset($_SERVER[''HTTP_RANGE'']))
{
file_put_contents(''showrange.txt'',$_SERVER[''HTTP_RANGE'']);
La reanudación de las descargas en HTTP se realiza a través del encabezado del Range
. Si la solicitud contiene un encabezado Range
, y si otros indicadores (por ejemplo, If-Match
, If-Unmodified-Since
) indican que el contenido no ha cambiado desde que se inició la descarga, usted proporciona un código de respuesta 206 (en lugar de 200), indique el rango de bytes que está devolviendo en el encabezado Content-Range
, luego proporcione ese rango en el cuerpo de la respuesta.
Aunque no sé cómo hacer eso en PHP.
La respuesta superior tiene varios errores.
- El error principal: no maneja el encabezado del rango correctamente.
bytes ab
debe significar[a, b]
lugar de[a, b)
, y losbytes a-
no se manejan. - El error menor: no usa el búfer para manejar la salida. Esto puede consumir demasiada memoria y causar baja velocidad para archivos grandes.
Aquí está mi código modificado:
// TODO: configurations here
$fileName = "File Name";
$file = "File Path";
$bufferSize = 2097152;
$filesize = filesize($file);
$offset = 0;
$length = $filesize;
if (isset($_SERVER[''HTTP_RANGE''])) {
// if the HTTP_RANGE header is set we''re dealing with partial content
// find the requested range
// this might be too simplistic, apparently the client can request
// multiple ranges, which can become pretty complex, so ignore it for now
preg_match(''/bytes=(/d+)-(/d+)?/'', $_SERVER[''HTTP_RANGE''], $matches);
$offset = intval($matches[1]);
$end = $matches[2] || $matches[2] === ''0'' ? intval($matches[2]) : $filesize - 1;
$length = $end + 1 - $offset;
// output the right headers for partial content
header(''HTTP/1.1 206 Partial Content'');
header("Content-Range: bytes $offset-$end/$filesize");
}
// output the regular HTTP headers
header(''Content-Type: '' . mime_content_type($file));
header("Content-Length: $filesize");
header("Content-Disposition: attachment; filename=/"$fileName/"");
header(''Accept-Ranges: bytes'');
$file = fopen($file, ''r'');
// seek to the requested offset, this is 0 if it''s not a partial content request
fseek($file, $offset);
// don''t forget to send the data too
ini_set(''memory_limit'', ''-1'');
while ($length >= $bufferSize)
{
print(fread($file, $bufferSize));
$length -= $bufferSize;
}
if ($length) print(fread($file, $length));
fclose($file);
Lo primero que debe hacer es enviar el encabezado Accept-Ranges: bytes
en todas las respuestas, para decirle al cliente que admite contenido parcial. Luego, si se recibe la solicitud con un encabezado Range: bytes=xy
(con y
siendo números) se analiza el rango que el cliente está solicitando, abra el archivo como de costumbre, busque x
bytes adelante y envíe los siguientes y
- x
bytes. Establezca también la respuesta a HTTP/1.0 206 Partial Content
.
Sin haber probado nada, esto podría funcionar, más o menos:
$filesize = filesize($file);
$offset = 0;
$length = $filesize;
if ( isset($_SERVER[''HTTP_RANGE'']) ) {
// if the HTTP_RANGE header is set we''re dealing with partial content
$partialContent = true;
// find the requested range
// this might be too simplistic, apparently the client can request
// multiple ranges, which can become pretty complex, so ignore it for now
preg_match(''/bytes=(/d+)-(/d+)?/'', $_SERVER[''HTTP_RANGE''], $matches);
$offset = intval($matches[1]);
$length = intval($matches[2]) - $offset;
} else {
$partialContent = false;
}
$file = fopen($file, ''r'');
// seek to the requested offset, this is 0 if it''s not a partial content request
fseek($file, $offset);
$data = fread($file, $length);
fclose($file);
if ( $partialContent ) {
// output the right headers for partial content
header(''HTTP/1.1 206 Partial Content'');
header(''Content-Range: bytes '' . $offset . ''-'' . ($offset + $length) . ''/'' . $filesize);
}
// output the regular HTTP headers
header(''Content-Type: '' . $ctype);
header(''Content-Length: '' . $filesize);
header(''Content-Disposition: attachment; filename="'' . $fileName . ''"'');
header(''Accept-Ranges: bytes'');
// don''t forget to send the data too
print($data);
Puede que me haya olvidado algo obvio, y definitivamente ignoré algunas posibles fuentes de errores, pero debería ser un comienzo.
Aquí hay una descripción del contenido parcial y encontré información sobre el contenido parcial en la página de documentación de fread .
Puede usar el siguiente código para el soporte de solicitud de rango de bytes en cualquier navegador
<?php
$file = ''YouTube360p.mp4'';
$fileLoc = $file;
$filesize = filesize($file);
$offset = 0;
$fileLength = $filesize;
$length = $filesize - 1;
if ( isset($_SERVER[''HTTP_RANGE'']) ) {
// if the HTTP_RANGE header is set we''re dealing with partial content
$partialContent = true;
preg_match(''/bytes=(/d+)-(/d+)?/'', $_SERVER[''HTTP_RANGE''], $matches);
$offset = intval($matches[1]);
$tempLength = intval($matches[2]) - 0;
if($tempLength != 0)
{
$length = $tempLength;
}
$fileLength = ($length - $offset) + 1;
} else {
$partialContent = false;
$offset = $length;
}
$file = fopen($file, ''r'');
// seek to the requested offset, this is 0 if it''s not a partial content request
fseek($file, $offset);
$data = fread($file, $length);
fclose($file);
if ( $partialContent ) {
// output the right headers for partial content
header(''HTTP/1.1 206 Partial Content'');
}
// output the regular HTTP headers
header(''Content-Type: '' . mime_content_type($fileLoc));
header(''Content-Length: '' . $fileLength);
header(''Content-Disposition: inline; filename="'' . $file . ''"'');
header(''Accept-Ranges: bytes'');
header(''Content-Range: bytes '' . $offset . ''-'' . $length . ''/'' . $filesize);
// don''t forget to send the data too
print($data);
?>
Sí, puedes usar el encabezado de rango para eso. Necesitas dar 3 encabezados más al cliente para una descarga completa:
header ("Accept-Ranges: bytes");
header ("Content-Length: " . $fileSize);
header ("Content-Range: bytes 0-" . $fileSize - 1 . "/" . $fileSize . ";");
Luego de una descarga interrumpida, debe verificar el encabezado de solicitud de Rango de la siguiente manera:
$headers = getAllHeaders ();
$range = substr ($headers[''Range''], ''6'');
Y en este caso, no te olvides de mostrar el contenido con el código de estado 206:
header ("HTTP/1.1 206 Partial content");
header ("Accept-Ranges: bytes");
header ("Content-Length: " . $remaining_length);
header ("Content-Range: bytes " . $start . "-" . $to . "/" . $fileSize . ";");
Obtendrá las variables $ start y $ to desde el encabezado de solicitud, y use fseek () para buscar la posición correcta en el archivo.
Sí. Apoyo byteranges. Ver RFC 2616 sección 14.35 .
Básicamente significa que debe leer el encabezado del Range
y comenzar a servir el archivo desde el desplazamiento especificado.
Esto significa que no puede usar readfile (), ya que sirve para todo el archivo. En su lugar, use fopen() primero, luego fseek() a la posición correcta, y luego use fpassthru() para servir el archivo.
Si está dispuesto a instalar un nuevo módulo PECL, la forma más fácil de respaldar las descargas reanudables con PHP es a través de http_send_file()
, como este
<?php
http_send_content_disposition("document.pdf", true);
http_send_content_type("application/pdf");
http_throttle(0.1, 2048);
http_send_file("../report.pdf");
?>
fuente: http://www.php.net/manual/en/function.http-send-file.php
¡Lo utilizamos para servir contenido almacenado en la base de datos y funciona como un encanto!
Una forma muy buena de resolver esto sin tener que "hacer rodar tu propio código PHP" es usar el módulo mod_xsendfile Apache. Luego, en PHP, simplemente establece los encabezados apropiados. Apache hace lo suyo.
header("X-Sendfile: /path/to/file");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; file=/"filename/"");
EDIT 2017/01 - Escribí una biblioteca para hacer esto en PHP> = 7.0 https://github.com/DaveRandom/Resume
EDITAR 2016/02 - Código completamente reescrito para un conjunto de herramientas modulares, un ejemplo de uso, en lugar de una función monolítica. Las correcciones mencionadas en los comentarios a continuación han sido incorporadas.
Una solución probada y funcional (basada en gran medida en la respuesta de Theo anterior) que trata con las descargas reanudables, en un conjunto de algunas herramientas independientes. Este código requiere PHP 5.4 o posterior.
Esta solución solo puede hacer frente a un rango por solicitud, pero bajo ninguna circunstancia con un navegador estándar que yo pueda pensar, esto no debería causar un problema.
<?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 < 0 || $this->lastByte < 0) {
throw new InvalidRangeHeaderException(
''Position specifiers cannot be negative''
);
} else if ($this->lastByte !== null && $this->lastByte < $this->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);
}
}
Ejemplo de uso:
<?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 send
ini_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 file
exit;