CHANSHIYU
GITHUBZERO
  • README
  • 時雨
    • 2017
      • 01 网站动态标题的两种方式
      • 02 RN App 外部唤醒踩坑记
    • 2018
      • 01 不一样の烟火
      • 02 Python 之禅
      • 03 Python 文件操作
    • 2019
      • 01 Aurora 食用指南
      • 02 Godaddy 域名找回记事
      • 03 一个接口的诞生
      • 04 SpringMVC 前后端传参协调
      • 05 主题集成友链访问统计
      • 06 Github Style 博客主题
      • 07 字符编码の小常识
      • 08 WSL 安装 Docker 实录
      • 09 Eriri comic reader
      • 10 Aurora 2.0
      • 11 jsDelivr 全站托管
      • 12 两年工作台变迁史
      • 13 春物
      • 14 一种优雅の笔记方式
    • 2020
      • 01 Telegram 电报机器人
      • 02 她的眼里有星辰
      • 03 文心雕龙
      • 04 软萌木子の有趣笔谈
      • 05 Telegram RSS 订阅频道
      • 06 水月雨银色飞船
      • 07 五年前旧照
    • 2021
      • 01 春宵苦短 2020
      • 02 风花
    • 2022
      • 01 小城新貌
      • 02 原神满级纪念
    • 2023
      • 01 2022 逆旅
      • 02 半透明背景图实现
      • 03 新年攒台海景房
  • 前端
    • JavaScript
      • 01 JavaScript 秘密花园
      • 02 JavaScript 正则技巧
      • 03 从浏览器解析 JS 运行机制
      • 04 Canvas 基础用法
      • 05 Blob Url And Data Url
      • 06 函数节流与函数防抖
      • 07 排序算法初探
      • 08 洗牌算法实现数组乱序
      • 09 正则匹配 match 和 exec
      • 10 正则匹配汉字
      • 11 JSX.Element vs ReactElement
      • 12 可选链与空值合并
      • 13 TypeScript 编码规范
      • 14 Typescript 中 interface 和 type 区别
      • 15 TypeScript 高级类型
      • 16 TypeScript 关键字
      • 17 TypeScript 映射类型
    • CSS
      • 01 Flex 弹性布局
      • 02 Position 定位
      • 03 CSS 逻辑属性
    • Node
      • 01 Node Tips
      • 02 七天学会 NodeJS
    • Note
      • 01 Note
      • 02 Code
      • 03 Snippets
      • 04 Git
    • React
      • 01 React Props Children 传值
      • 02 Use a Render Prop!
      • 03 React Hook
      • 04 React Hook 定时器
      • 05 Fetch data with React Hooks
      • 06 React 和 Vue 中 key 的作用
      • 07 useCallback 的正确使用方式
      • 08 useLayoutEffect 和 useEffect 的区别
      • 09 forwardRef 逃生舱
      • 10 React 条件渲染
    • Vue
      • 01 Vue Tips
      • 02 Vue 构建项目写入配置文件
      • 03 Vue 项目引入 SVG 图标
      • 04 Vue 一键导出 PDF
      • 05 动态可响应对象
      • 06 Vue 引入 SCSS
      • 07 Vue 路由权限控制
    • 实战系列
      • 01 WebSocket 心跳重连机制
      • 02 图片加解密二三事
      • 03 优雅实现 BackTop
      • 04 动态加载 JS 文件
      • 05 常用 DOM 方法比较
      • 06 AbortController 中断 fetch
      • 07 计算字符所占字节数
      • 08 Axios 自定义返回值类型
  • 后端
    • Java
      • 01 面向对象基本特征与原则
      • 02 Java 数据类型
      • 03 Java String
      • 04 Java 只有值传递
      • 05 Java final 与 static
      • 06 Java Object 通用方法
      • 07 Java 继承
      • 08 Java 反射
      • 09 Java 异常
      • 10 Java 容器
      • 11 Java 虚拟机
      • 12 Java IO
      • 13 Java HashMap
      • 14 Java List
      • 15 Java Stream
      • 16 Java 枚举
      • 17 Java 日期与时间
      • 18 Java fail fast
      • 19 Java BiFunction 和 BinaryOperator
    • 并发编程
      • 01 Java 并发
      • 02 synchronized
      • 03 volatile
      • 04 ReentrantLock
      • 05 ReadWriteLock
      • 06 StampedLock
      • 07 CompletableFuture
      • 08 ForkJoin
      • 09 ThreadLocal
      • 10 CountDownLatch
      • 11 ThreadPoolExecutor
      • 12 ExecutorService
      • 13 Atom 原子类
      • 14 BlockingQueue
    • 高效编程
      • 01 30 seconds of java8
      • 02 函数式替代 for 循环
      • 03 Java 字符串拼接
      • 04 单例模式的几种实现
      • 05 HashMap 排序
    • 理论概念
      • 01 Java Servlet
      • 02 Java 服务端分层模型
      • 03 经典排序算法
      • 04 LRU 缓存淘汰算法
      • 05 BloomFilter 判断元素存在
      • 06 Java HashMap 面试大全
      • 07 HTTP 状态码详解
      • 08 Cookie 和 Session
      • 09 基于消息队列的分布式事务解决方案
      • 10 微服务之所见
    • 实战系列
      • 01 AES CBC 加解密
      • 02 Magic 魔数获取文件类型
      • 03 获取请求 IP 地址
      • 04 Kaptcha 与数学公式验证码
      • 05 Netty 获取客户端 IP.md
      • 06 高性能无锁队列 Disruptor.md
      • 07 前后端接入阿里云盾
    • Linux
      • 01 Linux 文件权限系统
      • 02 Linux 常用软件安装
      • 03 CentOS 防火墙
    • MySQL
      • 01 MySQL
      • 02 SQL 语句 where 1=1
      • 03 truncate 和 delete
      • 04 事务
      • 05 关系模型
      • 06 Mybatis
      • 07 MySQL 查看数据库表详情
    • Nginx
      • 01 Nginx 指北
      • 02 nginx gzip 压缩
    • Note
      • 01 Vagrant
      • 02 Docker
      • 03 Lombok
      • 04 Swagger
      • 05 Redis
    • Spring
      • 01 Spring Boot
      • 02 Spring Validation
      • 03 Spring Data
      • 04 Spring 容器
      • 05 Spring AOP
      • 06 Spring Transactional 注解
      • 07 Spring Cloud Netflix
      • 08 Spring Cloud Alibaba
      • 09 Spring Security oAuth2
      • 10 Spring Boot 跨域解决方式
      • 11 Spring Boot 请求拦截
      • 12 Spring Boot 异步编程
      • 13 Spring Boot 定时任务
      • 14 Spring Boot 管理 bean
      • 15 Mybatis 逆向代码生成
      • 16 JWT
      • 17 JPA
      • 18 Apache Shiro
      • 19 Spring 异步请求
  • 书斋
    • ES6 标准入门
      • 01 变量声明与解构赋值
      • 02 语法的扩展
      • 03 数据类型与数据结构
      • 04 Proxy 和 Reflect
      • 05 异步编程 Promise
      • 06 Iterator 和 for of 循环
      • 07 Generator 函数
      • 08 Async 函数
      • 09 Class 类
    • JavaScript 设计模式
      • 01 基础知识
      • 02 设计模式(上)
      • 03 设计模式(下)
      • 04 设计原则和编程技巧
  • 纸函
    • 01 Interview
    • 02 Ceph
    • 03 动态规划
    • 04 Document.designMode
    • 2023-01-10
  • 万藏
    • 文档
      • 01 Git 文档
      • 02 Linux 命令大全
      • 03 七天学会 NodeJS
      • 04 Algorithms
    • 工具
      • 01 Nginx Config
      • 02 ProcessOn
      • 03 Flat Icon
      • 04 Regexper
      • 05 TempMail
      • 06 Carbon
