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协议栈是个高性能,通用的协议栈,但是,我们忽略一些细节,比如异常封装等。大家可以自己完善。如果有其他问题,欢迎讨论。