【九】Netty HTTP+XML协议栈开发

news2024/11/23 13:02:58

HTTP协议

  • 介绍
  • 业务场景
  • 流程图
  • 技术栈设计
    • 流程图的分析:
      • Step 1
      • Step2
      • Step3
      • Step4
      • Step5
      • 分析结果
  • 开发
    • 开发流程图
    • 代码
      • jar 依赖
      • 代码结构如图
      • pojo 包
      • request包
      • response 包
      • client 包
      • server包
      • 编码解码基类
      • 代码说明
  • 测试
    • 服务端打印结果
      • 客户端打印结果
  • 总结

介绍

由于HTTP协议的通用性,很多异构系统间的通信交互采用HTTP协议,通过HTTP协议承载业务数据进行消息交互,例如非常流行的HTTP+XML 或者 RESTful +JSON。在Java领域,最常用的HTTP协议栈就是基于Servlet规范的Tomcat Web容器和Jetty等轻量级容器。但是,很多基于HTTP的应用都是后台应用,HTTP仅仅是承载数据交换的一个通道,是一个载体而不是容器,在这种场景下,一般不需要类似Tomcat这样重量型Web容器。
下面我们就利用Netty提供的基础HTTP协议栈功能,来扩展开发HTTP+XML协议栈。

业务场景

模拟简单的用户订购系统。客户端填写订单,通过HTTP客户端像服务端发送订购请求,请求消息放在HTTP消息体中,以XML承载,即采用 HTTP+XML方式进行通信。HTTP服务端接收到订购请求后,对订单进行修改,然后通过HTTP+XML的方式返回应答消息。双方采用HTTP1.1协议。

流程图

在这里插入图片描述

技术栈设计

流程图的分析:

Step 1

构造订购请求消息并将其编码为HTTP+XML形式.Netty 的HTTP协议栈提供了构造HTTP请求消息的相关接口。但是无法将普通的POJO对象转换为HTTP+XML 的HTTP请求消息。需要自定义开发HTTP+XML 格式的消息编码器

Step2

利用Netty 的HTTP协议栈,可以支持HTTP链路的建立和请求消息的发送。Netty支持,不需要开发

Step3

HTTP服务端要将HTTP+XML 格式的订购请求消息解码为订购请求的POJO对象。同时获取HTTP请求消息头信息。利用Netty的HTTP 请求消息的解码。但是,如果消息体为XML格式,Netty 无支持将其解码为POJO对象,需要在Netty协议栈的基础上面进行开发。

Step4

服务端对订购请求消息处理完成后,重新将其封装成功XML。通过HTTP应答消息体携带给客户端。Netty的HTTP协议栈不支持直接将POJO对象的应答消息以XML方式发送。需要自定义开发。

Step5

HTTP 客户端需要将HTTP+XML格式的应答消息解码为订购POJO对象,同时能够获取应答消息的HTTP头消息。Netty的协议栈不支持自动的消息解码。需要自定义开发。

分析结果

对于HTTP的编码和解码。Netty已经实现了,我们可以直接拿来用。
如果不清楚Netty 实现HTTP服务,可以查看相关的博客文章:
https://blog.csdn.net/echohuangshihuxue/article/details/128634868;
https://blog.csdn.net/echohuangshihuxue/article/details/128618860;
https://blog.csdn.net/echohuangshihuxue/article/details/128632486;

但是HTTP+XML 请求消息和应答消息的解码和编码,目前Netty 不支持,需要我们手动开发。
下面我们就是自己开发 这些编码器和解码器来实现之前的业务场景。

开发

有了之前的分析,我们现在的开发重点其实就是开发出对应的编码器和解码器。然后实现HTTP+XML 的自动编码和解码功能。整个流程就能在Netty 框架的基础上面实现了。

开发流程图

在这里插入图片描述

代码

