Как динамически установить tld_length в приложении Rails с помощью puma (потокобезопасность) - PullRequest
2 голосов
/ 14 июля 2020

У нас есть приложение Rails, которое отвечает на несколько TLD, включая поддомены. Один из этих доменов является доменом .co.uk., поэтому длина TLD в этом случае равна 2 (например: ourapp.es, ourapp.co.uk, api.ourapp.es, api.ourapp.co.uk.

Для динамического изменения длина TLD, которую мы использовали это промежуточное ПО для стойки :

class Rack::TldLength

  def initialize(app, host_pattern, host_tld_length)
    @app = app
    @host_pattern = Regexp.new(host_pattern)
    @host_tld_length = host_tld_length
  end

  def call(env)
    original_tld_length = tld_length

    request = Rack::Request.new(env)

    set_tld_length(@host_tld_length) if request.host =~ @host_pattern

    @app.call(env)
  ensure
    set_tld_length(original_tld_length)
  end

  private

  def tld_length
    ActionDispatch::Http::URL.tld_length
  end
  def set_tld_length(length)
    ActionDispatch::Http::URL.tld_length = length
  end
end

Это работало до тех пор, пока мы не решили перейти с Unicorn на puma . С Unicorn каждый запрос будет go к другому рабочему (процессу) единорога, и проблем не было. Однако с puma каждый запрос может обрабатываться другим потоком. Мы подозреваем, что изменение значения ActionDispatch::Http::URL.tld_length не является поточно-безопасным, но мы мы пытаемся найти альтернативу этому.

Кажется, что маршрутизация Rails (где мы определяем маршруты с ограничениями субдоменов) зависит от правильной установки ActionDispatch::Http::URL.tld_length. сохранить параллелизм, предлагаемый наличием нескольких потоков, и при этом иметь возможность обрабатывать несколько доменов с разной длиной TLD?

Ответы [ 2 ]

1 голос
/ 23 июля 2020

Вы утверждаете, что:

Кажется, что маршрутизация Rails (где мы определяем маршруты с ограничениями субдоменов) зависит от правильной установки ActionDispatch :: Http :: URL.tld_length.

Мне кажется, что самый простой способ - нормализовать параметр "HOST" в env, чтобы позволить всем именам хостов вести себя одинаково.

т.е.

# Place this middleware at the top of the chain, before any Rails middleware.
class Rack::FixedHost

  # a host_pattern can be: /(foo.com|foo.co.uk|foo.bor.co.uk)$/
  def initialize(app, host_pattern, normalized_host)
    @app = app
    @host_pattern = Regexp.new(host_pattern)
    @normalized_host = normalized_host
  end

  def call(env)
    env[:ORIGINAL_HOST] = env['HTTP_HOST'.freeze] || @normalized_host
    env[:ORIGINAL_DOMAIN] = env[:ORIGINAL_HOST].match(@host_pattern).to_a[0] || @normalized_host
    env['HTTP_HOST'.freeze] = env[:ORIGINAL_HOST].to_s.sub(@host_pattern, @normalized_host)
    @app.call(env)
  end
end

Для пояснения: нормализация хоста означает, что он всегда имеет один и тот же постфикс имени хоста, независимо от исходного постфикса, что позволяет упростить извлечение поддоменов.

т.е. для sub.foo.com, sub.foo.co.uk и sub.foo.bor.co.uk normalized_host всегда будет sub.foo.com.

В этом примере sub легко извлекается после нормализации различных вариантов хоста (foo.com, foo.co.uk и foo.bor.co.uk). к единственному «нормализованному» варианту (foo.com).

По умолчанию такие методы, как url_for, будут создавать относительный URL, поэтому фактическое имя хоста не имеет значения.

Однако, если вы используете url_for или другие функции для предоставления полного URL, вы можете рассмотреть возможность использования явного :host, чтобы направить трафик c на региональное имя хоста, которое вы используете. т.е.:

url_for(action: 'index', host: "admin.#{request.env[:ORIGINAL_DOMAIN]}")

Это, конечно, можно было бы сделать еще более мощным, извлекая исходное доменное имя перед нормализацией хоста, что позволяет вам маршрутизировать к указанным c субдоменам, сохраняя при этом региональный домен.

Примечание (мое первоначальное наблюдение / ответ):

Ваш код хранит длину TLD каждого запроса в общей глобальной переменной.

Когда поступают два параллельных запроса в двух разных потоках, это вопрос случайности узнать, какая длина TLD будет использоваться (последний из записанных, скорее всего, если не происходит «обрезания» данных).

Поточно-ориентированный подход сохранит информацию в env, позволяющая каждому запросу иметь собственную длину TLD.

Следующий пример НЕ будет работать, потому что я не обрабатываю длины TLD и не знаю, как их рассчитать ... но он показывает использование env как поточно-ориентированное хранилище для каждого запроса.

class Rack::TldLength

  def initialize(app, host_pattern, host_tld_length)
    @app = app
    @host_pattern = Regexp.new(host_pattern)
    @default_tld_length = host_tld_length
  end

  def call(env)
    # ActionDispatch::Http::URL.tld_length = @default_tld_length if(env["HTTP_HOST".freeze].to_s =~ @host_pattern)
    env[:hosts_tld] = (env["HTTP_HOST".freeze].to_s =~ @host_pattern) ? @default_tld_length : ActionDispatch::Http::URL.tld_length
    @app.call(env)
  end
end
0 голосов
/ 25 июля 2020

ActionDispatch::Http::URL сохраняет tld_length как модульную переменную , то есть как единую глобальную переменную для всего вашего приложения. Невозможно сделать это потокобезопасным. Я подозреваю, что дизайнерское мышление заключалось в том, что ваше приложение будет находиться только в одном домене, поэтому одной глобальной настройки, установленной при запуске, будет достаточно, поэтому нет необходимости делать tld_length потокобезопасным.

ActionDispatch - это занимает центральное место в Rails, так что я бы старался не возиться с ним. Насколько сложно было бы запустить 2 сервера Puma и отправить весь tld_length = 2 трафик c на один сервер и tld_length = 1 на другой сервер? Если вы используете серверную ферму, это будет разумным ключом сегментирования и избавит вас от дополнительных трюков.

Если бы мне пришлось запустить его на 1 сервере, я бы решил изменить ActionDispatch::Http::URL чтобы он сохранял tld_length в локальной переменной потока вместо переменной модуля и устанавливал ее при каждом запросе. Вам также придется изменить функции, которые используют переменную модуля в качестве значения по умолчанию, например domain, чтобы использовать переменную потока по умолчанию, что может быть проще всего с помощью функции доступа.

...