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]

HTTP协议开发应用-HTTP&XML协议栈开发的更多相关文章

  1. HTTP协议开发应用-文件服务器

    HTTP(超文本传输协议)协议是建立在TCP传输协议之上的应用层协议.HTTP是一个属于应用层的面向对象的协议,由于其简捷.快速的方式,适用于分布式超媒体信息系统. 本文将重点介绍如何基于Netty的 ...

  2. Modbus库开发笔记之十一:关于Modbus协议栈开发的说明

    对于Modbus协议栈的整个开发内容,前面已经说得很清楚了,接下来我们说明一下与开发没有直接关系的内容. 首先,关于我为什么开发这个协议栈的问题.我们的初衷只是想能够在开发产品时不用每次都重写这一部分 ...

  3. LwIP协议栈开发嵌入式网络的三种方法分析

    LwIP协议栈开发嵌入式网络的三种方法分析   摘要  轻量级的TCP/IP协议栈LwIP,提供了三种应用程序设计方法,且很容易被移植到多任务的操作系统中.本文结合μC/OS-II这一实时操作系统,以 ...

  4. BLE 蓝牙协议栈开发

    1.由浅入深,蓝牙4.0/BLE协议栈开发攻略大全(1) 2.由浅入深,蓝牙4.0/BLE协议栈开发攻略大全(2) 3.由浅入深,蓝牙4.0/BLE协议栈开发攻略大全(3)

  5. netty——私有协议栈开发案例

    netty--私有协议栈开发案例 摘要: 在学习李林峰老师的Netty权威指南中,觉得第十二章<私有协议栈开发>中的案例代码比较有代表性,讲的也不错,但是代码中个人认为有些简单的错误,个人 ...

  6. Modbus库开发笔记之九:利用协议栈开发Modbus TCP Server应用

    前面我们已经完成了Modbus协议栈的开发,但这不是我们的目的.我们开发它的目的当然是要使用它来解决我们的实际问题.接下来我们就使用刚开发的Modbus协议栈开发一个Modbus TCP Server ...

  7. Modbus库开发笔记之十一:关于Modbus协议栈开发的说明(转)

    源: Modbus库开发笔记之十一:关于Modbus协议栈开发的说明

  8. 基于XML的开发

    基于XML的开发 1.定义一个切面类 /** * Created by zejian on 2017/2/20.*/ public class MyAspectXML { public void be ...

  9. spring 使用XML配置开发Spring AOP

      XML方式开发AOP与注解开发原理是相同的,所以这里主要介绍一些用法即可.这里需要在XML中引入AOP的命名空间,所以先来了解一下AOP可配置的元素 代码清单:切面类 package com.ss ...

随机推荐

  1. sql server 取日期

    Select CONVERT(varchar(100), GETDATE(), 0): 05 16 2006 10:57AM Select CONVERT(varchar(100), GETDATE( ...

  2. 编译QtAV工程库

    去https://github.com/wang-bin/QtAV下载源代码 去https://sourceforge.net/projects/qtav/files/depends/QtAV-dep ...

  3. C#实现把指定文件夹下的所有文件复制到指定路径下以及修改指定文件的后缀名

    1.实现把指定文件夹下的所有文件复制到指定路径下 public static void copyFiles(string path) { DirectoryInfo dir = new Directo ...

  4. WaxPatch中demo注意问题

    问题一 https://github.com/mmin18/WaxPatch网址中提供的demo是可以运行,但是存在一个问题,如果把patch.zip换成自己的并且上传到自己的服务器(github), ...

  5. !对c++类的理解

    c++的类可以分为两类,一种是entity的类(i.e.,实体类),一种是function的类(i.e.,功能类). 对于构造entity的类,包括这种entity的属性已经它本身具备的功能: 而fu ...

  6. 指针和引用的区别(c/c++)

      http://blog.csdn.net/thisispan/article/details/7456169 ★ 相同点: 1. 都是地址的概念: 指针指向一块内存,它的内容是所指内存的地址:引用 ...

  7. Cocoapods的安装报错 - Error installing pods:activesupport requires Ruby version >=2.2.2

    1.打开终端 2 移除现有 Ruby 默认源 输入以下指令 $gem sources --remove https://rubygems.org/ 3.使用新的源 输入以下指令 $gem source ...

  8. NYOJ题目889求距离

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAsYAAAJ2CAIAAADTwNOXAAAgAElEQVR4nO3dPVLrSteG4W8S5B4IsQ

  9. WPF控件

    1:内容控件(Content Controls)2:条目控件(Items Controls)3:文本控件(Text Controls)4:范围控件(Range Controls) 一:内容控件 内容控 ...

  10. css控制文字显示长度,超过用省略号替代

    .line_text { width:200px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } <span cl ...