netty对proxy protocol代理协议的支持详解

目录
  • 简介
  • netty对proxy protocol协议的支持
  • HAProxyMessage的编码解码器
  • 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来表示:

public enum HAProxyCommand {
    LOCAL(HAProxyConstants.COMMAND_LOCAL_BYTE),
    PROXY(HAProxyConstants.COMMAND_PROXY_BYTE);

HAProxyCommand也是一个枚举类,里面定义了两个command的值,分别是local和proxy。

其中local表示该请求是代理服务器主动发起的,而不是客户端发起的,比如监控检测等请求。

proxy表示该请求是一个代理请求。

接下来是AddressFamily和TransportProtocol,这两个字段用同一个byte来表示,所以这两个类都是HAProxyProxiedProtocol的内部类。

先看下AddressFamily的定义:

    public enum AddressFamily {
        AF_UNSPEC(AF_UNSPEC_BYTE),
        AF_IPv4(AF_IPV4_BYTE),
        AF_IPv6(AF_IPV6_BYTE),
        AF_UNIX(AF_UNIX_BYTE);

AddressFamily中定义了4个address family类型,分别是unspec,ipv4,ipv6和unix。分别对应未知family,ipv4,ipv6和unix domain socket。

再看下TransportProtocol的定义:

    public enum TransportProtocol {
        UNSPEC(TRANSPORT_UNSPEC_BYTE),
        STREAM(TRANSPORT_STREAM_BYTE),
        DGRAM(TRANSPORT_DGRAM_BYTE);

TransportProtocol有3个值,分别是unspec,stream和dgram。分别对应未知协议,http/https协议,udp/tcp协议。

因为AddressFamily和TransportProtocol实际上是同一个byte,所以经过组合之后可以得到下面的几个枚举值:

    UNKNOWN(TPAF_UNKNOWN_BYTE, AddressFamily.AF_UNSPEC, TransportProtocol.UNSPEC),
    TCP4(TPAF_TCP4_BYTE, AddressFamily.AF_IPv4, TransportProtocol.STREAM),
    TCP6(TPAF_TCP6_BYTE, AddressFamily.AF_IPv6, TransportProtocol.STREAM),
    UDP4(TPAF_UDP4_BYTE, AddressFamily.AF_IPv4, TransportProtocol.DGRAM),
    UDP6(TPAF_UDP6_BYTE, AddressFamily.AF_IPv6, TransportProtocol.DGRAM),
    UNIX_STREAM(TPAF_UNIX_STREAM_BYTE, AddressFamily.AF_UNIX, TransportProtocol.STREAM),
    UNIX_DGRAM(TPAF_UNIX_DGRAM_BYTE, AddressFamily.AF_UNIX, TransportProtocol.DGRAM);

以上的枚举值也是HAProxyProxiedProtocol中定义的值。

接下就是源ip地址,目标地ip地址,源端口和目标端口这几个值,定义为属性表示如下:

    private final String sourceAddress;
    private final String destinationAddress;
    private final int sourcePort;
    private final int destinationPort;

最后,proxy protocol中还可以包含额外的字段tlv,tlv在netty中也是一种byteBuf,使用HAProxyTLV表示:

public class HAProxyTLV extends DefaultByteBufHolder

因为tlv是key value结构,所以看下HAProxyTLV的构造函数:

    public HAProxyTLV(Type type, ByteBuf content) {
        this(type, Type.byteValueForType(type), content);
    }

HAProxyTLV接受一个type和byteBuf的value。

Type是一个枚举类,在netty中可以支持下面的值:

    public enum Type {
        PP2_TYPE_ALPN,
        PP2_TYPE_AUTHORITY,
        PP2_TYPE_SSL,
        PP2_TYPE_SSL_VERSION,
        PP2_TYPE_SSL_CN,
        PP2_TYPE_NETNS,
        OTHER;

在HAProxyMessage中,tlv是一个list来保存的:

private final List<HAProxyTLV> tlvs;

到此,所有HAProxyMessage所需要的参数都齐了,我们看下HAProxyMessage的构造函数:

    public HAProxyMessage(
            HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol,
            String sourceAddress, String destinationAddress, int sourcePort, int destinationPort,
            List<? extends HAProxyTLV> tlvs)

HAProxyMessage会将所有的参数都存储到本地的变量中,供后续使用。

因为proxy protocol有两个版本,v1和v2,所以HAProxyMessage中提供了两个将header编码为AProxyMessage对象的方法,分别是:

static HAProxyMessage decodeHeader(ByteBuf header)

和:

static HAProxyMessage decodeHeader(String header)

有了proxy protocol的java表示之后,我们再来看一下HAProxyMessage的编码解码器。

HAProxyMessage的编码解码器

netty对HAProxyMessage对象的支持表现在两个地方,netty提供了两个类分别对HAProxyMessage进行编码和解码,这两个类是HAProxyMessageEncoder和HAProxyMessageDecoder。

先看一下HAProxyMessageEncoder:

public final class HAProxyMessageEncoder extends MessageToByteEncoder<HAProxyMessage>

HAProxyMessageEncoder继承自MessageToByteEncoder,传入的泛型是HAProxyMessage,表示是将HAProxyMessage编码成为ByteBuf。

它的encode方法很简单,根据HAProxyMessage传入的message版本信息,分别进行编码:

    protected void encode(ChannelHandlerContext ctx, HAProxyMessage msg, ByteBuf out) throws Exception {
        switch (msg.protocolVersion()) {
            case V1:
                encodeV1(msg, out);
                break;
            case V2:
                encodeV2(msg, out);
                break;
            default:
                throw new HAProxyProtocolException("Unsupported version: " + msg.protocolVersion());
        }
    }

HAProxyMessageDecoder是跟HAProxyMessageEncoder相反的动作,是将接收到的ByteBuf解析成为HAProxyMessage:

public class HAProxyMessageDecoder extends ByteToMessageDecoder

因为HAProxyMessage有两个版本,那么怎么判断接收到的ByeBuf是哪个版本呢?

其实很简单,因为v1版本和v2版本的开始字符是不一样的,v1版本的开头是一个text:"PROXY", v2版本的开头是一个固定的二进制串,如下所示:

    static final byte[] BINARY_PREFIX = {
            (byte) 0x0D,
            (byte) 0x0A,
            (byte) 0x0D,
            (byte) 0x0A,
            (byte) 0x00,
            (byte) 0x0D,
            (byte) 0x0A,
            (byte) 0x51,
            (byte) 0x55,
            (byte) 0x49,
            (byte) 0x54,
            (byte) 0x0A
    };
    static final byte[] TEXT_PREFIX = {
            (byte) 'P',
            (byte) 'R',
            (byte) 'O',
            (byte) 'X',
            (byte) 'Y',
    };

看下它的decode方法实现:

    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        if (version == -1) {
            if ((version = findVersion(in)) == -1) {
                return;
            }
        }
        ByteBuf decoded;
        if (version == 1) {
            decoded = decodeLine(ctx, in);
        } else {
            decoded = decodeStruct(ctx, in);
        }
        if (decoded != null) {
            finished = true;
            try {
                if (version == 1) {
                    out.add(HAProxyMessage.decodeHeader(decoded.toString(CharsetUtil.US_ASCII)));
                } else {
                    out.add(HAProxyMessage.decodeHeader(decoded));
                }
            } catch (HAProxyProtocolException e) {
                fail(ctx, null, e);
            }
        }
    }

上面代码的逻辑是先从ByteBuf中根据版本号decode出header信息放到ByteBuf中。

然后再根据版本号的不同,分别调用HAProxyMessage的两个不同版本的decodeHeader方法进行解码。最终得到HAProxyMessage。

netty中proxy protocol的代码示例

有了netty对proxy protocol的支持,那么在netty中搭建支持proxy protocol的服务器和客户端就很容易了。

先看一下如何搭建支持proxy protocol的服务器:

    private static void startServer(int port) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ServerInitializer());
            b.bind(port).sync().channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

代码和常规的netty server一样,这里使用了NioEventLoopGroup和NioServerSocketChannel,搭建了一个支持TCP协议的netty服务器。

ServerInitializer中包含了netty自带的HAProxy编码器和自定义的消息处理器:

class ServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(
                new LoggingHandler(LogLevel.DEBUG),
                new HAProxyMessageDecoder(),
                new SimpleChannelInboundHandler() {
                    @Override
                    protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
                        if (msg instanceof HAProxyMessage) {
                            log.info("proxy message is : {}", msg);
                        } else if (msg instanceof ByteBuf) {
                            log.info("bytebuf message is : {}", ByteBufUtil.prettyHexDump((ByteBuf) msg));
                        }
                    }
                });
    }
}

这里使用netty自带的HAProxyMessageDecoder,用来将ByteBuf消息解码为HAProxyMessage,然后在自定义的SimpleChannelInboundHandler中对HAProxyMessage进行处理。

这里的服务器可以处理两种消息,一种是HAProxyMessage,一种是原始的ByteBuf。处理的结果就是将消息打印出来。

然后看下客户端的定义:

EventLoopGroup group = new NioEventLoopGroup();
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ClientHander());
            Channel ch = b.connect(host, port).sync().channel();

客户端使用的是EventLoopGroup和NioSocketChannel,是基于TCP协议的请求。

这里添加了自定义的handler:ClientHander,ClientHander继承自ChannelOutboundHandlerAdapter用来对client发出的消息进行处理。

这里看一下它的handlerAdded方法:

    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        ctx.pipeline().addBefore(ctx.name(), null, HAProxyMessageEncoder.INSTANCE);
        super.handlerAdded(ctx);
    }

可以看到handlerAdded方法向channelPipeline中添加了HAProxyMessageEncoder,用于编码HAProxyMessage。

因为对于一个connection来说,HAProxyMessage只需要用到一次,后续的正常消息就不需要这个编码器了,所以我们需要在write方法中监听HAProxyMessage的状态,如果写入成功之后,就从pipeline中移出HAProxyMessageEncoder和ClientHander。

    public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ChannelFuture future1 = ctx.write(msg, promise);
        if (msg instanceof HAProxyMessage) {
            future1.addListener((ChannelFutureListener) future2 -> {
                if (future2.isSuccess()) {
                    ctx.pipeline().remove(HAProxyMessageEncoder.INSTANCE);
                    ctx.pipeline().remove(ClientHander.this);
                } else {
                    ctx.close();
                }
            });
        }
    }

最后我们构建了一个虚拟的HAProxyMessage,然后通过netty客户端进行发送:

HAProxyMessage message = new HAProxyMessage(
                    HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.TCP4,
                    "127.0.0.1", "127.0.0.2", 8000, 9000);
            ch.writeAndFlush(message).sync();
            ch.writeAndFlush(Unpooled.copiedBuffer("this is a proxy protocol message!", CharsetUtil.UTF_8)).sync();
            ch.close().sync();

总结

上面的代码只是一个简单的模拟proxy protocol在netty中的使用情况,并不代表上面的代码就可以在实际的项目中应用了。如果你想使用的话,可以在下面的代码上面继续丰富和完善。

本文的代码,大家可以参考:

learn-netty4

以上就是netty对proxy protocol代理协议的支持详解的详细内容,更多关于netty支持proxy protocol代理协议的资料请关注我们其它相关文章!

(0)

