HTTP协议开发应用-HTTP&XML协议栈开发

     由于HTTP协议的通用性,很多异构系统间的通信交互采用HTTP协议,通过HTTP协议承载业务数据进行消息交互,例如非常流行的HTTP+XML或者RESTful+JSON。

 场景设计

       模拟一个简单的用户订购系统。客户端填写订单,通过HTTP客户端向服务端发送订购请求,请求消息放在HTTP消息体中,以XML承载,即采用HTTP+XML的方式进行通信。HTTP服务端接收到订购请求后,对订单请求进行修改,然后通过HTTP+XML的方式返回应答消息。双方采用HTTP1.1协议,连接类型为CLOSE方式,即双方交互完成,由HTTP服务端主动关闭链路,随后客户端也关闭链路并退出。

 image.png

   首先对订购流程图进行分析:

          先看步骤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对象进行编排。

        

1.HTTP + XML请求编码类:

        对于上层业务侧,构造订购请求消息后,以HTTP+XML协议将消息发送给服务端,如果要实现对业务零侵入或者尽可能少的侵入,协议层和应用层应该解耦。

       考虑到HTTP+XML协议栈需要一定的定制扩展能力,例如通过HTTP消息头携带业务自定义字段,所以,应该允许业务利用Netty的HTTP协议栈接口自行构造私有的HTTP消息头。

      HTTP+XML的协议编码仍然采用ChannelPipeline中增加对应的编码handler类实现。

     下面我们来一起看下HTTP+XML请求消息编码类的源码实现

public class HttpXmlRequestEncoder extends
		AbstractHttpXmlEncoder<HttpXmlRequest> {

	@Override
	protected void encode(ChannelHandlerContext ctx, HttpXmlRequest msg,
			List<Object> out) throws Exception {

		// 调用父类的encode0方法将Order对象转换为xml字符串,并将其封装为ByteBuf
		ByteBuf body = encode0(ctx, msg.getBody());
		FullHttpRequest request = msg.getRequest();
		// 如request为空,则新建一个FullHttpRequest对象,并将设置消息头
		if (request == null) {
			// 在构造方法中,将body设置为请求消息体
			request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
					HttpMethod.GET, "/do", body);
			HttpHeaders headers = request.headers();
			// 表示请求的服务器网址
			headers.set(HttpHeaderNames.HOST, InetAddress.getLocalHost()
					.getHostAddress());
			// Connection表示客户端与服务连接类型;Keep-Alive表示长连接;CLOSE表示短连接
			// header中包含了值为close的connection,都表明当前正在使用的tcp链接在请求处理完毕后会被断掉。
			 // 以后client再进行新的请求时就必须创建新的tcp链接了。  
            headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);  
            // 浏览器支持的压缩编码是 gzip 和 deflate  
            headers.set(HttpHeaderNames.ACCEPT_ENCODING,  
                    HttpHeaderValues.GZIP.toString() + ',' + HttpHeaderValues.DEFLATE.toString());  
            
            // 浏览器支持的解码集  
            headers.set(HttpHeaderNames.ACCEPT_CHARSET, "ISO-8859-1,utf-8;q=0.7,*;q=0.7");  
            
            // 浏览器支持的语言  
            headers.set(HttpHeaderNames.ACCEPT_LANGUAGE, "zh");        
            // 使用的用户代理是 Netty xml Http Client side  
            headers.set(HttpHeaderNames.USER_AGENT, "Netty xml Http Client side");         
            // 浏览器支持的 MIME类型,优先顺序为从左到右  
            headers.set(HttpHeaderNames.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");          
		}
		// 由于此处没有使用chunk方式,所以要设置消息头中设置消息体的CONTENT_LENGTH     
            request.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, body.readableBytes());  
        // 将请求消息添加进out中,待后面的编码器对消息进行编码  
        out.add(request);  
	}
}

      1.首先调用父类的encode0,将业务需要发送的POJO对象Order实例通过XStream序列化为XML字符串,随后将它封装成Netty的ByteBuf。对消息头进行判断,如果业务侧自定义和定制了消息头,则使用业务侧设置的HTTP消息头,如果业务侧没有设置,则构造新的HTTP消息头。 

     2. 用来构造和设置默认的HTTP消息头,由于通常情况下HTTP通信双方更关注消息体本身,所以这里采用了硬编码的方式,如果要产品化,可以做成XML配置文件,允许业务自定义配置,以提升定制的灵活性。

     3.  由于请求消息消息体不为空,也没有使用Chunk方式,所以在HTTP消息头中设置消息体的长度Content-Length,完成消息体的XML序列化后将重新构造的HTTP请求消息加入到out中,由后续Netty的HTTP请求编码器继续对HTTP请求消息进行编码。

  下面我们来看父类AbstractHttpXmlEncoder的实现。

