Once upon a time in one far-distant ... project, it took me to do the processing of http requests for Netty . Unfortunately, there was no standard convenient mechanism for mapping http requests in Netty (and this framework was not for that), therefore, it was decided to implement its own mechanism.
If the reader began to worry about the fate of the project, then you should not, everything is fine with him, because In the future, it was decided to rewrite the web service on a framework more focused on RESTful services, without using our own bicycles. But the developments have remained, and they can be useful to someone, so I would like to share them.
Netty is a framework that allows you to develop high-performance network applications. More information about him can be found on the project website .
Netty provides very convenient functionality for creating socket servers, but in my opinion, this functionality is not very convenient for creating REST servers.
To process requests in Netty, you must inherit from the ChannelInboundHandlerAdapter
class and override the channelRead
method.
public class HttpMappingHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { } }
To obtain the necessary information in processing HTTP requests, the msg
object can be brought to HttpRequest
.
HttpRequest request = (HttpRequest) msg;
After that, you can get any information from this request. For example, the URL of the request.
String uri = request.uri();
Type of request.
HttpMethod httpMethod = request.method();
And content.
ByteBuf byteBuf = ((HttpContent) request).content();
The content may be, for example, json transmitted in the body of a POST
request. ByteBuf
is a class from the Netty library, so json parsers are unlikely to be able to work with it, but it can very easily be converted to a string.
String content = byteBuf.toString(StandardCharsets.UTF_8);
Here, in general, that's all. Using the above methods, you can handle http requests. True, everything will have to be processed in one place, namely in the channelRead
method. Even if you spread the logic of processing requests for different methods and classes, you still have to match the URL with these methods somewhere in the same place.
Well, as you can see, it is quite possible to implement the mapping of http requests using standard Netty functionality. True, it will not be very convenient. I would like to somehow distribute the processing of http-requests for different methods (for example, as is done in Spring ). Using reflection, an attempt was made to implement a similar approach. The library num has turned out from it. Its source code can be found at the link .
To use query mapping using the num library, it is enough to inherit from the AbstractHttpMappingHandler
class, after which you can create query handler methods in this class. The main requirement for these methods is that they return FullHttpResponse
or its heirs. You can show which http-request this method will be called with using annotations:
@Get
@Post
@Put
@Delete
The annotation name indicates what type of query will be invoked. Four types of requests are supported: GET
, POST
, PUT
and DELETE
. As the value
parameter in the annotation, you must specify the URL address for which the desired method will be called.
An example of what a GET
request will look like that returns the 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)); } }
Passing parameters from the request to the handler method is also performed using annotations. To do this, you can use one of the following annotations:
@PathParam
@QueryParam
@RequestBody
To pass path parameters, the @PathParam
annotation is @PathParam
. When using it as the value
parameter of the annotation, you must specify the name of the parameter. In addition, the name of the parameter must be specified in the request URL.
An example of how a GET
request will look like, to which the path-parameter id
passed and which returns this parameter.
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)); } }
To pass query parameters, the @QueryParam
annotation is @QueryParam
. When using it as the value
parameter of the annotation, you must specify the name of the parameter. The parameter binding can be controlled using the required
annotation parameter.
An example of how a GET
-query handler will look like, to which the query-parameter message
passed and which returns this parameter.
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)); } }
To send the body of POST
requests, the @RequestBody
annotation is @RequestBody
. Therefore, it is allowed to use it only in POST
requests. It is assumed that the data in the json format will be transmitted as the request body. Therefore, to use @RequestBody
it is necessary to transfer the implementation of the JsonParser
interface to the constructor of the handler class, which will be engaged in parsing data from the request body. Also in the library there is already a default implementation of JsonParserDefault
. This implementation uses jackson
as a parser.
An example of what a POST
request handler will look like with a request body.
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)); } }
The Message
class looks like this.
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; } }
If any Exception
occurs during the processing of requests and it is not intercepted in the code of the handler methods, the answer with the 500th code will be returned. In order to write some kind of error-handling logic, it suffices to override the exceptionCaught
method in the handler class.
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)); } }
Here, in general, that's all. I hope it was interesting and will be useful to someone.
The code of the example http-server on Netty using the num library is available here .
Source: https://habr.com/ru/post/435864/
All Articles