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 .