HTTP(超文本传输协议)协议是建立在TCP传输协议之上的应用层协议,它的发展是万维网协会和Internet工作小组IETF合作的结果。HTTP是一个属于应用层的面向对象的协议,由于其简洁、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过多年的使用和发展,得到了不断的完善和扩展。本文将介绍如何基于Netty 的HTTP协议栈进行HTTP服务端和客户端的开发。由于Netty的HTTP协议栈是基于Netty的NIO通信框架开发的,因此,Netty的HTTP协议也是异步非阻塞的。
HTTP服务端开发
HttpFileServer.java
package emulator.http;
import emulator.Constants;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
/**
* 模拟服务端
* @author lh
* @date 2015-08-11 14:33
* @version 1.0
*
*/
public class HttpFileServer {
public void run(final int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch)
throws Exception {
// 服务端,对请求解码
ch.pipeline().addLast("http-decoder",
new HttpRequestDecoder());
// 聚合器,把多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse
ch.pipeline().addLast("http-aggregator",
new HttpObjectAggregator(65536));
// 服务端,对响应编码
ch.pipeline().addLast("http-encoder",
new HttpResponseEncoder());
// 块写入处理器
ch.pipeline().addLast("http-chunked",
new ChunkedWriteHandler());
// 自定义服务端处理器
ch.pipeline().addLast("fileServerHandler",
new HttpFileServerHandler());
}
});
ChannelFuture future = b.bind(Constants.DEFAULT_IP, port).sync();
System.out.println("HTTP文件目录服务器启动,网址是 : " + "http://" + Constants.DEFAULT_IP + ":" + port);
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new HttpFileServer().run(Constants.DEFAULT_PORT);
}
}
HttpFileServerHandler.java
package emulator.http;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import emulator.Constants;
import emulator.util.Dom4JUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
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.HttpResponseStatus;
import io.netty.util.CharsetUtil;
/**
* 模拟服务端处理器
* @author lh
* @date 2015-08-11 14:33
* @version 1.0
*
*/
public class HttpFileServerHandler extends
SimpleChannelInboundHandler<FullHttpRequest> {
@Override
public void messageReceived(ChannelHandlerContext ctx,
FullHttpRequest request) throws Exception {
if (!request.getDecoderResult().isSuccess()) {
sendError(ctx, BAD_REQUEST);
return;
}
ByteBuf buf = request.content();
byte [] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String xml = new String(req,"UTF-8");
resp(ctx,xml);
}
/**
*
* @param xml
*/
private void resp(ChannelHandlerContext ctx, String xml){
String transCode = Dom4JUtil.header(xml, "transcode");
String retUrl = "D:\\workspaces\\eclipse-huifu\\emulator\\xml\\error.xml";
String retCtt = null;
if(equal(transCode, Constants.TC_DZZH)){//电子账户
retUrl = "D:\\workspaces\\eclipse-huifu\\emulator\\xml\\account\\manage\\resp.xml";
}else if(equal(transCode, Constants.TC_YHKBD)){//银行卡绑定
}
try {
retCtt = FileUtils.readFileToString(new File(retUrl));
} catch (IOException e) {
e.printStackTrace();
}
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
HttpResponseStatus.FOUND, Unpooled.copiedBuffer(retCtt, CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "text/xml; charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
public static boolean equal(String var, String cons){
return isNotEmpty(var) && cons.equals(var);
}
private static boolean isNotEmpty(String s){
return (null != s && !"".equals(s));
}
/**
* 错误处理
* @param ctx
* @param status
*/
private static void sendError(ChannelHandlerContext ctx,
HttpResponseStatus status) {
String ret = null;
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
status, Unpooled.copiedBuffer(ret, CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "text/xml; charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.close();
cause.printStackTrace();
}
}
HTTP客户端开发
HttpFileClient.java
package emulator.http;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import java.net.InetSocketAddress;
import emulator.Constants;
/**
* 模拟客户端
*
* @author lh
* @date 2015-08-11 14:31
* @version 1.0
*
*/
public class HttpFileClient {
public void connect(int port) throws Exception {
// 配置客户端NIO线程组
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
// 客户端,对请求编码
ch.pipeline().addLast("http-encoder",
new HttpRequestEncoder());
// 客户端,对响应解码
ch.pipeline().addLast("http-decoder",
new HttpResponseDecoder());
// 聚合器,把多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse
ch.pipeline().addLast("http-aggregator",
new HttpObjectAggregator(65536));
// 块写入处理器
ch.pipeline().addLast("http-chunked",
new ChunkedWriteHandler());
// 自定义客户端处理器
ch.pipeline().addLast("fileClientHandler",
new HttpFileClientHandler());
}
});
// 发起异步连接操作
ChannelFuture f = b.connect(new InetSocketAddress(port)).sync();
// 当代客户端链路关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
new HttpFileClient().connect(Constants.DEFAULT_PORT);
}
}
HttpFileClientHandler.java
package emulator.http;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.commons.io.FileUtils;
import emulator.Constants;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.util.CharsetUtil;
/**
* 模拟客户端处理器
* @author lh
* @date 2015-08-11 14:32
* @version 1.0
*
*/
public class HttpFileClientHandler extends
SimpleChannelInboundHandler<FullHttpResponse> {
@Override
public void channelActive(ChannelHandlerContext ctx) {
String xml = null;
try {
xml = FileUtils.readFileToString(new File("D:\\workspaces\\eclipse-huifu\\emulator\\xml\\account\\manage\\req.xml"));
} catch (IOException e) {
e.printStackTrace();
}
URI uri = null;
try {
uri = new URI("http://"+Constants.DEFAULT_IP+":"+Constants.DEFAULT_PORT);
} catch (URISyntaxException e) {
e.printStackTrace();
}
FullHttpRequest req = new DefaultFullHttpRequest(HTTP_1_1,
HttpMethod.GET,uri.toASCIIString(),
Unpooled.copiedBuffer(xml, CharsetUtil.UTF_8));
req.headers().set(HttpHeaders.Names.HOST, Constants.DEFAULT_IP);
req.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
req.headers().set(HttpHeaders.Names.CONTENT_LENGTH, req.content().readableBytes());
ctx.writeAndFlush(req);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
@Override
protected void messageReceived(ChannelHandlerContext ctx,
FullHttpResponse msg) throws Exception {
ByteBuf buf = msg.content();
byte [] resp = new byte[buf.readableBytes()];
buf.readBytes(resp);
String xml = new String(resp,"UTF-8");
System.out.println("server cotent:\n"+xml);
}
}
Constants.java
package emulator;
public final class Constants {
/**
* 电子账户
*/
public static final String TC_DZZH = "31001";
/**
* 银行卡绑定
*/
public static final String TC_YHKBD = "31002";
/**
* 客户充值
*/
public static final String TC_KHCZ = "31021";
/**
* 协议管理
*/
public static final String TC_XYGL = "31004";
/**
* 项目管理
*/
public static final String TC_XMGL = "31005";
/**
* 交易成功
*/
public static final String TC_JYCG = "0000";
/**
* 交易失败
*/
public static final String TC_JYSB = "0001";
/**
* 部分成功
*/
public static final String TC_BFCG = "0002";
/**
* 默认IP
*/
public static final String DEFAULT_IP = "192.168.1.64";
/**
* 默认端口
*/
public static final int DEFAULT_PORT = 8080;
}