Relativní odkazy a chybová stránka

Školení, která pořádám

Při vytváření interních odkazů používám relativní cesty a ne absolutní. Jednak je s tím méně psaní a jednak mi odkazy správně fungují např. i na adrese http://localhost/projekt/. Výborně se to používá za předpokladu, že jsou všechny stránky na stejné úrovni.

Tento předpoklad ale není splněn v případě, že pomocí direktivy ErrorDocument definujeme chybovou stránku se stejným vzhledem jako má zbytek webu. Stránka se totiž zobrazí i v podřízených adresářích (např. img/chybi.html) a relativní odkazy tím pádem povedou do daného adresáře.

Vyřešit se to dá tak, že pro tuto chybovou stránku definujeme ve značce <base href=""> jiný základ pro výpočet relativních odkazů:

<?php
if ($_SERVER["REDIRECT_STATUS"] == 404) {
    echo "<base href='http://$_SERVER[SERVER_NAME]" . preg_replace('~[^/]*$~', '', $_SERVER["SCRIPT_NAME"]) . "' />\n";
}
?>

Pokud má web různé stránky v různých úrovních, je samozřejmě pohodlnější vytvářet absolutní odkazy a pro vývoj používat pojmenovaného virtuálního hosta např. http://projekt-dev.

Jakub Vrána, Dobře míněné rady, 16.4.2008, diskuse: 27 (nové: 0)

Diskuse

ikona Petr Soukup:

Osobně před všemi odkazy uvádím lomítko. Funguje to bezpečně i "ve vnořených složkách" při použití mod_rewrite, přežije to bez problémů přesun na jinou doménu (popř. localhost) a obecně s tím není problém.

ikona Michal Aichinger:

No lomítko je sice pěkné ale na tím pádem nemůžeš vyvíjet na localhostu tak jako Jakub. Tedy projekty umístěné v adresářích (http://localhost/projekt/) jsou pro tebe tabu :-) Radši tedy nemať čtenáře.

Ondřej Brejla:

Snad to chápu správně, ale pokud píšu na localhostu, tak mi přijde pohodlnější nastavit si pro každý projekt virtual host. Mohu pak beztrestně využívat adresy s lomítkem na začátku a přenositelnost není problémem.

afu:

nejsou tabu, stačí si cestu z document root do podadresáře s projektem nadefinovat jako konstantu a používat ji coby součást odkazů, např.:

define(BASEDIR,'/projekt/');
...
echo BASEDIR.'odkaz.php?id='.$id;

při přesunu do jiného adresáře pak stačí jen změnit hodnotu konstanty.

ikona Jakub Vrána OpenID:

Tomuto přístupu bych se za každou cenu vyhnul a to i ve variantě, kdy by se BASEDIR získával dynamicky. Je to spousta psaní a starostí navíc a nic z toho.

Vojtěch Vysloužil:

Já naopak dynamické získávání BASEDIR vřele doporučuji :-) Toho psaní navíc zase tak moc není, starosti s tím nejsou + získate výhody, které už uvedl Petr Soukup.

ikona Jakub Vrána OpenID:

U každého odkazu psát místo <a href="archiv.php"> ošklivé <a href="<?php echo BASEDIR; ?>archiv.php"> za spoustu psaní považuji. Naopak <a href="/archiv.php"> by bylo z pohledu psaní OK.

vkuzel:

Používám přístup s použitím BASEDIR. Odkazy generuju v šablonovacím systému dynamicky, protože mám několik modulů, snažím se tak předejít tomu aby se dvě stránky se shodnými názvy z různých modulů tloukly, tzn. jedné se automaticky přiřadí nějaký prefix.

V šabloně mám tedy záspis <a href="{href page="nazevStranky" module="nazevModulu"}">...

Abych nemusel při každém přesunutí projektu nahánět konfiguraci BASEDIR, případně, abych předešel problémům které popisuje Martin Hassman ve svém příspěvku z 17.4, získávám tuto hodnotu automaticky pomocí nějakého skriptu, který jsem kdesi stáhnul z webu.

<?php
function getURL()
{
  $s = empty($_SERVER["HTTPS"]) ? '' : ($_SERVER["HTTPS"] == "on") ? "s" : "";

  $protocol = substr(strtolower($_SERVER["SERVER_PROTOCOL"]), 0, strpos(strtolower($_SERVER["SERVER_PROTOCOL"]), "/"));

  $port = ($_SERVER["SERVER_PORT"] == "80") ? "" : (":".$_SERVER["SERVER_PORT"]);
  return $protocol."://".$_SERVER['SERVER_NAME'].$port.$_SERVER['REQUEST_URI'];
}
?>

Výsledek této funkce, lze potom jednoduše rozložit funkcí parse_url().

Nevýhody:

* Komplikovanější zápis, někdo cizí musí nastudovat filosofii, jakou se odkazy zapisují, než může nějaký vytvořit.

Výhody:

* Funguje to, při přesunu projektu do jakéhokoli adresáře nebo na jakoukoli doménu, bez nutnosti konfigurace (snad).

* Lze snadno zapnout/vypnout/upravit rewrite, protože odkaz je výsledkem funkce. Kvůli tomuto bodu jsem to celé dělal. Předtím jsem též používal relativní cesty, ale začalo mi to dělat brikule se styly externími javascripty a pod., když jsem chtěl použít rewrite ve tvaru /server/stranka/polozka/

