通过 Elasticsearch 无痛脚本,您可以使用如下代码:
POST ip_search/doc/_search
{
"query": {
"match_all": {}
},
"script_fields": {
"originating_ip_calc": {
"script": {
"source": """
String ip_addr = params['_source']['originating_ip'];
def ip_chars = ip_addr.toCharArray();
int chars_len = ip_chars.length;
long result = 0;
int cur_power = 0;
int last_dot = chars_len;
for(int i = chars_len -1; i>=-1; i--) {
if (i == -1 || ip_chars[i] == (char) '.' ){
result += (Integer.parseInt(ip_addr.substring(i+ 1, last_dot)) * Math.pow(256, cur_power));
last_dot = i;
cur_power += 1;
}
}
return result
""",
"lang": "painless"
}
}
},
"_source": ["originating_ip"]
}
(请注意,我使用Kibana console 将请求发送到 ES,它会在发送前进行一些转义以使其成为有效的 JSON。)
这将给出如下响应:
"hits": [
{
"_index": "ip_search",
"_type": "doc",
"_id": "2",
"_score": 1,
"_source": {
"originating_ip": "10.0.0.1"
},
"fields": {
"originating_ip_calc": [
167772161
]
}
},
{
"_index": "ip_search",
"_type": "doc",
"_id": "1",
"_score": 1,
"_source": {
"originating_ip": "1.2.3.4"
},
"fields": {
"originating_ip_calc": [
16909060
]
}
}
]
但为什么一定要这样呢?
为什么.split 的方法不起作用?
如果您将问题中的代码发送到 ES,它会回复如下错误:
"script": "String[] ipAddressInArray = \"1.2.3.4\".split(\"\\\\.\");\n\nlong result = 0;\nfor (int i = 0; i < ipAddressInArray.length; i++) {\n int power = 3 - i;\n int ip = Integer.parseInt(ipAddressInArray[i]);\n long longIP = (ip * Math.pow(256, power)).toLong();\n result = result + longIP;\n}\nreturn result;",
"lang": "painless",
"caused_by": {
"type": "illegal_argument_exception",
"reason": "Unknown call [split] with [1] arguments on type [String]."
这主要是因为Java的String.split() is not considered safe to use(因为它隐式地创建了正则表达式模式)。他们建议使用Pattern#split,但要这样做,您应该在索引中启用正则表达式。
默认情况下,它们被禁用:
"script": "String[] ipAddressInArray = /\\./.split(\"1.2.3.4\");...
"lang": "painless",
"caused_by": {
"type": "illegal_state_exception",
"reason": "Regexes are disabled. Set [script.painless.regex.enabled] to [true] in elasticsearch.yaml to allow them. Be careful though, regexes break out of Painless's protection against deep recursion and long loops."
为什么我们必须显式转换(char) '.'?
因此,我们必须手动将字符串拆分为点。直接的方法是将字符串的每个字符与'.'(在Java 中表示char 文字,而不是String)进行比较。
但对于painless,它意味着String。所以我们必须对char 进行显式转换(因为我们正在迭代一个字符数组)。
为什么我们必须直接使用 char 数组?
因为显然painless 也不允许使用String 的.length 方法:
"reason": {
"type": "script_exception",
"reason": "compile error",
"script_stack": [
"\"1.2.3.4\".length",
" ^---- HERE"
],
"script": "\"1.2.3.4\".length",
"lang": "painless",
"caused_by": {
"type": "illegal_argument_exception",
"reason": "Unknown field [length] for type [String]."
}
}
那为什么叫painless?
虽然我在快速谷歌搜索后找不到任何关于命名的历史记录,但从documentation page 和一些经验(如上面的这个答案)我可以推断出它的设计是无痛的使用生产。
它的前身,Groovy,由于资源使用和security vulnerabilities,是一颗定时炸弹。因此,Elasticsearch 团队创建了一个非常有限的 Java/Groovy 脚本子集,它具有可预测的性能并且不会包含这些安全漏洞,并将其命名为 painless。
如果painless 脚本语言有任何真实性,那就是它是受限和沙盒。