目录
- HttpClient系列(一),JDK HttpURLConnection
- HttpClient系列(二),Apache HttpClient
- HttpClient系列(三),SpringBoot RestTemplate
- HttpClient系列(四),Vert.x HttpClient
- HttpClient系列(五),Netty HttpClient
正文
Netty是由JBoss公司开源的NIO框架。它是基于异步和事件驱动的。现今Java的框架中,Netty的身影几乎是无处不在。如Vert.x、Dubbo、Firebase等等。Netty把Java NIO技术发展到了高峰。
Netty一般用来编写Socket程序,在实际项目中很少用来写HttpClient。但还是有必要了解下的,万一用到了呢?
同样还是以GET和POST请求为例,先在pom.xml中加入Netty的依赖。
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.50.Final</version>
</dependency>
然后直接写代码,GET请求走起:
public static void main(String[] args) {
connect("www.baidu.com", 80, "http://www.baidu.com");
}
public static void connect(String host, int port, String url_str) {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup).channel(NioSocketChannel.class).option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
// HttpClientCodec 相当于同时添加了HttpResponseDecoder和HttpRequestEncoder
sc.pipeline().addLast(new HttpClientCodec());
sc.pipeline().addLast(new OutputResultHandler());
}
});
Channel channel = bootstrap.connect(host, port).sync().channel();
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, new URI(url_str).toASCIIString());
request.headers().set(HttpHeaders.Names.HOST, host);
request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
request.headers().set(HttpHeaders.Names.CONTENT_LENGTH, request.content().readableBytes());
channel.writeAndFlush(request).sync();
channel.closeFuture().sync();
} catch (InterruptedException | URISyntaxException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
private static class OutputResultHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpResponse) {
HttpResponse response = (HttpResponse) msg;
System.out.println(response.toString());
}
if (msg instanceof HttpContent) {
HttpContent content = (HttpContent) msg;
ByteBuf buf = content.content();
System.out.println(buf.toString(CharsetUtil.UTF_8));
buf.release();
}
}
}
看到代码,有没有感觉晕菜了。跟前面三节HttpClient比起来,Netty怎么这么复杂。请看笔者一一解释。
Netty是一个NIO Socket框架,所以在请求一个URL前需要先连接到服务器,所以建立了connect方法:
Channel channel = bootstrap.connect(host, port).sync().channel();
这个表示同步连接成功后返回一个Channel,Channel就是你跟服务器连接上的一个通道,通过这个通道才可以跟服务器交换数据。这一点跟我们第一节的HttpURLConnection打开连接一个道理。
再看DefaultFullHttpRequest类型对象的创建,这个才是真正的发起Http请求的时候。在参数里面指定了http协议版本,请求方法和URI。如果是POST请求,还会有一个类型为ByteBuf的参数。
那么怎么接收请求后的响应数据呢?需要回到前面看这行代码:
sc.pipeline().addLast(new OutputResultHandler());
因为Netty是异步、事件驱动的网络框架,所以无法在请求发送完就马上知道响应,需要通过添加pipeline来处理响应消息。pipeline又叫管道,是一条链式的结构。当请求发出后,服务端返回请求,会按照这个链条,一层层处理。
最终在OutputResultHandler类中,继承ChannelInboundHandlerAdapter来处理响应。不过在这里需要判断消息类型,因为ChannelInboundHandlerAdapter默认的消息体是拆分的,分成HttpResponse和HttpContent。HttpContent就是你想要的内容,响应中的body,而header信息往往在HttpResponse中。 当然如果你分两次解包很麻烦,也可以继承来SimpleChannelInboundHandler来一次性获得数据响应:
private static class OutputResultHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpResponse fullHttpResponse) throws Exception {
ByteBuf buf = fullHttpResponse.content();
System.out.println(buf.toString(CharsetUtil.UTF_8));
}
}
记得pipeline加上HttpObjectAggregator,最终pipeline链如下:
sc.pipeline().addLast(new HttpClientCodec());
sc.pipeline().addLast(new HttpObjectAggregator(512 * 1024));
sc.pipeline().addLast(new OutputResultHandler());
POST请求又怎么实现呢?
有两个问题需要解决:
1、怎么传递参数;
2、header中怎么模拟表单请求。
先来回答第一个问题:使用BasicNameValuePair来组装表单数据,在用UrlEncodedFormEntity来encode表单数据,再通过EntityUtils.toByteArray将表单数据变成byte数组,最后用Unpooled将表单数据的byte数组转成ByteBuf。
再说第二个问题:header中的Content-Type需要设置为application/x-www-form-urlencoded,并且要配置Content-Length。Content-Length就是ByteBuf的数据长度。
请看一个POST请的完整示例
public static void main(String[] args) throws URISyntaxException {
URI uri = new URI("http://192.168.1.111:8181/login");
connect(uri);
}
public static void connect(URI uri) {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup).channel(NioSocketChannel.class).option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
// HttpClientCodec 相当于同时添加了HttpResponseDecoder和HttpRequestEncoder
sc.pipeline().addLast(new HttpClientCodec());
sc.pipeline().addLast(new HttpObjectAggregator(512 * 1024));
sc.pipeline().addLast(new OutputResultHandler());
}
});
Channel channel = bootstrap.connect(uri.getHost(), uri.getPort()).sync().channel();
List<BasicNameValuePair> formData = new ArrayList<>();
formData.add(new BasicNameValuePair("account", "admin"));
formData.add(new BasicNameValuePair("password", "admin"));
HttpEntity httpEntity = new UrlEncodedFormEntity(formData);
ByteBuf byteBuf = Unpooled.wrappedBuffer(EntityUtils.toByteArray(httpEntity));
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uri.getPath(), byteBuf);
request.headers().set(HttpHeaders.Names.CONTENT_TYPE, HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED);
request.headers().set(HttpHeaders.Names.HOST, uri.getHost());
request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
request.headers().set(HttpHeaders.Names.CONTENT_LENGTH, request.content().readableBytes());
channel.writeAndFlush(request).sync();
channel.closeFuture().sync();
} catch (InterruptedException | IOException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
private static class OutputResultHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpResponse fullHttpResponse) throws Exception {
ByteBuf buf = fullHttpResponse.content();
System.out.println(buf.toString(CharsetUtil.UTF_8));
}
}
Netty HttpClient实际用的比较少,但前面四种http client还是经常用的,是有必要记住的。