由 GitBook 提供支持
在本页
  • 获取 IP
  • Nginx 配置

这有帮助吗?

  1. 后端
  2. 实战系列

05 Netty 获取客户端 IP.md

上一页04 Kaptcha 与数学公式验证码下一页06 高性能无锁队列 Disruptor.md

最后更新于2年前

这有帮助吗?

近期在后端摸鱼无聊,索性找个练手的项目,最终决定摸一个基于 netty 实现的 WEB IM(在线聊天项目)。项目不大,技术力也不高,期间依旧也踩了不少坑,毕竟咱还是太菜力 😹。

其中一个大坑就是获取 IM 中当前在线用户连接的 IP,这个看起来简单的功能咱着实折腾了好久。

Sping 中获取客户端 IP 很简单,前面咱也写文章介绍过 。不过从 netty 中获取远端连接的 IP 着实让咱有点小捉急,虽然 netty 官方原生提供了获取客户端 IP 的方法,但是如果服务器使用了 nginx 代理转发的话,原生提供的方法获取的却是服务器 IP 而非客户端真实 IP。

Google 查阅了不少资料,很多文章都是使用官方默认 IP 获取方式,与咱情况不符,很好奇他们真是这么用的嘛,不使用 nginx 反代???喵喵喵???

最后在 netty github 官方 issues 找到一个方案 ,用各种姿势尝试后摸索出一条解决方案。其实这个方法咱一开始就尝试了,但是食用姿势不对,给自己埋了坑,到后面第二次研究时才摸索出正确的解决方式。

