С помощью безболезненных сценариев 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 для отправки запроса в 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()
не считается безопасным для использования (поскольку он неявно создает шаблон регулярных выражений). Они предлагают использовать 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
(поскольку мы выполняем итерацию по массиву символов).
Почему мы должны работать с массивом символов напрямую?
Поскольку, очевидно, painless
не допускает .length
метод String
, а также:
"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
?
Хотя я не могу найти какой-либо исторической заметки об именах после быстрого поиска в Google, на странице документации и некотором опыте (как выше в этом ответе) я могу сделать вывод, что он разработан безболезненно для использования в производстве .
Его предшественник, Groovy , был тикающей бомбой из-за использования ресурсов и уязвимостей безопасности . Поэтому команда Elasticsearch создала очень ограниченный набор сценариев Java / Groovy, который имел бы предсказуемую производительность и не содержал бы этих уязвимостей безопасности, и назвал его painless
.
Если есть что-то правдивое в painless
языке сценариев, является ли он ограниченным и песочницей .
Надеюсь, это поможет!