public abstract class AbstractHttpXmlEncoder<T> extends
		MessageToMessageEncoder<T> {

	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流
		XStream xStream = new XStream();
		xStream.setMode(XStream.NO_REFERENCES);
		xStream.autodetectAnnotations(true); 
		
		String xml = xStream.toXML(body);
		ByteBuf encodeBuf = Unpooled.copiedBuffer(xml, UTF_8);
		return encodeBuf;
	}
	
	

	@Skip
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		System.out.println("fail to encode");
	}

}


public class HttpXmlRequest {

	private FullHttpRequest request;
	private Object body;

	public HttpXmlRequest(FullHttpRequest request, Object body) {
		this.request = request;
		this.body = body;
	}

	public final FullHttpRequest getRequest() {
		return request;
	}

	public final void setRequest(FullHttpRequest request) {
		this.request = request;
	}

	public final Object getBody() {
		return body;
	}

	public final void setBody(Object body) {
		this.body = body;
	}

	@Override
	public String toString() {
		return "HttpXmlRequest [request=" + request + ", body =" + body + "]";
	}
}

2.HTTP+XML请求消息解码类

    HTTP服务端接收到HTTP+XML请求消息后,需要从HTTP消息体中获取请求码流,通过 XStream 框架对它进行反序列化,得到请求POJO对象,然后对结果进行封装,回调到业务handler对象,业务得到的就是解码后的POJO对象和HTTP消息头。

public class HttpXmlRequestDecoder extends AbstractHttpXmlDecoder<FullHttpRequest> {

	public HttpXmlRequestDecoder(boolean isPrint) {
		super(isPrint);
	}

	@Override
	protected void decode(ChannelHandlerContext ctx, FullHttpRequest request,
			List<Object> out) throws Exception {
		// 返回客户端错误信息
		if (!request.decoderResult().isSuccess()) {
			sendError(ctx, HttpResponseStatus.BAD_REQUEST);
			return;
		}
		HttpXmlRequest httpXmlRequest = new HttpXmlRequest(request, decode0(ctx, request.content()));
		// 将请求交给下一个解码器处理
		out.add(httpXmlRequest);
	}

	private static void sendError(ChannelHandlerContext ctx,
			HttpResponseStatus status) {
		FullHttpResponse response = new DefaultFullHttpResponse(
				HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer("Failure: "
						+ status.toString() + "\r\n", CharsetUtil.UTF_8));
		response.headers().set(HttpHeaderNames.CONTENT_TYPE,
				"text/plain; charset=UTF-8");
		ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
	}

}

public abstract class AbstractHttpXmlDecoder<T> extends
		MessageToMessageDecoder<T> {

	// 是否输出码流的标志,默认为false
	private boolean isPrint;
	private final static String CHARSET_NAME = "UTF-8";
	private final static Charset UTF_8 = Charset.forName(CHARSET_NAME);

	// 当调用这个构造方法是,默认设置isPrint为false
	protected AbstractHttpXmlDecoder() {
		this(false);
	}

	protected AbstractHttpXmlDecoder( boolean isPrint) {
		this.isPrint = isPrint;
	}

	protected Object decode0(ChannelHandlerContext arg0, ByteBuf body)
			throws Exception {
		
		String content = body.toString(UTF_8);  
		
		 if (isPrint)  
	            System.out.println("The body is : " + content);  
		 
		 XStream xs = new XStream();  
	     xs.setMode(XStream.NO_REFERENCES);  
	   //  xs.autodetectAnnotations(true);
	     xs.processAnnotations(new Class[] { Order.class, Customer.class, Shipping.class, Address.class });
	     // 注册使用了注解的VO  
	     Object result = xs.fromXML(content);  
		 System.out.println("decode0 xml => "+result );
		 
		 return result;
	}

	@Skip
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception {
		
	}

}

