58-netty-haproxy
83. netty系列之:在netty中使用proxy protocol
简介
我们知道proxy protocol是haproxy提出的一个代理协议,通过这个协议,所有实现这个协议的proxy或者LBS,都可以附带真实客户端的IP地址和端口号,这使得proxy protocol在实际应用中非常有用。
这么优秀的协议,没有理由netty不支持。本文将会谈一下netty中对proxy protoco代理协议的支持。
netty对proxy protocol协议的支持
proxy protocol协议其实很简单,就是在请求前面带了proxy header信息。
在netty中这个header信息叫做HAProxyMessage:
public final class HAProxyMessage extends AbstractReferenceCounted {
HAProxyMessage是一个ReferenceCounted,这一点和ByteBuf很类似,说明HAProxyMessage保留着和ByteBuf很类似的特性。
根据proxy protocol协议,该协议可以分为两个版本,分别是v1和v2,其中v1版本是文本协议,而v2版本支持二进制的格式。
显然从代码编写和调试的角度来看v1更加友好,但是从程序的角度来看,v2可能性能更高。
HAProxyMessage中有个专门的HAProxyProtocolVersion类,来表示proxy protocol的版本信息:
public enum HAProxyProtocolVersion {
V1(VERSION_ONE_BYTE),
V2(VERSION_TWO_BYTE);
HAProxyProtocolVersion是一个枚举类,在它里面定义了和proxy协议相对应的两个版本号。
在版本号之后是command,在netty中用HAProxyCommand来表示:
HAProxyCommand也是一个枚举类,里面定义了两个command的值,分别是local和proxy。
其中local表示该请求是代理服务器主动发起的,而不是客户端发起的,比如监控检测等请求。
proxy表示该请求是一个代理请求。
接下来是AddressFamily和TransportProtocol,这两个字段用同一个byte来表示,所以这两个类都是HAProxyProxiedProtocol的内部类。
先看下AddressFamily的定义:
AddressFamily中定义了4个address family类型,分别是unspec,ipv4,ipv6和unix。分别对应未知family,ipv4,ipv6和unix domain socket。
再看下TransportProtocol的定义:
TransportProtocol有3个值,分别是unspec,stream和dgram。分别对应未知协议,http/https协议,udp/tcp协议。
因为AddressFamily和TransportProtocol实际上是同一个byte,所以经过组合之后可以得到下面的几个枚举值:
以上的枚举值也是HAProxyProxiedProtocol中定义的值。
接下就是源ip地址,目标地ip地址,源端口和目标端口这几个值,定义为属性表示如下:
最后,proxy protocol中还可以包含额外的字段tlv,tlv在netty中也是一种byteBuf,使用HAProxyTLV表示:
因为tlv是key value结构,所以看下HAProxyTLV的构造函数:
HAProxyTLV接受一个type和byteBuf的value。
Type是一个枚举类,在netty中可以支持下面的值:
在HAProxyMessage中,tlv是一个list来保存的:
到此,所有HAProxyMessage所需要的参数都齐了,我们看下HAProxyMessage的构造函数:
HAProxyMessage会将所有的参数都存储到本地的变量中,供后续使用。
因为proxy protocol有两个版本,v1和v2,所以HAProxyMessage中提供了两个将header编码为AProxyMessage对象的方法,分别是:
和:
有了proxy protocol的java表示之后,我们再来看一下HAProxyMessage的编码解码器。
HAProxyMessage的编码解码器
netty对HAProxyMessage对象的支持表现在两个地方,netty提供了两个类分别对HAProxyMessage进行编码和解码,这两个类是HAProxyMessageEncoder和HAProxyMessageDecoder。
先看一下HAProxyMessageEncoder:
HAProxyMessageEncoder继承自MessageToByteEncoder,传入的泛型是HAProxyMessage,表示是将HAProxyMessage编码成为ByteBuf。
它的encode方法很简单,根据HAProxyMessage传入的message版本信息,分别进行编码:
HAProxyMessageDecoder是跟HAProxyMessageEncoder相反的动作,是将接收到的ByteBuf解析成为HAProxyMessage:
因为HAProxyMessage有两个版本,那么怎么判断接收到的ByeBuf是哪个版本呢?
其实很简单,因为v1版本和v2版本的开始字符是不一样的,v1版本的开头是一个text:"PROXY", v2版本的开头是一个固定的二进制串,如下所示:
看下它的decode方法实现:
上面代码的逻辑是先从ByteBuf中根据版本号decode出header信息放到ByteBuf中。
然后再根据版本号的不同,分别调用HAProxyMessage的两个不同版本的decodeHeader方法进行解码。最终得到HAProxyMessage。
netty中proxy protocol的代码示例
有了netty对proxy protocol的支持,那么在netty中搭建支持proxy protocol的服务器和客户端就很容易了。
先看一下如何搭建支持proxy protocol的服务器:
代码和常规的netty server一样,这里使用了NioEventLoopGroup和NioServerSocketChannel,搭建了一个支持TCP协议的netty服务器。
ServerInitializer中包含了netty自带的HAProxy编码器和自定义的消息处理器:
这里使用netty自带的HAProxyMessageDecoder,用来将ByteBuf消息解码为HAProxyMessage,然后在自定义的SimpleChannelInboundHandler中对HAProxyMessage进行处理。
这里的服务器可以处理两种消息,一种是HAProxyMessage,一种是原始的ByteBuf。处理的结果就是将消息打印出来。
然后看下客户端的定义:
客户端使用的是EventLoopGroup和NioSocketChannel,是基于TCP协议的请求。
这里添加了自定义的handler:ClientHander,ClientHander继承自ChannelOutboundHandlerAdapter用来对client发出的消息进行处理。
这里看一下它的handlerAdded方法:
可以看到handlerAdded方法向channelPipeline中添加了HAProxyMessageEncoder,用于编码HAProxyMessage。
因为对于一个connection来说,HAProxyMessage只需要用到一次,后续的正常消息就不需要这个编码器了,所以我们需要在write方法中监听HAProxyMessage的状态,如果写入成功之后,就从pipeline中移出HAProxyMessageEncoder和ClientHander。
最后我们构建了一个虚拟的HAProxyMessage,然后通过netty客户端进行发送:
总结
上面的代码只是一个简单的模拟proxy protocol在netty中的使用情况,并不代表上面的代码就可以在实际的项目中应用了。如果你想使用的话,可以在下面的代码上面继续丰富和完善。
本文的代码,大家可以参考:
最后更新于
这有帮助吗?