Цель этой статьи - показать вам основы httr2

httr2 - переосмысленная реализация пакета httr, т.е. интерфейс для работы с HTTP запросами на языке R.

Из статьи вы узнаете, как создавать и отправлять HTTP-запросы и работать с полученными HTTP-ответами. httr2 разработан для точного сопоставления с базовым протоколом HTTP, который я объясню по мере продвижения. Для получения дополнительных сведений я также рекомендую ознакомиться со статьёй "An overview of HTTP" от MDN.

Для начала необходимо установить и подключить httr2.

install.packages("httr2")
library(httr2)

Содержание

Если вы интересуетесь анализом данных возможно вам будут полезны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.

  1. Построение HTTP запроса

  2. Отправка запроса и обработка ответа

  3. Контролирование процесса запроса

  4. От автора перевода

Построение HTTP запроса

Работа с httr2 начинается с создания HTTP запроса. Это ключевое отличие от предшественника httr, в предыдущей версии вы одной командой выполняли сразу несколько действий: создавали запрос, отправляли его, и получали ответ. httr2 имеет явный объект запроса, что значительно упрощает процесс компоновки сложных запросов. Процесс построения запроса начинается с базового URL:

req <- request("https://httpbin.org/get")
req
#> <httr2_request>
#> GET https://httpbin.org/get
#> Body: empty

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

req %>% req_dry_run()
#> GET /get HTTP/1.1
#> Host: httpbin.org
#> User-Agent: httr2/0.1.1 r-curl/4.3.2 libcurl/7.64.1
#> Accept: */*
#> Accept-Encoding: deflate, gzip

Первая строка содержит три важных составляющих запроса

  • HTTP метод, т.е. глагол, который сообщает серверу, какое действие должен выполнить ваш запрос. По умолчанию подразумевается метод GET, самый распространенный метод, указывающий, что мы хотим получить ресурс от сервера. Другие так же есть и другие HTTP методы: POST, для создания ресурса, PUT, для изменения ресурса, и DELETE, для его удаления.

  • Путь, URL адрес сервера, который состоит из: протокола (httpили https), хоста (httpbin.org), и порта (в нашем примере не использовался).

  • Версия протокола HTTP. В данном случае эта информация нам не важна, т.к. обработка протокола идёт на более низком уровне.

Далее идут заголовки запроса. В заголовках зачастую передаётся некоторая служебная информация, представленная в виде пар ключ-значение, разделенных знаком :. Заголовки в нашем примере были автоматически добавлены httr2, но вы можете переопределить их или добавить свои с помощью req_headers():

req %>%
 req_headers(
 Name = "Hadley", 
 `Shoe-Size` = "11", 
 Accept = "application/json"
 ) %>% 
 req_dry_run()
#> GET /get HTTP/1.1
#> Host: httpbin.org
#> User-Agent: httr2/0.1.1 r-curl/4.3.2 libcurl/7.64.1
#> Accept-Encoding: deflate, gzip
#> Name: Hadley
#> Shoe-Size: 11
#> Accept: application/json

Имена заголовков не чувствительны к регистру, и сервера игнорируют неизвестные им заголовки.

Заголовки заканчиваются пустой строкой, за которой следует тело запроса. Приведённые выше запросы (как и все GET запросы) не имеют тела, поэтому давайте добавим его, чтобы посмотреть, что произойдет. функции семейства req_body_*() обеспечивают различные способы добавить данные к телу запроса. В качестве примера мы используем req_body_json() для добавления данных в виде JSON структуры:

req %>%
 req_body_json(list(x = 1, y = "a")) %>% 
 req_dry_run()
#> POST /get HTTP/1.1
#> Host: httpbin.org
#> User-Agent: httr2/0.1.1 r-curl/4.3.2 libcurl/7.64.1
#> Accept: */*
#> Accept-Encoding: deflate, gzip
#> Content-Type: application/json
#> Content-Length: 15
#> 
#> {"x":1,"y":"a"}

