1.前言

  这几天使用mongo的时候遇到了一个异常:Invalid BSON field name $gte,该问题可能会有很多小伙伴会遇到,因此记录一下解决过程。起因是用JAVA翻译一个其他语言写的程序,需要在mongo中保存某次的查询条件,以便下次使用。但是保存的时候抛出了这个异常,原程序却没有问题,这个肯定和JAVA的实现有关,与mongo服务本身关系不大了。下面是一个简略的排查过程,纯文字,尽量会写的简洁些。

2.排查过程

  1.定位异常抛出的位置:

    该异常由AbstractBsonWriter的writeName方法抛出,其由FieldNameValidator进行校验,校验不通过就会抛出该异常。

    分析:这个过程十有八九就是用于校验mongo的键名称了,作用应该和防止SQL注入一样,用于防止命令注入的。这个时候就要明确,这个不是mongo本身限制的,而是Java的mongo driver限制的,不然没道理其他语言能够插入。

  2.确定FieldNameValidator的规则:

    该接口有四个实现类:

      CollectibleDocumentFieldNameValidator:为空,包含.,以$开头非$db,$ref,$id校验失败

      MappedFieldNameValidator:由其内部持有的FieldNameValidator对象决定相关校验规则

      NoOpFieldNameValidator:不校验,直接返回true

      UpdateFieldNameValidator:$开头的返回true

    分析:这些实现类中,$gte唯一可能失败的就是CollectibleDocumentFieldNameValidator了,这个时候猜测是不是由于某些原因,选择生成错了validator,毕竟应该通过才对,或者是我插入的数据没有满足相关规则,这个可能性很低,还是因为有成功的例子。

  3.排查CollectibleDocumentFieldNameValidator生成过程:

    通过错误堆栈信息,可以逆推执行链:1.AbstractBsonWriter实际的对象是BsonBinaryWriter类,其在CommandMessage类的133行encodeMessageBodyWithMetadata方法中,通过new创建,携带了CommandMessage的payloadFieldNameValidator。2.CommandMessage是在CommandProtocolImpl类的80行调用了getCommandMessage方法,这个validator来自CommandProtocolImpl的payloadFieldNameValidator字段。3.CommandProtocolImpl是由DefaultServerConnection的command的方法中new创建而来的,第127行。payloadFieldNameValidator是作为参数传递过来的。4.参数由MixedBulkWriteOperation的executeCommand传递,调用的方法是BulkWriteBatch的getFieldNameValidator方法获得,该方法具体内容如下:

  可以看到其是根据batchType来进行操作的,这个值怎么来的这里不再多说明,但是很显然我们是要做一个插入操作,除非有bug,不然此处的batchType应该是INSERT。这样就清楚了为什么抛出了这个异常,原因都在于这里生成的校验器是CollectibleDocumentFieldNameValidator。

  4.知道了原因,但是如何解决呢?

    第一个思路是能不能不走那个逻辑,即不走writeMap方法。通过断点发现对象的所有字段都转成了BsonString类,只有那个是map,查看基础体系发现其继承自BsonValue, BsonValue有个实现类BsonDocument,是不是要用这个类可以绕过writeMap方法呢?实际上是不行的,这个类也是一个map对象。

    第二个思路在查询代码中看到了MixedBulkWriteOperation类有一个bypassDocumentValidation属性,这个是不是有什么关系呢?蛋疼的是mongoTemplate.insert方法不能设置这个属性,必须通过getCollection.insert可以通过传入InsertOneOptions进行设置。实际上这个也没用,其含义是:绕过设置的校验规则,插入数据。但是这个是针对mongo而言,现在在Java的driver层就over了,这个属性在这里没有太多作用。

  5.陷入了死局,借助网络的力量,来看看其他人的解决思路。

    常见做法:替换掉$符号,用$来绕过验证,使用的时候再换回来。这样做确实有效,但是在多系统公用一个数据库的情况下,让所有模块都取出来的时候替换回去无疑是一个很麻烦的做法。

  6.意外收获:

    查询过程中,突然发现mongo在3.6版本之前都是不能插入$等特殊字符的,心中一凉,但是我用的是高版本的,而且有成功的例子,这个应该不是主要原因。

    后来又查到另一个人的解决方法是重写了driver的部分代码,替换了那部分校验逻辑。但是这无疑是一个比较麻烦的操作,而且难保不出现什么问题。

    最后找到了 https://jira.mongodb.org/browse/JAVA-2810。这特么是个Java版本的driver的bug,没有跟上服务端的版本更新,毕竟3.6之前还是不允许的,所以一直遗留到现在,该任务至今还是Unresolved状态。

  7.如何解决:

    确定这个是一个未来得及同步服务端特性的bug之后,之前的排查就没有意义了,通过API接口是无法解决的了。那么到底怎么做呢?替换掉字符,代价太大,重写driver很难保证是否有其他坑,有风险。有没有最小代价解决该问题的方法。回顾CollectibleDocumentFieldNameValidator,其并不是所有的$开头的都禁止了,不是有三个放行了吗。这个是用私有静态常量的List完成的,第一个反应就是扩大不校验的区间。如何扩大?通过反射。主要代码如下:

Field field = CollectibleDocumentFieldNameValidator.class.getDeclaredField("EXCEPTIONS");
field.setAccessible(true); Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL); List<String> newValue = Arrays.asList("$db","$ref","$id","$gte");
field.set(null, newValue);

  8.验证问题解决。

