Tratando Exceções em uma Aplicação Web

Pessoal, boa noite. Tudo bem?

Vamos falar um pouco sobre como capturar exceções em uma aplicação web? Para aprender como criar uma aplicação web, visite esse tutorial: Criando um WebServer. Vamos usar esse projeto adicionando o tratamento de erros.

Situações inesperadas vão sempre acontecer, e pior ainda é quando problemas inesperados são exibidos ao cliente através mensagens que faz com que ele passe a ter certa desconfiança da funcionalidade. Esteja certo de que todo problema que ocorre a partir de então será “causado” pelo seu sistema na visão do usuário (mesmo sendo a internet que tenha caído).

OBS.: Nos artigos que costumo escrever não peço por tecnologia específica, mas por enquanto terei que fazer. Por favor, não usem o Intert Explorer (ou o browser do eclipse)! Ao final do artigo irei explicar o porquê desse pedido, pois esse pequeno detalhe me custou quase 120 minutos para descobrir o motivo do código não funcionar no IE (em pleno final de semana).

No código do projeto Servlet (Criando um WebServer) caso o usuário não digite um valor válido, utilizamos o seguinte código para tratar essa situação:

try {
    // I know this is ugly, but in a future we can do some refactor and apply some OO and Design principles
    value1 = Integer.parseInt((String) req.getParameter("valor1"));
} catch (NumberFormatException e) {
    // Catching this execption in case some invalid value was sent
    value1 = 0;
    warning = "We got some bad value(like blank or non numerics values, we set 0 instead";
}

Nesse exemplo quando acontece o erro um valor padrão para o usuário é passado, mas e quando essa situação não é possível? Existem situações em que quando uma exceção acontece temos que parar todo o processo e informar ao usuário que algo está errado. Existem também erros causados e que não são capturadas por nossos servlets, um exemplo disso é quando o usuário tenta acessar um recurso que não existe e um erro HTTP 404 é exibido em sua tela. Vamos tratar o erro do acesso indevido (404) e depois o erro nos dados (valor inválido digitado).

Vamos criar primeiro uma jsp para tratar nossos erros que ficará assim (crie a jsp dentro da pasta WebContent com o nome de “error.jsp”):

<%@ page isErrorPage="true" language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
        <title>Erro</title>;
    </head>
    <body>
        Ops, something happend! <br/>
        Error code: ${pageContext.errorData.statusCode}
    </body>
</html>

A atribuição isErrorPage permite que tenhamos acesso ao objeto do exception. O objeto errorData é criado pelo container e colocado no pageContext de modo automático. Basta configurar o “web.xml” para que o servidor possa encaminhar o erro obtido até a jsp.

O nosso arquivo web.xml terá as seguintes linhas a mais:

<error-page>
    <error-code>404</error-code>
    <location>/error.jsp</location>
</error-page>

Com a tag configuramos o código do erro e qual o seu destino. Quando nosso usuário digitar um endereço inválido, ele será direcionado para nossa JSP de erro.

Para testar, inicie o container e depois basta digitar um endereço inválido apontando para dentro da nossa aplicação, como por exemplo:

http://localhost:8080/Servlet/ASD

Não existe a action ASD mapeada em nosso arquivo web.xml com isso ao invés de ser exibida a mensagem HTTP 404, será exibida nossa jsp responsável por tratar os erros. Veja como essa simples configuração conseguiu evitar uma mensagem que o usuário não iria entender nada.

Outro modo de tratar exceções pode ser direcionar o erro gerado para um servlet onde poderemos trata-los. O nosso código gera uma exceção quando tentamos realizar a soma sem digitar valor algum e na primeira versão desse programa, colocávamos um valor padrão para a variável, mas agora, iremos exibir uma página de erro onde o usuário verá que é necessária a entrada dos dados numéricos.

try {
    valor1 = Integer.parseInt((String) req.getParameter("valor1"));
} catch (NumberFormatException e) {
    throw new NumberFormatException("No numbers were typed");
}

Veja como nosso código ficou, não estamos tratando mais a exceção, mas estamos informando para o container que algo aconteceu e é para que ele trate essa exceção.

Mas essa alteração, ainda não foi mapeada no arquivo “web.xml”.