jar 依赖

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId> <!-- Use 'netty5-all' for 5.0-->
            <version>5.0.0.Alpha1</version>
            <scope>compile</scope>
        </dependency>

      <dependency>
            <groupId>org.jboss.marshalling</groupId>
            <artifactId>jboss-marshalling</artifactId>
            <version>1.4.11.Final</version>
        </dependency>

        <dependency>
            <groupId>org.jboss.marshalling</groupId>
            <artifactId>jboss-marshalling-serial</artifactId>
            <version>1.4.11.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jibx</groupId>
            <artifactId>jibx-run</artifactId>
            <version>1.4.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.jibx/jibx-extras -->
        <dependency>
            <groupId>org.jibx</groupId>
            <artifactId>jibx-extras</artifactId>
            <version>1.4.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.jibx/jibx-bind -->
        <dependency>
            <groupId>org.jibx</groupId>
            <artifactId>jibx-bind</artifactId>
            <version>1.4.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.jibx/jibx-tools -->
        <dependency>
            <groupId>org.jibx</groupId>
            <artifactId>jibx-tools</artifactId>
            <version>1.4.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.jibx/jibx-schema -->
        <dependency>
            <groupId>org.jibx</groupId>
            <artifactId>jibx-schema</artifactId>
            <version>1.4.2</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.apache.bcel/bcel -->
        <dependency>
            <groupId>org.apache.bcel</groupId>
            <artifactId>bcel</artifactId>
            <version>6.7.0</version>
        </dependency>

代码结构如图

在这里插入图片描述

