Próbuję stworzyć funkcję, która odbiera ścieżkę do pliku, identyfikuje, co to jest, ustawia odpowiednie nagłówki i obsługuje ją tak, jak zrobiłby to Apache.
Powodem, dla którego to robię, jest to, że muszę użyć PHP do przetworzenia niektórych informacji o żądaniu przed udostępnieniem pliku.
Szybkość jest krytyczna
virtual () nie wchodzi w grę
Musi pracować we współdzielonym środowisku hostingowym, w którym użytkownik nie ma kontroli nad serwerem internetowym (Apache / nginx itp.)
Oto, co mam do tej pory:
File::output($path);
<?php
class File {
static function output($path) {
// Check if the file exists
if(!File::exists($path)) {
header('HTTP/1.0 404 Not Found');
exit();
}
// Set the content-type header
header('Content-Type: '.File::mimeType($path));
// Handle caching
$fileModificationTime = gmdate('D, d M Y H:i:s', File::modificationTime($path)).' GMT';
$headers = getallheaders();
if(isset($headers['If-Modified-Since']) && $headers['If-Modified-Since'] == $fileModificationTime) {
header('HTTP/1.1 304 Not Modified');
exit();
}
header('Last-Modified: '.$fileModificationTime);
// Read the file
readfile($path);
exit();
}
static function mimeType($path) {
preg_match("|\.([a-z0-9]{2,4})$|i", $path, $fileSuffix);
switch(strtolower($fileSuffix[1])) {
case 'js' :
return 'application/x-javascript';
case 'json' :
return 'application/json';
case 'jpg' :
case 'jpeg' :
case 'jpe' :
return 'image/jpg';
case 'png' :
case 'gif' :
case 'bmp' :
case 'tiff' :
return 'image/'.strtolower($fileSuffix[1]);
case 'css' :
return 'text/css';
case 'xml' :
return 'application/xml';
case 'doc' :
case 'docx' :
return 'application/msword';
case 'xls' :
case 'xlt' :
case 'xlm' :
case 'xld' :
case 'xla' :
case 'xlc' :
case 'xlw' :
case 'xll' :
return 'application/vnd.ms-excel';
case 'ppt' :
case 'pps' :
return 'application/vnd.ms-powerpoint';
case 'rtf' :
return 'application/rtf';
case 'pdf' :
return 'application/pdf';
case 'html' :
case 'htm' :
case 'php' :
return 'text/html';
case 'txt' :
return 'text/plain';
case 'mpeg' :
case 'mpg' :
case 'mpe' :
return 'video/mpeg';
case 'mp3' :
return 'audio/mpeg3';
case 'wav' :
return 'audio/wav';
case 'aiff' :
case 'aif' :
return 'audio/aiff';
case 'avi' :
return 'video/msvideo';
case 'wmv' :
return 'video/x-ms-wmv';
case 'mov' :
return 'video/quicktime';
case 'zip' :
return 'application/zip';
case 'tar' :
return 'application/x-tar';
case 'swf' :
return 'application/x-shockwave-flash';
default :
if(function_exists('mime_content_type')) {
$fileSuffix = mime_content_type($path);
}
return 'unknown/' . trim($fileSuffix[0], '.');
}
}
}
?>
php
performance
file-io
x-sendfile
Kirk Ouimet
źródło
źródło
$extension = end(explode(".", $pathToFile))
czy można to zrobić z substr i strrpos:$extension = substr($pathToFile, strrpos($pathToFile, '.'))
. Dodatkowomime_content_type()
możesz spróbować wywołania systemowego:$mimetype = exec("file -bi '$pathToFile'", $output);
Odpowiedzi:
Moja poprzednia odpowiedź była częściowa i niezbyt dobrze udokumentowana, oto aktualizacja z podsumowaniem rozwiązań z niej i od innych w dyskusji.
Rozwiązania są uporządkowane od najlepszego do najgorszego, ale także od rozwiązania wymagającego największej kontroli nad serwerem WWW do tego, które potrzebuje mniej. Wydaje się, że nie ma łatwego sposobu na znalezienie jednego rozwiązania, które jest zarówno szybkie, jak i działa wszędzie.
Korzystanie z nagłówka X-SendFile
Jak udokumentowali inni, to właściwie najlepszy sposób. Podstawą jest to, że wykonujesz kontrolę dostępu w php, a następnie zamiast samodzielnie wysyłać plik, każesz to zrobić serwerowi WWW.
Podstawowy kod php to:
Gdzie
$file_name
jest pełna ścieżka w systemie plików.Głównym problemem związanym z tym rozwiązaniem jest to, że musi być dozwolone przez serwer sieciowy i albo nie jest instalowane domyślnie (apache), nie jest domyślnie aktywne (lighttpd) lub wymaga określonej konfiguracji (nginx).
Apache
Pod Apache, jeśli używasz mod_php, musisz zainstalować moduł o nazwie mod_xsendfile, a następnie skonfigurować go (w konfiguracji apache lub .htaccess, jeśli na to pozwolisz)
W tym module ścieżka pliku może być bezwzględna lub względna w stosunku do określonego
XSendFilePath
.Lighttpd
Mod_fastcgi obsługuje to, gdy jest skonfigurowany z
Dokumentacja funkcji znajduje się na lighttpd wiki, gdzie dokumentuje
X-LIGHTTPD-send-file
nagłówek, aleX-Sendfile
nazwa również działaNginx
Na Nginx nie możesz użyć
X-Sendfile
nagłówka, musisz użyć własnego nagłówka o nazwieX-Accel-Redirect
. Jest domyślnie włączony, a jedyną prawdziwą różnicą jest to, że jego argumentem powinien być identyfikator URI, a nie system plików. Konsekwencją jest to, że musisz zdefiniować lokalizację oznaczoną jako wewnętrzną w twojej konfiguracji, aby uniknąć znalezienia przez klientów prawdziwego adresu URL pliku i bezpośredniego przejścia do niego, ich wiki zawiera dobre wyjaśnienie tego.Nagłówek łącza symboliczne i lokalizacja
Możesz użyć linków symbolicznych i przekierować do nich, po prostu utwórz dowiązania symboliczne do swojego pliku z losowymi nazwami, gdy użytkownik jest upoważniony do dostępu do pliku i przekieruj użytkownika do niego za pomocą:
Oczywiście będziesz potrzebować sposobu, aby je przyciąć, gdy zostanie wywołany skrypt do ich utworzenia lub przez cron (na komputerze, jeśli masz dostęp lub za pośrednictwem usługi webcron w przeciwnym razie)
W Apache musisz mieć możliwość włączenia
FollowSymLinks
w.htaccess
konfiguracji Apache lub w konfiguracji Apache.Kontrola dostępu według adresu IP i nagłówka lokalizacji
Innym hackem jest generowanie plików dostępu Apache z php, umożliwiając jawne IP użytkownika. W apache oznacza to użycie poleceń
mod_authz_host
(mod_access
)Allow from
.Problem polega na tym, że blokowanie dostępu do pliku (ponieważ wielu użytkowników może chcieć to zrobić w tym samym czasie) jest nietrywialne i może prowadzić do długiego oczekiwania niektórych użytkowników. I tak nadal musisz przyciąć plik.
Oczywiście innym problemem byłoby to, że wiele osób korzystających z tego samego adresu IP mogłoby potencjalnie uzyskać dostęp do pliku.
Kiedy wszystko inne zawodzi
Jeśli naprawdę nie masz możliwości, aby Twój serwer WWW Ci pomógł, jedynym rozwiązaniem pozostaje readfile, który jest dostępny we wszystkich aktualnie używanych wersjach php i działa całkiem dobrze (ale nie jest zbyt wydajny).
Łączenie rozwiązań
W porządku, najlepszym sposobem na naprawdę szybkie wysłanie pliku, jeśli chcesz, aby kod php był użyteczny wszędzie, jest gdzieś konfigurowalna opcja, z instrukcjami, jak ją aktywować w zależności od serwera internetowego i być może automatycznego wykrywania podczas instalacji scenariusz.
Jest to bardzo podobne do tego, co robi się w wielu programach
mod_rewrite
na Apache)mcrypt
moduł PHP)mbstring
moduł PHP)źródło
header("Location: " . $path);
?Najszybszy sposób: nie rób tego. Zajrzyj do nagłówka x-sendfile dla nginx , są też podobne rzeczy dla innych serwerów WWW. Oznacza to, że nadal możesz kontrolować dostęp itp. W php, ale delegować faktyczne wysyłanie pliku na serwer sieciowy przeznaczony do tego celu.
PS: Przeraża mnie myślenie o tym, o ile wydajniejsze jest używanie tego z nginx w porównaniu do czytania i wysyłania pliku w php. Pomyśl tylko, jeśli 100 osób pobiera plik: z php + apache, będąc hojnym, to prawdopodobnie 100 * 15 MB = 1,5 GB (w przybliżeniu, zastrzel mnie) pamięci RAM. Nginx po prostu przekaże plik do jądra, a następnie zostanie załadowany bezpośrednio z dysku do buforów sieciowych. Szybki!
PPS: Dzięki tej metodzie nadal możesz wykonywać całą kontrolę dostępu, operacje związane z bazą danych.
źródło
readfile()
Oto czyste rozwiązanie PHP. Zaadaptowałem następującą funkcję z moich osobistych ram :
Kod jest tak wydajny, jak to tylko możliwe, zamyka procedurę obsługi sesji, aby inne skrypty PHP mogły działać jednocześnie dla tego samego użytkownika / sesji. Obsługuje również serwowanie pobrań w zakresach (co podejrzewam, że domyślnie robi to Apache), dzięki czemu ludzie mogą wstrzymywać / wznawiać pobieranie, a także korzystać z wyższych prędkości pobierania dzięki akceleratorom pobierania. Pozwala również określić maksymalną prędkość (w Kb / s), z jaką pobieranie (część) powinno być obsługiwane za pośrednictwem
$speed
argumentu.źródło
eio
nie zawsze jest dostępna. Mimo to +1 nie wiedziało o tym rozszerzeniu pecl. =)$size = sprintf('%u', filesize($path))
?Pozwól Apache wykonać pracę za Ciebie.
źródło
Lepsza implementacja z obsługą pamięci podręcznej i dostosowanymi nagłówkami HTTP.
źródło
jeśli masz możliwość dodania rozszerzeń PECL do swojego php, możesz po prostu użyć funkcji z pakietu Fileinfo, aby określić typ zawartości, a następnie wysłać odpowiednie nagłówki ...
źródło
Download
Wspomniana tutaj funkcja PHP powodowała pewne opóźnienie przed faktycznym rozpoczęciem pobierania pliku. Nie wiem, czy było to spowodowane użyciem cache lakieru czy co, ale u mnie pomogło tosleep(1);
całkowicie usunąć i ustawić$speed
na1024
. Teraz działa bez problemu i jest szybki jak diabli. Może mógłbyś zmodyfikować tę funkcję, ponieważ widziałem ją w całym internecie.źródło
Zakodowałem bardzo prostą funkcję do obsługi plików z PHP i automatycznym wykrywaniem typu MIME:
Stosowanie
źródło