Что изменилось?

  • Метод запроса автоматически изменился с GET на POST. POST - это стандартный метод отправки данных на веб-сервер, который автоматически используется всякий раз, когда вы добавляете тело запроса. Вы можете использовать req_method() для переопределения метода.

  • К запросу добавлены два новых заголовка: Content-Type и Content-Length. Они сообщают серверу, как интерпретировать тело - в нашем случае это JSON структура размером 15 байт.

  • У запроса есть тело, состоящее из какого-то JSON.

Разные API могут требовать различных вариантов кодировки тела запроса, поэтому httr2 предоставляет семейство функций, для реализации наиболее часто встречающихся форматов. Например, req_body_form() преобразует тело запроса, в вид отправляемой браузером формы:

req %>%
 req_body_form(list(x = "1", y = "a")) %>% 
 req_dry_run()
#> POST /get HTTP/1.1
#> Host: httpbin.org
#> User-Agent: httr2/0.1.1 r-curl/4.3.2 libcurl/7.64.1
#> Accept: */*
#> Accept-Encoding: deflate, gzip
#> Content-Type: application/x-www-form-urlencoded
#> Content-Length: 7
#> 
#> x=1&y=a

Для отправки данных большого объёма или бинарных файлов используйте req_body_multipart():

req %>%
 req_body_multipart(list(x = "1", y = "a")) %>% 
 req_dry_run()
#> POST /get HTTP/1.1
#> Host: httpbin.org
#> User-Agent: httr2/0.1.1 r-curl/4.3.2 libcurl/7.64.1
#> Accept: */*
#> Accept-Encoding: deflate, gzip
#> Content-Length: 228
#> Content-Type: multipart/form-data; boundary=------------------------cc86fca72508d8b0
#> 
#> --------------------------cc86fca72508d8b0
#> Content-Disposition: form-data; name="x"
#> 
#> 1
#> --------------------------cc86fca72508d8b0
#> Content-Disposition: form-data; name="y"
#> 
#> a
#> --------------------------cc86fca72508d8b0--

Если вам нужно отправить данные, закодированные в другой форме, вы можете использовать req_body_raw() для добавления данных в тело и передать тип отправляемых данных в заголовке Content-Type.

Отправка запроса и обработка ответа

Чтобы фактически выполнить запрос и получить ответ от сервера, используйте функцию  req_perform():

req <- request("https://httpbin.org/json")
resp <- req %>% req_perform()
resp
#> <httr2_response>
#> GET https://httpbin.org/json
#> Status: 200 OK
#> Content-Type: application/json
#> Body: In memory (429 bytes)

Посмотреть имитацию полученного ответа можно с помощью resp_raw():

resp %>% resp_raw()
#> HTTP/1.1 200 OK
#> date: Mon, 27 Sep 2021 20:40:32 GMT
#> content-type: application/json
#> content-length: 429
#> server: gunicorn/19.9.0
#> access-control-allow-origin: *
#> access-control-allow-credentials: true
#> 
#> {
#> "slideshow": {
#> "author": "Yours Truly", 
#> "date": "date of publication", 
#> "slides": [
#> {
#> "title": "Wake up to WonderWidgets!", 
#> "type": "all"
#> }, 
#> {
#> "items": [
#> "Why <em>WonderWidgets</em> are great", 
#> "Who <em>buys</em> WonderWidgets"
#> ], 
#> "title": "Overview", 
#> "type": "all"
#> }
#> ], 
#> "title": "Sample Slide Show"
#> }
#> }

Структура HTTP ответа очень похожа на структуру запроса. В первой строке указывается версия используемого HTTP и код состояния, за которым (необязательно) следует его краткое описание. Затем идут заголовки, за которыми следует пустая строка, за которой следует тело ответа. В отличие от запросов большинство ответов будет иметь тело.

