Filter Assincrono com VRaptor

104 views
Skip to first unread message

Guilherme Alves

unread,
Jun 3, 2015, 10:34:49 PM6/3/15
to caelum-...@googlegroups.com
Estou tentando fazer com que o Filter do VRaptor, faça requisições assíncronas. Implementando dessa maneira, o request é realmente assíncrono?

@WebFilter(filterName = "vraptor", urlPatterns = "/*", dispatcherTypes =
   {
       DispatcherType.FORWARD, DispatcherType.REQUEST
}, asyncSupported = true)
public class VRaptor implements Filter
{
   
    public static final String VERSION = "4.2.0-RC3-SNAPSHOT";

    private final Logger logger = getLogger(VRaptor.class);

    private ServletContext servletContext;

    @Inject
    private StaticContentHandler staticHandler;

    @Inject
    private EncodingHandler encodingHandler;

    @Inject
    private Event<VRaptorInitialized> initializedEvent;

    @Inject
    private Event<RequestStarted> requestStartedEvent;

    @Inject
    private RequestStartedFactory requestStartedFactory;

    @Inject
    private CDIRequestFactories cdiRequestFactories;

    @Override
    public void init(FilterConfig cfg) throws ServletException
    {
        servletContext = cfg.getServletContext();

        validateJavaEE7Environment();
        validateIfCdiIsFound();
        warnIfBeansXmlIsNotFound();

        initializedEvent.fire(new VRaptorInitialized(servletContext));

        logger.info("VRaptor {} successfuly initialized", VERSION);
    }

    @Override
    public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException,
                                                                                                              ServletException
    {
        validateServletEnvironment(req, res);

        final HttpServletRequest baseRequest = (HttpServletRequest) req;
        final HttpServletResponse baseResponse = (HttpServletResponse) res;

        if (isWebsocketRequest(baseRequest))
        {
            chain.doFilter(req, res);
            return;
        }
        else if (staticHandler.requestingStaticFile(baseRequest))
        {
            staticHandler.deferProcessingToContainer(chain, baseRequest, baseResponse);
            return;
        }

        logger.trace("VRaptor received a new request {}", req);

        if (!baseRequest.isAsyncSupported())
        {
            fireEvents(baseRequest, baseResponse, chain);
            return;
        }

        final AsyncContext async = !baseRequest.isAsyncStarted()
                                   ? baseRequest.startAsync(baseRequest, baseResponse)
                                   : baseRequest.getAsyncContext();

        async.start(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    fireEvents(baseRequest, baseResponse, chain);
                    async.complete();
                }
                catch (ApplicationLogicException e)
                {
                    // it is a business logic exception, we dont need to show
                    // all interceptors stack trace
                    async.dispatch(servletContext, servletContext.getContextPath());
                    throw new RuntimeException(e.getMessage(), e.getCause());
                }
            }
        });

        logger.debug("VRaptor ended the request");
    }

    private void fireEvents(final HttpServletRequest baseRequest, final HttpServletResponse baseResponse, final FilterChain chain)
    {
        encodingHandler.setEncoding(baseRequest, baseResponse);
        RequestStarted requestStarted = requestStartedFactory.createEvent(baseRequest, baseResponse, chain);
        cdiRequestFactories.setRequest(requestStarted);
        requestStartedEvent.fire(requestStarted);
    }

    @Override
    public void destroy()
    {
        servletContext = null;
    }

    private void validateServletEnvironment(ServletRequest req, ServletResponse res) throws ServletException
    {
        if (!(req instanceof HttpServletRequest) || !(res instanceof HttpServletResponse))
        {
            throw new ServletException("VRaptor must be run inside a Servlet environment. Portlets and others aren't supported.");
        }
    }

    private void warnIfBeansXmlIsNotFound() throws ServletException
    {

        URL webInfFile = getResource("/WEB-INF/beans.xml");
        URL metaInfFile = getResource("/WEB-INF/classes/META-INF/beans.xml");

        if (webInfFile == null && metaInfFile == null)
        {
            logger.warn("A beans.xml isn't found. Check if is properly located at "
                    + "/WEB-INF/beans.xml or /WEB-INF/classes/META-INF/beans.xml");
        }
    }

    private URL getResource(String path) throws ServletException
    {
        try
        {
            return servletContext.getResource(path);
        }
        catch (MalformedURLException e)
        {
            logger.error("Something went wrong when trying to locate a beans.xml file", e);
            return null;
        }
    }

    private void validateJavaEE7Environment() throws ServletException
    {
        try
        {
            servletContext.getJspConfigDescriptor(); // check servlet 3
            Priority.class.toString(); // check CDI 1.1
        }
        catch (NoClassDefFoundError | java.lang.NoSuchMethodError e)
        {
            throw new ServletException("VRaptor only runs under Java EE 7 environment or Servlet Containers that "
                    + "supports Servlets 3 with CDI 1.1 jars.");
        }
    }

    private void validateIfCdiIsFound() throws ServletException
    {
        if (staticHandler == null)
        {
            throw new ServletException("Dependencies were not set. Do you have a Weld/CDI listener setup in your web.xml?");
        }
    }

    /**
     * According to the Websocket spec (https://tools.ietf.org/html/rfc6455):
     * The WebSocket Protocol 5. The request MUST contain an |Upgrade| header
     * field whose value MUST include the "websocket" keyword.
     */
    private boolean isWebsocketRequest(HttpServletRequest request)
    {
        String upgradeHeader = request.getHeader("Upgrade");
        return upgradeHeader != null && upgradeHeader.toLowerCase().contains("websocket");
    }

}

