Fehlerseiten – Error Pages

Fehlerseiten werden bei einem Projekt oft als nebensächlich angesehen, da sie nicht die normale Benützung einer Webseite widerspiegeln.

Dennoch sind sie für eine grössere Webapplikation unabdingbar und oft hat der Kunde auch genaue Vorstellungen, wie Fehlerseiten auszusehen haben und welche Funktionen sie einem Benutzer bietet soll.

Die folgenden Beispiele beschränken sich auf 404 Fehlerseiten, die Art der Fehlerseite spielt allerdings keine Rolle, die Möglichkeiten gelten für andere Fehlercodes genauso.

Magnolia bietet von sich aus grundsätzlich zwei verschiedene Bordmittel, Fehlerseiten zu konfigurieren.

Statische Fehlerseiten

Eine statische Fehlerseite kann bei der Verwendung eines Tomcat Servers über die web.xml Datei konfiguriert werden, in einer Manier wie man sie auch von anderen Tomcat Applikationen her kennt; als statisch konfigurierte Location:

web.xml:

<error-page>
   <error-code>404</error-code>
   <location>/errorPages/404.html</location>
</error-page>

 

Der Vorteil dieser Art von Fehlerseite liegt in einer einfachen Konfiguration.

Der grösste und auch offensichtliche Nachteil ist dass diese eine Fehlerseite für eine ganze Instanz gilt, egal wieviele Webseiten betrieben werden.

Domain basierende Fehlerseiten

Oftmals genügt eine einzige statische Fehlerseite aber nicht, zum Beispiel für Publish Systeme auf denen mehrere Domains verwendet werden. Hier bietet Magnolia die Möglichkeit, mittels Virtual URI Mapping pro Domain eigene Fehlerseiten zu definieren und zu verwenden.
Hierfür werden in der web.xml Datei für die Fehlerseiten „virtuelle“ Pfade definiert welche über den Domain-based Virtual URI Mapping Filter auf einen Repository Pfad gemappt werden:

web.xml:

<!--
 Configuration
      + modules
        :
        + adminInterface
          :
          + virtualURIMapping
            :
            + my-error-forbidden
              + hosts
                - 1      .de=forward:/de/some/magnolia/forbidden/page
                - 2      .com=forward:/en/some/other/forbidden/page
              - class    info.magnolia.cms.beans.config.HostBasedVirtualURIMapping
              - fromURI  /my-virtual-uri-mapping/error-403
              - toURI    forward:/en/some/default/magnolia/forbidden/page
            + my-error-notfound
              + hosts
                - 1      .de=forward:/de/some/magnolia/notfound/page
                - 2      .com=forward:/en/some/other/notfound/page
              - class    info.magnolia.cms.beans.config.HostBasedVirtualURIMapping
              - fromURI  /my-virtual-uri-mapping/error-404
              - toURI    forward:/en/some/default/magnolia/notfound/page
-->
<error-page>
    <error-code>403</error-code>
    <location>/my-virtual-uri-mapping/error-403</location>
</error-page>
<error-page>
    <error-code>404</error-code>
    <location>/my-virtual-uri-mapping/error-404</location>
</error-page>

 

Diese Art der Konfiguration hat Vorteile:

  • Die Fehlerseiten können pro Domain konfiguriert werden, das erlaubt unterschiedliche Fehlerseiten für unterschiedliche Domains.
  • Die Fehlerseiten können mittels Magnolia Seiten abgebildet werden und gliedern sich somit gut in die restliche Seitenstruktur ein, sie können als normaler Content erfasst werden.

Der grosse Nachteil dieser Art der Konfiguration ist die Einschränkung das nur Domain basiert verschiedene Fehlerseiten erstellt werden können. Leider genügt das oftmals nicht.

Aus diesem Grund wurde von Namics eine weitere Art von Fehlerseiten implementiert.

Sprachbasierte Fehlerseiten

Sprachbasierte Fehlerseiten erlauben das konfigurieren von Fehlerseiten pro Webseite und pro Sprache. Das ist sehr nützlich in einer Webseite mit Multitree-Strategie.

Hier eine kurze Erklärung zur Multitree-Strategie:

Wenn eine Webseite mit der Multitree-Strategie betrieben wird, bildet jede Webseite ihre verschiedenen Sprachen als verschiedene Seitenäste ab – im Gegensatz zur direkten Übersetzung der Contentseiten wie es Magnolia standartmässig anbietet. Diese Art von Strategie wird verwendet wenn die Seitenstruktur zwischen den unterschiedlichen Sprachen teilweise unterschiedlich ausfallen kann. Ausserdem ist ein Author so nicht gezwungen, den gesamten Content einer Seite in alle Sprachen zu übersetzen.

Hier ein Beispiel einer solchen Konfiguration:

+ internet                      Root
  :
  + somedomain-com              Language Redirect
    :
    + en                        Home
      :
      + somepage                Content
        :
        + error_404             Content
    + de                        Home
      :
      + somepage                Content
        :
        + error_404             Content

 

Die Fehlerseiten sind auch in diesem Fall normale Magnolia Content Seiten, sie können frei unterhalb eines Sprach-Astes liegen und werden im Fehlerfall mittels eines custom Servlets aufgelöst.
Definiert werden die Fehlerseiten in den Page Properties des Home Template, als interner Link zur gewünschten Seite.

Um ein custom Servlet verwenden zu können wird zuerst das Mapping in der Modul Descriptor XML Datei des entsprechenden Magnolia Moduls konfiguriert:

module.xml

<servlets>
    <servlet>
        <name>Error404Servlet</name>
        <class>com.namics.mgnl.servlet.Error404Servlet</class>
        <comment>Servlet handling HTTP 404 errors. The webapp web.xml is configured to forward 404 errors to the defined mapping path.</comment>
        <mappings>
            <mapping>/error404.html</mapping>
        </mappings>
    </servlet>
    ...
<servlets>

 

In der web.xml Datei wird der 404 Fehler so konfiguriert das er auf das Mapping des Mangolia Moduls passt:

web.xml

<error-page>
    <error-code>404</error-code>
    <location>/error404.html</location>
</error-page>

 

Somit werden 404 Fehler auf /error404.html weitergeleitet, welches einem Mapping auf unser custom Servlet entspricht.

Das Sevlet selbst ist für die Auflösung der Fehlerseite ausgehend von der ursprünglich angeforderten Seite verantwortlich.
Die Auflösung und das Weiterleiten auf die Fehlerseite findet wiefolgt statt:

1. Auflösen der ursprünglichen Request-URI
2. Auflösen der Zielseite ausgehend von der Request-URI
3. Auflösen des Home Templates ausgehend von der Zielseite
4. Auflösen der Fehlerseite aufgrund der Konfiguration im Home Template
5. Forward zur Fehlerseite

Kann die Fehlerseite nicht aufgelöst werden wird eine Standardfehlermeldung in den Response Stream geschrieben um den Author darauf aufmerksam zu machen das keine Fehlerseite für die momentane Seite konfiguriert wurde.

Mit dieser Art von Fehlerseiten bleibt es den Authoren völlig freigestellt, ihre eigenen Fehlerseiten einfach und unkompliziert über das Home Template zu konfigurieren. Ausserdem kann pro Sprache eine eigene Fehlerseite angelegt werden. Die somit gewonnene Flexiblität hebt diese Lösung als die beste der drei vorgestellen Varianten hervor.

Zum Schluss folgt hier noch der Quellcode des Error Servlets. Zu beachten ist dass der Code nicht 1:1 verwendet werden kann da wir einige Custom Util Funktionen benutzen um zum Beispiel den Sprachknoten eines Website Pfades aufzulösen. Er gibt aber gute Anhaltspunkte wie es umgesetzt werden kann.

404ErrorServlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
public class Error404Servlet extends HttpServlet {

    private static final Logger log = LoggerFactory.getLogger(Error404Servlet.class);
    private static final String ERROR_PAGE_LINK_NODEDATA_NAME = "errorPageUUID";

