Cloudflare Worker TypeError: одноразовое тело

Я пытаюсь использовать Облачный рабочий для прокси-запроса POST на другой сервер.

Он выдает исключение JS — завернув блог try/catch, я установил, что ошибка:

TypeError: A request with a one-time-use body (it was initialized from a stream, not a buffer) encountered a redirect requiring the body to be retransmitted. To avoid this error in the future, construct this request from a buffer-like body initializer.

Я бы подумал, что это можно решить, просто скопировав ответ, чтобы он не использовался, например:

return new Response(response.body, { headers: response.headers })

Это не работает. Что мне не хватает в потоковой передаче и буферизации здесь?

addEventListener('fetch', event => {

  var url = new URL(event.request.url);

  if (url.pathname.startsWith('/blog') || url.pathname === '/blog') {
    if (reqType === 'POST') {
      event.respondWith(handleBlogPost(event, url));
    } else {
      handleBlog(event, url);
    }
  } else {
   event.respondWith(fetch(event.request));
  }
})

async function handleBlog(event, url) {
  var newBlog = "https://foo.com";
  var originUrl = url.toString().replace(
    'https://www.bar.com/blog', newBlog);
  event.respondWith(fetch(originUrl)); 
}

async function handleBlogPost(event, url) {
  try {
    var newBlog = "https://foo.com";
    var srcUrl = "https://www.bar.com/blog";

    const init = {
      method: 'POST',
      headers: event.request.headers,
      body: event.request.body
    };
    var originUrl = url.toString().replace( srcUrl, newBlog );

    const response = await fetch(originUrl, init)

    return new Response(response.body, { headers: response.headers })

  } catch (err) {
    // Display the error stack.
    return new Response(err.stack || err)
  }
}
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
3
0
1 483
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Несколько вопросов здесь.

Во-первых, сообщение об ошибке касается тела запроса, а не тела ответа.

По умолчанию объекты Request и Response, полученные из сети, имеют потоковые тела — request.body и response.body оба имеют тип ReadableStream. Когда вы пересылаете их, тело проходит через потоки — куски принимаются от отправителя и пересылаются конечному получателю без локального сохранения копии. Поскольку копии не сохраняются, поток можно отправить только один раз.

Однако проблема в вашем случае заключается в том, что после потоковой передачи тела запроса на исходный сервер источник ответил перенаправлением 301, 302, 307 или 308. Эти перенаправления требуют, чтобы клиент повторно передал точно такой же запрос на новый URL-адрес (в отличие от перенаправления 303, которое предписывает клиенту отправить запрос GET на новый URL-адрес). Но Cloudflare Workers не сохранили копию тела запроса, поэтому не могут отправить его снова!

Вы заметите, что эта проблема не возникает, когда вы делаете fetch(event.request), даже если запрос является POST. Причина в том, что свойство event.requestredirect установлено на "manual", что означает, что fetch() не будет пытаться автоматически следовать перенаправлениям. Вместо этого fetch() в этом случае возвращает сам ответ перенаправления 3xx и позволяет приложению обрабатывать его. Если вы вернете этот ответ в клиентский браузер, браузер позаботится о фактическом следовании перенаправлению.

Однако в вашем воркере кажется, что fetch() пытается автоматически следовать перенаправлению и выдает ошибку. Причина в том, что вы не установили свойство redirect при создании объекта Request:

const init = {
  method: 'POST',
  headers: event.request.headers,
  body: event.request.body
};
// ...
await fetch(originUrl, init)

Поскольку init.redirect не был установлен, fetch() использует поведение по умолчанию, такое же, как redirect = "automatic", то есть fetch() пытается следовать перенаправлениям. Если вы хотите, чтобы fetch() использовал поведение переадресации вручную, вы можете добавить redirect: "manual" к init. Однако похоже, что вы действительно пытаетесь здесь скопировать весь запрос. В этом случае вы должны просто передать event.request вместо структуры инициализации:

