Il était une fois dans un projet très, très loin ... , j'avais besoin de faire le traitement des requêtes http sur Netty . Malheureusement, il n'y avait pas de mécanismes standard standard pour mapper les requêtes http dans Netty (et ce cadre n'est pas du tout pour cela), par conséquent, il a été décidé de mettre en œuvre notre propre mécanisme.
Si le lecteur commence à s'inquiéter du sort du projet, alors ça ne vaut pas la peine, tout va bien pour lui, car à l'avenir, il a été décidé de réécrire le service Web sur un cadre plus affiné pour les services RESTful, sans utiliser vos propres vélos. Mais les réalisations sont restées, et elles peuvent être utiles à quelqu'un, donc je voudrais les partager.
Netty est un cadre de développement d'applications réseau hautes performances. Vous pouvez en savoir plus à ce sujet sur le site Web du projet.
Netty fournit des fonctionnalités très pratiques pour créer des serveurs socket, mais à mon avis, cette fonctionnalité n'est pas très pratique pour créer des serveurs REST.
Traitement des demandes à l'aide du moteur Netty standard
Pour traiter les demandes dans Netty, vous devez hériter de la classe ChannelInboundHandlerAdapter
et remplacer la méthode channelRead
.
public class HttpMappingHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { } }
Pour obtenir les informations nécessaires au traitement des requêtes http, l'objet msg
peut être HttpRequest
en HttpRequest
.
HttpRequest request = (HttpRequest) msg;
Après cela, vous pouvez obtenir toutes les informations de cette demande. Par exemple, l'URL de la demande.
String uri = request.uri();
Type de demande.
HttpMethod httpMethod = request.method();
Et le contenu.
ByteBuf byteBuf = ((HttpContent) request).content();
Le contenu peut être, par exemple, json passé dans le corps de la requête POST
. ByteBuf
est une classe de la bibliothèque Netty , il est donc peu probable que les analyseurs json fonctionnent avec elle, mais il peut très facilement être converti en chaîne.
String content = byteBuf.toString(StandardCharsets.UTF_8);
C'est, en général, tout. En utilisant les méthodes ci-dessus, vous pouvez traiter les requêtes http. Certes, tout devra être traité au même endroit, à savoir dans la méthode channelRead
. Même si nous séparons la logique de traitement des demandes en différentes méthodes et classes, vous devez toujours mapper l'URL à ces méthodes quelque part en un seul endroit.
Mappage de demande
Eh bien, comme vous pouvez le voir, il est possible d'implémenter le mappage des requêtes http en utilisant la fonctionnalité Netty standard. Certes, ce ne sera pas très pratique. Je voudrais en quelque sorte séparer complètement le traitement des requêtes http par différentes méthodes (par exemple, comme cela se fait au printemps ). A l'aide de la réflexion, une tentative a été faite pour mettre en œuvre une approche similaire. La bibliothèque num s'est avérée à partir de cela. Son code source se trouve ici .
Pour utiliser le mappage de demande à l'aide de la bibliothèque num , il suffit d'hériter de la classe AbstractHttpMappingHandler
, après quoi vous pouvez créer des méthodes de gestionnaire de demande dans cette classe. La principale exigence de ces méthodes est qu'elles retournent FullHttpResponse
ou ses descendants. Vous pouvez montrer par quelle requête http cette méthode sera appelée à l'aide d'annotations:
Le nom de l'annotation indique le type de demande qui sera appelé. Quatre types de demandes sont pris en charge: GET
, POST
, PUT
et DELETE
. En tant value
paramètre de value
dans l'annotation, vous devez spécifier l'URL, lorsque vous y accédez, la méthode souhaitée sera appelée.
Un exemple de ce à quoi ressemble le gestionnaire de GET
, qui renvoie la chaîne 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)); } }
Paramètres de demande
Le passage des paramètres de la requête à la méthode du gestionnaire s'effectue également à l'aide d'annotations. Pour ce faire, utilisez l'une des annotations suivantes:
@PathParam
@QueryParam
@RequestBody
@PathParam
Pour passer les paramètres de chemin, l'annotation @PathParam
est @PathParam
. Lorsque vous l'utilisez comme paramètre de value
de l'annotation, vous devez spécifier le nom du paramètre. De plus, le nom du paramètre doit également être spécifié dans l'URL de la demande.
Un exemple de la façon dont le gestionnaire de GET
recherchera dans lequel le paramètre id
path est passé et que ce paramètre renvoie.
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
Pour passer des paramètres de requête, l'annotation @QueryParam
est @QueryParam
. Lorsque vous l'utilisez comme paramètre de value
de l'annotation, vous devez spécifier le nom du paramètre. La liaison des paramètres peut être contrôlée à l'aide du paramètre d'annotation required
.
Un exemple de ce à quoi ressemblera le gestionnaire de GET
, auquel le message
paramètre de requête message
transmis et qui renvoie ce paramètre.
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
Pour transmettre le corps des requêtes POST
, l'annotation @RequestBody
est @RequestBody
. Par conséquent, il est autorisé à l'utiliser uniquement dans les requêtes POST
. Il est supposé que les données au format json seront transmises en tant que corps de demande. Par conséquent, pour utiliser @RequestBody
il est nécessaire de passer l'implémentation de l'interface JsonParser
au constructeur de la classe de gestionnaire, qui sera engagé dans l'analyse des données du corps de la demande. De plus, la bibliothèque a déjà une implémentation par défaut de JsonParserDefault
. Cette implémentation utilise jackson
comme analyseur.
Un exemple de ce à quoi ressemblera un gestionnaire de requêtes POST
dans lequel se trouve un corps de requête.
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 classe Message
est la suivante.
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; } }
Gestion des erreurs
Si, lors du traitement des demandes, une Exception
se produit et n'est pas interceptée dans le code des méthodes du gestionnaire, la réponse avec le 500e code sera retournée. Pour écrire une logique de gestion des erreurs, il suffit de redéfinir la méthode exceptionCaught
dans la classe du gestionnaire.
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)); } }
Conclusion
C'est, en général, tout. J'espère que cela a été intéressant et sera utile à quelqu'un.
Le code d'exemple du serveur http Netty utilisant la bibliothèque num est disponible ici .