    // this is the error message to display, if no error page is defined. B
    // Because IE does not display custom error pages, if they are smaller than 512 bytes,
    // there is some hidden content to get enough html code.
    private static final String DISPLAY_ERROR_MESSAGE = "" +
            "&lt;div style='color: red; margin-top: 200px; text-align:center'&gt;" +
            "&lt;h1&gt;CONFIGURATION ERROR&lt;/h1&gt;" +
            "&lt;h2&gt;This site has no error page assigned!&lt;/h2&gt;" +
            "&lt;p&gt;&lt;b&gt;Please check the following configurations:&lt;/b&gt;&lt;/p&gt;" +
            "&lt;ol&gt;" +
            "&lt;li&gt;Double check the site definition (website mapping, locales)&lt;/li&gt;" +
            "&lt;li&gt;Configure an error page in the 'Page Info' dialog in the home template of the target site.&lt;/li&gt;" +
            "&lt;li&gt;Retry to trigger the error page again.&lt;/li&gt;" +
            "&lt;/ol&gt;" +
            "&lt;/div&gt;" +
            "&lt;div style='display:none'&gt;" +
            "&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;" +
            "&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;" +
            "&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;" +
            "&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;" +
            "&lt;/div&gt;";

    private String aggregatedOriginalRequestUri;
    private Site resolvedSite;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String resolvedOriginalRequestUri = resolveOriginalRequestUri(req);
        log.debug("resolvedOriginalRequestUri = {}", resolvedOriginalRequestUri);

        //set the old url in the aggregation state.
        //this has the effect, that all site and language resolving logic will work with this uri.
        if (StringUtils.isNotEmpty(resolvedOriginalRequestUri)) {
            MgnlContext.getAggregationState().setCurrentURI(resolvedOriginalRequestUri);
            log.debug("resolvedOriginalRequestUri set as current uri in aggregation state {}.", resolvedOriginalRequestUri);

            aggregatedOriginalRequestUri = MgnlContext.getAggregationState().getCurrentURI();
            log.debug("aggregated URI is now: {}.", aggregatedOriginalRequestUri);
        }

        resolvedSite = resolveOriginalSite(req);
        log.debug("resolvedSite = {}.", resolvedSite);

        Content targetHomeTemplateNode = ResolveUtil.getLanguageNode(resolvedSite);
        log.debug("targetHomeTemplateNode = {}.", targetHomeTemplateNode);

        if (targetHomeTemplateNode != null) {
            //get the error page and forward the request or display a "configuration missing" error.
            String errorPageUuid = NodeDataUtil.getString(targetHomeTemplateNode, ERROR_PAGE_LINK_NODEDATA_NAME, "");
            log.debug("errorPageUuid = {}.", errorPageUuid);

            Content errorPage = MagnoliaTemplatingUtilities.getInstance().getContentByUUID(errorPageUuid);
            log.debug("errorPage = {}.", errorPage);

            boolean forwardSuccessful = ResolveUtil.forwardToContent(req, resp, errorPage);
            if (!forwardSuccessful) {
                displayError(resp);
            }
        }else{
            displayError(resp);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }

    private String resolveOriginalRequestUri(HttpServletRequest req) {
        //get the error uri: http://www.jsptutorial.org/content/errorHandling#innerLink4
        Object originalRequestURIObj = req.getAttribute("javax.servlet.error.request_uri");
        if (originalRequestURIObj instanceof String) {
            return (String) originalRequestURIObj;
        } else {
            log.warn("The Parameter 'javax.servlet.error.request_uri' was not found in ErrorServlet attributes. This means, that the error page was called directly, or the web container did not pass this parameter when triggering the error servlet.");
            return null;
        }
    }

    /**
     * Retrieves the Site of the original request that produced the 404 error.
     *
     * @param req the HttpServletRequest of the Servlet.
     * @return
     */

    private Site resolveOriginalSite(HttpServletRequest req) {
        if (aggregatedOriginalRequestUri != null) {
            String domain = req.getServerName();
            Site originalSite = NamicsUtil.getSiteManager().getAssignedSite(domain, aggregatedOriginalRequestUri);
            log.debug("Resolved original Site: {}", originalSite.toString());
            return originalSite;
        } else {
            /**
             * At least, we can retrieve the Site of the current request.
             * This only matches, if we surf on a domain, that is registered in a site def.
             * Otherwise, the default Site will be returned.
             */

            log.warn("Could not retrieve original URI. Trying to resolve the site by the current request...");
            return NamicsUtil.getStkFunctions().site();
        }
    }

    private void displayError(HttpServletResponse resp) {
        try {
            if (MagnoliaTemplatingUtilities.getInstance().isEditMode()) {
                resp.getWriter().append(DISPLAY_ERROR_MESSAGE);
                log.debug("displaying error message");
            }
        } catch (IOException e) {
            log.error("could not display displaying error message");
        }
    }
}

 

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

*

*

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>