<servlet>
    <servlet-name>errorServlet</servlet-name>
    <servlet-class>com.ErrorController</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>errorServlet</servlet-name>
    <url-pattern>/errorServlet</url-pattern>
</servlet-mapping>
<error-page>
    <exception-type>java.lang.NumberFormatException</exception-type>
    <location>/errorServlet</location>
</error-page>

Mapeamos o servlet que irá tratar o erro, e também informamos que todo erro do tipo NumberFormatException será tratado pelo nosso servlet. Poderíamos trambém declarar que ele irá capturar uma java.lang.Exception que toda e qualquer exceção seria tratada por nosso servlet.

Precisamos agora criar nosso servlet para tratar as exceções.

package com;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class ServletError
 */
public class ErrorController extends HttpServlet {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public void doPost(HttpServletRequest req, HttpServletResponse res)    throws ServletException, IOException{
        Throwable throwable = (Throwable) req.getAttribute("javax.servlet.error.exception");
        Integer statusCode = (Integer) req.getAttribute("javax.servlet.error.status_code");

        req.setAttribute("errorType", throwable);
        req.setAttribute("statusCode", statusCode);

        RequestDispatcher requestDispatcher = req.getRequestDispatcher("/error.jsp");
        requestDispatcher.forward(req, res);
    }

    public void doGet(HttpServletRequest req, HttpServletResponse res)    throws ServletException, IOException{
        doPost(req, res);
    }   
}

Vamos realizar uma pequena alteração em nossa JSP de erro como bonus? A partir de agora ela irá exibir também o tipo do erro:

<%@ page isErrorPage="true" language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
        <title>Erro</title>
    </head>
    <body>
        Ops, something happend! <br/>
        Error code: ${statusCode} <br/>
        The error type is: ${errorType.message}
        <br/>
    </body>
</html>

Foi alterado também o código do erro para ler o valor colocado no request em nossa Servlet, e na página estamos exibindo o conteúdo do objeto que foi criado de modo automático pelo container.

Inicie o Tomcat e mande realizar a soma sem ter número algum e o nosso container irá entender que houve uma exceção e irá redirecionar para o servlet que irá tratar o erro e finalizar a requisição na página de erro.
Utilizando um servlet para tratar de nossos erros podemos realizar diversas outras funções que não seria possível fazer apenas direcionando diretamente para a JSP. Podemos por exemplo, gravar no log da aplicação, enviar email para os responsáveis, etc.

Vou falar um pouco agora, do porque pedi a você para não usar o IE (Internet Explorer). Quando uma exceção acontece, o container altera o valor de uma variável chamada SC_INTERNAL_SERVER_ERROR, com isso o IE nota que esse cara teve seu valor alterado e passa a tratá-lo de um modo próprio que no caso, ignora o tratamento que foi dado no arquivo “web.xml”. Tente executar nossa aplicação agora pelo IE e note como ele acaba por tratar a exceção.

A primeira solução seria alterar uma configuração dentro do próprio IE. Ela fica em:

Opções da Internet > Avançadas > "Mostrar mensagens de erro http amigáveis"
Ou IE em inglês:
Internet Options > Advanced > "Show Friendly HTTP Error Messages"

Desmarque a opção, salve suas alterações, feche o IE e abra novamente e faça o mesmo teste. Perceba que agora ele já exibe a mensagem que desejávamos. Mas, como iríamos pedir que todo usuário de nosso sistema fizesse essa alteração? Imagine que um usuário iria pela primeira vez em nosso site, mas ele acabou por digitar o endereço errado, como ele iria ficar sabendo dessa opção?

A segunda solução, é alterar o status do objeto “response”. Quando alteramos esse status o IE passa a aceitar que não houve nenhum erro e nossa página será exibida normalmente.

req.setAttribute("statusCode", statusCode);

res.setStatus(HttpServletResponse.SC_OK); 
// We have to remove the exception from the request. If we do not remove the IE9 will not display out error page.
// IE9 will think that some error created a crash inside our application if we do not remove the exception from the request.
req.setAttribute("javax.servlet.error.exception", null);
req.setAttribute("javax.servlet.error.status_code", null);

RequestDispatcher requestDispatcher = req.getRequestDispatcher("/error.jsp");

