Firebase Admin更新自定义声明报SocketTimeoutException异常
这个无延迟抛出的java.net.SocketTimeoutException: Read timed out不是真实网络超时,核心原因是Firebase Admin SDK默认HTTP客户端与Spring Boot内嵌Tomcat的NIO连接池出现了错误复用,触发条件和客户端请求的连接属性直接相关:
- 你当前使用的Firebase Admin SDK默认依赖JDK原生
HttpURLConnection发起到Google认证服务的请求,没有配置独立的连接池与超时参数,运行时会复用当前Tomcat工作线程持有的Socket连接 - Postman发起请求时默认不会强制开启长连接,Tomcat会为工作线程分配独立的临时Socket资源,Firebase Admin SDK调用Google接口时使用独立出站连接,因此可以正常执行
- Android端Retrofit依赖的OkHttp默认会自动携带
Connection: keep-alive、Accept-Encoding: gzip请求头,Tomcat会将当前入站请求的Socket标记为长连接绑定到处理线程,Firebase Admin SDK发起出站请求时会错误复用这个和Android客户端通信的入站Socket,试图从该连接读取Google服务的响应,自然会立刻触发读超时——这也是异常没有任何等待耗时、根本没有产生真实出站网络请求的原因。
按优先级操作,第一个方案即可解决绝大多数同类问题:
给Firebase Admin SDK配置独立的HTTP传输实例,彻底和Tomcat容器连接隔离
初始化FirebaseApp时不要使用默认配置,手动指定独立的NetHttpTransport并单独设置超时参数,保证Firebase相关调用走独立连接池,不与Web容器资源混用:// 替换原有Firebase初始化逻辑 FirebaseOptions options = FirebaseOptions.builder() .setCredentials(GoogleCredentials.fromStream(serviceAccountInputStream)) // 配置独立HTTP传输,和Tomcat连接完全隔离 .setHttpTransport(new NetHttpTransport.Builder() .setConnectTimeout(30000) .setReadTimeout(30000) .build()) .build(); if (FirebaseApp.getApps().isEmpty()) { FirebaseApp.initializeApp(options); }注意不要把这个HttpTransport实例和项目内其他HTTP客户端逻辑共用。
检查自定义过滤器逻辑
排查你实现的FirebaseAuthenticationTokenFilter,不要在过滤器内读取、修改当前请求的连接属性,不要在过滤器内执行任何需要发起出站请求的Firebase操作(比如提前查询用户信息、更新用户属性),过滤器仅做Token解析、SecurityContext填充的轻量逻辑,所有自定义声明更新等写操作全部放到Service层业务逻辑中执行。临时验证方法
给Android端Retrofit的请求配置添加请求头Connection: close禁用长连接,发起测试请求。如果加头后接口恢复正常,可100%确认是上述连接复用问题,直接使用第一个方案做永久修复即可,不需要保留客户端的这个特殊头配置。
部分7.x版本的Firebase Admin SDK默认HTTP传输实现存在连接泄漏问题,和Spring Boot 2.4+内嵌的Tomcat 9.x NIO模型兼容性较差,如果当前使用版本较低,可升级到8.1.0以上稳定版减少兼容性问题。
内容的提问来源于stack exchange,提问作者ZMobile

