JSF 404 statt 500 bei nicht existenter JSF-Seite

xehpuk

Top Contributor
Hi,

ich nutze GlassFish 3.1.2 mit Mojarra 2.1.6.

Ruft man eine nicht existente JSF-Seite auf (etwa http://localhost:8080/Test/.jsf), tritt serverseitig eine Exception auf und es wird HTTP 500 zurückgegeben:
Java:
Schwerwiegend: PWC6117: File "…\Test\build\web\.jsp" not found
Warnung: StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception
java.lang.NullPointerException
	at com.sun.faces.context.flash.ELFlash.doLastPhaseActions(ELFlash.java:607)
	at com.sun.faces.context.ExternalContextImpl.responseFlushBuffer(ExternalContextImpl.java:857)
	at com.sun.faces.application.view.JspViewHandlingStrategy.buildView(JspViewHandlingStrategy.java:155)
	at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:100)
	at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
	at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
	at javax.faces.webapp.FacesServlet.service(FacesServlet.java:594)
	at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1550)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:281)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
	at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:655)
	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:595)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:161)
	at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:331)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:231)
	at com.sun.enterprise.v3.services.impl.ContainerMapper$AdapterCallable.call(ContainerMapper.java:317)
	at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:195)
	at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:860)
	at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:757)
	at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1056)
	at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:229)
	at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)
	at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)
	at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)
	at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)
	at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54)
	at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59)
	at com.sun.grizzly.ContextTask.run(ContextTask.java:71)
	at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532)
	at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513)
	at java.lang.Thread.run(Thread.java:722)
  1. Kann man verhindern, dass diese Exception fliegt?
  2. Wenn nicht, wie macht man aus dieser speziellen Exception ein HTTP 404 (sodass auf die ggf. vorhandene Fehlerseite weitergeleitet wird)?
Sowas in der web.xml wäre ja Quatsch (zumal es ein HTTP 500 bleibt):
[XML]<error-page>
<exception-type>java.lang.NullPointerException</exception-type>
<location>/error/404.jsf</location>
</error-page>[/XML]

Die gleiche Frage habe ich auch auf Stackoverflow gefunden, diese blieb jedoch unbeantwortet.
 
Ich konnte jetzt nicht herausfinden, was ein ExceptionListener ist.

Mittlerweile denke ich, dass es sich hierbei um einen Bug handelt. Bei einem neuen Projekt tritt dieser Fehler nicht auf. Es wird dort korrekterweise die Standardfehlerseite für 404 angezeigt.

Folgendermaßen lässt sich der Fehler reproduzieren:

Ein neues JSF-Projekt erstellen.

Eine JSF-Seite index.xhtml erstellen:
[XML]<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<head>
<title>Index</title>
</head>
<body>
<h😱utputText value="#{requestScope['javax.servlet.error.request_uri']}"/>
</body>
</html>[/XML]
Die individuelle 404-Fehlerseite in der web.xml eintragen:
[XML]<?xml version='1.0' encoding='UTF-8'?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsf</welcome-file>
</welcome-file-list>
<error-page>
<error-code>404</error-code>
<location>/index.jsf</location>
</error-page>
</web-app>[/XML]
Irgendeine URL mit der Endung .jsf (außer natürlich index.jsf) anfordern.

Der im Eingangsbeitrag genannte Fehler tritt auf. Beim Aufruf nicht existenter Seiten (ohne Endung .jsf) wird die individuelle Fehlerseite korrekt angezeigt.

Ich forsche da noch ein wenig weiter.
 
Kenne mich mit JSF überhaupt nicht aus, aber wenn ich mir das so anschaue, muss es am FacesServlet liegen. Bei allen anderen URLs findet der Webcontainer keine passende Resource und antwortet korrekt mit 404 Not Found.

Alles mit *.jsf wird wegen des das servlet-mapping in der web.xml ans FacesServlet geleitet. Dieses findet nun keine passende Resource und antwortet mit 500 Internal Server Error. Aber JSF ist ja sooo schlecht nicht. Ich bin sicher, dass man das auch irgendwie konfigurieren kann. Aber kenne mich wie gesagt nicht damit aus. Das deswegen nur als Tipp, in welcher Richtung Du suchen könntest.
 
Es ist normal, dass Mojara eine 500er-Meldung schmeißt, jedoch sollte aber eigentlich eine FacesFileNotFoundException geworfen werden.

