Netty HTTP+XML协议栈开发
由于HTTP协议的通用性,很多异构系统间的通信交互采用HTTP协议,通过HTTP协议承载业务数据进行消息交互,例如非常流行的HTTP+XML或者RESTful+JSON。
场景设计
模拟一个简单的用户订购系统。客户端填写订单,通过HTTP客户端向服务端发送订购请求,请求消息放在HTTP消息体中,以XML承载,即采用HTTP+XML的方式进行通信。HTTP服务端接收到订购请求后,对订单请求进行修改,然后通过HTTP+XML的方式返回应答消息。双方采用HTTP1.1协议,连接类型为CLOSE方式,即双方交互完成,由HTTP服务端主动关闭链路,随后客户端也关闭链路并退出。
订购请求消息定义如表:
客户信息定义如表:
地址信息定义如表:
邮递方式定义如表:
HTTP+XML协议栈设计
首先对订购流程图进行分析,先看步骤1,构造订购请求消息并将其编码为HTTP+XML形式。Netty的HTTP协议栈提供了构造HTTP请求消息的相关接口,但是无法将普通的POJO对象转换为HTTP+XML的HTTP请求消息,需要自定义HTTP+XML格式的请求消息编码器。
再看步骤2,利用Netty的HTTP协议栈,可以支持HTTP链路的建立和请求消息的发送,所以不需要额外开发,直接重用Netty的能力即可。
步骤3,HTTP服务端需要将HTTP+XML格式的订购请求消息解码为订购请求POJO对象,同时获取HTTP请求消息头信息。利用Netty的HTTP协议栈服务端,可以完成HTTP请求消息的解码,但是,如果消息体为XML格式,Netty无法支持将其解码为POJO对象,需要在Netty协议栈的基础上扩展实现。
步骤4,服务端对订购请求消息处理完成后,重新将其封装成XML,通过HTTP应答消息体携带给客户端,Netty的HTTP协议栈不支持直接将POJO对象的应答消息以XML方式发送,需要定制。
步骤5,HTTP客户端需要将HTTP+XML格式的应答消息解码为订购POJO对象,同时能够获取应答消息的HTTP头信息,Netty的协议栈不支持自动的消息解码。
通过分析,我们可以了解到哪些能力是Netty支持的,哪些需要扩展开发实现。下面给出设计思路。
(1)需要一套通用、高性能的XML序列化框架,它能够灵活地实现POJO-XML的互相转换,最好能够通过工具自动生成绑定关系,或者通过XML的方式配置双方的映射关系;
(2)作为通用的HTTP+XML协议栈,XML-POJO对象的映射关系应该非常灵活,支持命名空间和自定义标签;
(3)提供HTTP+XML请求消息编码器,供HTTP客户端发送请求消息自动编码使用;
(4)提供HTTP+XML请求消息解码器,供HTTP服务端对请求消息自动解码使用;
(5)提供HTTP+XML响应消息编码器,供HTTP服务端发送响应消息自动编码使用;
(6)提供HTTP+XML响应消息编码器,供HTTP客户端对应答消息进行自动解码使用;
(7)协议栈使用者不需要关心HTTP+XML的编解码,对上层业务零侵入,业务只需要对上层的业务POJO对象进行编排。
高效的XML绑定框架JiBx
使用JiBX绑定XML文档与Java对象需要分两步走:第一步是绑定XML文件,也就是映射XML文件与Java对象之间的对应关系;第二步是在运行时,实现XML文件与Java实例之间的互相转换。这时,它已经与绑定文件无关了,可以说是完全脱耦了。
在运行程序之前,需要先配置绑定文件并进行绑定,在绑定过程中它将会动态地修改程序中相应的class文件,主要是生成对应对象实例的方法和添加被绑定标记的属性JiBX_bindingList等。它使用的技术是BCEL(Byte Code Engineering Library),BCEL是Apache Software Foundation的Jakarta项目的一部分,也是目前Java classworking最广泛使用的一种框架,它可以让你深入JVM汇编语言进行类操作。在JiBX运行时,它使用了目前比较流行的一个技术XPP(Xml Pull Parsing),这也正是JiBX如此高效的原因。
JiBx有两个比较重要的概念:Unmarshal(数据分解)和Marshal(数据编排)。从字面意思也很容易理解,Unmarshal是将XML文件转换成Java对象,而Marshal则是将Java对象编排成规范的XML文件。JiBX在Unmarshal/Marshal上如此高效,这要归功于使用了XPP技术,而不是使用基于树型(tree-based)方式,将整个文档写入内存,然后进行操作的DOM(Document Object Model),也不是使用基于事件流(event stream)的SAX(Simple API for Xml)。XPP使用的是不断增加的数据流处理方式,同时允许在解析XML文件时中断。
POJO对象定义完成之后,通过Ant脚本来生成XML和POJO对象的绑定关系文件,同时也附加生成XML的Schema定义文件。
JiBx的绑定和编译,通过JiBx的org.jibx.binding.generator.BindGen工具类可以将指定的POJO对象Order类生成绑定文件和Schema定义文件。
JiBx的编译命令,它的作用是根据绑定文件和POJO对象的映射关系和规则动态修改POJO类
代码示例:
import lombok.Data; @Data public class Address { /** * First line of street information (required). */ private String street1; /** * Second line of street information (optional). */ private String street2; private String city; /** * State abbreviation (required for the U.S. and Canada, optional * otherwise). */ private String state; /** * Postal code(required for the U.S.and Canada,optional otherwise). */ private String postCode; /** * Country name (optional, U.S. assumed if not supplied). */ private String country; } import lombok.Data; import java.util.List; @Data public class Customer { private long customerNumber; /** * Personal name. */ private String firstName; /** * Family name. */ private String lastName; /** * Middle name(s), if any. */ private List middleNames; } import lombok.Data; @Data public class Order { private long orderNumber; private Customer customer; private Address billTo; private Shipping shipping; private Address shipTo; private Float total; } public enum Shipping { STANDARD_MAIL, PRIORITY_MAIL, INTERNATIONAL_MAIL, DOMESTIC_EXPRESS, INTERNATIONAL_EXPRESS } import org.jibx.runtime.*; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.util.Arrays; public class TestOrder { private IBindingFactory factory = null; private StringWriter writer = null; private StringReader reader = null; private final static String CHARSET_NAME = "UTF-8"; private String encode2Xml(Order order) throws JiBXException, IOException { factory = BindingDirectory.getFactory(Order.class); writer = new StringWriter(); IMarshallingContext mctx = factory.createMarshallingContext(); mctx.setIndent(2); mctx.marshalDocument(order, CHARSET_NAME, null, writer); String xmlStr = writer.toString(); writer.close(); System.out.println(xmlStr.toString()); return xmlStr; } private Order decode2Order(String xmlBody) throws JiBXException { reader = new StringReader(xmlBody); IUnmarshallingContext uctx = factory.createUnmarshallingContext(); Order order = (Order) uctx.unmarshalDocument(reader); return order; } public static void main(String[] args) throws JiBXException, IOException { TestOrder test = new TestOrder(); Order order = new Order(); order.setOrderNumber(123); Customer customer = new Customer(); customer.setFirstName("ali"); customer.setMiddleNames(Arrays.asList("baba")); customer.setLastName("taobao"); order.setCustomer(customer); Address address = new Address(); address.setCity("南京市"); address.setCountry("中国"); address.setPostCode("123321"); address.setState("江苏省"); address.setStreet1("龙眠大道"); address.setStreet2("INTERNATIONAL_MAIL"); order.setBillTo(address); order.setShipTo(address); order.setShipping(Shipping.INTERNATIONAL_MAIL); order.setTotal(33f); String body = test.encode2Xml(order); Order order2 = test.decode2Order(body); System.out.println(order2); } } <dependency> <groupId>org.jibx</groupId> <artifactId>jibx-bind</artifactId> <version>1.3.0</version> </dependency>
基础封装类示例
import io.netty.handler.codec.http.FullHttpRequest; import lombok.Data; @Data public class HttpXmlRequest { //它包含两个成员变量FullHttpRequest和编码对象Object,用于实现和协议栈之间的解耦。 private FullHttpRequest request; private Object body; public HttpXmlRequest(FullHttpRequest request, Object body) { this.body = body; this.request = request; } } import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; 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; import java.util.List; import static io.netty.handler.codec.http.HttpResponseStatus.*; import static io.netty.handler.codec.http.HttpHeaders.Names.*; import static io.netty.handler.codec.http.HttpVersion.*; public class HttpXmlRequestDecoder extends AbstractHttpXmlDecoder { public HttpXmlRequestDecoder(Class clazz) { this(clazz, false); } //HttpXmlRequestDecoder有两个参数,分别为需要解码的对象的类型信息和是否打印HTTP消息体码流的码流开关,码流开关默认关闭。 public HttpXmlRequestDecoder(Class clazz, boolean isPrint) { super(clazz, isPrint); } @Override protected void decode(ChannelHandlerContext arg0, Object o, List arg2) throws Exception { FullHttpRequest arg1 = (FullHttpRequest)o; //首先对HTTP请求消息本身的解码结果进行判断,如果已经解码失败,再对消息体进行二次解码已经没有意义。 if (!arg1.getDecoderResult().isSuccess()) { //如果HTTP消息本身解码失败,则构造处理结果异常的HTTP应答消息返回给客户端。 sendError(arg0, BAD_REQUEST); return; } //通过HttpXmlRequest和反序列化后的Order对象构造HttpXmlRequest实例,最后将它添加到解码结果List列表中。 HttpXmlRequest request = new HttpXmlRequest(arg1, decode0(arg0,arg1.content())); arg2.add(request); } private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status.toString() + "\r\n", CharsetUtil.UTF_8)); response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } } import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.*; import java.net.InetAddress; import java.util.List; public class HttpXmlRequestEncoder extends AbstractHttpXmlEncoder { @Override protected void encode(ChannelHandlerContext ctx, Object o,List out) throws Exception { HttpXmlRequest msg = (HttpXmlRequest)o; //首先调用父类的encode0,将业务需要发送的POJO对象Order实例通过JiBx序列化为XML字符串 //随后将它封装成Netty的ByteBuf。 ByteBuf body = encode0(ctx, msg.getBody()); FullHttpRequest request = msg.getRequest(); //对消息头进行判断,如果业务自定义和定制了消息头,则使用业务侧设置的HTTP消息头, //如果业务侧没有设置,则构造新的HTTP消息头。 if (request == null) { //用来构造和设置默认的HTTP消息头,由于通常情况下HTTP通信双方更关注消息体本身,所以这里采用了硬编码的方式, //如果要产品化,可以做成XML配置文件,允许业务自定义配置,以提升定制的灵活性。 request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,HttpMethod.GET, "/do", body); HttpHeaders headers = request.headers(); headers.set(HttpHeaders.Names.HOST, InetAddress.getLocalHost().getHostAddress()); headers.set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE); headers.set(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP.toString() + ',' + HttpHeaders.Values.DEFLATE.toString()); headers.set(HttpHeaders.Names.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.7"); headers.set(HttpHeaders.Names.ACCEPT_LANGUAGE, "zh"); headers.set(HttpHeaders.Names.USER_AGENT,"Netty xml Http Client side"); headers.set(HttpHeaders.Names.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); } //由于请求消息消息体不为空,也没有使用Chunk方式,所以在HTTP消息头中设置消息体的长度Content-Length, //完成消息体的XML序列化后将重新构造的HTTP请求消息加入到out中, //由后续Netty的HTTP请求编码器继续对HTTP请求消息进行编码。 HttpHeaders.setContentLength(request, body.readableBytes()); out.add(request); } } import io.netty.handler.codec.http.FullHttpResponse; //它包含两个成员变量:FullHttpResponse和Object,Object就是业务需要发送的应答POJO对象。 public class HttpXmlResponse { private FullHttpResponse httpResponse; private Object result; public HttpXmlResponse(FullHttpResponse httpResponse, Object result) { this.httpResponse = httpResponse; this.result = result; } public final FullHttpResponse getHttpResponse() { return httpResponse; } public final void setHttpResponse(FullHttpResponse httpResponse) { this.httpResponse = httpResponse; } public final Object getResult() { return result; } public final void setResult(Object result) { this.result = result; } } import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultFullHttpResponse; import java.util.List; public class HttpXmlResponseDecoder extends AbstractHttpXmlDecoder { public HttpXmlResponseDecoder(Class clazz) { this(clazz, false); } public HttpXmlResponseDecoder(Class clazz, boolean isPrintlog) { super(clazz, isPrintlog); } @Override protected void decode(ChannelHandlerContext ctx,Object o, List out) throws Exception { DefaultFullHttpResponse msg = (DefaultFullHttpResponse)o; HttpXmlResponse resHttpXmlResponse = new HttpXmlResponse(msg, decode0( ctx, msg.content())); out.add(resHttpXmlResponse); } } import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse; import java.util.List; import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpHeaders.setContentLength; import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; public class HttpXmlResponseEncoder extends AbstractHttpXmlEncoder { protected void encode(ChannelHandlerContext ctx, Object o, List out) throws Exception { HttpXmlResponse msg = (HttpXmlResponse) o; ByteBuf body = encode0(ctx, msg.getResult()); FullHttpResponse response = msg.getHttpResponse(); if (response == null) { response = new DefaultFullHttpResponse(HTTP_1_1, OK, body); } else { response = new DefaultFullHttpResponse(msg.getHttpResponse() .getProtocolVersion(), msg.getHttpResponse().getStatus(), body); } response.headers().set(CONTENT_TYPE, "text/xml"); setContentLength(response, body.readableBytes()); out.add(response); } } import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageDecoder; import org.jibx.runtime.BindingDirectory; import org.jibx.runtime.IBindingFactory; import org.jibx.runtime.IUnmarshallingContext; import java.io.StringReader; import java.nio.charset.Charset; public abstract class AbstractHttpXmlDecoder extends MessageToMessageDecoder { private IBindingFactory factory; private StringReader reader; private Class clazz; private boolean isPrint; private final static String CHARSET_NAME = "UTF-8"; private final static Charset UTF_8 = Charset.forName(CHARSET_NAME); protected AbstractHttpXmlDecoder(Class clazz) { this(clazz, false); } protected AbstractHttpXmlDecoder(Class clazz, boolean isPrint) { this.clazz = clazz; this.isPrint = isPrint; } protected Object decode0(ChannelHandlerContext arg0, ByteBuf body) throws Exception { //从HTTP的消息体中获取请求码流,然后通过JiBx类库将XML转换成POJO对象。 factory = BindingDirectory.getFactory(clazz); String content = body.toString(UTF_8); //根据码流开关决定是否打印消息体码流。 //增加码流开关往往是为了方便问题定位,在实际项目中,需要打印到日志中。 if (isPrint) { System.out.println("The body is : " + content); } reader = new StringReader(content); IUnmarshallingContext uctx = factory.createUnmarshallingContext(); Object result = uctx.unmarshalDocument(reader); reader.close(); reader = null; return result; } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // 释放资源 //如果解码发生异常,要判断StringReader是否已经关闭, //如果没有关闭,则关闭输入流并通知JVM对其进行垃圾回收。 if (reader != null) { reader.close(); reader = null; } } } import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageEncoder; import org.jibx.runtime.BindingDirectory; import org.jibx.runtime.IBindingFactory; import org.jibx.runtime.IMarshallingContext; import java.io.StringWriter; import java.nio.charset.Charset; public abstract class AbstractHttpXmlEncoder extends MessageToMessageEncoder { IBindingFactory factory = null; StringWriter writer = null; final static String CHARSET_NAME = "UTF-8"; final static Charset UTF_8 = Charset.forName(CHARSET_NAME); protected ByteBuf encode0(ChannelHandlerContext ctx, Object body) throws Exception { //在此将业务的Order实例序列化为XML字符串。 factory = BindingDirectory.getFactory(body.getClass()); writer = new StringWriter(); IMarshallingContext mctx = factory.createMarshallingContext(); mctx.setIndent(2); mctx.marshalDocument(body, CHARSET_NAME, null, writer); String xmlStr = writer.toString(); writer.close(); writer = null; //将XML字符串包装成Netty的ByteBuf并返回,实现了HTTP请求消息的XML编码。 ByteBuf encodeBuf = Unpooled.copiedBuffer(xmlStr, UTF_8); return encodeBuf; } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // 释放资源 if (writer != null) { writer.close(); writer = null; } } }
服务端代码示例
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; 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.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponseEncoder; import java.net.InetSocketAddress; public class HttpXmlServer { 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() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast("http-decoder",new HttpRequestDecoder()); ch.pipeline().addLast("http-aggregator",new HttpObjectAggregator(65536)); ch.pipeline().addLast("xml-decoder",new HttpXmlRequestDecoder(Order.class, true)); ch.pipeline().addLast("http-encoder",new HttpResponseEncoder()); ch.pipeline().addLast("xml-encoder",new HttpXmlResponseEncoder()); ch.pipeline().addLast("xmlServerHandler",new HttpXmlServerHandler()); } }); ChannelFuture future = b.bind(new InetSocketAddress(port)).sync(); System.out.println("HTTP订购服务器启动,网址是 : " + "http://localhost:" + port); future.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port = 8080; if (args.length > 0) { try { port = Integer.parseInt(args[0]); } catch (NumberFormatException e) { e.printStackTrace(); } } new HttpXmlServer().run(port); } } import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; 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.FullHttpResponse; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.util.CharsetUtil; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import java.util.ArrayList; import java.util.List; import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive; import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; public class HttpXmlServerHandler extends SimpleChannelInboundHandler { @Override public void messageReceived(final ChannelHandlerContext ctx,Object o) throws Exception { HttpXmlRequest xmlRequest = (HttpXmlRequest)o; HttpRequest request = xmlRequest.getRequest(); Order order = (Order) xmlRequest.getBody(); System.out.println("Http server receive request : " + order); dobusiness(order); ChannelFuture future = ctx.writeAndFlush(new HttpXmlResponse(null, order)); if (!isKeepAlive(request)) { future.addListener(new GenericFutureListener() { public void operationComplete (Future future)throws Exception { ctx.close(); } }); } } private void dobusiness(Order order) { order.getCustomer().setFirstName("狄"); order.getCustomer().setLastName("仁杰"); List midNames = new ArrayList(); midNames.add("李元芳"); order.getCustomer().setMiddleNames(midNames); Address address = order.getBillTo(); address.setCity("洛阳"); address.setCountry("大唐"); address.setState("河南道"); address.setPostCode("123456"); order.setBillTo(address); order.setShipTo(address); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception { cause.printStackTrace(); if (ctx.channel().isActive()) { sendError(ctx, INTERNAL_SERVER_ERROR); } } private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.copiedBuffer("失败: " + status.toString() + "\r\n", CharsetUtil.UTF_8)); response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } }
客户端代码示例
import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; 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 java.net.InetSocketAddress; public class HttpXmlClient { 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() { @Override public void initChannel(Channel ch) throws Exception { ch.pipeline().addLast("http-decoder",new HttpResponseDecoder()); ch.pipeline().addLast("http-aggregator",new HttpObjectAggregator(65536)); // XML解码器 ch.pipeline().addLast("xml-decoder",new HttpXmlResponseDecoder(Order.class,true)); ch.pipeline().addLast("http-encoder",new HttpRequestEncoder()); ch.pipeline().addLast("xml-encoder",new HttpXmlRequestEncoder()); ch.pipeline().addLast("xmlClientHandler",new HttpXmlClientHandle()); } }); // 发起异步连接操作 ChannelFuture f = b.connect(new InetSocketAddress(port)).sync(); // 等待客户端链路关闭 f.channel().closeFuture().sync(); } finally { // 优雅退出,释放NIO线程组 group.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port = 8080; if (args != null && args.length > 0) { try { port = Integer.valueOf(args[0]); } catch (NumberFormatException e) { // 采用默认值 } } new HttpXmlClient().connect(port); } } import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; public class HttpXmlClientHandle extends SimpleChannelInboundHandler { @Override public void channelActive(ChannelHandlerContext ctx) { HttpXmlRequest request = new HttpXmlRequest(null,OrderFactory.create(123)); ctx.writeAndFlush(request); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } @Override protected void messageReceived(ChannelHandlerContext ctx,Object o) throws Exception { HttpXmlResponse msg = (HttpXmlResponse)o; System.out.println("The client receive response of http header is : " + msg.getHttpResponse().headers().names()); System.out.println("The client receive response of http body is : " + msg.getResult()); } } public class OrderFactory { public static Order create(long orderID) { Order order = new Order(); order.setOrderNumber(orderID); order.setTotal(9999.999f); Address address = new Address(); address.setCity("南京市"); address.setCountry("中国"); address.setPostCode("123321"); address.setState("江苏省"); address.setStreet1("龙眠大道"); order.setBillTo(address); Customer customer = new Customer(); customer.setCustomerNumber(orderID); customer.setFirstName("李"); customer.setLastName("林峰"); order.setCustomer(customer); order.setShipping(Shipping.INTERNATIONAL_MAIL); order.setShipTo(address); return order; } }
测试结果:
服务端请求消息码流输出:
服务端解码后的业务对象输出
Http server receive request : Order [orderNumber=123, customer=Customer [customerNumber=123, firstName=李, lastName=林峰, middleNames=null], billTo= Address [street1=龙眠大道, street2=null, city=南京市, state=江苏省, postCode= 123321, country=中国], shipping=INTERNATIONAL_MAIL, shipTo=Address [street1=龙眠大道, street2=null, city=南京市, state=江苏省, postCode=123321, country=中国], total=9999.999]
客户端响应消息码流输出:
客户端解码后的业务对象输出
The client receive response of http body is : Order [orderNumber=123, customer=Customer [customerNumber=123, firstName=狄, lastName=仁杰, middleNames=[李元芳]], billTo=Address [street1=龙眠大道, street2=null, city=洛阳, state=河南道, postCode=123456, country=大唐], shipping=INTERNATIONAL_MAIL, shipTo=Address [street1=龙眠大道, street2=null, city=洛阳, state=河南道, postCode=123456, country=大唐], total=9999.999]