Lucas Cavalcanti

unread,
Jun 7, 2015, 11:19:46 PM6/7/15
to caelum-vraptor
Teoricamente sim. Se ele entrou no Runnable e a request terminou sem problemas é pq está funcionando de forma assíncrona.

[]'s
Lucas Cavalcanti
@lucascs

--
Você recebeu essa mensagem porque está inscrito no grupo "caelum-vraptor" dos Grupos do Google.
Para cancelar inscrição nesse grupo e parar de receber e-mails dele, envie um e-mail para caelum-vrapto...@googlegroups.com.
Para postar nesse grupo, envie um e-mail para caelum-...@googlegroups.com.
Acesse esse grupo em http://groups.google.com/group/caelum-vraptor.
Para mais opções, acesse https://groups.google.com/d/optout.

Guilherme Alves

unread,
Jun 8, 2015, 12:36:03 PM6/8/15
to caelum-...@googlegroups.com
Não sei como faço para testar um método assíncrono de maneira eficiente, mas vou testar pelo apache bench e ver se a performance melhorou ou se está consumindo menos memória.
Obrigado pela resposta Lucas.

Abraços...

Chico Sokol

unread,
Jun 9, 2015, 9:07:02 AM6/9/15
to caelum-...@googlegroups.com
Uma coisa importante quando estiver trabalhando com async servlet é fazer o IO http assíncrono também, o que não é tão simples. Tem um post legal do fundador do jetty sobre isso: https://webtide.com/servlet-3-1-async-io-and-jetty/

--

Fabrício Cabral

unread,
Jun 9, 2015, 10:19:31 AM6/9/15
to caelum-vraptor
Perguntinha interessante:

alguém aí que esteja usando o Jetty, arrisca fazer um teste no Undertow[1] e nos
contar como foi? Alguma coisa me diz [2] que talvez houvesse uma diferença de
desempenho interessante...

At.te.

--
--fx

Guilherme Alves

unread,
Jun 9, 2015, 1:57:40 PM6/9/15
to caelum-...@googlegroups.com
Estou estudando o vert.x, acho que é uma opção viável também, pois posso integrar ele com outras aplicações.
Se tiverem interesse, está dado a ideia.

Att,

Guilherme. 

Guilherme Alves

unread,
Jun 24, 2015, 11:20:18 PM6/24/15
to caelum-...@googlegroups.com
Boa noite pessoal,

Testei mais a fundo a implementação que coloquei de inicio, e é ineficiente\lenta, resumindo, não funciona.
Alterei a implementação de algumas classes da seguinte maneira:

No VRaptorRequest adicionei o método:

public AsyncContext getReadyAsync() {
     final AsyncContext async = !isAsyncStarted()? startAsync() : getAsyncContext();
     return async;
}

Na interface HttpResult adicionei o método a ser implementado:

HttpResult body(String body, AsyncContext async);

E no DefaultHttpResult adicionei a implementação assíncrona do método:

@Override  
public HttpResult body(final String body, final AsyncContext async) {
    try {
                final ServletOutputStream output = response.getOutputStream();
                output.setWriteListener(new WriteListener()
                {
                    @Override
                    public void onWritePossible() throws IOException
                    {
                        if (output.isReady())
                        {
                            output.print(body);
                            async.complete();
                        }
                    }

                    @Override
                    public void onError(Throwable error)
                    {
                        error.printStackTrace();
                        async.complete();
                    }
                });
    } catch (IOException e) {
        throw new ResultException("Couldn't write to response body", e);
    }
    return this;
}