获取 IP

首先新增 IP 处理 handler,之前就是因为 handler 顺序搞错了,食用姿势不对,折腾了很久。

You will need to put the handler in front of your upgrade handler.

public class WebSocketChannelInitializer extends ChannelInitializer<NioSocketChannel> {

    @Override
    protected void initChannel(NioSocketChannel ch) {
        // websocket 基于http协议,所以要有http编解码器
        ch.pipeline().addLast(new HttpServerCodec());
        // 对写大数据流的支持
        ch.pipeline().addLast(new ChunkedWriteHandler());
         // IP处理
        ch.pipeline().addLast(IPHandler.INSTANCE);
        // 对httpMessaHeartBeatHandlerge进行聚合,聚合成FullHttpRequest或FullHttpResponse
        ch.pipeline().addLast(new HttpObjectAggregator(1024 * 64));
        // 其他业务处理 handler...
    }
}

IPHandler 实现:

@Slf4j
@ChannelHandler.Sharable
public class IPHandler extends SimpleChannelInboundHandler<HttpObject> {
    public static final IPHandler INSTANCE = new IPHandler();

    @Override
    public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
        if (msg instanceof HttpRequest) {
            HttpRequest mReq = (HttpRequest) msg;
            // 从请求头获取 nginx 反代设置的客户端真实 IP
            String clientIP = mReq.headers().get("X-Real-IP");
            // 如果为空则使用 netty 默认获取的客户端 IP
            if (clientIP == null) {
                InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
                clientIP = insocket.getAddress().getHostAddress();
            }
            ctx.channel().attr(Attributes.IP).set(clientIP);
        }
        // 传递给下一个 handler
        ctx.fireChannelRead(msg);
    }
}

需要注意 SimpleChannelInboundHandler 的匹配规则,它会判断消息体类型,如果匹配则调用 channelRead0(ctx, msg) 处理消息,不会向下一个 handler 传递,否则的话需要调用 ctx.fireChannelRead(msg) 传递数据给下一个 handler。

Nginx 配置

上述方案还需要 nginx 配合,在 nginx 配置中加上客户端真实 IP proxy_set_header X-Real-IP $remote_addr;。

server {
    listen 80;
    server_name moechat.com;

    location / {
        proxy_connect_timeout 300s;
        proxy_send_timeout   300s;
        proxy_read_timeout   300s;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_pass http://api:8080;
        proxy_redirect http:// https://;
        client_max_body_size 300M;
    }

    location /chat {
        access_log off;
        proxy_pass http://api:7002;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_connect_timeout 1d;
        proxy_send_timeout 1d;
        proxy_read_timeout 1d;
    }

}
Spring 获取请求 IP 地址
Can't get websocket IP address through amazon ELB