3.HTTP+XML响应消息编码类

      对于响应消息,用户可能并不关心HTTP消息头之类的,它将业务处理后的POJO对象丢给HTTP+XML协议栈,由基础协议栈进行后续的处理。为了降低业务的定制开发难度,我们首先封装一个全新的HTTP XML应答对象,它的实现如下。

    HTTP+XML 应答消息编码类 HttpXmlResponseEncoder

    它的实现非常简单,对应答消息进行判断,如果业务侧已经构造了HTTP应答消息,则利用业务已有应答消息重新复制一个新的HTTP应答消息。无法重用业务侧自定义HTTP应答消息的主要原因,是Netty的DefaultFullHttpResponse没有提供动态设置消息体content的接口,只能在第一次构造的时候设置内容。由于这个局限,导致我们的实现有点麻烦。

作为示例程序并没有提供更多的API供业务侧灵活设置HTTP应答消息头,在实际商用时,可以基于本书提供的基础协议栈进行扩展。

设置消息体内容格式为“text/xml”,然后在消息头中设置消息体的长度。

把编码后的DefaultFullHttpResponse对象添加到编码结果列表中,由后续Netty的HTTP编码类进行二次编码。

4.HTTP+XML应答消息解码

   客户端接收到HTTP+XML应答消息后,对消息进行解码,获取HttpXmlResponse对象,源码如下。

public class HttpXmlResponse {

	public HttpXmlResponse(FullHttpResponse response, Object body) {
		super();
		this.response = response;
		this.body = body;
	}

	private FullHttpResponse response;
	
	private Object body;

	public FullHttpResponse getResponse() {
		return response;
	}

	public void setResponse(FullHttpResponse response) {
		this.response = response;
	}

	public Object getBody() {
		return body;
	}

	public void setBody(Object body) {
		this.body = body;
	}

}

   HTTP+XML 应答消息解码类 HttpXmlResponseDecoder

public class HttpXmlResponseEncoder extends AbstractHttpXmlEncoder<HttpXmlResponse> {

	@Override
	protected void encode(ChannelHandlerContext ctx, HttpXmlResponse msg,
			List<Object> out) throws Exception {
		
		// 调用父类的encode0方法将Order对象转换为xml字符串,并将其封装为ByteBuf
		ByteBuf body = encode0(ctx, msg.getBody());
		
		FullHttpResponse response = msg.getResponse();
		if(response==null){
			response=new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,body);
		}else{
			response=new DefaultFullHttpResponse(msg.getResponse().protocolVersion(), msg.getResponse().status(),body);
		}
		response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/xml");
		// 由于此处没有使用chunk方式,所以要设置消息头中设置消息体的CONTENT_LENGTH  
		response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, body.readableBytes());  
		out.add(response);
	}  
	
	

}

    通过DefaultFullHttpResponse和HTTP应答消息反序列化后的POJO对象构造HttpXmlResponse,并将其添加到解码结果列表中。

5.HTTP+XML客户端开发

客户端的功能如下。

    (1)发起HTTP连接请求;

    (2)构造订购请求消息,将其编码成XML,通过HTTP协议发送给服务端;

    (3)接收HTTP服务端的应答消息,将XML应答消息反序列化为订购消息POJO对象;

    (4)关闭HTTP连接。

基于它的功能定位,我们首先开始主程序的开发。

/**
 * http + xml 协议栈
 * 
 * @author Tony
 * 
 */
public class HttpXmlClient {