A classe que usei como teste foi está abaixo.O problema é que a função de escrita assincrona não é nem executada,
alguém tem idéia do que pode estar acontecendo? Cheguei a debugar e o método onWritePossible não é nem chamado.
Obrigado.

@Get("hospede/teste")
public void teste(VRaptorRequest req)
{
    AsyncContext async = req.getReadyAsync();
    System.out.println("Iniciado de maneira assincrona!");
    executor.submit(new Runnable()
    {
        @Override
        public void run()
        {
            result.use(Results.http()).body("<h1>Async TESTE</h1>", async);
        }
    });
}

Quando utilizo o código "async.start(new Runnable(){ public void run(){ /*implementação*/ }});",
se executado mais de uma vez, dá erro no CDI de RequestScoped, então utilizei um ExecutorService que é
proporcionado pela classe abaixo.

@ApplicationScoped
public class AsyncExecutorProducer
{
    private final ExecutorService executor;
   
    public AsyncExecutorProducer()
    {
        this.executor = Executors.newCachedThreadPool();
    }
   
    @Produces
    public ExecutorService getAsyncExecutor()
    {
        return executor;
    }
}

Obrigado,

Att,

Guilherme.

Raphael Almeida

unread,
Jun 25, 2015, 1:18:40 AM6/25/15
to caelum-...@googlegroups.com

VC tem esse código no git para acompanhar melhor e poder contribuir?

Tb estou querendo fazer algo do tipo, pensei em algumas possibilidades, entre elas remover a dependência forte do CDI e com isso remover as limitações impostas por ele e uma outra abordagem tentar prover algum suporte pra async no CDI.

Olhando o código do controller, acho q seria melhor o contexto async ser controlado por alguma anotação ou tipo, no caso retornando um CompletionStage.

Raphael Almeida

unread,
Jun 25, 2015, 1:33:52 AM6/25/15
to caelum-...@googlegroups.com

Complementando o pq, seria pra q toda a stack do VRaptor pudesse ser async e non-blocking, permitindo que a execução do controller fosse um Future q iria compor o resultado final, evitando um blocking ao final pra devolver o resultado.

Mas ainda não sei bem como chegar nesse resultado, olhando spec da servlet não sei se conseguiríamos garantir isso no VRaptor, parece q só na servlet 4 teria algo pra isso. Talvez usando websockets ou definindo um server base pra garantir o objetivo.

Guilherme Alves

unread,
Jun 25, 2015, 10:14:56 AM6/25/15
to caelum-...@googlegroups.com
No caso do CDI, eu tentei procurar uma maneira de fazer isso, porém, o método Async executa só uma vez, depois não funciona mais.
O código do que foi implementado, está no link.
Eu estudei esses locais para implementar:
https://weblogs.java.net/blog/swchan2/archive/2013/04/16/non-blocking-io-servlet-31-example
https://www.youtube.com/watch?v=mg04YoiCSmk
https://www.youtube.com/watch?v=uGXsnB2S_vc     //<- melhor vídeo que achei, explica bem.
Obs.:E sobre o VRaptor ser completamente async, eu acho uma ótima ideia.

Att,

Guilherme.

Guilherme Alves

unread,
Jun 25, 2015, 11:25:52 PM6/25/15
to caelum-...@googlegroups.com
Bom, estudei novamente o Servlet 3.1 e consegui fazer funcionar.
O WriteListener nunca era chamado pois não era notificado pelo ReadListener no onAllDataRead(),
devido a isso o método nunca era executado.
O seguinte código funciona e executa, porém vou tentar melhorar a interface do mesmo para que fique fácil a utilização.
Obs.: Estou aberto a sugestões.


@Get("hospede/teste")
public void teste(VRaptorRequest req)
{
    AsyncContext async = req.getReadyAsync();
    System.out.println("Iniciado de maneira assincrona!");

    try
    {
        ServletInputStream input = req.getInputStream();
        input.setReadListener(new ReadListener()
        {

            @Override
            public void onDataAvailable() throws IOException
            {
                //Os métodos abaixo devem ser chamados nesta ordem
                while (input.isReady() && !input.isFinished())
                {
                    int length = input.read("<h1>Async TESTE</h1>".getBytes());
                    if (-1 == length)
                    {
                        async.complete();
                        return;
                    }
                }
            }

            //Depois que toda a data foi lida, ai sim pode ser chamado o método de escrita
            @Override
            public void onAllDataRead() throws IOException

            {
                result.use(Results.http()).body("<h1>Async TESTE</h1>", async);
            }

            @Override
            public void onError(Throwable throwable)
            {
                System.out.println(throwable);
                async.complete();
            }
        });
    }
    catch (IOException ex)
    {
        System.out.println(ex);

    }
}

