Цель этой статьи - показать вам основы httr2
.
httr2
- переосмысленная реализация пакета httr
, т.е. интерфейс для работы с HTTP запросами на языке R.
Из статьи вы узнаете, как создавать и отправлять HTTP-запросы и работать с полученными HTTP-ответами. httr2
разработан для точного сопоставления с базовым протоколом HTTP, который я объясню по мере продвижения. Для получения дополнительных сведений я также рекомендую ознакомиться со статьёй "An overview of HTTP" от MDN.
Для начала необходимо установить и подключить httr2
.
install.packages("httr2")
library(httr2)
Содержание
Если вы интересуетесь анализом данных возможно вам будут полезны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.
Построение 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.
apawluczenko
Отличное введение, спасибо! Хэдли продолжает гнуть свою линию с единообразными названиями функций и предопределенными цепочками операций. Больше порядка в коде и в наших головах.
selesnow Автор
Благодарю, да, Хед приводит всё к единому стилю, так, что бы все пакеты были единообразны и хорошо работали с пайпами. Постараюсь в ближайшее время перевести вторую часть документации, в которой рассказывается о некоторых других, новы плюшках которые в httr2 подвезли.