// Copy all properties from event.request *except* URL.
await fetch(originUrl, event.request);

Это работает, потому что у Request есть все поля, которые нужны второму параметру fetch().

Что делать, если вы хотите автоматические перенаправления?

Если вы действительно хотите, чтобы fetch() выполнял перенаправление автоматически, вам нужно убедиться, что тело запроса буферизовано, а не передается в потоковом режиме, чтобы его можно было отправить дважды. Для этого вам нужно прочитать все тело в строку или ArrayBuffer, а затем использовать это, например:

const init = {
  method: 'POST',
  headers: event.request.headers,
  // Buffer whole body so that it can be redirected later.
  body: await event.request.arrayBuffer()
};
// ...
await fetch(originUrl, init)

Примечание об ответах

I would have thought this could be solved by simply copying the Response so that it's unused, like so:

return new Response(response.body, { headers: response.headers })

Как описано выше, ошибка, которую вы видите, не связана с этим кодом, но я все равно хотел прокомментировать здесь две проблемы, чтобы помочь.

Во-первых, эта строка кода не копирует все свойства ответа. Например, вам не хватает status и statusText. Есть также некоторые более неясные свойства, которые проявляются в определенных ситуациях (например, webSocket, специфичное для Cloudflare расширение спецификации).

Вместо того, чтобы пытаться перечислить каждое свойство, я снова рекомендую просто передать сам старый объект Response в качестве структуры параметров:

new Response(response.body, response)

Вторая проблема связана с вашим комментарием о копировании. Этот код копирует метаданные Response, но нет копирует тело. Это потому, что response.body — это ReadableStream. Этот код инициализирует новый объект Respnose, чтобы он содержал ссылку на тот же ReadableStream. Как только что-либо считывается из этого потока, этот поток используется для объектов обеResponse.

Обычно это нормально, потому что обычно вам нужна только одна копия ответа. Как правило, вы просто собираетесь отправить его клиенту. Однако есть несколько необычных случаев, когда вы можете захотеть отправить ответ в два разных места. Одним из примеров является использование Cache API для кэширования копии ответа. Вы можете сделать это, прочитав весь Response в память, как мы сделали с запросами выше. Однако для ответов нетривиального размера это может привести к трате памяти и увеличению задержки (вам придется дождаться всего ответа, прежде чем он будет отправлен клиенту).

Вместо этого в этих необычных случаях вы действительно хотите сделать «тройник» потока, чтобы каждый фрагмент, поступающий из сети, фактически записывался на два разных выхода (например, команда Unix tee, которая исходит из идеи T соединение в трубе).

// ONLY use this when there are TWO destinations for the
// response body!
new Response(response.body.tee(), response)

Или, как ярлык (когда вам не нужно изменять какие-либо заголовки), вы можете написать:

// ONLY use this when there are TWO destinations for the
// response body!
response.clone()

Как ни странно, response.clone() делает что-то полностью, отличное от new Response(response.body, response). response.clone() отображает тело ответа, но сохраняет неизменными заголовки (если они были неизменными в оригинале). new Response(response.body, response) использует ссылку на один и тот же основной поток, но клонирует заголовки и делает их изменяемыми. Лично я нахожу это довольно запутанным, но это то, что указано в стандарте Fetch API.

Вот это да. Спасибо за такой исчерпывающий ответ, Кентон. Передача event.request вместо блока инициализации устранила проблему. Спасибо также за много полезной информации. Мой Cloudflare Worker теперь работает отлично ?

Nick 05.05.2019 03:24

Я думал, что мне нужно следовать перенаправлениям в моем прокси-сервере CORS, но через несколько часов я обнаружил, что могу просто добавить «www». на URL-адрес, к которому я пытался получить доступ, и в первую очередь избежать перенаправления ... Стоит попробовать, если вы окажетесь здесь, как я.

FLeX 06.01.2021 18:59

Другие вопросы по теме