pojo 包

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;


    public String getStreet1() {
        return street1;
    }

    public void setStreet1(String street1) {
        this.street1 = street1;
    }

    public String getStreet2() {
        return street2;
    }

    public void setStreet2(String street2) {
        this.street2 = street2;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getPostCode() {
        return postCode;
    }

    public void setPostCode(String postCode) {
        this.postCode = postCode;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    @Override
    public String toString() {
        return "Address{" +
                "street1='" + street1 + '\'' +
                ", street2='" + street2 + '\'' +
                ", city='" + city + '\'' +
                ", state='" + state + '\'' +
                ", postCode='" + postCode + '\'' +
                ", country='" + country + '\'' +
                '}';
    }
}

public class Customer {
    private long customerNumber;
    /**
     * Personal name
     */
    private String firstName;

    /**
     * Family name .
     */
    private String lastName;

    /**
     * Middle name(s) if any.
     */
    private List<String> middleNames;


    public long getCustomerNumber() {
        return customerNumber;
    }

    public void setCustomerNumber(long customerNumber) {
        this.customerNumber = customerNumber;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public List<String> getMiddleNames() {
        return middleNames;
    }

    public void setMiddleNames(List<String> middleNames) {
        this.middleNames = middleNames;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "customerNumber=" + customerNumber +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", middleNames=" + middleNames +
                '}';
    }
}
public class Order {
    private long orderNumber;
    private Customer customer;

    /**
     * Billing address information
     */
    private Address bilTo;

    private String shipping;

    /**
     * Shipping address information. If missing ,the billing address is also
     * used as the shipping address.
     */
    private Address shipTo;

    private Float total;

    public long getOrderNumber() {
        return orderNumber;
    }

    public void setOrderNumber(long orderNumber) {
        this.orderNumber = orderNumber;
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    public Address getBilTo() {
        return bilTo;
    }

    public void setBilTo(Address bilTo) {
        this.bilTo = bilTo;
    }

    public String getShipping() {
        return shipping;
    }

    public void setShipping(String shipping) {
        this.shipping = shipping;
    }

    public Address getShipTo() {
        return shipTo;
    }

    public void setShipTo(Address shipTo) {
        this.shipTo = shipTo;
    }

    public Float getTotal() {
        return total;
    }

    public void setTotal(Float total) {
        this.total = total;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderNumber=" + orderNumber +
                ", customer=" + customer +
                ", bilTo=" + bilTo +
                ", shipping=" + shipping +
                ", shipTo=" + shipTo +
                ", total=" + total +
                '}';
    }
}

public class OrderFactory {
    public static Order create(int number){
        Order order=new Order();
        //orderNumber
        order.setOrderNumber(number);
        //total
        order.setTotal(9999.999f);
        //customer
        Customer customer=new Customer();
        customer.setFirstName("echo");
        customer.setLastName("tong");
        List<String>list=new ArrayList<>();
        list.add("666");
       // list.add("555");
        customer.setCustomerNumber(123);
        //customer.setMiddleNames(list);
        order.setCustomer(customer);

        //billTo
        Address billTo=new Address();
        billTo.setStreet1("西乡大道");
        billTo.setCity("深圳市");
        billTo.setState("广东省");
        billTo.setPostCode("123321");
        billTo.setCountry("中国");
        order.setBilTo(billTo);
        //shipping
        order.setShipping(Shipping.INTERNATIONAL_MAIL);
        //shipTo
        Address shipTo=new Address();
        shipTo.setStreet1("西乡大道");
        shipTo.setCity("深圳市");
        shipTo.setState("广东省");
        shipTo.setPostCode("123321");
        shipTo.setCountry("中国");
        order.setShipTo(shipTo);


        return order;
    }
}
public class Shipping {
    public final static String STANDARD_MAIL="STANDARD_MAIL";
    public final static String PRIORITY_MAIL="PRIORITY_MAIL";
    public final static String INTERNATIONAL_MAIL="StringINTERNATIONAL_MAIL";
    public final static String DOMESTIC_EXPRESS="DOMESTIC_EXPRESS";
    public final static String INTERNATIONAL_EXPRESS="INTERNATIONAL_EXPRESS";
}

request包

/**
 * 包含两个成员变量
 * FullHttpRequest
 * 编码对象 Object
 */
public class HttpXmlRequest {
    private FullHttpRequest request;
    private Object body;

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

    public FullHttpRequest getRequest() {
        return request;
    }

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

    public Object getBody() {
        return body;
    }

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

    @Override
    public String toString() {
        return "HttpXmlRequest{" +
                "request=" + request +
                ", body=" + body +
                '}';
    }
}
public class HttpXmlRequestDecoder extends AbstractHttpXmlDecoder<FullHttpRequest> {
    public HttpXmlRequestDecoder(Class<?> clazz) {
        this(clazz,false);
    }

    public HttpXmlRequestDecoder(Class<?> clazz,boolean isprint){
        super(clazz,isprint);
    }



    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, FullHttpRequest request,
                          List<Object> list) throws Exception {
        //如果解码失败,返回400
        if (!request.getDecoderResult().isSuccess()){
            sendError(channelHandlerContext,HttpResponseStatus.BAD_REQUEST);
            return;
        }
        //通过反序列化得到的ByteBuf来构造 HttpXmlRequest对象。
        HttpXmlRequest request1=new HttpXmlRequest(request,
                //反序列化Request 里面的内容。
                decode0(channelHandlerContext,request.content()));
        //添加到解码结果list列表
        list.add(request1);
    }


    private static void sendError(ChannelHandlerContext context, HttpResponseStatus status){
        //构造错误返回体
        FullHttpResponse response=new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                status, Unpooled.copiedBuffer("Failure: "+status.toString()+"\r\n",
                CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE,"text/plain; charset=UTF-8");
        context.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
}
public class HttpXmlRequestEncoder extends AbstractHttpXmlEncoder<HttpXmlRequest> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext,
                          HttpXmlRequest httpXmlRequest, List<Object> list) throws Exception {
        //调用父类的encode0,将业务需要发生的POJO对象Order实例通过 JiBx 序列化为XML,
        //随后转换为Netty的ByteBuf对象
        ByteBuf body = encode0(channelHandlerContext, httpXmlRequest.getBody());
        FullHttpRequest request = httpXmlRequest.getRequest();
        //request 如果为空,初始化一个request,并设置请求头
        if (request == null) {
            //此处采用了硬编码方式,如果要做成产品话,可以做成XML配置文件,
            // 允许业务自定义配置,提升定制的灵活性
            request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/do", body);
            HttpHeaders httpHeaders = request.headers();
            //设置host
            httpHeaders.set(HttpHeaders.Names.HOST, InetAddress.getLocalHost());
            //设置客户端可接受的内容编码
            httpHeaders.set(HttpHeaders.Names.ACCEPT_ENCODING,
                    HttpHeaders.Values.GZIP.toString() + "," + HttpHeaders.Values.DEFLATE.toString());
            //设置客户端可接受的字符集
            httpHeaders.set(HttpHeaders.Names.ACCEPT_CHARSET, "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
            //设置客户端可接受的语言
            httpHeaders.set(HttpHeaders.Names.ACCEPT_LANGUAGE, "zh");
            //设置 客户端的操作系统
            httpHeaders.set(HttpHeaders.Names.USER_AGENT, "Netty xml Http Client side");
            //设置客户端接收哪些类型
            httpHeaders.set(HttpHeaders.Names.ACCEPT,
                    "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
        }
        //设置请求字节长度
        //由于请求消息消息体不为空,也没有使用Chunk方式,所以在HTTP消息头中设置消息体的长度Content-Length.
        HttpHeaders.setContentLength(request, body.readableBytes());
        //完成消息体的XML序列化后将重新构造的HTTP请求消息加入到list,
        // 由后续Netty的HTTP请求编码器继续对HTTP请求消息进行编码
        list.add(request);
    }
}

response 包

public class HttpXmlResponse {
    private FullHttpResponse httpResponse;
    private Object result;

    public HttpXmlResponse(FullHttpResponse httpResponse, Object result) {
        this.httpResponse = httpResponse;
        this.result = result;
    }

    public FullHttpResponse getHttpResponse() {
        return httpResponse;
    }

    public void setHttpResponse(FullHttpResponse httpResponse) {
        this.httpResponse = httpResponse;
    }

    public Object getResult() {
        return result;
    }

    public void setResult(Object result) {
        this.result = result;
    }

    @Override
    public String toString() {
        return "HttpXmlResponse{" +
                "httpResponse=" + httpResponse +
                ", result=" + result +
                '}';
    }
}
public class HttpXmlResponseDecoder extends AbstractHttpXmlDecoder<DefaultFullHttpResponse> {

    public HttpXmlResponseDecoder(Class<?> clazz){
        this(clazz,false);
    }

    public HttpXmlResponseDecoder(Class<?> clazz,boolean isPrint){
        super(clazz,isPrint);
    }
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext,
                          DefaultFullHttpResponse defaultFullHttpResponse,
                          List<Object> list) throws Exception {
        //1.利用基类的decode0方法 解码响应信息
        //2.构造HttpXmlResponse 对象
        //3.添加到解码结果列表中。供后续继续解码
        HttpXmlResponse response =new HttpXmlResponse(defaultFullHttpResponse,
                decode0(channelHandlerContext,defaultFullHttpResponse.content()));
        list.add(response);
    }
}
public class HttpXmlResponseEncoder extends AbstractHttpXmlEncoder<HttpXmlResponse> {
    @Override
    protected void encode(ChannelHandlerContext context,
                          HttpXmlResponse responseMsg, List<Object> list) throws Exception {
        ByteBuf body = encode0(context, responseMsg.getResult());
        FullHttpResponse response = responseMsg.getHttpResponse();
        if (response == null) {
            //如果业务侧没有构造HttpResponse,则构造一个
            response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, body);
        } else {
            //如果业务侧已经构造了一个,但是由于 Netty的DefaultFullResponse 没有提供动态设置消息体content的接口。
            //只能在第一次构造的时候设置content。
            // 所以现在为了添加内容,只能是舍弃原先的FullHttpResponse,重新再构造一个。
            response = new DefaultFullHttpResponse(responseMsg.getHttpResponse().getProtocolVersion(),
                    responseMsg.getHttpResponse().getStatus(), body);
        }
        //消息头设置消息体内容格式
        response.headers().set(CONTENT_TYPE,"text/html");
        //消息头中设置消息体的长度
        setContentLength(response,body.readableBytes());
        //将编码后的DefaultFullHttpResponse 对象添加到编码结果列表中,
        //由后续Netty的HTTP编码类进行二次编码
        list.add(response);
    }
}

client 包

public class HttpXmlClient {

    public void connect(String host,int port){
        EventLoopGroup loopGroup=new NioEventLoopGroup();
        try {
            Bootstrap bootstrap=new Bootstrap();
            bootstrap.group(loopGroup)
                    .option(ChannelOption.TCP_NODELAY,true)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                                // HttpRequestEncoder 负责将二进制码流解码成为HTTP的应答消息
                                socketChannel.pipeline().
                                        addLast("http-decoder",new HttpResponseDecoder());
                                //HttpObjectAggregator 负责将一个HTTP请求消息的多个部分合并成一条完整的HTTP信息
                                socketChannel.pipeline()
                                        .addLast("http-aggregator",new HttpObjectAggregator(65536));
                                //xml解码器,实现HTTP+XML应答消息的自动解码
                                socketChannel.pipeline()
                                        .addLast("xml-decoder",new HttpXmlResponseDecoder(Order.class,true));
                                // HttpRequestEncoder 将POJO对象 编码为 HTTP接收的消息
                                socketChannel.pipeline()
                                        .addLast("http-encoder",new HttpRequestEncoder());
                                //HttpXmlRequestEncoder xml 编码器,实现HTTP-XML 请求消息的自动编码
                                socketChannel.pipeline()
                                        .addLast("xml-encoder",new HttpXmlRequestEncoder());
                                //自定义的业务逻辑处理类
                                socketChannel.pipeline()
                                        .addLast(new HttpXmlClientHandler());
                        }
                    });
            //发起异步链接操作
            ChannelFuture future=bootstrap.connect(host,port).sync();
            System.out.println("HTTP-XML client is stated..... and do connect with server ");
            //等待客户端链路关闭
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //优雅退出,释放NIO线程组.
            loopGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        int port=8080;
        new HttpXmlClient().connect("127.0.0.1",port);
    }
}
public class HttpXmlClientHandler extends SimpleChannelInboundHandler<HttpXmlResponse> {