3.后记

  该问题是mongo driver的一个bug,官方暂时未修复,需要自己想办法解决,此文给出了三种解决思路:

    1.替换字符,查询的时候替换回来。如果读写分离且涉及多个程序,该方法就比较麻烦了,但是最安全。

    2.重写mongo driver修复这个bug。风险过大,未完全了解driver的运行,修改出未知问题也是可能的。

    3.通过反射,扩大$开头的放行字段。这个是用于防止注入攻击的,所以会产生相关风险,但是自己代码中控制好就没有太大问题。另一个缺点是如果是包含.的字段这种手段就没有效果了。

  这三种方法根据个人需要进行调整。最后如果官方修复了这个问题,还是及时更新jar包才是上策。这一点要牢记。

杂记---Mongo的Invalid BSON field name $gte的更多相关文章

  1. 异常mongodb:Invalid BSON field name XXXXXX:YYYYY.zz

    1.本周遇到这个问题. 定位到发现一个很神奇的现象上面的结构无法顺利以map的key值存入mongodb里面. 而且到线上才发现这个问题. 而且是部分用户才会出现这样的情况 大部分人的该数据是这样的 ...

  2. java.io.IOException: invalid header field

    通过本文, 我们明白了什么是 jar的清单文件 MANIFEST.MF, 简单示例: E:\ws\Test\WEB-INF\classes>jar cvfm testCL.jar ListTes ...

  3. java打包遇到问题java.io.IOException: invalid header field

    问题:java打包时报以下错误 $ jar -cvmf main.txt test.jar Shufile1.class java.io.IOException: invalid header fie ...

  4. jar 问题 : java.io.IOException: invalid header field

    通过本文, 我们明白了什么是 jar的清单文件 MANIFEST.MF, 简单示例: E:\ws\Test\WEB-INF\classes>jar cvfm testCL.jar ListTes ...

  5. android编译make错误——"javalib.jar invalid header field”、"classes-full-debug.jar 错误 41 "

    错误:读取 out/target/common/obj/JAVA_LIBRARIES/core-tests_intermediates/javalib.jar 时出错:invalid header f ...

  6. Java打包问题之一:打包出现java.io.IOException: invalid header field

    前言 java的打包工具jar有时候会出一些莫名其妙的问题,比如不合法的头部字段等等.这些问题之前也没注意,因为一直是用eclipse打包.后来在公司的时候,要求统一编写shell脚本来进行打包. 其 ...

  7. 严重: Error in dependencyCheck java.io.IOException: invalid header field(tomcat启动成功可是訪问web项目404错误)

    tomcat启动的时候出现 严重: Error in dependencyCheck java.io.IOException: invalid header field 而且tomcat也不自己主动r ...

  8. MongoDB - Introduction to MongoDB, MongoDB Extended JSON

    JSON can only represent a subset of the types supported by BSON. To preserve type information, Mongo ...

  9. mongodb查询文档

    说到查询,我们一般就想起了关系型数据库的查询了,比如:order by(排序).limit(分页).范围查询(大于某个值,小于某个值..,in查询,on查询,like查询等待很多),同样mongodb ...

随机推荐

  1. Windows10和CentOS7双系统安装的一些小技巧

    我个人是先安装好了win10系统,且win10是单独在一个120g的盘里:而centOS7则是安装在另一个500g的磁盘的其中的380g里: 这里要着重注意的是,500g里分成380g的盘不要在win ...

  2. Matlab 中以分数显示结果

    转http://www.blogbus.com/shijuanfeng-logs/234881647.html Matlab,计算得到的结果一般是小数形式. 但为了更精确表示,我们有时候需要用到分数形 ...

  3. spring @Transactional 声明式事务

    项目地址:git@github.com:witaste/transaction-annotation.git 情景一: A external method calls a method of the ...

  4. 零停重启程序工具Huptime研究

    目录 目录 1 1. 官网 1 2. 功能 1 3. 环境要求 2 4. 实现原理 2 5. SIGHUP信号处理 3 6. 重启线程 4 7. 重启目标程序 5 8. 系统调用钩子辅助 6 9. 被 ...

  5. HDU 1754 I Hate It 2016-09-14 19:01 27人阅读 评论(0) 收藏

    I Hate It Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total ...

  6. 快速掌握Java中Lambda表达式的用法

    Lambda表达式的作用: Lambda表达式的作用就是简化代码开发,让代码看起来更加简介.它是用来简化匿名内部类的.但是并不是所有的匿名内部类都能用Lambda表达式简化,Lambda表达式是有使用 ...

  7. Hibernate中OpenSessionInViewFilter(通常配置在web.xml文件中)的作用

    Spring为我们解决Hibernate的Session的关闭与开启问题. Hibernate 允许对关联对象.属性进行延迟加载,但是必须保证延迟加载的操作限于同一个 Hibernate Sessio ...

  8. Paul Simon -- Duncan

    Paul Simon -- Duncan (London,January 1972) Couple in the next roomBound to win a prizeTheyve been go ...

  9. Python学习(一)——数据类型

    在大学学过一点python,只学了语法,关于实际应用却没怎么用过.现在用一些python的脚本来模拟webservices,挺好用的.这个语言,还是要好好学习学习了. 目前看着教材来的,这本教材,好像 ...

  10. Chrome自定义缩放百分比

    我想要设置Chrome页面缩放为120%,但是Chrome只提供110% 125%,根本没有让我舒心的缩放比例. 强迫症发作,谷歌了半天没有一个很好的解决方案. 虽然也有不少第三方扩展可以自定义缩放比 ...