Download binárního souboru přes HTTPS a Internet Explorer

Jsou chyby malé, velké, závažné i triviální, úsměvné, spletité i velmi hloupé. Z celého pokolení chyb je tahle velmi, velmi stará a také dost hloupá. A vypadá to, že z úcty k jejímu věku, ji nechá M$ už pokojně dožít spolu s chatrčí zvanou Internet Explorer.

Na chybu narazíte tehdy, když coby Java programátor napíšete servlet, který vrací binární data přes protokol HTTPS (např. vygenerovaný MS Excel jako já, nebo třeba PDF atd.). Aniž byste to explicitně nastavili do HttpResponse, bude vrácená odpověď (pravděpodobně) obsahovat v hlavičce tyto údaje:


Pragma: no-cache
Cache-Control: no-cache

O to se vám postará váš web server (v mém případě Tomcat i Apache zároveň). Internet Explorer se však k vrácené binární odpovědi s těmito hlavičkami obrátí zády a lakonicky vás informuje: "Aplikace Internet Explorer nemůže otevřít tento server v síti Internet. Požadovaný server není k dispozici nebo jej nelze najít. Akci zopakujte později."

Tato chybka je dle některých zdrojů v Exploreru už od verze 4.0 (byť záznam v knowledge base Microsoftu tvrdí, je reportovaná až u IE 6.0 SP1) a přetrvává až do současné verze 7.0, což jsem si měl bohužel možnost vyzkoušet na vlastní kůži.

Zobrazená chyba v Internet Exploreru

Naštěstí za ta léta již existují střípky rad, jak tuto chybku překonat. V mém případě bylo řešení složitější, jelikož v používáme dva web servery v kombinaci - Apache a Tomcat komunikující spolu protokolem AJP13. V mém případě totiž ony "no-cache" informace přidávaly do hlaviček oba dva tyto servery.

Řešením v mém případě tedy bylo:

1. v servletu definovat pouze tyto hlavičky:


public class XlsExportServlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			response.setContentType("application/vnd.ms-excel");
			response.setHeader("Content-Disposition", "attachment; filename=callMeExport.xls" );						
			//writeBinaryOutput(response.getOutputStream());
			}
		}
		catch(Exception e) {
			if(log.isFatalEnabled()) {
				log.fatal("Error during processing request!", e);
			}
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
		}
		finally {
			response.flushBuffer();
			response.getOutputStream().close();
		}
	}
}

2. v Contextu v Tomcatu vypnout proxy, která dodává "no-cache" informaci:

Dle všech informací by no-cache do hlavičky měl Tomcat přidávat pouze v okamžiku, kdy máte na dané url nakonfigurovanou autentizaci přes webový kontejner. V mém případě jsem měl v Tomcatu nakonfigurovanou exploded aplikaci přímo v server.xml:


<Host name="www.nesmysl.cz" appBase="/www/test-secure/" unpackWARs="false">
	<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="www_access_log." suffix=".txt" pattern="common"/>
	<Context path="/srv/www" docBase="_deploy/webapp">
		<Valve className="org.apache.catalina.authenticator.BasicAuthenticator"  disableProxyCaching="false" />
		<Realm className="org.apache.catalina.realm.MemoryRealm" pathname="/www/test-secure/etc/tomcat-users.xml"/>
	</Context>
	<Context path="/srv/manager" privileged="true" docBase="/usr/local/tomcat/server/webapps/manager"/>
</Host>

Nicméně v případě waru by mělo postačovat vložení následující konfigurace do souboru /META-INF/context.xml :


<Context>
	<Valve className="org.apache.catalina.authenticator.BasicAuthenticator"  disableProxyCaching="false" />
</Context>

Použijte samozřejmě správnou implementaci Authenticatoru, kterou vaše aplikace používá - na výběr máte:


BasicAuthenticator, DigestAuthenticator, FormAuthenticator, NonLoginAuthenticator, SSLAuthenticator

3. v Apachi předefinovat makro pro konkrétní URL, které nastaví hlavičky jak je třeba:



     Header set Pragma public
     Header set Cache-control max-age=0

Závěrem

Po restartu Tomcatu a Apache se mi podařilo stahovat a otevírat binární soubor i v Internet Exploreru. Toto řešení fungovalo v mém případě - to že to pomůže i Vám samozřejmě nemůžu zaručit. Nicméně dalo to docela práci tenhle problém rozlousknout, takže si myslím, že stojí jen na blogu zaznamenat pro příští generace :). Jen ještě jednu radu na závěr - pokud si nejste jistí, co vám server posílá za hlavičky, zkuste využít funkcí pluginu Firebug pro Firefox - ten hlavičky přehledně zobrazí:

Firebug snapshot

Bez něj bych nikdy nepřišel na to, že přestože můj servlet do hlaviček zapisuje do hlaviček informace o cachování, Tomcat s Apachem je vesele přenastavují po svém.

Související odkazy