    public void channelActive(ChannelHandlerContext context){
        //链接成功的时候,给服务端发送HttpXmlRequest对象
        //编码器会自动完成编码
        HttpXmlRequest request=new HttpXmlRequest(null, OrderFactory.create(123));
        context.writeAndFlush(request);
    }


    @Override
    protected void messageReceived(ChannelHandlerContext channelHandlerContext, HttpXmlResponse response) throws Exception {
        //此时接收到的信息 已经是自动解码后的HttpXmlResponse对象了
        System.out.println("The client receive response of http header is : "+response.getHttpResponse().headers().names());
        System.out.println("The client receive response of http body is : "+response.getResult());
    }
}

server包

public class HttpXmlServer {

    public void run(int port){
        EventLoopGroup boosGroup=new NioEventLoopGroup();
        EventLoopGroup workerGroup=new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap=new ServerBootstrap();
            bootstrap.group(boosGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //HttpRequestDecoder 负责将二进制码流解码成为HTTP的请求消息
                            socketChannel.pipeline()
                                    .addLast("http-decoder",new HttpRequestDecoder());
                            //HttpObjectAggregator 负责将一个HTTP请求消息的多个部分合并成一条完整的HTTP信息
                            socketChannel.pipeline()
                                    .addLast("http-aggregator",new HttpObjectAggregator(655326));
                            //xml解码器,实现HTTP+XML请求消息的自动解码
                            socketChannel.pipeline()
                                    .addLast("xml-decoder",new HttpXmlRequestDecoder(Order.class,true));
                            //HttpResponseEncoder 将应答消息编码为HTTP消息
                            socketChannel.pipeline()
                                    .addLast("http-encoder",new HttpResponseEncoder());
                            //HttpXmlResponseEncoder 将HTTP 实现HTTP+XML请求消息的自动编码
                            socketChannel.pipeline()
                                    .addLast("xml-encoder",new HttpXmlResponseEncoder());
                            //业务逻辑处理类
                            socketChannel.pipeline()
                                    .addLast(new HttpXmlServerHandler());
                        }
                    });
            ChannelFuture future=bootstrap.bind(port).sync();
            System.out.println("HTTP-XML Server is started,waiting for client to connect..");
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            workerGroup.shutdownGracefully();
            boosGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new HttpXmlServer().run(8080);
    }

}
public class HttpXmlServerHandler extends SimpleChannelInboundHandler<HttpXmlRequest> {
    @Override
    protected void messageReceived(ChannelHandlerContext channelHandlerContext, HttpXmlRequest httpXmlRequest) throws Exception {
       //可以看出服务端业务逻辑处理类接收到的已经是解码后的业务消息
        HttpRequest request=httpXmlRequest.getRequest();
        Order order=(Order) httpXmlRequest.getBody();
        System.out.println("Http server receive request : "+order);
        //修改order
       // doBusiness(order);
        //发送修改后的order 数据
      //  ChannelFuture future=
        channelHandlerContext.
                writeAndFlush(new HttpXmlResponse(null,order));/*.
                addListener(ChannelFutureListener.CLOSE);*/
        System.out.println("server  send data complete..");
        //if ()
    }

