Cliente HTTP Ratpack
1. Introdução
Nos últimos anos, testemunhamos o surgimento da maneira funcional e reativa de criar aplicativos em Java. O Ratpack oferece uma maneira de criar aplicativos HTTP nas mesmas linhas.
Como ele usa o Netty para suas necessidades de rede,it’s completely asynchronous and non-blocking. O Ratpack também fornece suporte para teste, fornecendo uma biblioteca de teste complementar.
Neste tutorial, examinaremos o uso do cliente Ratpack HTTP e componentes relacionados.
E ao fazer isso, vamos tentar levar nosso entendimento mais longe a partir do ponto de onde saímos no final de nossointroductory Ratpack tutorial.
2. Dependências do Maven
Para começar, vamos adicionar osRatpack dependencies necessários:
io.ratpack
ratpack-core
1.5.4
io.ratpack
ratpack-test
1.5.4
test
Curiosamente, precisamos apenas disso para criar e testar nosso aplicativo.
No entanto, sempre podemos optar por adicionar e estender usando outras bibliotecas Ratpack.
3. fundo
Antes de começarmos, vamos ver como as coisas são feitas nos aplicativos Ratpack.
3.1. Abordagem baseada em manipulador
O Ratpack usa uma abordagem baseada em manipulador para o processamento de solicitações. A ideia em si é bastante simples.
E, na sua forma mais simples, poderíamos ter cada manipulador atendendo solicitações em cada caminho específico:
public class FooHandler implements Handler {
@Override
public void handle(Context ctx) throws Exception {
ctx.getResponse().send("Hello Foo!");
}
}
3.2. Cadeia, registro e contexto
Handlers interact with the incoming request using a Context object. Por meio dele, obtemos acesso à solicitação e resposta HTTP e recursos para delegar a outros manipuladores.
Tomemos, por exemplo, o seguinte manipulador:
Handler allHandler = context -> {
Long id = Long.valueOf(context.getPathTokens().get("id"));
Employee employee = new Employee(id, "Mr", "NY");
context.next(Registry.single(Employee.class, employee));
};
Este manipulador é responsável por fazer algum pré-processamento, colocando o resultado no Registrye então delegando a solicitação aos outros manipuladores.
Through the use of the Registry, we can achieve inter-handler communication. O seguintehandler consulta o resultado calculado anteriormente deRegistry usando o tipo de objeto:
Handler empNameHandler = ctx -> {
Employee employee = ctx.get(Employee.class);
ctx.getResponse()
.send("Name of employee with ID " + employee.getId() + " is " + employee.getName());
};
Devemos ter em mente que, em um aplicativo de produção, teríamos esses manipuladores como classes separadas para melhor abstração, depuração e desenvolvimento de lógica de negócios elaborada.
Now we can use these handlers inside a Chain in order to create complex custom request processing pipelines.
Por exemplo:
Action chainAction = chain -> chain.prefix("employee/:id", empChain -> {
empChain.all(allHandler)
.get("name", empNameHandler)
.get("title", empTitleHandler);
});
Podemos levar essa abordagem mais adiante, compondo várias cadeias juntas usando o métodoinsert(..) emChaine tornar cada uma responsável por uma preocupação diferente.
O caso de teste a seguir mostra o uso dessas construções:
@Test
public void givenAnyUri_GetEmployeeFromSameRegistry() throws Exception {
EmbeddedApp.fromHandlers(chainAction)
.test(testHttpClient -> {
assertEquals("Name of employee with ID 1 is NY", testHttpClient.get("employee/1/name")
.getBody()
.getText());
assertEquals("Title of employee with ID 1 is Mr", testHttpClient.get("employee/1/title")
.getBody()
.getText());
});
}
Aqui, estamos usando a biblioteca de testes do Ratpack para testar nossa funcionalidade isoladamente e sem iniciar um servidor real.
4. HTTP com Ratpack
4.1. Trabalhando para a assincronia
O protocolo HTTP é de natureza síncrona. Consequentemente, na maioria das vezes, os aplicativos da web são síncronos e, portanto, estão bloqueando. Essa é uma abordagem extremamente intensiva em recursos, pois criamos um encadeamento para cada solicitação recebida.
Preferimos criar aplicativos não bloqueadores e assíncronos. Isso garantiria que precisamos apenas usar um pequeno pool de threads para lidar com solicitações.
4.2. Funções de retorno de chamada
When dealing with asynchronous API’s, we usually provide a callback function to the receiver so that the data can be returned to the caller. Em Java, isso geralmente assume a forma de classes internas anônimas e expressões lambda. Porém, conforme nosso aplicativo é escalado ou há várias chamadas assíncronas aninhadas, essa solução seria difícil de manter e mais difícil de depurar.
O Ratpack oferece uma solução elegante para lidar com essa complexidade na forma dePromises.
4.3. Ratpack Promises
Um RatpackPromise pode ser considerado semelhante a um objeto JavaFuture. It’s essentially a representation of a value which will become available later.
Podemos especificar um pipeline de operações que o valor passará assim que estiver disponível. Cada operação retornaria um novo objeto de promessa, uma versão transformada do objeto de promessa anterior.
Como podemos esperar, isso leva a poucas alternâncias de contexto entre threads e torna nosso aplicativo eficiente.
A seguir está uma implementação de manipulador que faz uso dePromise:
public class EmployeeHandler implements Handler {
@Override
public void handle(Context ctx) throws Exception {
EmployeeRepository repository = ctx.get(EmployeeRepository.class);
Long id = Long.valueOf(ctx.getPathTokens().get("id"));
Promise employeePromise = repository.findEmployeeById(id);
employeePromise.map(employee -> employee.getName())
.then(name -> ctx.getResponse()
.send(name));
}
}
Precisamos ter em mente que apromise is especially useful when we define what to do with the eventual value. Podemos fazer isso chamando a operação de terminalthen(Action) on it.
Se precisarmos enviar uma promessa, mas a fonte de dados for síncrona, ainda poderemos fazer isso:
@Test
public void givenSyncDataSource_GetDataFromPromise() throws Exception {
String value = ExecHarness.yieldSingle(execution -> Promise.sync(() -> "Foo"))
.getValueOrThrow();
assertEquals("Foo", value);
}
4.4. O cliente HTTP
O Ratpack fornece um cliente HTTP assíncrono, cuja instância pode ser recuperada no registro do servidor. No entanto,we’re encouraged to create and use alternative instances as the default one doesn’t use connection pooling and has quite conservative defaults.
Podemos criar uma instância usando o métodoof(Action) que toma como parâmetro umAction do tipoHttpClientSpec.
Usando isso, podemos ajustar nosso cliente às nossas preferências:
HttpClient httpClient = HttpClient.of(httpClientSpec -> {
httpClientSpec.poolSize(10)
.connectTimeout(Duration.of(60, ChronoUnit.SECONDS))
.maxContentLength(ServerConfig.DEFAULT_MAX_CONTENT_LENGTH)
.responseMaxChunkSize(16384)
.readTimeout(Duration.of(60, ChronoUnit.SECONDS))
.byteBufAllocator(PooledByteBufAllocator.DEFAULT);
});
Como podemos ter adivinhado por sua natureza assíncrona,HttpClient retorna um objetoPromise. Como resultado, podemos ter um pipeline complexo de operações de maneira não obstrutiva.
Para ilustração, vamos pedir a um cliente que ligue para nossoEmployeeHandler usando esteHttpClient:
public class RedirectHandler implements Handler {
@Override
public void handle(Context ctx) throws Exception {
HttpClient client = ctx.get(HttpClient.class);
URI uri = URI.create("http://localhost:5050/employee/1");
Promise responsePromise = client.get(uri);
responsePromise.map(response -> response.getBody()
.getText()
.toUpperCase())
.then(responseText -> ctx.getResponse()
.send(responseText));
}
}
Uma chamada rápida paracURL confirmaria que obtivemos uma resposta esperada:
curl http://localhost:5050/redirect
JANE DOE
5. Conclusão
Neste artigo, examinamos as construções de bibliotecas primárias disponíveis no Ratpack, que nos permitem desenvolver aplicativos da Web sem bloqueio e assíncronos.
Demos uma olhada no RatpackHttpClient e no sclassPromise que representa todas as coisas assíncronas no Ratpack. Também vimos como poderíamos facilmente testar nosso aplicativo HTTP usando oTestHttpClient que o acompanha.
E, como sempre, os trechos de código deste tutorial estão disponíveis em nossoGitHub repository.