【问题标题】:Cancel Regex match if timeout如果超时取消正则表达式匹配
【发布时间】:2021-01-08 19:12:18
【问题描述】:

如果完成时间超过 10 秒,是否可以取消 regex.match 操作?

我正在使用一个巨大的正则表达式来匹配特定的文本,有时可能会起作用,有时可能会失败......

正则表达式:MINISTÉRIO(?:[^P]*(?:P(?!ÁG\s:\s\d+\/\d+)[^P]*)(?:[\s\S]*?))PÁG\s:\s+\d+\/(\d+)\b(?:\D*(?:(?!\1\/\1)\d\D*)*)\1\/\1(?:[^Z]*(?:Z(?!6:\s\d+)[^Z]*)(?:[\s\S]*?))Z6:\s+\d+

工作示例:https://regex101.com/r/kU6rS5/1

所以.. 如果需要超过 10 秒,我想取消操作。可能吗?我没有在 sof 中找到任何相关的内容

谢谢。

【问题讨论】:

  • Ummmmmm...你到底想在这里匹配什么?
  • 在 regex101 上它说:“脚本已停止执行,因为它超过了 2 秒的最大执行时间。当您的表达式导致所谓的灾难性回溯时,可能会发生这种情况。我已停止执行为您服务,并在您修改表达式或匹配字符串后恢复它。” regular-expressions.info/catastrophic.html - 如果需要太多时间,您无法停止 regex.match,我认为您需要重新评估您的正则表达式。
  • 嗯,这就是在我的应用程序中工作的原因。但是,仍然......需要 3 分钟才能完成......我想取消以避免阻塞我的服务器......
  • 关键是上面的表达式写得不好:1) (?:[\s\S]*?) 必须删除,因为它们甚至不应该在那里,在非捕获组上使用 * 量词要正确展开惰性点匹配模式(这里有 2 个),2)第二个展开的模式没有意义,您可以使用 [\s\S]*?,3)应删除最终的子模式(在负前瞻中)量词以便更快地匹配。
  • 这是正确的正则表达式,但仍然会很慢,因为输入很大并且有很多 PZ 并且模式本身很长:MINISTÉRIO(?:[^P]*(?:P(?!ÁG\s:\s\d+\/\d)[^P]*)*)PÁG\s:\s+\d+\/(\d+)\b[\s\S]*?\1\/\1[^Z]*(?:Z(?!6:\s\d)[^Z]*)*Z6:\s+\d+

标签: javascript regex node.js


【解决方案1】:

您可以生成一个执行正则表达式匹配的子进程,如果它在 10 秒内没有完成,则将其终止。可能有点矫枉过正,但应该可以。

fork 可能是你应该使用的,如果你走这条路。

如果你能原谅我的非纯函数,这段代码将展示你如何在分叉的子进程和你的主进程之间来回通信的要点:

index.js

const { fork } = require('child_process');
const processPath = __dirname + '/regex-process.js';
const regexProcess = fork(processPath);
let received = null;

regexProcess.on('message', function(data) {
  console.log('received message from child:', data);
  clearTimeout(timeout);
  received = data;
  regexProcess.kill(); // or however you want to end it. just as an example.
  // you have access to the regex data here.
  // send to a callback, or resolve a promise with the value,
  // so the original calling code can access it as well.
});

const timeoutInMs = 10000;
let timeout = setTimeout(() => {
  if (!received) {
    console.error('regexProcess is still running!');
    regexProcess.kill(); // or however you want to shut it down.
  }
}, timeoutInMs);

regexProcess.send('message to match against');

regex-process.js

function respond(data) {
  process.send(data);
}

function handleMessage(data) {
  console.log('handing message:', data);
  // run your regex calculations in here
  // then respond with the data when it's done.

  // the following is just to emulate
  // a synchronous computational delay
  for (let i = 0; i < 500000000; i++) {
    // spin!
  }
  respond('return regex process data in here');
}

process.on('message', handleMessage);

不过,这最终可能只是掩盖了真正的问题。您可能需要考虑像其他海报建议的那样修改您的正则表达式。

【讨论】:

  • Hei @dvlsg,你会说这个解决方案不好吗?我需要阻止,因为外部用户会使用它...
  • 取决于你对坏的定义是什么!如果我正确理解了这个问题。如果您确定您将拥有一个regex.match,这可能需要也可能不需要长达 10 秒,并且正则表达式本身无法改进,但您想确保您的主进程没有被阻塞,那么类似的东西这可能是你想要的。正则表达式运行时子进程将被阻塞,您可能想监控机器上的 CPU 使用率,但主进程将继续不受阻碍地运行。
  • 生成一个子进程非常昂贵,但它确实有效。请注意,这会给以这种方式执行的正则表达式增加大量的非确定性开销。
  • 当同时评估的正则表达式数量很高时(想想每分钟服务数百万个请求的服务器),为每个正则表达式评估生成一个进程可能会变得非常昂贵。
  • 正确 - 您应该权衡您的需求并进行适当调整。您始终可以将进程保留在进程池中,并根据需要将正则表达式发送到池中,如果这更符合您的需求。我怀疑一个预计每分钟处理数百万个请求的服务器不会想要运行每个请求最多需要 10 秒的正则表达式,不管是否启动了新进程。
【解决方案2】:

我在这里找到的另一个解决方案: https://www.josephkirwin.com/2016/03/12/nodejs_redos_mitigation/

基于使用VM,没有进程fork。 挺好看的。

    const util = require('util');
    const vm = require('vm');

    var sandbox = {
        regex:/^(A+)*B/,
        string:"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC",
        result: null
    };

    var context = vm.createContext(sandbox);
    console.log('Sandbox initialized: ' + vm.isContext(sandbox));
    var script = new vm.Script('result = regex.test(string);');
    try{
        // One could argue if a RegExp hasn't processed in a given time.
        // then, its likely it will take exponential time.
        script.runInContext(context, { timeout: 1000 }); // milliseconds
    } catch(e){
        console.log('ReDos occurred',e); // Take some remedial action here...
    }

    console.log(util.inspect(sandbox)); // Check the results

【讨论】:

    猜你喜欢
    • 2021-09-26
    • 2022-12-11
    • 2015-07-06
    • 1970-01-01
    • 2010-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-10
    相关资源
    最近更新 更多