    private void doBusiness(Order order){
        order.getCustomer().setFirstName("张");
        order.getCustomer().setLastName("飞");
        List<String> midNames=new ArrayList<String >();
        midNames.add("关羽");
        midNames.add("刘备");
        order.getCustomer().setMiddleNames(midNames);

       order.getBilTo().setCity("荆州");
       order.getBilTo().setCountry("东汉末年");
        order.getBilTo().setState("war");
        order.getBilTo().setPostCode("654321");


        order.getShipTo().setCity("荆州");
        order.getShipTo().setCountry("东汉末年");
        order.getShipTo().setState("war");
        order.getShipTo().setPostCode("654321");
    }

    public void exceptionCaught(ChannelHandlerContext context,Throwable cause){
        if (context.channel().isActive()){
            sendError(context,HttpResponseStatus.INTERNAL_SERVER_ERROR);
        }
    }

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

编码解码基类

public abstract class AbstractHttpXmlDecoder<T> extends MessageToMessageDecoder<T> {
    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 context, ByteBuf body) throws JiBXException {
        factory = BindingDirectory.getFactory(clazz);
        //从消息体中获取码流
        String content = body.toString(UTF_8);
        //通过isPrint 来判断是否打印消息体,主要是为了方便定位问题
        if (isPrint) {
            System.out.println("The body is : " + content);
        }

        reader = new StringReader(content);
        //通过JiBx 类库将XML转换为POJO对象。
        IUnmarshallingContext unmarshallingContext = factory.createUnmarshallingContext();
        Object result = unmarshallingContext.unmarshalDocument(reader);
        //释放资源
        reader.close();
        reader = null;
        return result;
    }


