Netty4 学习笔记之四: Netty HTTP服务的实现
前言
目前主流的JAVA web 的HTTP服务主要是 springMVC和Struts2,更早的有JSP/servlet。
在学习Netty的时候,发现Netty 也可以作HTTP服务,于是便将此整理一篇博文,分享给大家。
开发准备
添加配置
将Netty作为HTTP服务,需要在过滤器中添加HttpRequest之类的配置,如:
ph.addLast("encoder",new HttpResponseEncoder());
ph.addLast("decoder",new HttpRequestDecoder());
ph.addLast("aggregator", new HttpObjectAggregator(10*1024*1024));
基本配置和之前的客户端和服务端通信Demo几乎一样,就是在业务处理器中略微的修改下业务逻辑处理就可以了。
HTTP GET请求测试
那么进行测试。
首先启动Netty服务,然后使用HTTP GET 方式测试(直接在浏览器上输入http://localhost:6789/test)
结果如下:
HTTP服务准备
一般来说,使用HttpRequest 类作为请求,但是该类中没有获取消息体的方法,获取消息体的类为HttpContent 或LastHttpContent,这样获取请求和消息体则相当不对不方便。
在查阅Netty相关资料后,发现这样一个请求类,可以完成上述所提的要求。这个类就是FullHttpRequest。
查看该类源码,可以发现该类继承HttpRequest, FullHttpMessage,而FullHttpMessage又继承LastHttpContent, 而LastHttpContent又继承HttpContent,所以该类可以实现上述要求。
源码示例图:
那么代码修改如下:
FullHttpRequest httpRequest = (FullHttpRequest)msg;
String path=httpRequest.uri(); //获取路径
String body = getBody(httpRequest); //获取参数
HttpMethod method=httpRequest.method();//获取请求方法
然后测试POST、PUT和DELETE请求并使用json格式传输。
我们可以通过postman等工具来直接调用,就不用写相关请求代码了
可以看见,Netty 作为HTTP服务可以接受基本的请求。
完整的代码如下:
服务端:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
*
* Title: NettyServer
* Description: Netty服务端 Http测试
* Version:1.0.0
* @author pancm
* @date 2017年10月26日
*/
public class NettyServer {
private static final int port = 6789; //设置服务端端口
private static EventLoopGroup group = new NioEventLoopGroup(); // 通过nio方式来接收连接和处理连接
private static ServerBootstrap b = new ServerBootstrap();
/**
* Netty创建全部都是实现自AbstractBootstrap。
* 客户端的是Bootstrap,服务端的则是 ServerBootstrap。
**/
public static void main(String[] args) throws InterruptedException {
try {
b.group(group);
b.channel(NioServerSocketChannel.class);
b.childHandler(new NettyServerFilter()); //设置过滤器
// 服务器绑定端口监听
ChannelFuture f = b.bind(port).sync();
System.out.println("服务端启动成功,端口是:"+port);
// 监听服务器关闭监听
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully(); //关闭EventLoopGroup,释放掉所有资源包括创建的线程
}
}
}
服务端过滤器:
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
/**
*
* Title: NettyServerFilter
* Description: Netty 服务端过滤器
* Version:1.0.0
* @author pancm
* @date 2017年10月26日
*/
public class NettyServerFilter extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
//处理http服务的关键handler
ph.addLast("encoder",new HttpResponseEncoder());
ph.addLast("decoder",new HttpRequestDecoder());
ph.addLast("aggregator", new HttpObjectAggregator(10*1024*1024));
ph.addLast("handler", new NettyServerHandler());// 服务端业务逻辑
}
}
服务端业务逻辑
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;
import java.net.InetAddress;
/**
*
* Title: NettyServerHandler
* Description: 服务端业务逻辑
* Version:1.0.0
* @author pancm
* @date 2017年10月26日
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
private String result="";
/*
* 收到消息时,返回信息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(! (msg instanceof FullHttpRequest)){
result="未知请求!";
send(ctx,result,HttpResponseStatus.BAD_REQUEST);
return;
}
FullHttpRequest httpRequest = (FullHttpRequest)msg;
try{
String path=httpRequest.uri(); //获取路径
String body = getBody(httpRequest); //获取参数
HttpMethod method=httpRequest.method();//获取请求方法
//如果不是这个路径,就直接返回错误
if(!"/test".equalsIgnoreCase(path)){
result="非法请求!";
send(ctx,result,HttpResponseStatus.BAD_REQUEST);
return;
}
System.out.println("接收到:"+method+" 请求");
//如果是GET请求
if(HttpMethod.GET.equals(method)){
//接受到的消息,做业务逻辑处理...
System.out.println("body:"+body);
result="GET请求";
send(ctx,result,HttpResponseStatus.OK);
return;
}
//如果是POST请求
if(HttpMethod.POST.equals(method)){
//接受到的消息,做业务逻辑处理...
System.out.println("body:"+body);
result="POST请求";
send(ctx,result,HttpResponseStatus.OK);
return;
}
//如果是PUT请求
if(HttpMethod.PUT.equals(method)){
//接受到的消息,做业务逻辑处理...
System.out.println("body:"+body);
result="PUT请求";
send(ctx,result,HttpResponseStatus.OK);
return;
}
//如果是DELETE请求
if(HttpMethod.DELETE.equals(method)){
//接受到的消息,做业务逻辑处理...
System.out.println("body:"+body);
result="DELETE请求";
send(ctx,result,HttpResponseStatus.OK);
return;
}
}catch(Exception e){
System.out.println("处理请求失败!");
e.printStackTrace();
}finally{
//释放请求
httpRequest.release();
}
}
/**
* 获取body参数
* @param request
* @return
*/
private String getBody(FullHttpRequest request){
ByteBuf buf = request.content();
return buf.toString(CharsetUtil.UTF_8);
}
/**
* 发送的返回值
* @param ctx 返回
* @param context 消息
* @param status 状态
*/
private void send(ChannelHandlerContext ctx, String context,HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(context, CharsetUtil.UTF_8));
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
/*
* 建立连接时,返回消息
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("连接的客户端地址:" + ctx.channel().remoteAddress());
ctx.writeAndFlush("客户端"+ InetAddress.getLocalHost().getHostName() + "成功与服务端建立连接! ");
super.channelActive(ctx);
}
}
结语
那么Netty HTTP 服务的相关测试就到这了,如果有什么疑问,欢迎讨论!
该项目我放在github上了,有兴趣的可以看看!https://github.com/xuwujing/Netty