Netty之HTTP+XML编解码框架开发
来源:互联网 发布:html5tooltips.js 编辑:程序博客网 时间:2024/06/06 05:34
Netty之HTTP+XML编解码框架开发
一.HTTP+XML请求消息编码类
对于上层业务,构造订购请求消息后,以HTTP+XML协议将消息发送给服务端,如果要实现对业务零侵入或者尽可能少的侵入看,协议层和应用层应该解耦。
考虑到HTTP+XML协议栈需要一定的定制扩展能力,例如通过HTTP消息头携带业务自定义字段,应该允许业务利用Netty的HTTP协议栈接口自行构造私有的HTTP消息头。
HTTP+XML的协议编码仍然采用ChannelPipeline中增加对应的编码handler类实现。
1.1 HttpXmlRequestEncode实现
packagejibx;
import java.net.InetAddress;
import java.util.List;
importio.netty.channel.ChannelHandlerContext;
importio.netty.handler.codec.http.DefaultFullHttpRequest;
importio.netty.handler.codec.http.FullHttpRequest;
importio.netty.handler.codec.http.HttpHeaders;
/*
*HTTP+XML HTTP请求消息编码类
*
*/
public class HttpXmlRequestEncoder
extends AbstarctHttpXmlEncoder<HttpXmlRequest>{
@Override
protected void encode(ChannelHandlerContext ctx,HttpXmlRequestmsg,List<Object> out)
throws Exception{
// 调用父类的encode0,将业务需要发送的POJO对象Order实例通过jibx序列化为XML字符串
// 随后将它封装成netty的bytebuf
ByteBufbody=encode0(ctx,msg.getBody());
FullHttpRequestrequest=msg.getRequest();
// 对消息头进行判断,如果业务侧自定义和定制了消息头,则使用业务侧设置的HTTP消息头
// 如果业务侧没有设置,则构造新的HTTP消息头
if(request==null){
// 构造和设置默认的HTTP消息头,由于通常情况下HTTP通信双方更关注消息体本身,
// 所以这里采用了硬编码方式,如果要产品化,可以做成XML配置文件,允许业务自定义配置,
// 以提升定制的灵活性
request=newDefaultFullHttpRequest(HttpVersion.HTTP_1_1,HttpMethod.GET,"/do",body);
HttpHeadersheaders=request.headers();
headers.set(HttpHeaders.Names.HOST,InetAddress.getLocalHost().getHostAddress());
headers.set(HttpHeaders.Names.CONNECTION,HttpHeaders.Value.CLOSE);
headers.set(HttpHeaders.Names.ACCEPT_ENCODING,HttpHeaders.Value.GZIP.toString()+','+
HttpHeaders.Value.DEFLATE.toString());
headers.set(HttpHeaders.Names.ACCEPT_CHARSET,"ISO-8859,utf-8,q=0.7,*;q=0.7");
headers.set(HttpHeaders.Names.USER_AGENT,"Nettyxml Http Client side");
headers.set(HttpHeaders.Names.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8");
}
// 由于请求消息消息体不为空,也没有chunk方式,所以在HTTP消息头中设置消息体的长度conent-length
// 完成消息体的XML序列化后将重新构造的HTTP请求消息加入到out中
// 由后续netty的http请求编码器继续对HTTP请求消息进行编码
HttpHeaders.setContentLength(request,body.eradableBytes());
out.add(request);
}
}
1.2 AbstrctHttpXmlEncoder实现
package jibx;
import java.io.StringWriter;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
importio.netty.channel.ChannelHandlerContext;
importio.netty.handler.codec.MessageToMessageEncoder;
/*
*HTTP+XML HTTP请求消息编码基类
*/
public classAbstractHttpXmlEncoder<T>
extends MessageToMessageEncoder<T>{
IBindingFactoryfactory=null;
StringWriterwriter=null;
finalstatic String CHARSET_NAME="UTF-8";
finalstatic Charset UTF-8=
Charset.forName(CHARSET_NAME);
//将业务的Order实例序列化为XML字符串
protectedByteBuf encode0(ChannelHandlerContext ctx,Object body) throws Exception{
factory=BindingDirectory.getFactory(body.getClass());
writer=newStringWriter();
IMarshallingContextmctx=factory.createMarshallingContext();
mctx.setIndent(2);
mctx.marshalDocument(body,CHARSET_NAME,null,writer);
StringxmlStr=writer.toString();
writer.close();
writer=null;
//将XML字符串包装成netty的ByteBuf并返回,实现了HTTP请求消息的XML编码
ByteBufencodeBuf=Unpooled.copiedBuffer(xmlStr,UTF-8);
returnencodeBuf;
}
@Override
publicvoid exceptionCaught(ChannelHandlerContext ctx,Throwable cause) throwsException{
//释放资源
if(writer!=null){
writer.close();
writer=null;
}
}
}
1.3 HttpXmlRequest实现
package jibx;
/*
*HTTP+XML请求消息 用于实现和协议栈之间的解耦
*/
importio.netty.handler.codec.http.FullHttpRequest;
public class HttpXmlRequest {
privateFullHttpRequest request;
privateObject body;
publicHttpXmlRequest(FullHttpRequest request,Object body){
this.request=request;
this.body=body;
}
publicfinal FullHttpRequest getRequest(){
returnrequest;
}
publicfinal void setRequest(FullHttpRequest request){
this.request=request;
}
publicfinal Object getBody(){
returnbody;
}
publicfinal void setBody(Object body){
this.body=body;
}
}
它包含2个成员变量FullHttpRequest和编码对象Object,用于实现和协议栈之间的解耦。
二.HTTP+XML请求消息解码类
Http服务端接收到HTTP+XML请求消息后,需要从http消息体重获取请求码流,通过jibx框架对它进行反序列化,得到请求POJO对象,然后对结果进行封装,回调到业务handler对象,业务得到的就是解码后的POJO对象和HTTP消息头。
2.1 HttpXmlRequestDecoder实现
package jibx;
import io.netty.buffer.Unpooled;
importio.netty.channel.ChannelFutureListener;
importio.netty.handler.codec.http.HttpResponseStatus;
/*
*HTTP+XML http请求消息解码类
*/
public class HttpXmlRequestDecoder
extends AbstactHttpXmlDecode<FullHttpRequest>{
publicHttpXmlRequestDecoder(Class<?> clazz) {
this(clazz,false);
}
//HttpXmlRequestDecoder有2个参数,分别为需要解码的对象的类型信息和
//是否打印HTTP消息体码流的码流开关,码流开关默认关闭
publicHttpXmlRequestDecoder(Class<?> clazz,boolean isPrint){
super(clazz,isPrint);
}
@Override
protectedvoid decode(ChannelHandlerContext arg0,FullHttpRequest arg1,
List<Object>arg2) throws Exception{
//对HTTP请求消息本身的解码结果进行判断,如果已经失败,二次解码就无意义
if(!arg1.getDecodeResult().isSuccess()){
sendError(arg0,BAD_REQUEST);
return;
}
//通过HttpXmlRequest和反序列化后的Order对象构造HttpXmlRequest实例,
//最后将它添加到解码结果list列表中
HttpXmlRequestrequest=new HttpXmlRequest(arg1,decode0(arg0,arg1.content()));
arg2.add(request);
}
/*
* 如果HTTP消息本身解码失败,则构造处理结果异常的HTTP应答消息返回给客户端。
* 作为演示程序,本例子没有考虑XML消息解码失败后的异常封装和处理,
* 在商用项目中需要统一的异常处理机制,提升协议栈的健壮性和可靠性
*/
privatestatic void sentError(ChannelHandlerContext ctx,HttpResponseStatus status){
FullHttpResponseresponse=new DefaultFullHttpResponse(HTTP_1_1,status,
Unpooled.copiedBuffer("Failure:"+status.toString()+"\r\n",CharsetUtil.UTF_8));
reponse.headers().set(CONTENT_TYPE,"text/plain,charset-UTF-8");
ctx.writeAndFlush(reponse).addListener(ChannelFutureListener.CLOSE);
}
}
2.2 AbstractHttpXmlDecoder实现
package jibx;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.MessageToMessageDecoder;
import java.io.StringReader;
import java.nio.charset.Charset;
/*
*HTTP+XML HTTP请求消息解码类
*/
public classAbstarctHttpXmlDecoder<T> extends
MessageToMessageDecoder<T>{
privateIBindingFactory factory;
privateStringReader reader;
privateClass<?> clazz;
privateboolean isPrint;
privatefinal static String CHARSET_NAME="UTF-8";
privatefinal static Charset UTF-8=Charset.forName(CHARSET_NAME);
protectedAbstarctHttpXmlDecoder(Class<?> clazz){
this(clazz,false);
}
protectedAbstrctHttpXMlDecoder(Class<?> clazz,boolean isPrint){
this.clazz=clazz;
this.isPrint=isPrint;
}
protectedObject decode0(ChannelHandlerContext arg0,ByteBuf body) throws Exception{
//从HTTP的消息体中获取请求码流,然后通过jibx类库将XML转换成POJO对象
factory=BindingDirectory.getFactory(clazz);
Stringcontent=body.toString(UTF-8);
//根据码流开关决定是否打印消息体码流
//增加码流开关往往是为了方便问题定位,在实际项目中,需要打印到日志中
if(isPrint)
System.out.println("Thebody is:"+content);
reader=newStringReader(content);
IUnmarshllingContextuctx=factory.createUnmarshallingContext();
Objectresult=uctx.unmarshalDocument(reader);
reader.close();
reader=null;
returnresult;
}
@Override
publicvoid exceptionCaught(ChannelHandlerContext ctx,Throwable cause) throwsException{
//释放资源
//如果解码发生异常,要判断StringReader是否已经关闭
//如果没有关闭,则关闭输入流并通知JVM对其进行垃圾回收
if(reader!=null){
reader.close();
reader=null;
}
}
}
2.3 HttpXmlResponse实现
package jibx;
importio.netty.handler.codec.http.FullHttpResponse;
/*
*HTTP+XML 响应消息编码类
*/
public class HttpXmlResponse {
privateFullHttpResponse httpResponse;
privateObject result;
publicHttpXmlResponse(FullHttpResponse httpResponse,Object result){
this.httpResponse=httpResponse;
this.result=result;
}
publicfinal FullHttpResponse getHttpResponse(){
returnhttpResponse;
}
publicfinal void setHttpResponse(FullHttpResponse httpResponse){
this.httpResponse=httpResponse;
}
publicfinal Object getResult(){
returnresult;
}
publicfinal void setResult(Object result){
this.result=result;
}
}
2.4 HttpXmlResponseEncoder实现
package jibx;
import java.util.List;
import io.netty.buffer.ByteBuf;
importio.netty.channel.ChannelHandlerContext;
importio.netty.handler.codec.http.DefaultFullHttpResponse;
importio.netty.handler.codec.http.FullHttpResponse;
/*
*HTTP+XML 应答消息编码类
*/
public class HttpXmlResponseEncoder
extends AbstractHttpXmlEncoder<HttpXmlResponse>{
protectedvoid encode(ChannelHandlerContext ctx,
HttpXmlResponsemsg,List<Object> out
)throws Exception{
ByteBufbody=encode0(ctx,msg.getResult());
FullHttpResponseresponse=msg.getHttpResponse();
//对应答消息进行判断,如果业务侧已经构造了HTTP应答消息
//则利用业务已有应答消息重新复制一个新的HTTP应答消息
if(response==null){
response=newDefaultFullHttpResponse(HTTP_1_1,OK,body);
}else{
response=newDefaultFullHttpResponse(msg.getHttpResponse()
.getProtocolVersion(),msg.getHttpResponse().getStatus(),body);
}
//设置消息体的内容格式为"text/xml",然后在消息头中设置消息体的长度
response.headers().set(CONTENT_TYPE,"text/xml");
setContentLength(response,body,readableBytes);
//把编码后的DefaultFullHttpResponse对象添加到编码结果列表中
//由后续Netty的HTTP编码类进行二次编码
out.add(response);
}
}
2.5 HttpXmlResponseDecoder实现
package jibx;
import java.util.List;
importio.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
public class HttpXmlResponseDecoder extends
AbstarctHttpXmlDecoder<DefaultFullHttpResponse>{
publicHttpXmlResponseDecoder(Class<?> clazz){
this(clazz,false);
}
publicHttpXmlResponseDecoder(Class<?> clazz,boolean isPrintLog){
super(clazz,isPrintLog);
}
@Override
protectedvoid decode(ChannelHandlerContext ctx,DefaultFullHttpResponse msg,
List<Object>out) throws Exception{
//通过DefaultFullHttpResponse和HTTP应答消息反序列化后的POJO对象构造HttpXmlResponse
//并将其添加到解码结果列表中
HttpXmlResponseresHttpXmlResponse=new HttpXmlResponse
(msg,decode0(ctx,msg.content()));
out.add(resHttpXmlResponse);
}
}
三.HTTP+XML客户端开发
客户端功能:
1.发起HTTP连接请求
2.构造订购请求消息,将其编码成XML,通过HTTP协议发送给服务端
3.接收HTTP服务端的应答消息,将XML应答消息反序列化为订购消息POJO对象
4.关闭HTTP连接
3.1 HttpXmlClient实现
package jibx;
import java.net.InetSocketAddress;
import client.TimeClient;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
importio.netty.channel.nio.NioEventLoopGroup;
importio.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
importio.netty.handler.codec.http.HttpResponseDecoder;
public class HttpXmlClient {
publicvoid connect(int port) throws Exception{
//配置客户端NIO线程组
EventLoopGroupgroup =new NioEventLoopGroup();
try{
Bootstrapb=new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(newChannelInitializer<SocketChannel>(){
@Override
publicvoid initChannel(SocketChannel ch) throws Exception{
//将二进制码流解码成为HTTP的应答消息
ch.pipeline().addLast("http-decoder",newHttpResponseDecoder());
//负责将1个HTTP请求消息的多个部分合并成一条完整的HTTP消息
ch.pipeline().addLast("http-aggregator",newHttpObjectAggregator(65536));
//XML解码器
//将前面开发的XML解码器HttpXmlResponseDecoder添加到ChannelPipeline中
//它有2个参数,分别是解码对象的类型信息和码流开关,这样就实现了HTTP+XML应答消息的自动解码
ch.pipeline().addLast(
"xml-decoder",
newHttpXmlResponseDecoder(Order.class,true);
//将HttpRequestEncoder解码器添加到ChannelPipeline中时,需要注意顺序,
// 编码的时候是按照从尾到头的顺序调度执行的,它后面放的是我们自定义开发的
//HTTP+XML请求消息解码器HttpXmlRequestEncoder
ch.pipeline().addLast("http-encoder",newHttpRequestEncoder());
ch.pipeline().addLast("xml-encoder,newHttpXmlRequestEncoder());
ch.pipeline().addLast("xmlClientHandler",newHttpXmlClientHandler());
}
});
//发起异步连接操作
ChannelFuturef=b.connect(new InetSocketAddress(port),sync());
//等待客户端链路关闭
f.channel().closeFuture().sync();
}catch (Exception e) {
//TODO: handle exception
}finally{
//优雅退出,释放NIO线程组
group.shutdownGracefully();
}
}
publicstatic void main(String[] args) throws Exception{
intport=8080;
if(args!=null&&args.length>0){
try{
port=Integer.valueOf(args[0]);
}catch (NumberFormatException e) {
//TODO: handle exception
}
newHttpXmlClient().connect(port);
}
}
}
3.2业务逻辑编排类HttpXmlClientHandler实现
package jibx;
import io.netty.channel.ChannelHandlerContext;
importio.netty.channel.SimpleChannelInboundHandler;
public class HttpXmlClientHandler extends
SimpleChannelInboundHandler<HttpXmlResponse>{
@Override
publicvoid channelActive(ChannelHandlerContext ctx){
HttpXmlRequest request=newHttpXmlRequest(null,OrderFactory.create(123));
ctx.writeAndFlush(request);
}
@Override
public voidexceptionCaught(ChannelHandlerContext ctx,Throwable cause){
cause.printStackTrace();
ctx.close();
}
@Override
protected voidmessageReceived(ChannelHandlerContext ctx,HttpXmlResponse msge)
throws Exception{
System.out.println("The client receiveresponse of http header is:"+msg.getHttpResponse().headers().names());
System.out.println("The client receiveresponse of http body is:"+msg.getResult());
}
}
3.3 订购对象工厂类OrderFactory实现
public class OrderFactory {
publicstatic Order create(long orderID){
Orderorder=new order();
order.setOrderNumber(orderID);
order.setTotal(9999.999f);
Addressaddress=new Address();
address.setCity("武汉市");
address.setCountry("中国");
address.setPostCode("11");
address.setState("湖北省");
address.setStreet1("龙阳大道");
order.setBillTo(address);
Customercustomer=new Customer();
customer.setCustomerNumber(orderID);
customer.setFirstName("郑");
customer.setLastName("广");
order.setCustomer(customer);
order.setShipping(Shipping.INTERNATIONAL_MAIL);
order.setShipTo(address);
returnorder;
}
}
四.HTTP+XML服务端开发
HTTP服务端的功能:
1.接收HTTP客户端连接
2.接收HTTP客户端的XML请求消息,并将其解码为POJO对象
3.对POJO对象进行业务处理,构造应答消息返回
4.通过HTTP+XML的格式返回应答消息
5.主动关闭HTTP连接
4.1 服务端主程序HttpXmlServer实现
package jibx;
import java.net.InetSocketAddress;
import client.TimeClient;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
importio.netty.channel.nio.NioEventLoopGroup;
importio.netty.channel.socket.SocketChannel;
importio.netty.channel.socket.nio.NioServerSocketChannel;
importio.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
/*
*HTTP+XML 服务端
*/
public class HttpXmlServer {
publicvoid run(final int port) throws Exception{
EventLoopGroupbossGroup=new NioEventLoopGroup();
EventLoopGroupworkerGroup=new NioEventLoopGroup();
try{
ServerBootstrapb=new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(newChannelInitializer<SocketChannel>() {
@Override
protectedvoid initChannel(SocketChannel ch) throws Exception{
//用于绑定HTTP请求消息解码器
ch.pipeline().addLast("http-decoder",newHttpRequestDecoder());
ch.pipeline().addLast("http-aggregator",new HttpObjectAggregator(65536));
ch.pipeline().addLast(
"xml-decoder",
newHttpXmlRequestDecoder(
Order.class,true));
ch.pipeline().addLast("http-encoder",newHttpResponseEncoder());
//添加自定义的HttpXmlResponseEncoder编码器用于响应消息的编码
ch.pipeline().addLast("xml-encoder",newHttpXmlResponseEncoder());
ch.pipeline().addLast("xmlServerHandler",newHttpXmlServerHandler());
}
});
ChannelFuturefuture=b.bind(new InetSocketAddress(port)).sync();
System.out.println("HTTP订购服务器启动,网址是"+http://localhost:"+port);
future.channel().closeFuture().sync();
}catch (Exception e) {
//TODO: handle exception
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
publicstatic void main(String[] args) throws Exception{
intport=8080;
if(args!=null&&args.length>0){
try{
port=Integer.valueOf(args[0]);
}catch (NumberFormatException e) {
//TODO: handle exception
e.printStackTrace();
}
newHttpXmlServer().run(port);
}
}
}
4.2 服务端处理类HttpXmlServerHandler实现
package jibx;
import com.sun.jndi.cosnaming.IiopUrl.Address;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
importio.netty.channel.ChannelHandlerContext;
importio.netty.handler.codec.http.FullHttpResponse;
importio.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
public class HttpXmlServerHandler extends
SimpleChannelInboundHandler<HttpXmlRequest> {
@Override
public void messgeReceived(final ChannelHandlerContextctx,HttpXmlRequest xmlRequest)
throws Exception{
HttpRequest request=xmlRequest.getRequest();
Order order=(Order)xmlRequest.getBody();
System.out.println("Http server receiverequest:"+order);
dobusiness(order);
ChannelFuture future=ctx.writeAndFlush(newHttpXmlResponse(null,order));
if(!isKeepAlive(request)) {
future.addListener(newGenericFutureListener<Future<? super void >> () {
public void operationComplete(Future future)throws Exception{
ctx.close();
}
}};
}
}
private void dobusiness(Order order){
order.getCustomer().setFirstName("郑");
order.getCustomer.setLastName("广");
List<String> midNames=new ArrayList<String>();
midName.add("李元芳");
order.getCustomer().setMiddleNames(midNames);
Address address=order.getBillTo();
address.setCity("武汉");
address.setCountry("大道");
address.setState("东阳大道");
address.setPostCode("123");
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(ChannelHandler ctx,HttpResponseStatusstatus){
FullHttpResponse response=newDefaultFullHttpResponse
(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;
}
}
上面例子是一个高性能,通用的协议栈,但是忽略了一些异常场景的处理,可扩展性API和一些配置能力,如果要在商用项目中使用,需要做一些产品化的完善工作。
- Netty之HTTP+XML编解码框架开发
- Netty系列之Netty编解码框架分析
- Netty系列之Netty编解码框架分析
- Netty系列之Netty编解码框架分析
- Netty系列之Netty编解码框架分析
- Netty系列之Netty编解码框架分析
- Netty系列之Netty编解码框架分析
- Netty系列之Netty编解码框架分析
- Netty编解码框架分析
- netty开发tcp数据传输编解码框架使用
- [netty]-消息编解码之google的Protobuf编解码
- Netty之Google Protobuf编解码
- Netty之Jboss Marshalling编解码
- netty编解码之使用protobuf
- netty编解码之jboss marshalling
- 从0到1 ▏Netty编解码框架之多种常用解码器使用示例解析
- Netty学习(九)-Netty编解码技术之Marshalling
- Netty编解码技术
- Netty的HTTP协议开发
- 详解Java API之正则表达式
- CodeForces 831B Keyboard Layouts
- IntelliJ IDEA 2017 下载和破解方法
- Hive常用的命令
- Netty之HTTP+XML编解码框架开发
- 分布式消息队列Kafka & RocketMQ 深度学习资料精选
- Java Web开发技术方案
- 阿里巴巴2016 实习生招聘 练习题(二)
- Codeforces Round #424 (Div. 2) A. Unimodal Array(水题)
- 【贪心】POJ 3262 Protecting the Flowers
- HashMap的简单使用
- JAVA企业面试题精选 Java基础 41-50
- Java实现-合并k个排序链表