前言
今天花了点时间去了解了一下关于Netty中如何自己实现一个HTTP服务,踩了点坑,特地记录一下。
思路
这里不对HTTP流程进行讲述,对HTTP协议感兴趣的可以自行百度。
我们需要了解一下如下几个内容
HttpRequestDecoder & HttpResponseEncoder & HttpServerCodec 这些不用详细了解,只是单纯对于Http请求的一个数据的编码器和解码器
HTTPObjectAggregator
public class HttpObjectAggregator
extends MessageAggregator<HttpObject, HttpMessage, HttpContent, FullHttpMessage> {
...
/**
* Creates a new instance.
* @param maxContentLength the maximum length of the aggregated content in bytes.
* If the length of the aggregated content exceeds this value,
* {@link #handleOversizedMessage(ChannelHandlerContext, HttpMessage)} will be called.
*/
public HttpObjectAggregator(int maxContentLength) {
this(maxContentLength, false);
}
...
}
根据源码所示,可以看到继承了很多与HTTP相关的类,我们可以简单理解为这个类是Netty对于HTTP流程的一个封装。
我们仅需提供一个maxCotentLength,注意请不要超过提供的这个参数值!
FullHttpRequest
/**
* Combine the {@link HttpRequest} and {@link FullHttpMessage}, so the request is a <i>complete</i> HTTP
* request.
*/
public interface FullHttpRequest extends HttpRequest, FullHttpMessage {
...
}
根据翻译所示,可以简单理解为:HTTP请求&HTTP消息的组合
因此我们在创建连接的实现类时,主要是对与这个类进行处理
功能实战
通过前面已经了解到主要内容,接下来将通过实战快速掌握
准备工作
搭建一个Netty的HTTPServer
public class HttpChatServer extends AbstractServer{
public HttpChatServer(int port) {
super(port);
}
@Override
public void startServer() {
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(loopGroup,workGroup);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new HttpServerCodec());
ch.pipeline().addLast(new HttpObjectAggregator(102428));
ch.pipeline().addLast(new HttpChatServerHandle());
}
});
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.option(ChannelOption.SO_BACKLOG,100);
serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);
ChannelFuture future = serverBootstrap.bind(port).sync();
future.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}
}
}
自定义Web接口
定义实现类
public class HttpChatServerHandle extends SimpleChannelInboundHandler<FullHttpRequest> {
...
}
GET接口
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
String url = msg.uri().toString();
String subUrl = url.substring(url.indexOf("/")+1,url.length());
HttpMethod method = msg.method();
if(subUrl.contains("login")) {
System.out.println("登录接口");
//url的所提交的字段解码
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(msg.uri());
Map<String,Object> paramsMap = new HashMap<>();
// 转化一下数据
for(String key : queryStringDecoder.parameters().keySet()){
Object value = queryStringDecoder.parameters().get(key).get(0);
paramsMap.put(key,value);
}
if (method == HttpMethod.GET) {
writeResponse(ctx, ChatUtil.toJSON(paramsMap));
}
}
}
Get相对简单,可以直接通过QueryStringDecoder来获取到所提交的字段信息
POST接口
还是以同接口为例,不过对于字段获取有差异
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
String url = msg.uri().toString();
String subUrl = url.substring(url.indexOf("/")+1,url.length());
HttpMethod method = msg.method();
System.out.println("执行的方法:" + method.toString());
if(subUrl.contains("login")) {
System.out.println("登录接口");
// Post
if (method == HttpMethod.POST) {
ByteBuf buf = msg.content();
String responseResult = buf.toString();
// 如果容量不为空
if (msg.content().capacity() != 0) {
int contentLen = buf.readableBytes();
byte[] bytes = new byte[contentLen];
msg.content().readBytes(bytes);
// 读取到的json结果string
String resultString = new String(bytes, StandardCharsets.UTF_8);
HashMap<String,Object> requestMap = new HashMap();
Map<String,Object> requestObject = (Map<String, Object>) ChatUtil.formJSON(resultString,requestMap.getClass());
Map<String,Object> obj = new HashMap<>();
obj.put("userName",requestObject.get("userName"));
obj.put("age",15);
writeResponse(ctx,ChatUtil.toJSON(obj));
}
}
}
}
POST请求方式不能通过QueryStringDecoder进行读取,因为经过测试:解析URL中的字段信息
想做到相应功能需要读取到Content,之后手动进行处理
如何返回响应数据
这里是重点 返回数据不与之前一致
/* * @Author Marinda * @Date 2023/5/29 14:59 * @Description 写入Response */ void writeResponse(ChannelHandlerContext ctx,Object obj){ FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK); response.headers().set("Content-Type","application/json"); ByteBuf buf = Unpooled.copiedBuffer(obj.toString(),StandardCharsets.UTF_8); response.content().writeBytes(buf); buf.release(); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); }
4.最后才是通过推送数据,这里推送值需要为Response 也就是FullHttpResponse,并且添加监听器,ChannelFutureListener.CLOSE1.创建一个FullHttpResponse 获取HTTP的响应信息,设置相应配置
2.设置相应头(这里设置返回头是application/json)
3.需要通过Unpooled类的copiedBuffer方法将你想发送的数据转化为ByteBuf类
效果
Get效果图
我这里发送的是JSON格式
结束语
感谢你的观看!