Эффективная фильтрация доменов с использованием регулярных выражений в ruby - PullRequest
0 голосов
/ 03 октября 2011

Я создаю серверное приложение для стойки, которое должно фильтровать домены, которые могут получить доступ к данным. Это в настоящее время, как я это делаю, и он отлично работает:

   authorized_domains = /domain1.com|domain2.com|domain3.com/
   return [417, {"Content-Type" => "text/html"}, ["Expectation Failed"]] unless env['HTTP_REFERER'].match(authorized_domains)

Проблема в том, что я получаю, вероятно, полмиллиона запросов в день или даже больше, и, кроме того, в списке было бы около 1000-3000 доменов.

Это самая эффективная фильтрация? делать регулярные выражения в списке доменов?

Ответы [ 3 ]

1 голос
/ 03 октября 2011

Храните домены как Set в константе класса, чтобы не создавать экземпляры при каждом запросе.Затем получите domain.tld из HTTP_REFERER и выполните поиск в наборе.

Что-то вроде

require 'rubygems'
require 'rack'
require 'set'
require 'URI'

class FontServer
  DOMAINS = %w[domain1.com domain2.com domain3.com].to_set
  def call(env)
    uri = URI.parse(env['HTTP_REFERER'])
    domain_tld = uri.host.split('.')[-2..-1].join('.')
    return [417, {"Content-Type" => "text/html"}, ["Expectation Failed"]] unless DOMAINS.include? domain
  end
end

@ pguardiario утверждает, что приведенное выше медленнее, чем его решение для регулярных выражений.Для сравнения это знать.

require 'rubygems'
require 'rack'
require 'set'
require 'URI'
require 'benchmark'

domains=[]
3000.times {|i| domains<<"domain#{i}.com"}
DOMAINS = domains.to_set
re = Regexp.new('^https?:\/\/(' + domains.join('|') + ')')
env = {'HTTP_REFERER' => 'http://domain4711.com/'}

Benchmark.bm do |benchmark|
puts "pguardiario regex"
benchmark.report do
    100000.times do
      env['HTTP_REFERER'] =~ re
    end
  end
  # Parsing the URL with the URI gem and getting domain.tld
  # About three times faster than the regex solution
  puts "jonelf original"
  benchmark.report do
    100000.times do
      uri = URI.parse(env['HTTP_REFERER'])
      domain_tld = uri.host.split('.')[-2..-1].join('.')
      DOMAINS.include? domain_tld
    end
  end
  # Set::include? is really fast. If we only got a
  # fast way to get the domain it would rock.
  puts "Only the set look-up"
  benchmark.report do
    domain_tld="domain4711.com"
    100000.times do
      DOMAINS.include? domain_tld
    end
  end
  # Gets the full host instead of just domain.tld
  # so that it does the same as pguardiario did
  # About 3.8 times faster than the regex on my machine.
  puts "A tweaked solution"
  benchmark.report do
    100000.times do
      DOMAINS.include? URI.parse(env['HTTP_REFERER']).host
    end
  end
end
      user     system      total        real
pguardiario regex
  8.437000   0.000000   8.437000 (  8.538086)
jonelf original
  2.719000   0.000000   2.719000 (  2.734375)
Only the set look-up
  0.047000   0.000000   0.047000 (  0.040039)
A tweaked solution
  2.203000   0.000000   2.203000 (  2.222656)
1 голос
/ 03 октября 2011

Я думаю, что из-за количества доменов и посещений в день лучшим способом было бы сохранить их в таблице, добавить индекс и затем запрашивать его при необходимости.Вы даже можете хранить таблицу в памяти (в СУБД), чтобы запросы выполнялись быстрее.

0 голосов
/ 03 октября 2011

Добавлять ^ https?: // быстрее, потому что регулярное выражение не нужно искать везде в строке, только в начале:

domains = ['domain1.com', 'domain2.com', 'domain3.com'] * 1000
env = {'HTTP_REFERER' => 'http://domain4.com/'}
re = Regexp.new('^https?:\/\/(' + domains.join('|') + ')')

500000.times do
    env['HTTP_REFERER'] =~ re
end

Редактировать

Ну, есть и тесты, и тесты @Jonas Elfstrom, чтобы принять пирог :) Давайте попробуем показать, почему вы не должны относиться ко всем тестам серьезно:

domains=[]
3000.times {|i| domains<<"domain#{i}.com"}
# let's assume the referrer is in the top 10 of whitelisted referrers.
# I'm not trying to introduce bias, I actually think that's reasonable.
env = {'HTTP_REFERER' => 'http://domain10.com/'}

Benchmark.bm do |benchmark|
  puts "pguardiario regex"
  benchmark.report do
    re = Regexp.new('^https?:\/\/(' + domains.join('|') + ')')
    100000.times do
      env['HTTP_REFERER'] =~ re
    end
  end

  puts "Only the set look-up"
  benchmark.report do
    DOMAINS = domains.to_set
    domain_tld = "domain4711.com" # huh?
    # Um, excuse me? You think you can get away with hardcoding that? lol :)

    100000.times do
      # how about if we parse on the fly like we would have to do in real life.
      domain_tld = URI.parse(env['HTTP_REFERER']).host
      DOMAINS.include? domain_tld
    end
  end
end

   user     system      total        real

регулярное выражение pguardiario

0,203000 0,000000 0,203000 (0,200012)

Только установленный просмотр

2.371000 0.000000 2.371000 (2.386136)

Результат: Извини @Jonas, даже не близко.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...