Solicitudes de mapeo para Netty

Érase una vez en un proyecto muy, muy lejano ... necesitaba hacer el procesamiento de solicitudes http en Netty . Desafortunadamente, no había mecanismos convenientes estándar para mapear las solicitudes http en Netty (y este marco no es para eso), por lo tanto, se decidió implementar nuestro propio mecanismo.


Si el lector comenzó a preocuparse por el destino del proyecto, entonces no vale la pena; En el futuro, se decidió reescribir el servicio web en un marco más preciso para los servicios RESTful, sin utilizar sus propias bicicletas. Pero los logros se mantuvieron y pueden ser útiles para alguien, por lo que me gustaría compartirlos.

Netty es un marco para desarrollar aplicaciones de red de alto rendimiento. Puede leer más al respecto en el sitio web del proyecto.
Netty proporciona una funcionalidad muy conveniente para crear servidores de socket, pero en mi opinión, esta funcionalidad no es muy conveniente para crear servidores REST.


Procesamiento de solicitudes utilizando el motor estándar Netty


Para procesar solicitudes en Netty, debe heredar de la clase ChannelInboundHandlerAdapter y anular el método channelRead .


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

Para obtener la información necesaria para procesar solicitudes http, el objeto msg se puede convertir a HttpRequest .


 HttpRequest request = (HttpRequest) msg; 

Después de eso, puede obtener cualquier información de esta solicitud. Por ejemplo, la URL de la solicitud.


 String uri = request.uri(); 

Tipo de solicitud


 HttpMethod httpMethod = request.method(); 

Y el contenido.


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

El contenido puede ser, por ejemplo, json pasado en el cuerpo de la solicitud POST . ByteBuf es una clase de la biblioteca Netty , por lo que es poco probable que los analizadores json trabajen con ella, pero se puede convertir fácilmente en una cadena.


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

Eso, en general, es todo. Usando los métodos anteriores, puede procesar solicitudes http. Es cierto que todo tendrá que ser procesado en un solo lugar, es decir, en el método channelRead . Incluso si separamos la lógica de procesamiento de solicitudes en diferentes métodos y clases, aún debe asignar la URL a estos métodos en algún lugar en un solo lugar.


Solicitud de mapeo


Bueno, como puede ver, es posible implementar la asignación de solicitudes http utilizando la funcionalidad estándar de Netty . Es cierto que no será muy conveniente. Me gustaría separar de alguna manera completamente el procesamiento de solicitudes http por diferentes métodos (por ejemplo, como se hace en Spring ). Con la ayuda de la reflexión, se intentó implementar un enfoque similar. La biblioteca numérica resultó de esto. Su código fuente se puede encontrar aquí .
Para usar la asignación de solicitudes usando la biblioteca num , es suficiente heredar de la clase AbstractHttpMappingHandler , después de lo cual puede crear métodos de manejo de solicitudes en esta clase. El principal requisito para estos métodos es que devuelvan FullHttpResponse o sus descendientes. Puede mostrar por qué solicitud http se llamará este método usando anotaciones:


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

El nombre de la anotación indica a qué tipo de solicitud se llamará. Se admiten cuatro tipos de solicitudes: GET , POST , PUT y DELETE . Como parámetro de value en la anotación, debe especificar la URL, cuando se accede, se llamará al método deseado.


Un ejemplo de cómo se ve el controlador de GET , que devuelve la cadena 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 solicitud


El paso de parámetros de la solicitud al método del controlador también se realiza mediante anotaciones. Para hacer esto, use una de las siguientes anotaciones:


  • @PathParam
  • @QueryParam
  • @RequestBody

@PathParam


Para pasar parámetros de ruta, se @PathParam anotación @PathParam . Cuando lo use como parámetro de value de la anotación, debe especificar el nombre del parámetro. Además, el nombre del parámetro también debe especificarse en la URL de solicitud.


Un ejemplo de cómo se verá el controlador de GET en el que se pasa el parámetro de ruta de acceso id y que este parámetro devuelve


 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 pasar los parámetros de consulta, se @QueryParam anotación @QueryParam . Cuando lo use como parámetro de value de la anotación, debe especificar el nombre del parámetro. El enlace de parámetros se puede controlar utilizando el parámetro de anotación required .


Un ejemplo de cómo se verá el controlador de GET , al que message pasa el message parámetro de consulta y que devuelve este 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 el cuerpo de las solicitudes POST , se @RequestBody anotación @RequestBody . Por lo tanto, está permitido usarlo solo en solicitudes POST . Se supone que los datos en formato json se transmitirán como el cuerpo de la solicitud. Por lo tanto, para usar @RequestBody es necesario pasar la implementación de la interfaz JsonParser al constructor de la clase de controlador, que se ocupará de analizar los datos del cuerpo de la solicitud. Además, la biblioteca ya tiene una implementación predeterminada de JsonParserDefault . Esta implementación usa a jackson como analizador.


Un ejemplo de cómo se verá un controlador de solicitud POST en el que hay un cuerpo de solicitud.


 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)); } } 

La clase de Message es la siguiente.


 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; } } 

Manejo de errores


Si durante el procesamiento de las solicitudes se produce una Exception y no se interceptará en el código de los métodos del controlador, se devolverá la respuesta con el código 500. Para escribir algo de lógica para el manejo de errores, es suficiente redefinir el método exceptionCaught en la clase de controlador.


 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)); } } 

Conclusión


Eso, en general, es todo. Espero que esto haya sido interesante y sea útil para alguien.




El código de ejemplo del servidor Netty http que usa la biblioteca num está disponible aquí .

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


All Articles