Brak danych wyjściowych przed wysłaniem nagłówków!
Funkcje wysyłające / modyfikujące nagłówki HTTP muszą być wywoływane przed wykonaniem jakiegokolwiek wyniku .
summary ⇊
W przeciwnym razie połączenie nie powiedzie się:
Ostrzeżenie: Nie można modyfikować informacji nagłówka - nagłówki już wysłane (wyjście rozpoczęte w skrypcie: wiersz )
Niektóre funkcje modyfikujące nagłówek HTTP to:
Dane wyjściowe mogą być:
Nieumyślny:
- Biała spacja przed
<?php
lub po?>
- UTF-8 Byte Order Mark specjalnie
- Poprzednie komunikaty o błędach lub powiadomienia
Zamierzony:
print
, echo
Oraz inne funkcje wyświetlania produkcji
- Surowe
<html>
odcinki przed <?php
kodu.
Dlaczego tak się dzieje?
Aby zrozumieć, dlaczego nagłówki muszą być wysyłane przed wyjściem, należy spojrzeć na typową
odpowiedź HTTP . Skrypty PHP generują głównie treść HTML, ale także przekazują zestaw nagłówków HTTP / CGI do serwera:
HTTP/1.1 200 OK
Powered-By: PHP/5.3.7
Vary: Accept-Encoding
Content-Type: text/html; charset=utf-8
<html><head><title>PHP page output page</title></head>
<body><h1>Content</h1> <p>Some more output follows...</p>
and <a href="/"> <img src=internal-icon-delayed> </a>
Strona / wyjście zawsze następuje po nagłówkach. PHP musi najpierw przekazać nagłówki do serwera WWW. Może to zrobić tylko raz. Po podwójnym podziale wiersza nie można ich już zmienić.
Gdy PHP odbiera pierwsze wyjście ( print
, echo
, <html>
) będzie
opróżnić wszystkich zebranych nagłówków. Następnie może wysłać wszystkie potrzebne dane wyjściowe. Ale wysyłanie kolejnych nagłówków HTTP jest wówczas niemożliwe.
Jak dowiedzieć się, gdzie wystąpiło przedwczesne wyjście?
header()
Ostrzeżenie zawiera wszystkie istotne informacje, aby znaleźć przyczynę problemu:
Ostrzeżenie: Nie można modyfikować informacji nagłówka - nagłówki zostały już wysłane przez
(wyjście rozpoczęto w / www / usr2345 / htdocs / auth.php: 52 ) w /www/usr2345/htdocs/index.php w linii 100
Tutaj „wiersz 100” odnosi się do skryptu, w którym header()
wywołanie nie powiodło się.
Uwaga „ wyjście rozpoczęte o ” w nawiasie jest bardziej znacząca. Określa źródło poprzedniej produkcji. W tym przykładzie jest to auth.php
i linia52
. Właśnie tam trzeba było szukać przedwczesnych wyników.
Typowe przyczyny:
Drukuj, echo
Umyślne wyjście print
i echo
instrukcje zakończą możliwość wysyłania nagłówków HTTP. Aby tego uniknąć, przepływ aplikacji musi zostać zrestrukturyzowany. Użyj funkcji
i schematów szablonów. Upewnij się, że header()
połączenia są wykonywane przed wypisaniem wiadomości.
Funkcje generujące dane wyjściowe obejmują
print
, echo
, printf
,vprintf
trigger_error
, ob_flush
, ob_end_flush
, var_dump
,print_r
readfile
, passthru
, flush
, imagepng
,imagejpeg
między innymi funkcje zdefiniowane przez użytkownika.
Surowe obszary HTML
Nieprzetworzone sekcje HTML w .php
pliku są również wyprowadzane bezpośrednio. Warunki skrypt, który wywoła header()
połączenie należy zaznaczyć przed wszelkimi surowych <html>
bloków.
<!DOCTYPE html>
<?php
// Too late for headers already.
Użyj schematu szablonów, aby oddzielić przetwarzanie od logiki wyjściowej.
- Umieść kod przetwarzania formularza na skryptach.
- Użyj tymczasowych zmiennych łańcuchowych, aby odroczyć komunikaty.
- Rzeczywista logika wyjściowa i zmieszane wyjście HTML powinny następować na końcu.
Wcześniej <?php
spacje dla ostrzeżeń „skrypt.php wiersz 1 ”
Jeśli ostrzeżenie odnosi się do danych wyjściowych w linii 1
, oznacza to przede wszystkim białe znaki , tekst lub HTML przed <?php
tokenem otwierającym .
<?php
# There's a SINGLE space/newline before <? - Which already seals it.
Podobnie może się zdarzyć w przypadku dołączonych skryptów lub sekcji skryptów:
?>
<?php
PHP zjada pojedynczy podział wiersza po zamknięciu tagów. Ale to nie zrekompensuje wielu nowych linii, tabulatorów lub spacji przesuniętych w takie luki.
LM UTF-8
Problemem mogą być same łamanie linii i spacje. Ale są też „niewidzialne” sekwencje znaków, które mogą to powodować. Najbardziej znany
BOM UTF-8 (Bajt-Znak-Znak),
który nie jest wyświetlany przez większość edytorów tekstu. Jest to sekwencja bajtów EF BB BF
, która jest opcjonalna i nadmiarowa dla dokumentów zakodowanych w UTF-8. PHP musi jednak traktować to jako surowe wyjście. Może pojawiać się jako znaki 
na wyjściu (jeśli klient interpretuje dokument jako Latin-1) lub podobne „śmieci”.
W szczególności edytory graficzne i środowiska IDE oparte na Javie są nieświadome jego obecności. Nie wizualizują go (zgodnie ze standardem Unicode). Jednak większość programistów i edytorów konsolowych:
Łatwo jest wcześnie rozpoznać problem. Inni redaktorzy mogą zidentyfikować jego obecność w menu pliku / ustawień (Notepad ++ w systemie Windows może zidentyfikować i
rozwiązać problem ). Inną opcją sprawdzenia obecności BOM jest uciekanie się do hekseditora . W systemach * nix hexdump
jest zwykle dostępna wersja graficzna, która upraszcza kontrolę tych i innych problemów:
Łatwym rozwiązaniem jest ustawienie edytora tekstu do zapisywania plików jako „UTF-8 (bez BOM)” lub podobnej nomenklatury. Często nowi użytkownicy uciekają się do tworzenia nowych plików i kopiowania i wklejania poprzedniego kodu.
Narzędzia korekcyjne
Istnieją również zautomatyzowane narzędzia do sprawdzania i przepisywania plików tekstowych ( sed
/awk
lub recode
). W przypadku PHP istnieje phptags
tag tidier . Przeredagowuje zamykanie i otwieranie znaczników w długie i krótkie formy, ale z łatwością rozwiązuje także wiodące i końcowe problemy z białymi znakami, Unicode i UTF-x BOM:
phptags --whitespace *.php
Używanie całego katalogu włączeń lub projektów jest rozsądne.
Biała spacja po ?>
Jeśli źródło błędu jest wspomniane jako za
zamknięciem,?>
to tutaj zapisano trochę białych znaków lub nieprzetworzonego tekstu. Znacznik końca PHP nie kończy w tym momencie wykonywania skryptu. Wszelkie znaki tekstu / spacji po nim zostaną zapisane jako treść strony.
Powszechnie zaleca się, szczególnie początkującym, że końcowe ?>
znaczniki zamykające PHP powinny zostać pominięte. Pozwala to uniknąć niewielkiej części tych przypadków. (Dość często include()d
sprawcami są skrypty).
Źródło błędu wymienione jako „Nieznany w linii 0”
Zazwyczaj jest to rozszerzenie PHP lub php.ini, jeśli żadne źródło błędu nie jest konkretyzowane.
- Czasami jest to
gzip
ustawienie kodowania strumienia
lubob_gzhandler
.
- Ale może to być również
extension=
moduł podwójnie załadowany generujący domyślny komunikat startowy / ostrzegawczy PHP.
Poprzednie komunikaty o błędach
Jeśli inna instrukcja lub wyrażenie PHP powoduje wydrukowanie komunikatu ostrzegawczego lub powiadomienia, jest to również liczone jako przedwczesne wyjście.
W takim przypadku musisz uniknąć błędu, opóźnić wykonanie instrukcji lub ukryć komunikat za pomocą np.
isset()
Lub @()
- gdy albo nie utrudni to późniejszego debugowania.
Brak komunikatu o błędzie
Jeśli masz error_reporting
lub display_errors
wyłączyłeś php.ini
, nie pojawi się żadne ostrzeżenie. Ale ignorowanie błędów nie spowoduje, że problem zniknie. Nagłówków nadal nie można wysłać po przedwczesnym wyjściu.
Dlatego gdy header("Location: ...")
przekierowania po cichu nie działają, wskazane jest sprawdzenie ostrzeżeń. Ponownie włącz je za pomocą dwóch prostych poleceń na skrypcie wywołania:
error_reporting(E_ALL);
ini_set("display_errors", 1);
Lub set_error_handler("var_dump");
jeśli wszystko inne zawiedzie.
Mówiąc o nagłówkach przekierowań, powinieneś często używać takiego idiomu dla końcowych ścieżek kodu:
exit(header("Location: /finished.html"));
Najlepiej nawet funkcja użyteczna, która drukuje komunikat użytkownika w przypadku header()
awarii.
Buforowanie danych wyjściowych jako obejście
Buforowanie danych wyjściowych PHPs
jest obejściem tego problemu. Często działa niezawodnie, ale nie powinno zastępować właściwej struktury aplikacji i oddzielania danych wyjściowych od logiki sterowania. Jego rzeczywistym celem jest zminimalizowanie porcji przesyłanych na serwer internetowy.
output_buffering=
Ustawienie mimo to może pomóc. Skonfiguruj go w php.ini
lub poprzez .htaccess,
a nawet .user.ini w nowoczesnych konfiguracjach FPM / FastCGI.
Włączenie go pozwoli PHP buforować dane wyjściowe zamiast natychmiastowego przekazywania ich do serwera WWW. PHP może zatem agregować nagłówki HTTP.
Może być również zaangażowany w wywołanie na ob_start();
szczycie skryptu wywołania. Który jednak jest mniej niezawodny z wielu powodów:
Nawet jeśli <?php ob_start(); ?>
uruchamia się pierwszy skrypt, białe znaki lub LM mogą być wcześniej tasowane, co czyni go nieskutecznym .
Może ukrywać białe znaki dla danych wyjściowych HTML. Ale gdy tylko logika aplikacji próbuje wysłać zawartość binarną (na przykład wygenerowany obraz), buforowane zbędne dane wyjściowe stają się problemem. (Konieczne ob_clean()
jako dalsze obejście).
Bufor ma ograniczony rozmiar i może łatwo zostać przekroczony, gdy zostanie pozostawiony domyślny. I nie jest to również rzadkie zjawisko, trudne do wyśledzenia,
kiedy to się dzieje.
Oba podejścia mogą zatem stać się zawodne - w szczególności przy przełączaniu między konfiguracjami programistycznymi i / lub serwerami produkcyjnymi. Dlatego buforowanie danych wyjściowych jest powszechnie uważane za zwykłe rozwiązanie.
Zobacz także przykład użycia podstawowego
w instrukcji oraz dodatkowe zalety i wady:
Ale działało na drugim serwerze !?
Jeśli wcześniej nie otrzymałeś ostrzeżenia w nagłówkach, oznacza to, że ustawienie bufora wyjściowego php.ini
uległo zmianie. Prawdopodobnie jest nieskonfigurowany na bieżącym / nowym serwerze.
Sprawdzanie za pomocą headers_sent()
Zawsze możesz użyć headers_sent()
do sprawdzenia, czy nadal można ... wysłać nagłówki. Co jest przydatne do warunkowego wydrukowania informacji lub zastosowania innej logiki zastępczej.
if (headers_sent()) {
die("Redirect failed. Please click on this link: <a href=...>");
}
else{
exit(header("Location: /user.php"));
}
Przydatne obejścia zastępcze to:
<meta>
Tag HTML
Jeśli aplikacja jest strukturalnie trudna do naprawienia, to łatwym (ale nieco nieprofesjonalnym) sposobem na umożliwienie przekierowań jest wstrzykiwanie <meta>
znacznika HTML
. Przekierowanie można osiągnąć za pomocą:
<meta http-equiv="Location" content="http://example.com/">
Lub z krótkim opóźnieniem:
<meta http-equiv="Refresh" content="2; url=../target.html">
Prowadzi to do nieprawidłowego kodu HTML, jeśli zostanie wykorzystany poza <head>
sekcją. Większość przeglądarek nadal to akceptuje.
Przekierowanie JavaScript
Alternatywnie do przekierowań stron
można użyć przekierowania JavaScript :
<script> location.replace("target.html"); </script>
Chociaż jest to często bardziej zgodne z HTML niż <meta>
obejście, powoduje to poleganie na klientach obsługujących JavaScript.
Oba podejścia powodują jednak akceptowalne awarie w przypadku niepowodzenia autentycznych wywołań nagłówka HTTP (). Idealnie byłoby zawsze połączyć to z przyjazną dla użytkownika wiadomością i klikalnym linkiem w ostateczności. (Na przykład to, co
robi rozszerzenie PECL http_redirect () .)
Dlaczego setcookie()
i session_start()
to również dotyczy
Zarówno setcookie()
i session_start()
trzeba wysłać Set-Cookie:
nagłówek HTTP. Dlatego obowiązują te same warunki i podobne komunikaty o błędach będą generowane w przypadku przedwczesnych sytuacji wyjściowych.
(Oczywiście wpływają na to wyłączone pliki cookie w przeglądarce, a nawet problemy z serwerem proxy. Funkcjonalność sesji oczywiście zależy również od wolnego miejsca na dysku i innych ustawień php.ini itp.)
Dalsze linki