@Override
public HttpResult body(final String body, final AsyncContext async) {
    try {
                final ServletOutputStream output = response.getOutputStream();
                output.setWriteListener(new WriteListener()
                {
                    @Override
                    public void onWritePossible() throws IOException
                    {
                        //Só pode escrever o write, se o isReady retornar true

                        if (output.isReady())
                        {
                            output.print(body);
                            async.complete();
                        }
                    }

                    @Override
                    public void onError(Throwable error)
                    {
                        error.printStackTrace();
                        async.complete();
                    }
                });
    } catch (IOException e) {
        throw new ResultException("Couldn't write to response body", e);
    }
    return this;
}
   
@Override
public HttpResult body(final InputStream body, final AsyncContext async) {

    try {
                final ServletOutputStream output = response.getOutputStream();
                output.setWriteListener(new WriteListener()
                {
                    @Override
                    public void onWritePossible() throws IOException
                    {
                        byte[] buffer = new byte[4024];
                        //A cada isReady() verdadeiro, o método write pode ser executado só uma vez
                        while (output.isReady())
                        {
                            int dataRead = body.read(buffer);
                            if (-1 == dataRead)
                            {
                                async.complete();
                                break;
                            }
                            output.write(buffer);

Raphael Almeida

unread,
Jun 26, 2015, 12:25:45 AM6/26/15
to caelum-...@googlegroups.com
Opa bacana vou dar uma olhada.

--

Renato Resende Ribeiro de Oliveira

unread,
Jun 26, 2015, 9:19:23 AM6/26/15
to caelum-...@googlegroups.com
Cria um fork e um branch pra essa impl e compartilha aqui com a gente.
Abs.
--
Renato Resende Ribeiro de Oliveira
CTO - ProGolden Soluções Tecnológicas
MSc - Computer Science - Universidade Federal de Lavras

Skype: renatorro.comp.ufla.br
ICQ: 669012672
Phone: +55 (31) 9823-9631

Conheça o PrêmioIdeia - Inovação Colaborativa na sua empresa!

Guilherme Alves

unread,
Jun 26, 2015, 12:54:45 PM6/26/15
to caelum-...@googlegroups.com
Boa tarde,


Abraços.

Renato Resende Ribeiro de Oliveira

unread,
Jun 26, 2015, 12:55:44 PM6/26/15
to caelum-...@googlegroups.com
Boa

Raphael Almeida

unread,
Jun 26, 2015, 11:39:11 PM6/26/15
to caelum-...@googlegroups.com
Boa noite Guilherme,

Estava revisando os commits, parece que teve algum problema talvez com espaço vs tab, digo isso pq o diff aparece como várias classes foram alteradas por completo ao invés de apenas os trechos.

Teria como vc desfazer e refazer os commits para ficar mais fácil de entender as mudanças e depois para facilitar os PR?

Vc está usando qual server para ter certeza que será non blocking io? Eu vi que o Tomcat  tem uma forma diferente de fazer o async e daria problema com o CDI (What do WELD-000225, WELD-000335 and WELD-000715 warnings mean?), o Jetty ou Undertow seria uma boa. Digo isso para escrever os testes depois acho que será necessário.

Na versão 9.3 do Jetty foi melhorado a stack para non blocking e async.

Raphael Almeida

unread,
Jun 27, 2015, 12:16:38 AM6/27/15
to caelum-...@googlegroups.com
Acabei de ver o video do JavaOne de Servlet Async IO do pessoal do Jetty, bem bacana as explicações.

Pelo jeito vai ser bem difícil de testar isso... estou estudando como fizeram no Play2-war-plugin pra ver se tem algo que nos ajude.

Guilherme Alves

unread,
Jun 27, 2015, 8:43:01 PM6/27/15
to caelum-...@googlegroups.com
Na verdade essa diferença é mais sobre formatação do código mesmo, o espaçamento de texto do netbeans é diferente do eclipse.
As classes TemplateAsyncReadListener e TemplateAsyncWriteListener são implementações dos seus respectivos Listeners, onde seguem a ideia do padrão de projeto Template.
Não tenho certeza ainda, mas o ReadListener e o WriteListener só servem pra upload e download respectivamente.

Rubens Saraiva Nogueira

unread,
Jun 27, 2015, 9:48:02 PM6/27/15
to caelum-...@googlegroups.com
Fala pessoal!

Uma dica pra que o GitHub ignore os white spaces, é adicionar "?w=1" na URL.

Abraço
Rubens

Reply all
Reply to author
Forward
0 new messages