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

Netty HTTP+XML协议栈开发

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

场景设计

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

订购请求消息定义如表:

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

客户信息定义如表:

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

地址信息定义如表:

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

邮递方式定义如表:

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

HTTP+XML协议栈设计

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定义文件。

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

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

JiBx的编译命令,它的作用是根据绑定文件和POJO对象的映射关系和规则动态修改POJO类

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

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

 代码示例:

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协议开发应用-HTTP&XML协议栈开发

服务端解码后的业务对象输出

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]

客户端响应消息码流输出:

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

客户端解码后的业务对象输出

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] 

 

上一篇:上课摸鱼必备 -- Vscode网页版的搭建教程


下一篇:Spring Data + Thymeleaf 3 + Bootstrap 4 实现分页器