Работа с внешними сервисами

Для интеграций со сторонними сервисами в сценариях доступна глобальная функция fetch. Функция использует нативный Fetch API в браузере и библиотеку-полифил на сервере, поэтому её интерфейс в нашем SDK совмещает доступные для них возможности.

Сервер On-Premises может находиться за прокси-сервером. В этом случае для работы с внешними сервисами необходимо указать параметры прокси-сервера в переменных окружения HTTP_PROXY и HTTPS_PROXY сервера системы.

О том, как это сделать, читайте в статье «Установка ELMA365 в MicroK8s с proxy-сервером».

Пример использования функции fetch():

const res = await fetch('https://my.server/api/products', {
    method: 'POST',
    headers: {
        Authorization: 'Bearer SOME-TOKEN-HERE',
    },
    body: JSON.stringify({
        name: 'New product',
        cost: 13.20,
    })
});
if (!res.ok) {
    // Обработка ошибки с кодом ответа >=300
}
const product = await res.json();

Простой GET-запрос можно выполнить без передачи дополнительных параметров:

const res = await fetch(`https://my.server/api/products/${ Context.data.itemId }`);

В параметрах запроса можно передать метод FetchRequest.method, заголовки запроса FetchRequest.headers в виде обычного объекта Record<string, string> и тело запроса FetchRequest.body в виде строки.

Подробнее о fetch()

Источник: https://learn.javascript.ru/fetch

JavaScript может отправлять сетевые запросы на сервер и подгружать новую информацию по мере необходимости.

Например, мы можем использовать сетевой запрос, чтобы:

  • Отправить заказ,
  • Загрузить информацию о пользователе,
  • Запросить последние обновления с сервера,
  • ...и т.п.

Для сетевых запросов из JavaScript есть широко известный термин "AJAX" (аббревиатура от Asynchronous JavaScript And XML). XML мы использовать не обязаны, просто термин старый, поэтому в нём есть это слово. Возможно, вы его уже где-то слышали.

Есть несколько способов делать сетевые запросы и получать информацию с сервера.

Метод fetch() — современный и очень мощный, поэтому начнём с него.

Базовый синтаксис:

let promise = fetch(url, options)
  • url — URL для отправки запроса.
  • options — дополнительные параметры: метод, заголовки и так далее.

Без options это простой GET-запрос, скачивающий содержимое по адресу url.

Браузер сразу же начинает запрос и возвращает промис, который внешний код использует для получения результата.

Процесс получения ответа обычно происходит в два этапа.

Во-первых, promise выполняется с объектом встроенного класса Response в качестве результата, как только сервер пришлёт заголовки ответа.

На этом этапе мы можем проверить статус HTTP-запроса и определить, выполнился ли он успешно, а также посмотреть заголовки, но пока без тела ответа.

Промис завершается с ошибкой, если fetch не смог выполнить HTTP-запрос, например при ошибке сети или если нет такого сайта. HTTP-статусы 404 и 500 не являются ошибкой.

Мы можем увидеть HTTP-статус в свойствах ответа:

  • status — код статуса HTTP-запроса, например 200.
  • ok — логическое значение: будет true, если код HTTP-статуса в диапазоне 200-299.

Например:

let response = await fetch(url);

if (response.ok) { // если HTTP-статус в диапазоне 200-299
  // получаем тело ответа (см. про этот метод ниже)
  let json = await response.json();
} else {
  alert("Ошибка HTTP: " + response.status);
}

Во-вторых, для получения тела ответа нам нужно использовать дополнительный вызов метода.

Response предоставляет несколько методов, основанных на промисах, для доступа к телу ответа в различных форматах:

  • response.text() — читает ответ и возвращает как обычный текст,
  • response.json() — декодирует ответ в формате JSON,
  • response.arrayBuffer() — возвращает ответ как ArrayBuffer (низкоуровневое представление бинарных данных),

Например, получим JSON-объект с последними коммитами из репозитория на GitHub:

let url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits';
let response = await fetch(url);

let commits = await response.json(); // читаем ответ в формате JSON

alert(commits[0].author.login);

