Рубрики
Без рубрики

Как контролировать запросы API в Node.js

HTTP-приборимость – отличный способ автоматически контролировать вызовы API и реагируют, когда что-то неожиданное. В этом посте мы посмотрим на инструменты Node.js приложения.

Автор оригинала: Corentin.

Эта статья была первоначально опубликована Марком Мишоном на blog.bearer.sh Отказ

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

Добавление крюка в каждый HTTP-запрос Ваше приложение позволяет автоматически записывать запросы, контролировать API, обрабатывать проблемы с исправительными исправителями и многое другое. Это верно для обеих внутренних запросов к вашим собственным услугам, но, что более важно, он работает с любым запросом к внешним сторонам API. Даже те, которые используют свой собственный клиент SDK.

Создание полной системы для управления этим немного сложнее. Вам нужна панель инструментов для просмотра метрик, хранение для обработки журналов и способа захвата данных. К счастью, концепция HTTP-контрольных приборов легче в Node.js благодаря зависимости от опоры экосистемы на основе http модуль. Почти каждая клиент API и библиотека запроса, используемая разработчиками узла, зависит от этого модуля.

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

Как это работает

Чтобы сделать наш инструментальный слой, мы будем исправлять основные методы http / https модуль. Это означает переопределение их функциональности, выполняя некоторые действия, затем вызывая оригинальный метод. Это звучит более сложно, чем это. Для начала, давайте посмотрим на минимальный пример, без каких-либо новых функций. Мы можем создать модуль в hijack.js следующим образом:

// hijack.js
const http = require("http")

function hijack() {
  override(http)
}

function override(module) {
  let original = module.request

  function wrapper(outgoing) {
    // Store a call to the original in req
    let req = original.apply(this, arguments)
    // return the original call
    return req
  }

  module.request = wrapper
}

module.exports = hijack

Давайте сломаемся, что делает этот код. Мы импортируем http модуль на вершине. Для более полной версии нам также понадобится покрытие для https модуль. Далее Hijack Функция устанавливает патчи для http Призывая Переопределить функция. Функция переопределения делает три вещи:

  1. Это ссылается на оригинальный метод запроса.
  2. Это создает обертку, которая принимает оригинальные параметры запроса и возвращает оригинал, с Применить Способ называется (больше на это вскоре).
  3. Он переопределяет оригинальный запрос модуля, модуль.Рус с нашей новой функцией обертки. Это значит http.request Теперь установлен в функцию обертки.

Что это применить метод?

.apply Метод существует на всех функциях в JavaScript. Это позволяет «вызывать» функцию и пройти ее это контекст и массив аргументов. Это похоже на .call За исключением того, как это структурирует аргументы.

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

Регистрация запроса

Один общий корпус использования для HTTP-приборирования должен автоматически регистрировать части запроса. Это обеспечивает наблюдаемость и метрики о запросе. В функции обертки выше обратите внимание, что мы принимаем аргумент под названием req Отказ Это несколько вводит в заблуждение, так как это не может быть запросом. Что это на самом деле принимает, являются аргументами, переданными на http.request Отказ В зависимости от клиента, который использует http Под капотом это может быть разным. Для большинства, как Axios Наш пример будет работать нормально. Для других вам нужно будет написать больше логики, чтобы обрабатывать краевые чехлы. Для этих примеров можно предположить, что первый аргумент, Аргументы [0] , карты на запрос.

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

// hijack.js
function logger(req) {
  let log = {
    method: req.method || "GET",
    host: req.host || req.hostname || "localhost",
    port: req.port || "443",
    path: req.pathname || req.path || "/",
    headers: req.headers || {},
  }
  console.log(log)
}

В то время как запрос на большинстве больше всего должен быть униформенный, мы добавляем некоторые дополнительные затылки, чтобы избежать каких-либо неопределенных значений. Мы захватываем ограниченное количество информации из запроса в этой реализации. Вы можете просмотреть все доступные варианты, осматривая или вхожу в вынос req Отказ

В сочетании с нашим кодом ранее мы получаем что-то вроде следующего:

function override(module) {
  let original = module.request

  function wrapper(outgoing) {
    let req = original.apply(this, arguments)
    logger(outgoing) // NEW
    return req
  }

  module.request = wrapper
}

Захват ответа

Мы захватили подробности о запросе, но не телом запроса. Больше на этом позже. На данный момент давайте посмотрим на захват ответа, который возвращается. Это требует использования того же методики, которую мы использовали ранее переопределения функциональности по умолчанию метода. Наша цель состоит в том, чтобы слушать события ответа, которые огонь, когда возврат HTTP возвращается. Чтобы повторить, нам нужно:

  1. Сделайте ссылку на оригинальный метод.
  2. Создайте свою собственную функцию для использования на своем месте.
  3. Вернуть оригинал, с .apply используется, чтобы назвать это нормальным.

В этом случае мы хотим информацию о событиях, которые http.request излучает, когда вступает в ответ. В частности, данные и конец События. Для этого мы будем исправлять функциональность request.emit Отказ HTTP-запрос выделяет события, такие как событие отклика, что сами выделяют события. Если вы знакомы с созданием HTTP Вызывы с использованием стандартной библиотеки HTTP , это будет похоже.

Начать, давайте переопределим Эмит Отказ Мы будем сосредоточиться только на коде внутри обертка Отказ

function wrapper(outgoing) {
  let req = original.apply(this, arguments)
  let emit = req.emit // New

  // New
  req.emit = function (eventName, response) {
    switch (eventName) {
      case "response": {
        response.on("data", (d) => {
          // build body from chunks of data
        })

        response.on("end", () => {
          // handle final response
        })
      }
    }
    return emit.apply(this, arguments)
  }

  logger(outgoing)
  return req
}