Komischerweise läuft bei mir mit dem mapping auf /faces/* alles wie erwartet. Ändere ich mein mapping auf *.jsf, wie du es hast, dann hab ich das gleiche Problem und die gleiche merkwürdige Fehlermeldung, die du da bekommst.

Wenn du mit dem alternativen mapping leben kannst, dann kannst du die "richtige 500er"-Meldung natürlich abfangen und auf deine 404er-page umleiten. Ich sehe nicht wirklich einen Grund der dagegen spricht ... mal davon abgesehen, dass ich auch keine andere Lösung kenne.

HTML:
<error-page>
  <exception-type>java.io.FileNotFoundException</exception-type>
  <location>....</location>
</error-page>
 
Bin auch Laie und eigne mir das Zeug nach und nach an. Wusste daher nun auch wenig damit anzufangen, aber bei mir verstärkt sich doch stark der Gedanke, dass es sich hier um einen Bug handeln muss.

Ich finde dieses alternative Mapping ziemlich hässlich, daher würde ich gern darauf verzichten. Darum teste ich damit erst mal nicht weiter.

Der Fehler scheint nicht bei Mojarra, sondern bei GlassFish zu liegen. Mit Tomcat und Mojarra funktioniert es (liefert brav 404 auch bei der Endung .jsf).

Ich habe den Stacktrace anhand des Sourcecodes ein wenig zurückverfolgt:
Java:
public class ELFlash extends Flash {
    // ...
    public void doLastPhaseActions(FacesContext context, boolean outgoingResponseIsRedirect) {
        ExternalContext extContext = context.getExternalContext(); // <--- NPE, also context == null
        // ...
    }
    // ...
}
Java:
public class ExternalContextImpl extends ExternalContext {
    // ...
    public void responseFlushBuffer() throws IOException {
        getELFlash().doLastPhaseActions(FacesContext.getCurrentInstance() /* == null */, false);
        // ...
    }
    // ...
}
Java:
public abstract class FacesContext {
    // ...
    public static FacesContext getCurrentInstance() {
        FacesContext facesContext = instance.get(); // == null
        if (null == facesContext) {
            facesContext = (FacesContext) threadInitContext.get(Thread.currentThread()); // == null
        }
        return facesContext; // == null
    }
    // ...
}
Von der Materie habe ich wie gesagt nicht viel Ahnung, aber gehe nun davon aus, dass es die Aufgabe des Servers ist, dort immer eine Instanz von FacesContext bereitzustellen.

Ich durchforste nachher mal den Bugtracker von GlassFish. Vielleicht werde ich ja fündig. Für mich wäre das jetzt schon Grund genug, es mit einem anderen Server zu probieren.
 
Okay, mein Hackaround sieht dann so aus:

GlassFishNullPointerExceptionHandler.java
Java:
import java.util.Iterator;
import javax.faces.FacesException;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerWrapper;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.ExceptionQueuedEventContext;

public class GlassFishNullPointerExceptionHandler extends ExceptionHandlerWrapper {
	private final ExceptionHandler wrapped;

	public GlassFishNullPointerExceptionHandler(final ExceptionHandler wrapped) {
		this.wrapped = wrapped;
	}

	@Override
	public ExceptionHandler getWrapped() {
		return wrapped;
	}

	@Override
	public void handle() throws FacesException {
		for (final Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator(); i.hasNext();) {
			final ExceptionQueuedEvent event = i.next();
			final ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
			final Throwable t = context.getException();
			if (t instanceof NullPointerException) {
				final StackTraceElement[] stackTrace = t.getStackTrace();
				final StackTraceElement lastCall = stackTrace[0];
				if ("com.sun.faces.context.flash.ELFlash".equals(lastCall.getClassName()) &&
						"doLastPhaseActions".equals(lastCall.getMethodName())) {
					i.remove(); // Suck it!
				}
			}
		}
		getWrapped().handle();
	}
}
GlassFishNullPointerExceptionHandlerFactory.java
Java:
import javax.faces.context.ExceptionHandlerFactory;

public class GlassFishNullPointerExceptionHandlerFactory extends ExceptionHandlerFactory {
	private final ExceptionHandlerFactory parent;

	public GlassFishNullPointerExceptionHandlerFactory(final ExceptionHandlerFactory parent) {
		this.parent = parent;
	}

	@Override
	public ExceptionHandlerFactory getWrapped() {
		return parent;
	}

	@Override
	public GlassFishNullPointerExceptionHandler getExceptionHandler() {
		return new GlassFishNullPointerExceptionHandler(getWrapped().getExceptionHandler());
	}
}
faces-config.xml
[XML]<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.1"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd">
<factory>
<exception-handler-factory>
[packages]GlassFishNullPointerExceptionHandlerFactory
</exception-handler-factory>
</factory>
</faces-config>[/XML]
Eine unglaublich edle Lösung! 😀

Die individuelle 404-Fehlerseite wird nun mit korrektem HTTP-Code zurückgegeben.
Ändert natürlich nichts daran, dass das totaler Käse ist. Den Bugtracker muss ich noch immer konsultieren …
 

Neue Themen


Zurück
Oben