概述
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()+"rn",
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()+"rn",
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协议栈是个高性能,通用的协议栈,但是,我们忽略一些细节,比如异常封装等。大家可以自己完善。如果有其他问题,欢迎讨论。
最后
以上就是可耐季节为你收集整理的【九】Netty HTTP+XML协议栈开发介绍业务场景流程图技术栈设计开发测试总结的全部内容,希望文章能够帮你解决【九】Netty HTTP+XML协议栈开发介绍业务场景流程图技术栈设计开发测试总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复