Realmente, essa parte do IE me tomou boa parte do fim de semana e inclusive durante a semana conversei muito com meu chefe sobre isso e só depois de mais pesquisa que encontrei esse assunto de modo mais detalhado.

Espero ter ajudado. Qualquer dúvida basta falar.

10 thoughts on “Tratando Exceções em uma Aplicação Web

  1. Ótimo post! No caso do JSF, ao tentar acessar uma página que não existe na aplicação, ocorre o erro abaixo:
    http://localhost:8080/View/qweqwe -> mostra o erro 404 na navegador.

    se tentar com:
    http://localhost:8080/View/qweqwe.xhtm -> Ocorre um erro 500.
    14:15:30,314 ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[default-host].[/View].[Faces Servlet]] (http–127.0.0.1-8080-1) Servlet.service() for servlet Faces Servlet threw exception: com.sun.faces.context.FacesFileNotFoundException: /qweqwe.xhtml Not Found in ExternalContext as a Resource
    at com.sun.faces.facelets.impl.DefaultFaceletFactory.resolveURL(DefaultFaceletFactory.java:232) [jsf-impl-2.1.7-jbossorg-2.jar:]
    at com.sun.faces.facelets.impl.DefaultFaceletFactory.resolveURL(DefaultFaceletFactory.java:273) [jsf-impl-2.1.7-jbossorg-2.jar:]
    at com.sun.faces.facelets.impl.DefaultFaceletFactory.getMetadataFacelet(DefaultFaceletFactory.java:209) [jsf-impl-2.1.7-jbossorg-2.jar:]
    at com.sun.faces.application.view.ViewMetadataImpl.createMetadataView(ViewMetadataImpl.java:114) [jsf-impl-2.1.7-jbossorg-2.jar:]
    at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:233) [jsf-impl-2.1.7-jbossorg-2.jar:]
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101) [jsf-impl-2.1.7-jbossorg-2.jar:]
    at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:116) [jsf-impl-2.1.7-jbossorg-2.jar:]
    at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118) [jsf-impl-2.1.7-jbossorg-2.jar:]
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:593) [jboss-jsf-api_2.1_spec-2.0.1.Final.jar:2.0.1.Final]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:329) [jbossweb-7.0.13.Final.jar:]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:248) [jbossweb-7.0.13.Final.jar:]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:275) [jbossweb-7.0.13.Final.jar:]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:161) [jbossweb-7.0.13.Final.jar:]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:397) [jbossweb-7.0.13.Final.jar:]
    at org.jboss.as.jpa.interceptor.WebNonTxEmCloserValve.invoke(WebNonTxEmCloserValve.java:50) [jboss-as-jpa-7.1.1.Final.jar:7.1.1.Final]
    at org.jboss.as.web.security.SecurityContextAssociationValve.invoke(SecurityContextAssociationValve.java:153) [jboss-as-web-7.1.1.Final.jar:7.1.1.Final]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:155) [jbossweb-7.0.13.Final.jar:]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) [jbossweb-7.0.13.Final.jar:]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) [jbossweb-7.0.13.Final.jar:]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:368) [jbossweb-7.0.13.Final.jar:]
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:877) [jbossweb-7.0.13.Final.jar:]
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:671) [jbossweb-7.0.13.Final.jar:]
    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:930) [jbossweb-7.0.13.Final.jar:]
    at java.lang.Thread.run(Unknown Source) [rt.jar:1.6.0_32]

    Como posso resolver isso?

    • Olá Donizete, boa tarde.

      Você deve implementar todo o tipo de erro, ou então criar servlet que receba todos os erros.

      Ao utilizar xhtm é um extensão que não está sendo reconhecida pela sua apliação, o que irá gerar um outro código de erro e não 404.

  2. Muito bacana, parabens.
    Estava sofrendo aqui tentando rodar a mensagem de erro no IE e não dava. Nesse tópico as dúvidas foram esclarecidas.
    Obrigado

  3. Mais um ótimo artigo, poderia ter descoberto esse site anteriormente. O material é do excelente qualidade, a tempos procurava um bom material sobre Java web icluindo JSF. E encontrei!
    Parabéns e obrigado por disceminar seu conhecimento!

Leave a Comment