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; }