В этом коде мы захватываем ссылку на req.emit в Эмит Отказ Это позволяет нам делать следующий шаг, где мы переопределяем req.emit с нашей собственной функцией. Требуется те же два аргумента, которые Эмиттер события узла взял бы. Название события и любые конкретные аргументы (в этом случае, ответ).

Затем мы создадим некоторые слушатели, когда ответ Событие приходит. Вы можете добавить дополнительные случаи переключателя для ошибки, прерывания, время ожидания или любых событий, излучаемых http.clientRequest Отказ На данный момент мы сосредоточимся только на ответах, которые успешно вернулись.

Наконец, мы возвращаем emit.apply (это, аргументы) Подобно тому, как мы сделали ранее. Это гарантирует, что «реальный» запрос по-прежнему излучает события, как и ожидалось.

Давайте заполним Переключатель Блокируйте для обработки тела ответа и регистрируйте некоторые детали о ответе на консоль:

function wrapper(outgoing) {
  let req = original.apply(this, arguments)
  let emit = req.emit
  let body = ""

  req.emit = function (eventName, response) {
    switch (eventName) {
      case "response": {
        response.on("data", (d) => {
          // NEW: Collect data chunks
          body += d
        })

        response.on("end", () => {
          // NEW: Complete response
          let res = {
            statusCode: response.statusCode,
            headers: response.headers,
            message: response.statusMessage,
            body,
          }
          console.log(res)
        })
      }
    }
    return emit.apply(this, arguments)
  }

  logger(outgoing)
  return req
}

Две основные изменения здесь:

  • Мы определяем переменную, Тело , удерживать данные из ответа.
  • Мы строим данные каждый раз, когда события срабатывают. (Вы можете попеременно сделать это как массив и объединять куски в буфер)
  • Мы выходим из системы полезных данных и тело на консоль.

Это еще один случай, когда создание специализированного метода ведения журнала будет полезен. Вы также можете объединить данные запроса и ответа в один журнал, прикрепите временные метки или использовать Process.hrte () Время просьбы и многое другое.

Это покрывает большую часть нашей вступительной реализации. Последний код выглядит так:

// hijack.js
const http = require("http")
const https = require("https")

function hijack() {
  override(http)
  override(https)
}

function override(module) {
  let original = module.request
  function wrapper(outgoing) {
    let req = original.apply(this, arguments)
    let emit = req.emit
    let body = ""

    req.emit = function (eventName, response) {
      switch (eventName) {
        case "response": {
          response.on("data", (d) => {
            // NEW: Collect data chunks
            body += d
          })

          response.on("end", () => {
            // NEW: Complete response
            let res = {
              statusCode: response.statusCode,
              headers: response.headers,
              message: response.statusMessage,
              body,
            }
            console.log(res)
          })
        }
      }
      return emit.apply(this, arguments)
    }

    logger(outgoing)
    return req
  }

  function logger(req) {
    let log = {
      method: req.method || "GET",
      host: req.host || req.hostname || "localhost",
      port: req.port || "443",
      path: req.pathname || req.path || "/",
      headers: req.headers || {},
    }
    console.log(log)
  }

  module.request = wrapper
}

module.exports = hijack

Чтобы получить его и запустить в своем приложении, вы бы включите следующее в начале вашей прикладной логики:

const hijack = require("./hijack")

hijack()

// ...

Теперь модуль Hijack заберет каждый исходящий HTTP-запрос в вашем приложении. Метрики и приборы, все с небольшой библиотекой. Это только начало!

Что еще вы можете сделать?

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

  • Изменение запросов : С полным доступом к Аргументы которые передаются обратно в Original.apply , вы можете изменить любые данные перед отправкой. Это позволяет вам изменить URL-адреса, вставить дополнительные заголовки и многое другое. Например, если API меняет версии, вы можете заменить части пути.
  • Захватить тело запроса : В нашем примере мы только захватываем подробную информацию о запросе, но путем переопределения Написать Метод вы также можете захватить полезную нагрузку в организме таким образом, как мы захватили ответ.
  • Реагируйте на сбои автоматически С тех пор мы входим между запросом и сервером, мы можем внести изменения в лету. Это означает, что такие вещи, как повторные попытки неудачных запросов, изменяющие целевые URL-адреса во время простоя, принудительные тайм-ауты и многое другое.
  • Автоматически поймать http против https Использование : Прямо сейчас реализация выше требуется вручную установку модуля. Если вы используете смешанную кодовую базу, это может вызвать проблемы. Вместо этого напишите способ исправить обе модули.
  • Ручка .get : http Модуль включает в себя спиральный помощник для Получить Запросы. Использует http.request под капотом, но это не зависит от нашей логики выше.
  • Оберните логику в обработке ошибок : Если какой-либо из нашего кода перехвата терпит неудачу, так что исходный запрос. Чтобы обойти это, вы захотите, чтобы убедиться, что обернут необходимые части в блоках TRY/CALL и убедитесь, что Применить Звонит огонь независимо от того, что происходит.

Обертывание

HTTP-приборивание намного проще в Node.js, чем на многих языках из-за использования узла http модуль под капотом. На других языках вам нужно будет обрабатывать отдельные библиотеки в каждом конкретном случае. Вам также необходимо учитывать, как обработать все эти данные, как запугивать конфиденциальный контент и многое другое.

Ищете готовый инструмент для производства, который контролирует ваши запросы API? 👉 попробуйте носитель .sh ! Мы создали клиенты для нескольких языков, которые работают аналогично основной идее этого поста (хотя и гораздо более сложный).

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