    public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
        //释放资源
        if (reader != null) {
            reader.close();
            reader = null;
        }
    }

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

    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 contextReq, Object body) throws JiBXException, IOException {
        factory = BindingDirectory.getFactory(body.getClass());
        writer = new StringWriter();
        IMarshallingContext context = factory.createMarshallingContext();
        context.setIndent(2);
        //将业务POJO(Order)类实例化为XML字符串。
        context.marshalDocument(body, CHARSET_NAME, null, writer);
        String xmlStr = writer.toString();
        writer.close();
        writer = null;
        //将字符串包装成Netty的ByteBuf并返回,实现了HTTP请求消息的XML编码
        ByteBuf encodeBuf = Unpooled.copiedBuffer(xmlStr, UTF_8);
        return encodeBuf;
    }

    public void exceptionCaught(ChannelHandlerContext context, Throwable throwable) throws IOException {
        //释放资源
        if (writer != null) {
            writer.close();
            writer = null;
        }
    }
}

代码说明

这里自定义开发的HTTP-XML 的编码器和解码器。主要是用到了 JiBX技术。它很方便实现XML和POJO对象的转变。
如果不清楚JiBX。可以查看博客学习。
https://blog.csdn.net/echohuangshihuxue/article/details/128653428

测试

服务端打印结果

在这里插入图片描述
成功的解码了客户端发送过来的HTTP-XML 消息并解码为POJO对象进行了打印。 然后构造HTTP-XML 消息,编码后发送给客户端。

客户端打印结果

在这里插入图片描述
客户端能成功的解码服务端发送过来的HTTP-XML消息。说明服务端的编码和客户端的解码都成功了。

目前代码里面注释写的比较详细了。大家可以先敲敲实现功能。如果还有疑问,可以对照开发流程图查看会比较清楚。
在这里插入图片描述

总结

本章重点介绍了如何利用Netty的HTTP协议栈开发基于HTTP的应用程序。尽管HTTP+XML协议栈是个高性能,通用的协议栈,但是,我们忽略一些细节,比如异常封装等。大家可以自己完善。如果有其他问题,欢迎讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/158380.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用js实现一个可以【放烟花】的小游戏