	public void connect(int port) throws Exception {

		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 {
							// xml解码器
							ch.pipeline().addLast("http-decoder",new HttpResponseDecoder());
							ch.pipeline().addLast("http-aggregator",new HttpObjectAggregator(65536));
							ch.pipeline().addLast("xml-decoder",new HttpXmlResponseDecoder(true));
							ch.pipeline().addLast("http-encoder",new HttpRequestEncoder());
							// xml编码器
							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 {
			group.shutdownGracefully();
		}
	}
	
	public static void main(String[] args) {
		HttpXmlClient httpXmlClient=new HttpXmlClient();
		try {
			httpXmlClient.connect(8080);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}


在ChannelPipeline中新增了HttpResponseDecoder,它负责将二进制码流解码成为HTTP的应答消息;
新增了HttpObjectAggregator,它负责将1个HTTP请求消息的多个部分合并成一条完整的HTTP消息;
将前面开发的XML解码器HttpXmlResponseDecoder添加到ChannelPipeline中,
它有两个参数,分别是解码对象的类型信息和码流开关,这样就实现了HTTP+XML应答消息的自动解码。
将HttpRequestEncoder编码器添加到ChannelPipeline中时,需要注意顺序,
编码的时候是按照从尾到头的顺序调度执行的,它后面放的是我们自定义开发的HTTP+XML请求消息编码器HttpXmlRequestEncoder。
最后是业务的逻辑编排类HttpXmlClientHandle,我们继续分析它的实现。


public class HttpXmlClientHandle extends SimpleChannelInboundHandler<HttpXmlResponse> {

	@Override
	public void channelActive(ChannelHandlerContext ctx) {
		// 给客户端发送请求消息,HttpXmlRequest包含FullHttpRequest和Order这个了类
		
		Long orderID=new Random().nextLong();
		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);  
		
		HttpXmlRequest request = new HttpXmlRequest(null,order);
		ctx.writeAndFlush(request);
	}
	

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		ctx.close();
	}

	@Override
	protected void messageReceived(ChannelHandlerContext ctx,
			HttpXmlResponse msg) throws Exception {
		System.out.println("The client receive response of http header is : "
				+ msg.getResponse().headers().names());
		System.out.println("The client receive response of http body is : "
				+ msg.getBody());
	}
}

        客户端的实现非常简单,造HttpXmlRequest对象,调用ChannelHandlerContext的writeAndFlush发送HttpXmlRequest。

用于接收服务端的应答消息,从接口看,它接收到的已经是自动解码后的HttpXmlResponse对象了;将应答POJO消息打印出来,可以与服务端发送的原始对象进行比对,两者的内容将完全一致。

6.HTTP+XML服务端开发

HTTP服务端的功能如下。

    (1)接收HTTP客户端的连接;

    (2)接收HTTP客户端的XML请求消息,并将其解码为POJO对象;

    (3)对POJO对象进行业务处理,构造应答消息返回;

    (4)通过HTTP+XML的格式返回应答消息;

