Как осуществлять потоковую загрузку с помощью rack.hijack и IO без преждевременного отключения? - PullRequest
0 голосов
/ 17 октября 2019

Наш сервер пользовательского интерфейса должен сделать доступными пользователям определенные загружаемые файлы. Файлы живут на специализированном сервере хранения. Сервер пользовательского интерфейса применяет некоторые разрешения и использует пользователя HTTP Basic Auth / pwd для получения файлов с сервера хранения, но пользователь никогда не должен знать эти учетные данные хранения, поэтому файлы передаются пользователям через наш сервер пользовательского интерфейса.

Нам нужно передать их через наш интерфейсный сервер, на котором запущены Rails 4.2.11 и Ruby 2.4.9. До сих пор мы присваивали перечислитель телу ответа, например Ruby on Rails 3: потоковая передача данных через Rails на клиент , но это было довольно ненадежно, и у нас было много отрезанных или неполных загрузок.

Оказалось, что метод стоечного захвата (https://blog.chumakoff.com/en/posts/rails_sse_rack_hijacking_api) был более новым, и мы надеялись, что он будет более надежным, но он также обрезает загрузки, когда более медленные клиенты подключаются к загрузке, и это большой файл. .

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

  • Я получаю поток ввода-вывода в стойку (PhusionPassenger::Utils::UnseekableSocket) и записываю результаты фрагментов из ответа GET на него.

  • Я пробовал многоразличные гемы http-клиентов, и все они имеют похожие проблемы, я думаю, потому что он заполняет буфер вывода при медленной загрузке клиентов, а не потому, что у них есть проблемы, хотяошибки выглядят таким образом.

  • Когда у клиентов http возникают проблемы в этом сценарии, они по-разному жалуются либо на EOF, либо на error reading from socket: Could not parse data entirely (1 != 0).

  • I 'Я пробовал разные версии Passenger и пробовал разные общие решения.

  • Я видел похожий вопрос на Rails: Как отправить файл с S3 на удаленный сервер ,В ответ он предлагает использовать <obj>.stat.size, чтобы проверить буфер и не допустить его переполнения, чтобы он больше соответствовал скорости загрузки пользователя. Тем не менее, поток, который мне дал rack-hijack, похоже, никак не может проверить размер или буфер, который я могу найти (PhusionPassenger::Utils::UnseekableSocket). Если бы это раскрыло способ сделать это, я, вероятно, мог бы сделать это.

  • Я просмотрел документацию Passenger для информации о буферизации и изменил некоторые настройки буферизации, но это не помогло.

  • Я изучил методы в потоке, которые я получаю от Пассажира, а также на настройках .to_io, но мне трудно найти инструменты, которые мне нужны, и имеющиепроблема с поиском большого количества документации.

  • Если я сопоставлю скорости загрузки с обоих концов, то проблем не будетТакие вещи, как wget --limit-rate=2m http://localhost:3000/download_test/stream1, позволяют мне снизить скорость клиента, и я обычно получаю ошибку около 180 МБ при загрузке.

  • Он работает на Amazon Linux, но работает либо намного лучше, либо безмного проблем на локальной машине для разработки Mac. Трудно сказать точно, если это все еще проблематично или просто не так много, так как проблема может немного зависеть от размера и других факторов.

  # a simplified version of the code, using a public URL
  def stream1
    url = 'https://www.spacetelescope.org/static/archives/images/publicationtiff40k/heic1502a.tif'
    response.headers['Content-Type'] = 'image/tiff'
    response.headers['Content-Disposition'] = 'attachment; filename="funn.tif"'
    response.headers["X-Accel-Buffering"] = 'no'
    response.headers["Cache-Control"] = 'no-cache'
    response.headers["Last-Modified"] = Time.zone.now.ctime.to_s

    response.headers["rack.hijack"] = proc do |stream|
      Thread.new do
        begin
          response = HTTP.get(url)
          response.body.each do |chunk|
            stream.write(chunk)
          end
        rescue HTTP::Error => ex
          logger.error("while streaming: #{ex}")
          logger.error("while streaming: #{ex.backtrace.join("\n")}")
        ensure
          stream.close
        end
      end
    end
    head :ok
  end

Результаты, которые я ищудолжны сделать загрузку надежной независимо от (разумной) скорости загрузки клиента из пользовательского интерфейса.

Спасибо за любую помощь, которую вы можете оказать, так как я чувствую себя застрявшим.

...