web-dev-qa-db-fra.com

java.lang.IllegalStateException: getReader () a déjà été appelé pour cette requête

Je veux ajouter la journalisation à mon Servlet, j'ai donc créé un filtre qui devrait afficher la demande et aller au Servlet. Mais malheureusement, j'ai rencontré une exception:

Java.lang.IllegalStateException: getReader() has already been called for this request
    at org.Apache.catalina.connector.Request.getInputStream(Request.Java:948)
    at org.Apache.catalina.connector.RequestFacade.getInputStream(RequestFacade.Java:338)
    at com.noelios.restlet.ext.servlet.ServletCall.getRequestEntityStream(ServletCall.Java:190)

Donc, pour résoudre ce problème, j'ai trouvé une solution avec Wrapper, mais cela ne fonctionne pas. Que puis-je utiliser/changer de code? Des idées?

[MyHttpServletRequestWrapper]

public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper
{
    public MyHttpServletRequestWrapper(HttpServletRequest request)
    {
        super(request);
    }

    private String getBodyAsString()
    {
        StringBuffer buff = new StringBuffer();
        buff.append(" BODY_DATA START [ ");
        char[] charArr = new char[getContentLength()];
        try
        {
            BufferedReader reader = new BufferedReader(getReader());
            reader.read(charArr, 0, charArr.length);
            reader.close();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        buff.append(charArr);
        buff.append(" ] BODY_DATA END ");
        return buff.toString();
    }

    public String toString()
    {
        return getBodyAsString();
    }
}

[MyFilter]

public class MyFilterimplements Filter
{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException
    {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    {
        final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        final HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        final HttpServletRequestWrapper requestWrapper = new MyHttpServletRequestWrapper(httpServletRequest);
        final String requestBody = requestWrapper.toString();

        chain.doFilter(request, response);
    }
}
18
smas

On dirait que la structure de restlet a appelé getRequestEntityStream() sur l'objet Request qui appelle à son tour getInputStream(); l'appel à getReader() sur la demande renvoie donc IllegalStateException. La documentation de l'API Servlet pour getReader () et getInputStream () indique:

 public Java.io.BufferedReader getReader()
    ...
    ...
Throws:
    Java.lang.IllegalStateException - if getInputStream() method has been called on this request

 public ServletInputStream getInputStream()
    ...
    ...
    Throws:
    Java.lang.IllegalStateException - if the getReader() method has already been called for this request

D'après la documentation, il semble que nous ne pouvons pas appeler à la fois getReader () et getInputStream () sur l'objet Request. Je vous suggère d'utiliser getInputStream() plutôt que getReader() dans votre wrapper.

13
Suresh Kumar

Le problème principal est que vous ne pouvez pas lire l'entrée à la fois en tant que flux binaire et flux de caractères, même si l'une est appelée dans un filtre et l'autre dans le servlet. 

5
Kennet

Autant que je sache, les servlets sont fondamentalement brisés à cet égard. Vous pouvez essayer de contourner ce problème comme indiqué ici , mais cela pose d’autres problèmes mystérieux lorsque vous tentez de résoudre ce problème.

Effectivement, il suggère de cloner la demande, de lire le corps, puis, dans la classe clonée, de remplacer les méthodes getReader et getInputStream pour renvoyer les éléments déjà récupérés.

Le code que j'ai fini avec était ceci:

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import Java.io.*;

//this class stops reading the request payload twice causing an exception
public class WrappedRequest extends HttpServletRequestWrapper
{
    private String _body;
    private HttpServletRequest _request;

    public WrappedRequest(HttpServletRequest request) throws IOException
    {
        super(request);
        _request = request;

        _body = "";
        try (BufferedReader bufferedReader = request.getReader())
        {
            String line;
            while ((line = bufferedReader.readLine()) != null)
                _body += line;
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException
    {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(_body.getBytes());
        return new ServletInputStream()
        {
            public int read() throws IOException
            {
                return byteArrayInputStream.read();
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException
    {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
}

Quoi qu'il en soit, cela semblait bien fonctionner jusqu'à ce que nous réalisions que le téléchargement d'un fichier à partir du navigateur ne fonctionnait pas. J'ai traversé les changements et découvert que c'était le coupable.

Certaines personnes dans les commentaires de cet article indiquent que vous devez redéfinir les méthodes relatives aux paramètres, mais n'expliquent pas comment procéder. 

En conséquence, j'ai vérifié s'il y avait une différence entre les deux demandes. Cependant, après le clonage de la requête, il avait des ensembles de paramètres identiques (la requête initiale et la requête clonée n'en avaient aucun) ainsi qu'un ensemble identique d'en-têtes. 

Cependant, d’une manière ou d’une autre, la requête était exécutée et entravait sa compréhension un peu plus tard - dans mon cas, provoquant une erreur grave dans une bibliothèque (extdirectspring) où quelque chose essayait de lire le contenu en Json. Supprimer le code qui lisait le corps dans le filtre le faisait fonctionner à nouveau.

Mon code d'appel ressemblait à ceci:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
{
    HttpServletRequest properRequest = ((HttpServletRequest)request);

    String pathInfo = properRequest.getPathInfo();
    String target = "";
    if(pathInfo == null)
        pathInfo = "";

    if(pathInfo.equals("/router"))
    {
        //note this is because servlet requests hate you!
        //if you read their contents more than once then they throw an exception so we need to do some madness
        //to make this not the case
        WrappedRequest wrappedRequest = new WrappedRequest(properRequest);
        target = ParseExtDirectTargetFrom(wrappedRequest);
        request = wrappedRequest;
    }

    boolean callingSpecialResetMethod = pathInfo.equals("/resetErrorState") || target.equals("resetErrorState");
    if(_errorHandler.IsRejectingRequests() && !callingSpecialResetMethod)
        return;

    try {
        filterChain.doFilter(request, response);
    }
    catch (Exception exception) {
        ((HttpServletResponse) response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "ERROR");
        _errorHandler.NotifyOf(exception);
    }
}

J'ai omis le contenu de ParseExtDirectTargetFrom mais il appelle getReader ().

Dans mon cas, le filtre fonctionnait pour toutes les autres demandes, mais le comportement étrange dans ce cas m'a fait comprendre que quelque chose n'allait pas et que ce que j'essayais de faire (implémenter un comportement de gestion des exceptions raisonnable pour les tests) ne valait pas la peine de rompre de manière aléatoire. demandes futures (car je ne pouvais pas comprendre ce qui avait causé la rupture de la demande).

Il est également intéressant de noter que le code erroné est inévitable - j'ai supposé qu'il pourrait s'agir de quelque chose de printemps, mais ServletRequest va jusqu'au bout - c'est tout ce que vous obtenez, même si vous fabriquiez une servlet à partir de zéro en sous-classant HttpServlet

Ma recommandation serait la suivante - ne lisez pas le corps de la requête dans un filtre . Vous allez ouvrir une boîte de Pandore qui causera des problèmes étranges plus tard.

4
JonnyRaa

Utilisez ContentCachingRequestWrapper class. Envelopper HttpServletRequest dans cela résoudra le problème

0
Swarit Agarwal