Skip to content

Instantly share code, notes, and snippets.

@kryzhovnik
Last active March 7, 2018 10:09
Show Gist options
  • Save kryzhovnik/5857670 to your computer and use it in GitHub Desktop.
Save kryzhovnik/5857670 to your computer and use it in GitHub Desktop.

Когда мы хотим скопировать данные из production окружения Ruby on Rails приложения в development или staging, обычно, нам нужно скопировать дамп базы данных и статические файлы (например, изображения загруженные пользователями). Копирование базы может не представляет проблем (например, ее можно копировать из бэкапов или резервных серверов БД). А вот копирование статических файлов занимает много времени и ресурсов сервера с которого копируют (и на который копируются) файлы.

В рассылке ror2ru Макс Лапшин предложил копировать статические файлы с production в текущее окружение по запросу, обработку производить в middleware.

Ниже, моя реализация такой middleware.

Подключение.

Поместите следующий код в файл соответствующего окружения (например config/environments/development.rb).

public_path = Rails.root.join('public')
# регулярные выражения запросов, которые будут обрабатываться
matching_paths = [/^\/uploads/, /^\/system/] # localhost:300/uploads/pic.jpg
# сервер с которого будем пытаться подгрузить файлы, если они отсутствуют
# на текущем сервере
remote_host = 'http://production-example.com'
config.middleware.insert_after ActionDispatch::Static,
  StaticMissing::Middleware, public_path, matching_paths, remote_host

Наше middleware подключается после ActionDispatch::Static - это middleware, которое отдает статические файлы в Ruby on Rails. Если ActionDispatch::Static не найдет файла соответствующего запросу в текущем окружении, то оно передает обработку запроса ниже по цепи middleware. Вот тут-то мы и должны вклиниться!

В нашей StaticMissing::Middleware мы проверим, есть ли файл соответствующий запросу на production-сервере. Если есть - то загрузим, сохраним в текущем окружении и передадим обработку запроса Rack::File middleware.

Файл lib/static_missing.rb

module StaticMissing
  class Middleware
    def initialize(app, public_path, matching_paths, remote_host)
      @app          = app
      @root         = public_path.to_s.chomp('/')
      @file_server  = ::Rack::File.new(@root)
      @file_handler = FileHandler.new(@root, matching_paths, remote_host)
    end

    def call(env)
      case env['REQUEST_METHOD']
      when 'GET', 'HEAD'
        path = env['PATH_INFO'].chomp('/')
        if @file_handler.load_if_static_path(path)
          env['PATH_INFO'] = path
          return @file_server.call(env)
        end
      end

      @app.call(env)
    end
  end

  class FileHandler
    def initialize(root, matching_paths, remote_host)
      @root, @matching_paths, @remote_host = root, matching_paths, remote_host
    end

    def load_if_static_path(path)
      static_path?(path) && load_file(path) == '200'
    end

    protected

      def static_path?(path)
        @matching_paths.any? do |matching_path|
          path.match matching_path
        end
      end

      def load_file(path)
        uri = URI.join(@remote_host, path)
        response = Net::HTTP.get_response(uri)
        if response.code == '200'
          full_path = escape_glob_chars(unescape_path File.join(@root, path))
          FileUtils.mkdir_p File.dirname(full_path)

          open(full_path, 'wb') do |file|
            file.write response.body
          end
          Rails.logger.info("Static file downloaded from #{uri} to #{full_path}")
        end
        response.code
      end

      def escape_glob_chars(path)
        path.force_encoding('binary') if path.respond_to? :force_encoding
        path.gsub(/[*?{}\[\]]/, "\\\\\\&")
      end

      def unescape_path(path)
        URI.parser.unescape(path)
      end

  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment