解决DBApi gateway 中文乱码问题

问题链接

gateway中文乱码

dbapi的gateway中文乱码问题

排查思路

初步分析

dbapi支持两种部署模式,一种是单机版(standalone模式),一种是集群版(gatewayapiserver版本).两种模式同一请求返回结果不一致,
那么一定是两种模式下存在某些差异导致中文乱码

环境搭建

dbapi调用服务拓扑图

需要启动dbapi-standalone,dbapi-cluster-gateway,dbapi-cluster-apiServer
其中dbapi-cluster-gateway,dbapi-cluster-apiServer需要通过nacos暴露服务

复现

在复现的时候,我发现的确如果返回值中存在中文,单机版返回正常,访问gateway返回的中文乱码。
因为gateway最终也是调用的apiserver,所以我们直接调用apiserver,发现apiserver返回的中文乱码
至此我们怀疑是apiserver造成中文乱码问题
同时注意到单机版返回的responsecontent-type正常,apiserver返回的response没有content-type

单机版返回response示例:

单机返回response

apiserver返回response示例:

apiserver返回response

因此我们怀疑是apiserver没有为response设置content-type造成中文乱码

代码分析

使用代码版本: dev分支:409a6cf2c0ec222946754d195846bb7023ca0c3b

使用关键字.setContentType(搜索,代码中为返回值设置response的地方共有6处,其中我们需要关心以下两个地方:

  • com.gitee.freakchicken.dbapi.basic.conf.JwtAuthenticationInterceptor
  • com.gitee.freakchicken.dbapi.basic.filter.ApiIPFilter

通过com.gitee.freakchicken.dbapi.basic.conf.MyConfig#addInterceptors发现JwtAuthenticationInterceptor主要校验前端UI,因此请求进来的时候起作用只能是ApiIPFilter
要么是请求打到apiserver,请求调用的时候没有经历这个ApiIPFilter,要么就是请求通过这个ApiIPFilter设置的response content-type没有效果

通过测试,发现当直接请求apiserver时并没有通过ApiIPFilter,通过分析apiserver代码,在com.gitee.freakchicken.dbapi.apiserver.DBApiApiServerComponentScan中排除了com.gitee.freakchicken.dbapi.basic.filter.ApiIPFilter

同时我们发现gateway存在com.gitee.freakchicken.dbapi.gateway.filter.GatewayIPFilter,应该是作者认为gateway存在了ip过滤,apiserver就不应该再过滤一遍ip,但是却忽略了response设置编码这一环

修复

添加一个统一的请求头过滤器com.gitee.freakchicken.dbapi.basic.filter.ApiHeaderFilter只针对dbapi.api.context配置的地址,同时调整注册filter与sevlet的代码结构

分别为:

  • com.gitee.freakchicken.dbapi.conf.FilterConfig
    ipFilter order设置为1,authFilter order设置为2,apiHeaderFilter order设置为3(这里有坑,下面有解释)
  • com.gitee.freakchicken.dbapi.conf.ServletConfig
  • com.gitee.freakchicken.dbapi.apiserver.conf.FilterConfig
    authFilter order设置为2,apiHeaderFilter order设置为3(这里有坑,下面有解释)
  • com.gitee.freakchicken.dbapi.apiserver.conf.ServletConfig

调试

  1. 发现单机模式下返回值正常,集群模式下返回值有content-type,但是content-type却不是我们设置的utf-8

集群模式下response
第一次修复response

  1. 我将com.gitee.freakchicken.dbapi.basic.filter.ApiHeaderFilter#doFilter的代码改为
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    String characterEncoding = response.getCharacterEncoding();
    System.out.println("设置CharacterEncoding前: "+characterEncoding);
    response.setCharacterEncoding("UTF-8");
    characterEncoding = response.getCharacterEncoding();
    System.out.println("设置CharacterEncoding后: "+characterEncoding);

    String contentType = response.getContentType();
    System.out.println("设置ContentType前: "+contentType);
    response.setContentType("application/json; charset=utf-8");
    contentType = response.getContentType();
    System.out.println("设置ContentType后: "+contentType);
    用以调试

发现response.setContentTyperesponse.setCharacterEncoding不起作用

当请求单机版时发现输出为

1
2
3
4
设置CharacterEncoding前: utf-8
设置CharacterEncoding后: utf-8
设置ContentType前: application/json;charset=utf-8
设置ContentType后: application/json;charset=utf-8

当请求apiserver时发现输出为

1
2
3
4
设置CharacterEncoding前: ISO-8859-1
设置CharacterEncoding后: ISO-8859-1
设置ContentType前: null
设置ContentType后: application/json;charset=ISO-8859-1

下面就response.setContentTyperesponse.setCharacterEncoding问题分为单机版与集群版两种模式下讨论
首先我们要注意,dbapi在两种模式下的FilterChain是不同的

  1. 单机版FilterChain

单机版FilterChain

当请求单机版时,apiIPFilterapiHeaderFilter之前,因此此时apiHeaderFilter设不设置无所谓

  1. 集群版FilterChain

集群版FilterChain

当请求集群版时,apiAuthFilterapiHeaderFilter之前,此时apiHeaderFilter再设置就不起作用了

通过断点调试,发现在apiAuthFilterresponse.getWriter()时会setCharacterEncoding,而这个set的值就是ISO-8859-1,同时org.apache.catalina.connector.Response在设置完编码后会将usingWriter设为true导致下次设置就不会起作用

这告诉我们不使用writer的时候不要提前获取response的writer,否则无法在后续filter中设置编码,同时设置response的编码最好放在第一个

按照这个原则,我们再次修改代码,调试提交