То же самое без await, с использованием промисов:

fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits')
  .then(response => response.json())
  .then(commits => alert(commits[0].author.login));

Для получения ответа в виде текста используем await response.text() вместо .json():

let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');

let text = await response.text(); // прочитать тело ответа как текст

alert(text.slice(0, 80) + '...');

Внимание! Мы можем выбрать только один метод чтения ответа.

Если мы уже получили ответ с response.text(), тогда response.json() не сработает, так как данные уже были обработаны.

let text = await response.text(); // тело ответа обработано
let parsed = await response.json(); // ошибка (данные уже были обработаны)

Заголовки ответа

Заголовки ответа хранятся в похожем на Map объекте response.headers.

Это не совсем Map, но мы можем использовать такие же методы, как с Map, чтобы получить заголовок по его имени или перебрать заголовки в цикле:

let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');

// получить один заголовок
alert(response.headers.get('Content-Type')); // application/json; charset=utf-8

// перебрать все заголовки
for (let [key, value] of response.headers) {
  alert(`${key} = ${value}`);
}

Заголовки запроса

Для установки заголовка запроса в fetch мы можем использовать опцию headers. Она содержит объект с исходящими заголовками, например:

let response = fetch(protectedUrl, {
  headers: {
    Authentication: 'secret'
  }
});

Есть список запрещённых HTTP-заголовков, которые мы не можем установить:

  • Accept-Charset, Accept-Encoding
  • Access-Control-Request-Headers
  • Access-Control-Request-Method
  • Connection
  • Content-Length
  • Cookie, Cookie2
  • Date
  • DNT
  • Expect
  • Host
  • Keep-Alive
  • Origin
  • Referer
  • TE
  • Trailer
  • Transfer-Encoding
  • Upgrade
  • Via
  • Proxy-*
  • Sec-*

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

POST-запросы

Для отправки POST-запроса или запроса с другим методом, нам необходимо использовать fetch параметры:

  • method — HTTP метод, например POST,
  • body — тело запроса, одно из списка:
    • строка (например, в формате JSON),
    • объект FormData для отправки данных как multipart/form-data.

Чаще всего используется строка в формате JSON.

Например, этот код отправляет объект user как JSON:

let user = {
  name: 'John',
  surname: 'Smith'
};

let response = await fetch('/article/fetch/post/user', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  },
  body: JSON.stringify(user)
});

let result = await response.json();
alert(result.message);

Заметим, что так как тело запроса body - строка, то заголовок Content-Type по умолчанию будет text/plain;charset=UTF-8.

Но, так как мы посылаем JSON, то используем параметр headers для отправки вместо этого application/json, правильный Content-Type для JSON.

Отправка файла

Мы можем отправить бинарные данные при помощи fetch, используя объекты FormData и ArrayBuffer.

async function submit() {
    let data_req = await fetch('/external/data/to/load');
    let buffer = await data_req.arrayBuffer();
    let form = new FormData();
    form.append('file', buffer, 'filename.ext');
    let response = await fetch('/article/fetch/post/file', {
        method: 'POST',
        body: form
    });

    // сервер ответит подтверждением и размером изображения
    let result = await response.json();
    alert(result.message);
}

Заметим, что здесь нам не нужно вручную устанавливать заголовок Content-Type, потому что он автоматически установится в multipart/form-data.

Итого

Типичный запрос с помощью fetch состоит из двух операторов await:

let response = await fetch(url, options); // завершается с заголовками ответа
let result = await response.json(); // читать тело ответа в формате JSON

Или, без await:

fetch(url, options)
  .then(response => response.json())
  .then(result => /* обрабатываем результат */)

Параметры ответа:

  • response.status — HTTP-код ответа,
  • response.oktrue, если статус ответа в диапазоне 200-299.
  • response.headers — похожий на Map объект с HTTP-заголовками.

Методы для получения тела ответа:

  • response.text() — возвращает ответ как обычный текст,
  • response.json() — преобразовывает ответ в JSON-объект,
  • response.arrayBuffer() — возвращает ответ как ArrayBuffer (низкоуровневые бинарные данные).