Nesse artigo vamos combinar algumas soluções muito simples para construir uma aplicação web rodando em um servidor web Jetty configurado programaticamente, que faça controle de entrada das requisições através de uma fila e ainda seja possível acompanhar isso através de uma interface de monitoramento bem simples.

jetty
Figura 1. Jetty

1ª receita: Iniciando e finalizando o Jetty programaticamente

Para quem não conhece, o Jetty é um servidor HTTP e container Servlet inteiramente implementado em Java free e open source já com vários anos de mercado e uma infinidade de versões. Ele hoje é mantido pelo Eclipse Foundation e sua principal característica é a leveza e a facilidade de embutir ele em outros containers, principalmente OSGI. Outro fator que chama atenção é a sua API fácil e sua modularidade, o que permite criarmos servidores extremamente leves. Hoje o Jetty já suporta inclusive o SPDY e Websocket.

No nosso exemplo, utilizamos o Jetty servidor programaticamente e o Jetty cliente para fazer a requisição de teste ao nosso pequeno servidor. Vejamos o código na Listagem 1.


URL indexUrl = DelayQueueTest.class.getResource("/web-app/index.html");
String absolutePath = new File(indexUrl.toURI()).getParent();
System.out.println(absolutePath);

org.eclipse.jetty.server.Server server = new org.eclipse.jetty.server.Server(5678);
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
contextHandler.setContextPath("/");
contextHandler.addServlet(new ServletHolder(new MetricsServlet()), "/metrics");
contextHandler.addServlet(new ServletHolder(new SimpleThroughputControllerServlet()), "/service");

ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setResourceBase(absolutePath);

HandlerList handlerList = new HandlerList();
handlerList.setHandlers(new Handler[] { contextHandler, resourceHandler });
server.setHandler(handlerList);
server.start();
Listagem 1. Exemplo de servidor Jetty

Na Listagem 1 vemos algumas coisas interessantes. Primeiro, estamos servindo conteúdo estático de dentro das pastas do nosso projeto no Eclipse, o que é bem útil, pois deixa nosso teste mais autônomo e robusto.

Em seguida iniciamos o servidor Jetty na porta 5678 e configuramos dois “ContextHandlers”. O primeiro, ServletContextHandler é responsável por dar suporte a servlets. Já o ResourceHandler, dá suporte para que seja servido conteúdo estático.

Passamos finalmente essa lista de Handlers para o servidor e chamamos o método “start()”. Pronto, já está no ar.

2ª Receita: Cliente HTTP utilizando a API do Jetty

Além de servidor, podemos utilizar a o Jetty como cliente HTTP. Vejamos como isso pode ser feito na Listagem 2.


HttpClient client = new HttpClient();
client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
client.start();
HttpExchange exchange = new HttpExchange();
exchange.setMethod("GET");
exchange.setURL("http://localhost:5678/service?id=4321");
client.send(exchange);
int state = exchange.waitForDone();
Listagem 2. Cliente HTTP usando api do Jetty

O código é muito simples: instanciamos a classe HttpClient e depois passamos para ela um HttpExchange, fazendo uma chamada GET no endereço do nosso servidor Http. Depois disso, esperamos sincronamente pela resposta chamando no statement “Exchange.waitForDone()”.

3ª Receita: Controlando o Throughput através de uma DelayQueue

No nosso exemplo, toda requisição é processada no método doGet na servlet br.com.devmedia.delayqueue.SimpleThroughputControllerServlet. Vemos que toda requisição é repassada para o nosso gerenciador de filas “QueueManager”. O nosso gerenciador de filas controla uma instância de fila do tipo DelayQueue. Uma DelayQueue serve objetos que implementam a interface java.util.concurrent.Delayed. O interessante é que essa implementação de fila só permite que sejam consumidos elementos da fila que estejam expirados. O tempo de expiração é calculado no método getDelay() da classe DelayEvent.

Logo, represar requisições ou seja, limitar o throughput do consumidor dessa fila é simples: basta que seja calculado um tempo de expiração para cada instância de DelayedEvent que entra na fila. O consumidor receberá objetos da fila à medida que eles forem expirando. Simples! Vejamos como isso é na Listagem 3.


DelayedEvent event = new DelayedEvent(id, delay(), TimeUnit.SECONDS);
public synchronized Long delay() {
  if (messageCounter.get() % 2 == 0) {
    messageCounter.set(1);
    return delay.incrementAndGet();
  } else {
    messageCounter.incrementAndGet();
    return delay.get();
  }
}
Listagem 3. Implementando a interface Delayed

4ª Receita: Monitorando o que está acontecendo

Finalmente, sabemos que construir uma boa solução também compreende ter meios de se recuperar de falhas e monitorar o que está acontecendo com o ambiente. Para isso, no nosso exemplo utilizamos uma API muito simples que descreve alguns conceitos primitivos como contadores, histogramas, métricas e assim por diante. Essa API é a Metric e maiores detalhes podem ser encontrados no site http://metrics.codahale.com/.

No nosso exemplo, as métricas expostas pela nossa aplicação estão sendo consumidas em uma página criada com o framework Bootstrap do Twitter como na Figura 2.

Console de acompanhamento
Figura 2. Console de acompanhamento

Essa página simplesmente renderiza as informações que estão vindo do endereço http://localhost:5678/metrics. Acesse essa URL e você verá que várias informações estão sendo dispostas em formato JSON. Maiores informações sobre o belo Bootstrap você pode encontrar em http://twitter.github.com/bootstrap/.

Conclusão

Atualmente existem várias ferramentas e APIs interessantes prontas para usarmos imediatamente nas nossas aplicações. Para conhecermos o que existe por aí, o único critério realmente necessário é termos curiosidade.