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:
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 .