Mapeando solicitações para Netty

Era uma vez em um projeto distante, distante ... eu precisava fazer o processamento de solicitações http no Netty . Infelizmente, não havia mecanismos convenientes padrão para mapear solicitações de HTTP no Netty (e essa estrutura não serve para isso), portanto, foi decidido implementar nosso próprio mecanismo.


Se o leitor começou a se preocupar com o destino do projeto, então não vale a pena, está tudo bem com ele, porque no futuro, foi decidido reescrever o serviço da web em uma estrutura mais aprimorada para serviços RESTful, sem usar suas próprias bicicletas. Mas as conquistas permaneceram e podem ser úteis para alguém, então eu gostaria de compartilhá-las.

Netty é uma estrutura para o desenvolvimento de aplicativos de rede de alto desempenho. Você pode ler mais sobre isso no site do projeto.
O Netty fornece funcionalidade muito conveniente para a criação de servidores de soquete, mas, para a criação de servidores REST, essa funcionalidade, na minha opinião, não é muito conveniente.


Solicitar processamento usando o mecanismo Netty padrão


Para processar solicitações no Netty, você precisa herdar da classe ChannelInboundHandlerAdapter e substituir o método channelRead .


 public class HttpMappingHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { } } 

Para obter as informações necessárias para o processamento de solicitações http, o objeto msg pode ser convertido em HttpRequest .


 HttpRequest request = (HttpRequest) msg; 

Depois disso, você pode obter qualquer informação dessa solicitação. Por exemplo, o URL da solicitação.


 String uri = request.uri(); 

Tipo de solicitação.


 HttpMethod httpMethod = request.method(); 

E o conteúdo.


 ByteBuf byteBuf = ((HttpContent) request).content(); 

O conteúdo pode ser, por exemplo, json passado no corpo da solicitação POST . ByteBuf é uma classe da biblioteca Netty , portanto, é improvável que os analisadores json trabalhem com ele, mas pode muito facilmente ser convertido em uma string.


 String content = byteBuf.toString(StandardCharsets.UTF_8); 

Isso, em geral, é tudo. Usando os métodos acima, você pode processar solicitações http. É verdade que tudo terá que ser processado em um só lugar, a saber, no método channelRead . Mesmo se separarmos a lógica do processamento de solicitações em diferentes métodos e classes, você ainda precisará mapear a URL para esses métodos em algum lugar do mesmo local.


Mapeamento de Solicitações


Bem, como você pode ver, é possível implementar o mapeamento de solicitações HTTP usando a funcionalidade Netty padrão. É verdade que não será muito conveniente. Gostaria de alguma forma separar completamente o processamento de solicitações http por métodos diferentes (por exemplo, como é feito no Spring ). Com a ajuda da reflexão, foi feita uma tentativa de implementar uma abordagem semelhante. A biblioteca num resultou disso. Seu código fonte pode ser encontrado aqui .
Para usar o mapeamento de solicitação usando a biblioteca num , basta herdar a classe AbstractHttpMappingHandler , após a qual você pode criar métodos de manipulador de solicitação nessa classe. O principal requisito para esses métodos é que eles retornem FullHttpResponse ou seus descendentes. Você pode mostrar por qual solicitação http este método será chamado usando anotações:


  • @Get
  • @Post
  • @Put
  • @Delete

O nome da anotação indica que tipo de solicitação será chamada. Quatro tipos de pedidos são suportados: GET , POST , PUT e DELETE . Como parâmetro de value na anotação, você deve especificar a URL, quando acessada, o método desejado será chamado.


Um exemplo da aparência do manipulador de GET , que retorna a string Hello, world! .


 public class HelloHttpHandler extends AbstractHttpMappingHandler { @Get("/test/get") public DefaultFullHttpResponse test() { return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK, Unpooled.copiedBuffer("Hello, world!", StandardCharsets.UTF_8)); } } 

Parâmetros de solicitação


A passagem de parâmetros da solicitação para o método manipulador também é realizada usando anotações. Para fazer isso, use uma das seguintes anotações:


  • @PathParam
  • @QueryParam
  • @RequestBody

@PathParam


Para passar parâmetros de caminho, a anotação @PathParam é @PathParam . Ao usá-lo como o parâmetro de value da anotação, você deve especificar o nome do parâmetro. Além disso, o nome do parâmetro também deve ser especificado no URL da solicitação.


Um exemplo de como o manipulador de GET examinará qual parâmetro do caminho do id é passado e qual esse parâmetro retorna.


 public class HelloHttpHandler extends AbstractHttpMappingHandler { @Get("/test/get/{id}") public DefaultFullHttpResponse test(@PathParam(value = "id") int id) { return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK, Unpooled.copiedBuffer(id, StandardCharsets.UTF_8)); } } 

@QueryParam


Para passar parâmetros de consulta, a anotação @QueryParam é @QueryParam . Ao usá-lo como o parâmetro de value da anotação, você deve especificar o nome do parâmetro. A ligação do parâmetro pode ser controlada usando o parâmetro de anotação required .


Um exemplo de como será o manipulador de GET , para o qual a message parâmetro de consulta message passada e que retorna esse parâmetro.


 public class HelloHttpHandler extends AbstractHttpMappingHandler { @Get("/test/get") public DefaultFullHttpResponse test(@QueryParam(value = "message") String message) { return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK, Unpooled.copiedBuffer(message, StandardCharsets.UTF_8)); } } 

@RequestBody


Para transmitir o corpo das solicitações POST , a anotação @RequestBody é @RequestBody . Portanto, é permitido usá-lo apenas em solicitações POST . Supõe-se que os dados no formato json serão transmitidos como o corpo da solicitação. Portanto, para usar @RequestBody é necessário passar a implementação da interface JsonParser para o construtor da classe manipulador, que estará envolvido na análise de dados do corpo da solicitação. Além disso, a biblioteca já possui uma implementação padrão do JsonParserDefault . Esta implementação usa jackson como um analisador.


Um exemplo da aparência de um manipulador de solicitação POST no qual existe um corpo de solicitação.


 public class HelloHttpHandler extends AbstractHttpMappingHandler { @Post("/test/post") public DefaultFullHttpResponse test(@RequestBody Message message) { return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK, Unpooled.copiedBuffer("{id: '" + message.getId() +"', msg: '" + message.getMessage() + "'}", StandardCharsets.UTF_8)); } } 

A classe Message é a seguinte.


 public class Message { private int id; private String message; public Message() { } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } 

Tratamento de erros


Se durante o processamento de solicitações ocorrer alguma Exception e ela não for interceptada no código dos métodos do manipulador, a resposta com o 500º código será retornada. Para escrever alguma lógica para o tratamento de erros, basta redefinir o método exceptionCaught na classe do manipulador.


 public class HelloHttpHandler extends AbstractHttpMappingHandler { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); ctx.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR)); } } 

Conclusão


Isso, em geral, é tudo. Espero que tenha sido interessante e útil para alguém.




O código de exemplo do servidor http Netty usando a biblioteca num está disponível aqui .

Source: https://habr.com/ru/post/pt435864/


All Articles