afu:

V tom případě chápu, jako uživatel Smarty to vidím pouze jako {$bd}odkaz.php. Každý projekt ale vyžaduje své, proto basedir používám tam, kde nemám zaručenou práci v kořeni webu (na dev i ostrém serveru), u těch ostatních začínám cestu přímo lomítkem. Miniaplikace, které po mě mohou upravovat jiní pak používám relativní odkazy.

ikona Petr Soukup:

Jak už to padlo, tak pohodlné řešení je virtualhost. Ale pokud se nepletu, tak jeden řádek v mod_rewrite by to hravě zvládl taky

ikona Techi:

Jsem rád, že tohle už s MVC přístupem v Zend Frameworku řešit nemusím :)
Všechny odkazy jsou relativní a začínají s lomítkem.
Pokud chci web někam přemístit, stačí jenom v konfiguračním souboru změnit baseUrl

ikona Jakub Vrána OpenID:

Odkazy začínající na lomítko označuji v článku (ne zcela správně) jako absolutní.

ikona David Grudl:

Je to sice *relativní* odkaz, ale tvořený *absolutní* cestou. Relativní odkaz je vlastně i //example.com/produkt/123. Takže je možná lepší v článku diskutovat relativní a absolutní cesty, namísto odkazů.

Každopádně s relativními odkazy je docela kříž, viz třeba http://php.vrana.cz/index.php/

ikona Jakub Vrána OpenID:

Toto rozlišení mi chybělo, napadal mě pouze relativně-absolutní odkaz :-).

Za upozornění na slabé místo blogu děkuji, chybovou stránku tam vkládám programově, takže se automaticky nenastavil REDIRECT_STATUS.

ikona David Grudl:

Tak tady máš další slabé místo http://php.vrana.cz/index.php/test/ ;)

Snažil jsem se tím naznačit, že než vynakládat obrovské úsilí na vychytání problémů s relativními cestami, je lepší používat cesty absolutní. Myslím že to je i snadnější, zmíněné echo BASEDIR, 'odkaz.php' zase tolik nebolí.

Já používám trik, kdy v šabloně píšu absolutní cesty a pak je jedním regulárním výrazem nahradím za BASEDIR. Cca takto:

$s = preg_replace('#(src|href|action)\s*=\s*"/#', '$1="' . $basePath, $s);

Rychlé, kešovatelné, přenositelné, a hlavně: bez relativních cest.

ikona tiso:

Šikovné... Ja som prešiel od relatívnych v HTML cez relatívne v PHP a odkazy s absolútnou cestou až k BASEDIR.

Martin Hassman:

Pár let nazpátek bych s tebou bez výjimky souhlasil, než jsem jednou narazil. Problém může nastat, pokud je web přístupný přes reverzní proxy (třeba na základě nějakých politik organizace).

Kdysi jsem dělal http://15esf.vscht.cz/ (dostupné jen z intranetu), které bylo z internetu ve výsledku vidět jako http://www.vscht.cz/15esfc/ - na veškerých absolutních dotazech jsem samozřejmě pohořel (směřovaly na úplně odlišný webserver než ten můj, proxy předávala pouze dotazy z adresáře 15esfc). Od té doby vím, že absolutní URL jen v 80% případů.

ajs:

Jaka je z toho plyne ponauceni :) ? Jak elegantne zaridit aby fungoval web zaroven na adrese xyz.domena.cz a www.domena.cz/xyz pokud ma stranky v ruznych urovnich. jak na odkazy?

ikona Jakub Vrána OpenID:

Nejlepší je, když web běží jen na jedné adrese a z druhé je na ni přesměrování.

ikona Jakub Vrána OpenID:

Tak napotřetí snad už :-).

Trik je prima do té doby, než začneš řešit problémy s tím, že se ti nepřekládá <a href='/'> nebo <param name="movie" value="/movie.swf">…

JsemNikdo :):

Mohu se zeptat, jak jsi opravil web.cz/index.php/  ?
Na svých webech mám web.cz/index.php?page=... , ale na tuhle bezpečnostní chybu nevím jak vyzrát. Děkuji

ikona Jakub Vrána OpenID:

Na chybovou stránku se vloží <base href> z cestou zjištěnou z $_SERVER["SCRIPT_NAME"].

Martin Hassman:

Base a funguje ti to dobře v prohlížečích? Já si pamatuji, že svého času s tím byl docela problém, a proto jsem se base vyhýbal, ale možná že se mezitím situace zlepšila.

ikona Jakub Vrána OpenID:

Já si naopak nepamatuji, že by s tím kdykoliv jakýkoliv problém byl (tedy pokud se uvede absolutní URL). Tuto značku s oblibou používají např. cache vyhledávačů.

ikona v6ak:

Já osobně mám něco jako webroot.cfg.php, kde napíšu <?php return 'cestaDoKorenu'; ?>. Místo cestaDoKorenu píšu doma /projekt/ a na hostingu /.

ikona Maxell:

Já k překladu využívám výhod šablon, kdy všechny adresy píšu s / a na localhostu se automaticky nahradí za /projekt/. Viz http://blog.maxell-cz.cz/item/absolutni-adresy-a-mod-rewrite

fos4:

Ja pouzivam virtual host a host. Pokud mam web example.com tak pouzivam example.my..

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2018 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.