放烟花游戏需求&#x1f447;核心玩法&#x1f447;&#x1f447;界面原型&#x1f447;&#x1f447;成品演示&#x1f447;游戏开发1.游戏素材准备2.代码实现1.创建index.html页面2.首页-开始3.加载烟花音效4.重玩儿放烟花的小游戏。点击页面放烟花。兼容移动端。 游戏需求 …

作为普通网民,这些“实用电脑神器”,值得你知道

国内软件夹缝里求生存&#xff0c;由于某些不良软件&#xff0c;许多人对于国产软件认识多为“流氓、捆绑、多广告”&#xff0c;其实并非如此&#xff0c;下面几款让你刮目相看&#xff0c;扭转观念。 1、图片视频画质增强器 这是一款功能极其强大的图片与视频画质增强器&…

阿里云数据湖3.0解决方案两度登上InfoQ 2022年度榜单

经过一个多月的层层竞选&#xff0c;【阿里云数据湖 3.0 解决方案】从 130 多个方案中脱颖而出&#xff0c;荣获 InfoQ 2022 年度中国技术力量年度榜单《十大云原生创新技术方案》&《云原生十大场景化落地方案》双料大奖&#xff0c;这是头部技术媒体对阿里云存储的再一次认…

低代码是什么?有什么优势?一文看懂LowCode

低代码到底是什么&#xff1f;用最简单的方式告诉我&#xff1f;低代码是近两年来一个很热门的概念&#xff0c;尤其是疫情的影响&#xff0c;市场对低代码的需求不断增加&#xff0c;但到底什么是低代码&#xff1f;它到底有什么好处&#xff1f;这篇就为大家解答这个问题&…

vue2.0 插槽不是响应性的

请注意插槽不是响应性的。如果你需要一个组件可以在被传入的数据发生变化时重渲染&#xff0c;我们建议改变策略&#xff0c;依赖诸如 props 或 data 等响应性实例选项。-- vm.$slots 问题描述 项目中自定了组件 widget&#xff0c;作为容器&#xff0c;其中 header 部分做了预…

SCI投稿:MDPI旗下期刊Mathematics投稿经历

最近写了篇论文&#xff0c;由于国内期刊现状&#xff08;懂的都懂&#xff09;&#xff0c;打算投国外的期刊&#xff0c;看来看去选择投MDPI旗下的Mathematics。手稿经过一轮大修之后顺利收到了Accepted&#xff0c;过程还是比较顺利的&#xff0c;记录一下投稿过程。 论文撰…

Matlab实现的FEMIC的说明书

FEMIC程序是用来反演小回路频域电磁感应数据的。要启动代码,在Matlab命令窗口中输入start,然后点击“Enter”或“返回”按钮。然后会出现FEMIC的主界面,见图1。 它由几个输入区域组成,这几个区分别实现了:加载数据,反演过程控制和最终显示。 图1 主界面 下面对这些输入…

[oeasy]python0045_四种进制_binary_octal_decimal_hexadecimal

四种进制 回忆上次内容 上次研究了 通过 八进制数值 转义 \ooo把(ooo)8进制对应的ascii字符输出 转义序列 \n、\t 是 转义序列\xhh 也是 转义序列\ooo 还是 转义序列 现在 总共有 几种进制 了呢&#xff1f;&#x1f914; 先数一下 树 数树 树 就是这么多棵树 用八进制的…

Redis持久化Redis主从

Redis持久化 RDB持久化 RDB: Redis数据备份文件。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后&#xff0c;从磁盘读取快照文件&#xff0c;恢复数据。 主要流程 bgsave开始时会fork主进程得到子进程&#xff0c;子进程共享主进程的内存数据。完成f…

误删的文件不在回收站如何找回?分享一些恢复数据的教程

电脑清理的文件数据&#xff0c;一般都会经过回收站。如果想要恢复回来&#xff0c;可以直接打开电脑的回收站来寻找。可凡事都有万一&#xff0c;我们删除的文件不在回收站里面。这是什么原因&#xff1f;误删的文件不在回收站如何找回&#xff1f;今天就来分享如何恢复不在回…

git快速学习笔记

