Skryté pastičky v Tomcatu aneb zpětná kompatibilita se všude nenosí

Nespoléhejte se na to, že, tak jako Java samotná, budou i základní knihovny a nástroje respektovat důležitost zpětné kompatibility. Například v případě Tomcatu se nám už několikrát stalo, že při upgradu na verzi, kde se mění pouze číslo patche, se kompletně rozpadla funkčnost aplikace. Poprvé to bylo myslím, když v patchi vyupgradovali na novější specku JSP a teď nám zase přihodili bombičku v podobě změny obsahu httpServletRequest.getPathInfo(), která nově vrací i tzv. path parametry.

Že nevíte o co se jedná? Jedná se o parametry připojené k url pomocí znaku středník. Zatím jsem neviděl mnoho případů, kdy by se k něčemu využívaly s vyjímkou jediného a tím je jsessionid parametr, který je používán k přenosu vygenerovaného SESSION_ID do doby než je známo, jestli klientská strana podporuje Cookies (pak už se toto id předává většinou pomocí cookie). Podrobnosti o zmatcích panujících v této části specky moc hezky rozebírá tento příspěvek (navíc má hezkou statistiku toho, jaký bordel v tom mají různé Servlet kontejnery): Java Servlet URI Parameters.

Vývojáři Tomcatu se rozhodli, že si tento nepořádek uklidí a udělali tak ve verzi 6.0.33. Jediné, podle čeho mohlo okolí zjistit (kromě své nefunkční aplikace) byl tento záznam v changelogu:

Improve handling of URLs with path parameters and prevent incorrect 404 responses that could occur when path parameters were present. (kkolinko)

Důvody které je k tomu vedly jsou rozepsány v těchto materiálech:

Záměr je to jistě bohulibý, ale provedení typicky Tomcatovské - nic neříkající záznam v changelogu a zvednutí čísla patche. Teď můžeme čekat jak se postupně v mnoha aplikacích začnou objevovat hlášení podobná tomuto: Incorrect handling of path parameters in PathDispatcher

Takže jestli se vám po upgradu na Apache Tomcat verze 6.0.33 rozpadne vaše webová aplikace, protože webový framework nepočítal s dodatečnými parametry v pathInfo (a to třeba i z toho důvodu, že si mohli vyložit Servlet specku stejně špatně jako dřív sami autoři Tomcatu), už víte o co jde. Při získávání pathInfo je nutné jej takto oříznout:


    /**
     * Removes path parameters from each path segment in the supplied path and truncates sequences of multiple '/'
     * characters to a single '/'.
     *
     * @param path either the {@code servletPath} and {@code pathInfo} from the original request
     *
     * @return the supplied value, with path parameters removed and sequences of multiple '/' characters truncated,
     *  or null if the supplied path was null.
     */
    private String strip(String path) {
        if (path == null) {
            return null;
        }
        int scIndex = path.indexOf(';');
        if (scIndex < 0) {
            int doubleSlashIndex = path.indexOf("//");
            if (doubleSlashIndex < 0) {
                // Most likely case, no parameters in any segment and no '//', so no stripping required
                return path;
            }
        }
        StringTokenizer st = new StringTokenizer(path, "/");
        StringBuilder stripped = new StringBuilder(path.length());
        if (path.charAt(0) == '/') {
            stripped.append('/');
        }
        while(st.hasMoreTokens()) {
            String segment = st.nextToken();
            scIndex = segment.indexOf(';');
            if (scIndex >= 0) {
                segment = segment.substring(0, scIndex);
            }
            stripped.append(segment).append('/');
        }
        // Remove the trailing slash if the original path didn't have one
        if (path.charAt(path.length() - 1) != '/') {
            stripped.deleteCharAt(stripped.length() - 1);
        }
        return stripped.toString();
    }

Poznámka ke kódu: Díky za upozornění Ariel na chybu v mém původním kódu pro ořez. Svůj původní kód jsem nahradil lepším, který jsem vybral přímo ze Springu, takže by již měl být v pořádku.

Všechno bych pochopil, co ale nechápu je, proč při nekompatibilních změnách tohoto typu autoři Tomcatu nepoužijí druhé verzovací číslo, aby upozornili na to, že tam může čekat vývojáře nějaký problém. V patchi podobné změny podle mého názoru nikdo nečeká. Určitě by se vyhnuli podobným "hate postům", který jsem právě teď stvořil já.

Fuj!