相关推荐

  • Netty分布式高性能工具类同线程下回收对象解析

    目录 同线程回收对象 回顾第三小节的demo中的main方法 我们跟进recycle方法 然后获取当前size 同线程回收对象 上一小节剖析了从recycler中获取一个对象, 这一小节分析在创建和回收是同线程的前提下, recycler是如何进行回收的 回顾第三小节的demo中的main方法 public static void main(String[] args){ User user1 = RECYCLER.get(); user1.recycle(); User user2 = REC

  • 解决Netty解码http请求获取URL乱码问题

    目录 Netty解码http请求获取URL乱码 解决方案 原因 Netty---编解码(原理) 1.ByteToMessageDecoder 2.FixedLengthFrameDecoder 3.MessageToByteEncoder Netty解码http请求获取URL乱码 解决方案 获取URI时,使用URLDecoder进行解码     public void channelRead(final ChannelHandlerContext ctx, Object msg) throws

  • nginx中配置使用proxy protocol协议的全过程

    目录 简介 proxy protocol在nginx中应用 在nginx中配置使用proxy protocol 在nginx中启用proxy protocol 使用Real‑IP modules 请求转发 日志记录 总结 简介 我们已经介绍了haproxy提出的proxy protocol协议,通过proxy protocol协议,服务器端可以获得客户端的真实IP地址和端口,从而可以进行一些非常有意义的操作. 为什么获得客户端的真实IP地址会非常有意义呢? 考虑一个藏在proxy背后的数据库,如

  • Netty分布式高性能工具类异线程下回收对象解析

    目录 异线程回收对象 跟到pushLater方法中 跟到allocate方法中 回到pushLater方法中 简单看下link的类的定义 回到pushLater方法中 前文传送门:Netty分布式高性能工具类同线程下回收对象解析 异线程回收对象 就是创建对象和回收对象不在同一条线程的情况下, 对象回收的逻辑 我们之前小节简单介绍过, 异线程回收对象, 是不会放在当前线程的stack中的, 而是放在一个WeakOrderQueue的数据结构中, 回顾我们之前的一个图: 8-6-1 相关的逻辑, 我

  • Netty如何设置为Https访问

    目录 Netty设置为Https访问 SSLContextFactory 处理类 Netty实现Http协议 maven依赖的包 1.netty启动入口 2.编写NettyHttpServer 3.处理http请求.处理.返回 Netty设置为Https访问 SSLContextFactory public class SSLContextFactory {        public static SSLContext getSslContext() throws Exception {   

  • netty对proxy protocol代理协议的支持详解

    目录 简介 netty对proxy protocol协议的支持 HAProxyMessage的编码解码器 netty中proxy protocol的代码示例 总结 简介 我们知道proxy protocol是haproxy提出的一个代理协议,通过这个协议,所有实现这个协议的proxy或者LBS,都可以附带真实客户端的IP地址和端口号,这使得proxy protocol在实际应用中非常有用. 这么优秀的协议,没有理由netty不支持.本文将会谈一下netty中对proxy protoco代理协议的

  • redis protocol通信协议及使用详解

    目录 简介 redis的高级用法 Redis中的pipline Redis中的Pub/Sub RESP protocol Simple Strings Bulk Strings RESP Integers RESP Arrays RESP Errors Inline commands 总结 简介 redis是一个非常优秀的软件,它可以用作内存数据库或者缓存.因为他的优秀性能,redis被应用在很多场合中. redis是一个客户端和服务器端的模式,客户端和服务器端是通过TCP协议进行连接的,客户端

  • PHP4之COOKIE支持详解

    PHP4之COOKIE支持详解 建立商业站点或者功能比较完善的个人站点,常常需要记录访问者的信息,在PHP中提供了两种方便的手段:session和cookie功能.为了永久的保持用户信息,则cookie是最便捷的手段.在这里我将为大家详细的讲解cookie的功能及使用方法. 一: 设置cookie         使用cookie前必须设置cookie.         函数原型:int setcookie(string name,string value,int expire,string p

  • Go1.18新特性对泛型支持详解

    目录 1.泛型是什么 2.泛型类型的定义 2.1.声明一个自定义类型 2.2.内置的泛型类型any和comparable 2.3.泛型中的~符号是什么 1.泛型是什么 Go1.18增加了对泛型的支持,泛型是一种独立于使用的特定类型编写代码的方式.现在可以编写函数和类型适用于一组类型集合的任何一种.泛型生命周期只在编译期,旨在开发中减少重复代码的编写. 由于go属于静态强类型语言,例如在比较两个数的大小时,没有泛型的时候,仅仅只是传入类型不一样,我们就要再复制一份一样的函数,如果有了泛型就可以减少

  • MongoDB 事务支持详解

    目录 事务简介 如何使用事务 事务语法 API 区别 实际使用 事务调优 时间限制 oplog 大小限制 事务简介 事务是数据库中处理的逻辑单元,每个事务中包括一个或多个数据库操作,既可以是读操作,也可以是写操作. ACID 是一个“真正”事务所需要具备的一组属性集合,指的是原子性(Atomicity).一致性(Consistency).隔离性(Isolation)和持久性(Durability). 原子性指的是,事务中的所有操作要么都被应用,要么都不被应用. 一致性指的是,如果数据库在执行事务

  • C++11学习之多线程的支持详解

    目录 C++11中的多线程的支持 1.C++11中的原子类型 1.1 原子类型的接口 1.2简单自旋锁的实现 2.提高并行程度 2.1 memory_order的参数 2.2 release-acquire内存顺序 2.3 release-consume内存顺序 2.4 小结 3.线程局部存储 4.快速退出 C++11中的多线程的支持 千禧年以后,主流的芯片厂商都开始生产多核处理器,所以并行编程越来越重要了.在C++98中根本没有自己的一套多线程编程库,它采用的是C99中的POSIX标准的pth

  • Netty粘包拆包及使用原理详解

    目录 为什么使用Netty框架 Netty框架介绍 Netty实战 Netty编写服务器端 Netty客户端 粘包与拆包 为什么使用Netty框架 NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector.ServerSocketChannel.SocketChannel.ByteBuffer等. 需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程.这是因为NIO编程涉及到 Reactor 模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的NIO程序. 可靠性能力补齐,工

  • webstorm中vue语法的支持详解

    俗话说,要想攻其玉,必先利其器.工作中老是在用angular开发,但慢慢会有vue的业务,所以需要学习学习vue.今天开始折腾vue,我是用webstorm开发前端的,当使用vue-cli创建好一个项目后,使用webstorm打开简直不能入目啊,倒是没有啥错误提示,倒是一大堆的各种编辑器警告,无奈,还得去搞好IDE对vue的支持啊. 前提 vue的官方文档上建议新手是先不要使用vue-cli,而是直接先用类似于JQuery一样的方式引入vue来学习.鉴于前面已经搞过了angular的cli,所以

  • 如何为asp.net core添加protobuf支持详解

    前言 在一些性能要求很高的应用中,使用protocol buffer序列化,优于Json.而且protocol buffer向后兼容的能力比较好. 由于Asp.net core 采用了全新的MiddleWare方式,因此使用protobuf序列化,只需要使用Protobuf-net修饰需要序列化的对象,并在MVC初始化的时候增加相应的Formatter就可以了. 没时间解释了,快上车. 通过NuGet获取Zaabee.AspNetCoreProtobuf Install-Package Zaab

  • 使用Netty搭建服务端和客户端过程详解

    前言 前面我们介绍了网络一些基本的概念,虽然说这些很难吧,但是至少要做到理解吧.有了之前的基础,我们来正式揭开Netty这神秘的面纱就会简单很多. 服务端 public class PrintServer { public void bind(int port) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); //1 EventLoopGroup workerGroup = new NioEventLo

随机推荐