    (5)主动关闭HTTP连接。

下面我们首先看下服务端监听主程序的实现。

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<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel ch)
								throws Exception {
							ch.pipeline().addLast("http-decoder",new HttpRequestDecoder()); //HTTP 请求消息解码器
							 // 聚合器,把多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse  
							ch.pipeline().addLast("http-aggregator",new HttpObjectAggregator(65536)); 
							//XML协议解码器
							ch.pipeline().addLast("xml-decoder",new HttpXmlRequestDecoder(true));
							 // 服务端,对响应编码  
							ch.pipeline().addLast("http-encoder",new HttpResponseEncoder());
							 // 服务端,对XML响应编码
							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) {
		HttpXmlServer httpXmlServer=new HttpXmlServer();
		try {
			httpXmlServer.run(8080);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

HTTP服务端的启动与之前一样,在此不再详述,我们具体看下编解码handler是如何设置的。

用于绑定HTTP请求消息解码器;

将我们自定义的HttpXml RequestDecoder添加到HTTP解码器之后;

添加自定义的HttpXmlResponseEncoder编码器用于响应消息的编码。

下面我们继续看HttpXmlServerHandler的实现

public class HttpXmlServerHandler extends SimpleChannelInboundHandler<HttpXmlRequest> {

	@Override
	protected void messageReceived(final ChannelHandlerContext ctx,
			HttpXmlRequest xmlRequest) throws Exception {
		HttpRequest request = xmlRequest.getRequest();
		Order order = (Order) xmlRequest.getBody();
		// 输出解码获得的Order对象
		System.out.println("Http server receive request : " + order);
		dobusiness(order);
		System.out.println(order);

		ChannelFuture future = ctx.writeAndFlush(new HttpXmlResponse(null,
				order));
		
		 if(!HttpHeaderUtil.isKeepAlive(request)){
				future.addListener(new GenericFutureListener<Future<? super Void>>() {
					public void operationComplete(Future future) throws Exception {
						ctx.close();
					}
				});
		 }
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception {
		cause.printStackTrace();
		// 在链路没有关闭并且出现异常的时候发送给客户端错误信息
		if (ctx.channel().isActive()) {
			sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
		}
	}

	private static void sendError(ChannelHandlerContext ctx,
			HttpResponseStatus status) {
		FullHttpResponse response = new DefaultFullHttpResponse(
				HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer("失败: "
						+ status.toString() + "\r\n", CharsetUtil.UTF_8));
		response.headers().set(HttpHeaderNames.CONTENT_TYPE,
				"text/plain; charset=UTF-8");
		ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
	}

	private void dobusiness(Order order) {
		order.getCustomer().setFirstName("狄");
		order.getCustomer().setLastName("仁杰");
		List<String> midNames = new ArrayList<String>();
		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);
	}

}

     通过messageReceived的方法入参HttpXmlRequest,可以看出服务端业务处理类接收到的已经是解码后的业务消息了。

     获取请求消息对象;随后将它打印出来,可以与客户端发送的原始消息进行对比;

    对订购请求消息进行业务逻辑编排;

   发送应答消息,并且在发送成功之后主动关闭HTTP连接。

     在发生异常并且链路没有关闭的情况下,构造内部异常消息发送给客户端,发送完成之后关闭HTTP链路。

      到此,HTTP+XML的协议栈开发工作全部完成,下个小节我们看下运行结果。

测试:

1.服务端

服务端接收到的请求消息码流打印如下。

HTTP订购服务器启动,网址是 :  http://localhost:8080
The body is : <order orderNumber="8067699050259646116" total="9999.999">
  <customer customerNumber="8067699050259646116">
    <firstName>李</firstName>
    <lastName>林峰</lastName>
  </customer>
  <billTo>
    <street1>龙眠大道</street1>
    <city>南京市</city>
    <state>江苏省</state>
    <postCode>123321</postCode>
    <country>中国</country>
  </billTo>
  <shipping>INTERNATIONAL_MAIL</shipping>
  <shipTo>
    <street1>龙眠大道</street1>
    <city>南京市</city>
    <state>江苏省</state>
    <postCode>123321</postCode>
    <country>中国</country>
  </shipTo>
</order>
decode0 xml => Order [orderNumber=8067699050259646116, customer=Customer [customerNumber=8067699050259646116, 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]
Http server receive request : Order [orderNumber=8067699050259646116, customer=Customer [customerNumber=8067699050259646116, 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]
Order [orderNumber=8067699050259646116, customer=Customer [customerNumber=8067699050259646116, 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]

2.客户端

客户端接收到的响应消息体码流如下。

The body is : <order orderNumber="8067699050259646116" total="9999.999">
  <customer customerNumber="8067699050259646116">
    <firstName>狄</firstName>
    <lastName>仁杰</lastName>
    <middleNames>
      <string>李元芳</string>
    </middleNames>
  </customer>
  <billTo>
    <street1>龙眠大道</street1>
    <city>洛阳</city>
    <state>河南道</state>
    <postCode>123456</postCode>
    <country>大唐</country>
  </billTo>
  <shipping>INTERNATIONAL_MAIL</shipping>
  <shipTo>
    <street1>龙眠大道</street1>
    <city>洛阳</city>
    <state>河南道</state>
    <postCode>123456</postCode>
    <country>大唐</country>
  </shipTo>
</order>
decode0 xml => Order [orderNumber=8067699050259646116, customer=Customer [customerNumber=8067699050259646116, 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]
The client receive response of http header is : [content-length, content-type]
The client receive response of http body is : Order [orderNumber=8067699050259646116, customer=Customer [customerNumber=8067699050259646116, 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+XML协议栈功能正常,达到了设计预期。

发表评论