Вы можете извлечь данные из ответа с помощью функций семейства resp_():

  • resp_status() возвращает код состояния и resp_status_desc() возвращает его описание:

resp %>% resp_status()
#> [1] 200
resp %>% resp_status_desc()
#> [1] "OK"
  • Вы можете извлечь все заголовки используя resp_headers() или получить значение конкретного заголовок с помощью resp_header():

resp %>% resp_headers()
#> <httr2_headers>
#> date: Mon, 27 Sep 2021 20:40:32 GMT
#> content-type: application/json
#> content-length: 429
#> server: gunicorn/19.9.0
#> access-control-allow-origin: *
#> access-control-allow-credentials: true
resp %>% resp_header("Content-Length")
#> [1] "429"

Заголовки нечувствительны к регистру:

resp %>% resp_header("ConTEnT-LeNgTH")
#> [1] "429"

Тело ответа, так же как и тело запроса, в зависимости от устройства API может приходить в разных форматах. Для извлечения тела ответа используйте функции семейства  resp_body_*(). В нашем примере мы получили ответ в виде JSON структуры, поэтому для его извлечения необходимо использовать resp_body_json():

resp %>% resp_body_json() %>% str()
#> List of 1
#> $ slideshow:List of 4
#> ..$ author: chr "Yours Truly"
#> ..$ date : chr "date of publication"
#> ..$ slides:List of 2
#> .. ..$ :List of 2
#> .. .. ..$ title: chr "Wake up to WonderWidgets!"
#> .. .. ..$ type : chr "all"
#> .. ..$ :List of 3
#> .. .. ..$ items:List of 2
#> .. .. .. ..$ : chr "Why <em>WonderWidgets</em> are great"
#> .. .. .. ..$ : chr "Who <em>buys</em> WonderWidgets"
#> .. .. ..$ title: chr "Overview"
#> .. .. ..$ type : chr "all"
#> ..$ title : chr "Sample Slide Show"

Ответы с кодами состояния 4xx и 5xx являются ошибками HTTP. httr2 автоматически преобразует их в ошибки R:

request("https://httpbin.org/status/404") %>% req_perform()
#> Error: HTTP 404 Not Found.
request("https://httpbin.org/status/500") %>% req_perform()
#> Error: HTTP 500 Internal Server Error.

Это еще одно важное отличие от httr, который требовал явного вызова httr::stop_for_status() для преобразования ошибок HTTP в ошибки R. Вы можете вернуться к поведению httr с помощью req_error(req, is_error = ~ FALSE).

Контролирование процесса запроса

Некоторые req_ функции не влияют напрямую на HTTP-запрос, а вместо этого позволяют управлять общим процессом отправки запроса и обработки ответа. К ним относятся:

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

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

  • req_retry() устанавливает стратегию повторных попыток отправки запросов. В случае сбоя запроса или получения временной ошибки HTTP запрос будет отправлен повторно после небольшой паузы.

Подробнее см. документацию, а также примеры использования в реальных API в vignette("wrapping-apis.Rmd").

От автора перевода

Интерфейс и функционал пакета httr2 был полностью переосмыслен, он даёт значительно больший контроль над процессом компоновки и отправки HTTP запросов на сервер. К тому даёт вам гибкие возможности по управлению политикой отправки запросов, например добавлением паузу между запросами, и повторной отправкой запросов в случае временной ошибки.

Буду рад предложениям по улучшению качества перевода в комментариях к посту или личных сообщениях, а так же видеть вас среди подписчиков своих каналов в telegram и youtube.

Комментарии (2)


  1. apawluczenko
    17.02.2022 13:23

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


    1. selesnow Автор
      17.02.2022 13:34
      +3

      Благодарю, да, Хед приводит всё к единому стилю, так, что бы все пакеты были единообразны и хорошо работали с пайпами. Постараюсь в ближайшее время перевести вторую часть документации, в которой рассказывается о некоторых других, новы плюшках которые в httr2 подвезли.