1.目标 了解Git基本概念能够概述git工作流程能够使用Git常用命令熟悉Git代码托管服务能够使用idea操作git 2.概述 2.1开发中的实际场景 场景一&#xff1a;备份 小明负责的模块就要完成了&#xff0c;就在即将Release之前的一瞬间&#xff0c;电脑突然蓝屏&#xff0c;硬盘光…

C语言进阶(6)——结构体

文章目录1.结构体的基础知识2.结构体的声明3.特殊的声明4.结构体的自引用6. 结构体的内存对齐7.修改默认对齐数8.结构体传参位段1、位段定义2. 位段的内存分配3.位段的跨平台问题4.位段的运用场景1.结构体的基础知识 结构是一些值的集合&#xff0c;这些值称为成员变量。结构的…

51单片机——LED基础

从小就对电器元件比较感兴趣吧&#xff0c;经常拿坏的电器里面的芯片拆下来玩&#xff0c;甚至那些没坏的电器&#xff0c;比如我家的电视&#xff0c;也会希望它能坏掉&#xff0c;我好去看看里面是什么样子的&#xff0c;为什么能播放节目……&#xff0c;所以我第一眼看到51…

阿里云-解决EDAS创建应用文件过大无法上传部署问题

文章目录1、背景2、问题具体描述3、解决方案3.1、 2种方案3.2 、OSS 简介3.3、 OSS 功能特性4、 OSS 实操4.1、 上传程序文件4.2、 创建应用1、背景 在一次使用阿里云-EDAS发布应用服务过程中出现EDAS 上传应用包过大无法上传现象。 2、问题具体描述 最近在使用阿里云-EDAS发…

1.1.2续 特殊二极管部分选型

目录 1.稳压管 2.发光二极管 4.光电二极管 5.整流二极管 1.稳压管 利用二极管的反向击穿特性制成的&#xff0c;在电路中其两端的电压保持基本不变&#xff0c;起到稳定电压的作用 Vz 稳定电压&#xff1a;反向击穿后稳定工作的电压 Iz 稳定电流&#xff1a;工作电压等于…

Java异常情况了解

作者&#xff1a;爱塔居的博客_CSDN博客-JavaSE,数据结构领域博主 专栏&#xff1a;JavaSE 作者介绍&#xff1a;大三学生&#xff0c;希望一起进步~ 文章目录 目录 文章目录 一、异常结构体系 二、异常分类 三、异常处理 3.1异常抛出 3.2 异常捕获 四.【面试题】 五、题目练习…

大数据必学Java基础(一百一十九):Maven仓库与JDK的配置

文章目录 Maven仓库与JDK的配置 一、Maven仓库 二、JDK的配置 Maven仓库与JDK的配置 一、Maven仓库 Maven仓库是基于简单文件系统存储的,集中化管理Java API资源(构件)的一个服务。 仓库中的任何一个构件都有其唯一的坐标,根据这个坐标可以定义其在仓库中的唯一存储路…

Linux:gcc工具

文章目录一.程序的翻译二.gcc工具的使用2.1gcc 文件名(直接编译)2.2gcc -o 自定义 原文件(自定义生成可执行程序名称)2.3./可执行文件2.4gcc -E(程序运行到预处理后结束)2.5gcc -S(程序运行到编译之后结束)2.6gcc -i(程序运行到汇编之后结束)2.7小结三.链接的过程3.1ldd命令3.2…

2023年最新谷歌Google帐号Gmail邮箱账号怎么注册成功的方法与教程?

因为工作、游戏或其他需求&#xff0c;有时需要使用谷歌Google帐号或Gmail邮箱账号。首先明确&#xff1a;谷歌google帐号不一定是Gmail邮箱帐号&#xff0c;但是Gmail邮箱账号一定是谷歌帐号。所以&#xff0c;大家注册Google谷歌帐号时默认为谷歌Gmail邮箱帐号就可以满足谷歌…

Linux基础命令,常用操作

Linux基础命令Linux的目录结构ls命令(list)隐藏文件、文件夹pwd命令(print work directory)cd命令(change directory)HOME目录相对路径、绝对路径特殊路径符mkdir命令(make directory)touch命令(touch)cat命令more命令cp命令mv命令rm命令(remove)which命令find命令grep命令wc命…