The goal of this document is to get you up and running with httr as quickly as possible. httr is designed to map closely to the underlying http protocol. I’ll try and explain the basics in this intro, but I’d also recommend “HTTP: The Protocol Every Web Developer Must Know” or “HTTP made really easy”.
This vignette (and parts of the httr API) derived from the excellent “Requests quickstart guide” by Kenneth Reitz. Requests is a python library similar in spirit to httr.
There are two important parts to http: the request, the data sent to the server, and the response, the data sent back from the server. In the first section, you’ll learn about the basics of constructing a request and accessing the response. In the second and third sections, you’ll dive into more details of each.
To make a request, first load httr, then call GET()
with
a url:
library(httr)
<- GET("http://httpbin.org/get") r
This gives you a response object. Printing a response object gives you some useful information: the actual url used (after any redirects), the http status, the file (content) type, the size, and if it’s a text file, the first few lines of output.
r#> Response [http://httpbin.org/get]
#> Date: 2022-05-03 20:41
#> Status: 200
#> Content-Type: application/json
#> Size: 367 B
#> {
#> "args": {},
#> "headers": {
#> "Accept": "application/json, text/xml, application/xml, */*",
#> "Accept-Encoding": "deflate, gzip",
#> "Host": "httpbin.org",
#> "User-Agent": "libcurl/7.79.1 r-curl/4.3.2 httr/1.4.3",
#> "X-Amzn-Trace-Id": "Root=1-6271936c-5d62ff576ebd9cc7378fbee6"
#> },
#> "origin": "73.115.118.38",
#> ...
You can pull out important parts of the response with various helper methods, or dig directly into the object:
status_code(r)
#> [1] 200
headers(r)
#> $date
#> [1] "Tue, 03 May 2022 20:41:16 GMT"
#>
#> $`content-type`
#> [1] "application/json"
#>
#> $`content-length`
#> [1] "367"
#>
#> $connection
#> [1] "keep-alive"
#>
#> $server
#> [1] "gunicorn/19.9.0"
#>
#> $`access-control-allow-origin`
#> [1] "*"
#>
#> $`access-control-allow-credentials`
#> [1] "true"
#>
#> attr(,"class")
#> [1] "insensitive" "list"
str(content(r))
#> List of 4
#> $ args : Named list()
#> $ headers:List of 5
#> ..$ Accept : chr "application/json, text/xml, application/xml, */*"
#> ..$ Accept-Encoding: chr "deflate, gzip"
#> ..$ Host : chr "httpbin.org"
#> ..$ User-Agent : chr "libcurl/7.79.1 r-curl/4.3.2 httr/1.4.3"
#> ..$ X-Amzn-Trace-Id: chr "Root=1-6271936c-5d62ff576ebd9cc7378fbee6"
#> $ origin : chr "73.115.118.38"
#> $ url : chr "http://httpbin.org/get"
I’ll use httpbin.org
throughout this introduction. It
accepts many types of http request and returns json that describes the
data that it received. This makes it easy to see what httr is doing.
As well as GET()
, you can also use the
HEAD()
, POST()
, PATCH()
,
PUT()
and DELETE()
verbs. You’re probably most
familiar with GET()
and POST()
:
GET()
is used by your browser when requesting a page, and
POST()
is (usually) used when submitting a form to a
server. PUT()
, PATCH()
and
DELETE()
are used most often by web APIs.
The data sent back from the server consists of three parts: the status line, the headers and the body. The most important part of the status line is the http status code: it tells you whether or not the request was successful. I’ll show you how to access that data, then how to access the body and headers.
The status code is a three digit number that summarises whether or
not the request was successful (as defined by the server that you’re
talking to). You can access the status code along with a descriptive
message using http_status()
:
<- GET("http://httpbin.org/get")
r # Get an informative description:
http_status(r)
#> $category
#> [1] "Success"
#>
#> $reason
#> [1] "OK"
#>
#> $message
#> [1] "Success: (200) OK"
# Or just access the raw code:
$status_code
r#> [1] 200
A successful request always returns a status of 200. Common errors are 404 (file not found) and 403 (permission denied). If you’re talking to web APIs you might also see 500, which is a generic failure code (and thus not very helpful). If you’d like to learn more, the most memorable guides are the http status cats.
You can automatically throw a warning or raise an error if a request did not succeed:
warn_for_status(r)
stop_for_status(r)
I highly recommend using one of these functions whenever you’re using httr inside a function (i.e. not interactively) to make sure you find out about errors as soon as possible.
There are three ways to access the body of the request, all using
content()
:
content(r, "text")
accesses the body as a character
vector:
<- GET("http://httpbin.org/get")
r content(r, "text")
#> No encoding supplied: defaulting to UTF-8.
#> [1] "{\n \"args\": {}, \n \"headers\": {\n \"Accept\": \"application/json, text/xml, application/xml, */*\", \n \"Accept-Encoding\": \"deflate, gzip\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"libcurl/7.79.1 r-curl/4.3.2 httr/1.4.3\", \n \"X-Amzn-Trace-Id\": \"Root=1-6271936c-156c15820ea5227d3e608e7d\"\n }, \n \"origin\": \"73.115.118.38\", \n \"url\": \"http://httpbin.org/get\"\n}\n"
httr will automatically decode content from the server using the
encoding supplied in the content-type
HTTP header.
Unfortunately you can’t always trust what the server tells you, so you
can override encoding if needed:
content(r, "text", encoding = "ISO-8859-1")
If you’re having problems figuring out what the correct encoding
should be, try
stringi::stri_enc_detect(content(r, "raw"))
.
For non-text requests, you can access the body of the request as a raw vector:
content(r, "raw")
#> [1] 7b 0a 20 20 22 61 72 67 73 22 3a 20 7b 7d 2c 20 0a 20 20 22 68 65 61 64 65
#> [26] 72 73 22 3a 20 7b 0a 20 20 20 20 22 41 63 63 65 70 74 22 3a 20 22 61 70 70
#> [51] 6c 69 63 61 74 69 6f 6e 2f 6a 73 6f 6e 2c 20 74 65 78 74 2f 78 6d 6c 2c 20
#> [76] 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 6d 6c 2c 20 2a 2f 2a 22 2c 20 0a 20
#> [101] 20 20 20 22 41 63 63 65 70 74 2d 45 6e 63 6f 64 69 6e 67 22 3a 20 22 64 65
#> [126] 66 6c 61 74 65 2c 20 67 7a 69 70 22 2c 20 0a 20 20 20 20 22 48 6f 73 74 22
#> [151] 3a 20 22 68 74 74 70 62 69 6e 2e 6f 72 67 22 2c 20 0a 20 20 20 20 22 55 73
#> [176] 65 72 2d 41 67 65 6e 74 22 3a 20 22 6c 69 62 63 75 72 6c 2f 37 2e 37 39 2e
#> [201] 31 20 72 2d 63 75 72 6c 2f 34 2e 33 2e 32 20 68 74 74 72 2f 31 2e 34 2e 33
#> [226] 22 2c 20 0a 20 20 20 20 22 58 2d 41 6d 7a 6e 2d 54 72 61 63 65 2d 49 64 22
#> [251] 3a 20 22 52 6f 6f 74 3d 31 2d 36 32 37 31 39 33 36 63 2d 31 35 36 63 31 35
#> [276] 38 32 30 65 61 35 32 32 37 64 33 65 36 30 38 65 37 64 22 0a 20 20 7d 2c 20
#> [301] 0a 20 20 22 6f 72 69 67 69 6e 22 3a 20 22 37 33 2e 31 31 35 2e 31 31 38 2e
#> [326] 33 38 22 2c 20 0a 20 20 22 75 72 6c 22 3a 20 22 68 74 74 70 3a 2f 2f 68 74
#> [351] 74 70 62 69 6e 2e 6f 72 67 2f 67 65 74 22 0a 7d 0a
This is exactly the sequence of bytes that the web server sent, so this is the highest fidelity way of saving files to disk:
<- content(r, "raw")
bin writeBin(bin, "myfile.txt")
httr provides a number of default parsers for common file types:
# JSON automatically parsed into named list
str(content(r, "parsed"))
#> List of 4
#> $ args : Named list()
#> $ headers:List of 5
#> ..$ Accept : chr "application/json, text/xml, application/xml, */*"
#> ..$ Accept-Encoding: chr "deflate, gzip"
#> ..$ Host : chr "httpbin.org"
#> ..$ User-Agent : chr "libcurl/7.79.1 r-curl/4.3.2 httr/1.4.3"
#> ..$ X-Amzn-Trace-Id: chr "Root=1-6271936c-156c15820ea5227d3e608e7d"
#> $ origin : chr "73.115.118.38"
#> $ url : chr "http://httpbin.org/get"
See ?content
for a complete list.
These are convenient for interactive usage, but if you’re writing an API wrapper, it’s best to parse the text or raw content yourself and check it is as you expect. See the API wrappers vignette for more details.
Access response headers with headers()
:
headers(r)
#> $date
#> [1] "Tue, 03 May 2022 20:41:16 GMT"
#>
#> $`content-type`
#> [1] "application/json"
#>
#> $`content-length`
#> [1] "367"
#>
#> $connection
#> [1] "keep-alive"
#>
#> $server
#> [1] "gunicorn/19.9.0"
#>
#> $`access-control-allow-origin`
#> [1] "*"
#>
#> $`access-control-allow-credentials`
#> [1] "true"
#>
#> attr(,"class")
#> [1] "insensitive" "list"
This is basically a named list, but because http headers are case insensitive, indexing this object ignores case:
headers(r)$date
#> [1] "Tue, 03 May 2022 20:41:16 GMT"
headers(r)$DATE
#> [1] "Tue, 03 May 2022 20:41:16 GMT"
Like the response, the request consists of three pieces: a status
line, headers and a body. The status line defines the http method (GET,
POST, DELETE, etc) and the url. You can send additional data to the
server in the url (with the query string), in the headers (including
cookies) and in the body of POST()
, PUT()
and
PATCH()
requests.
A common way of sending simple key-value pairs to the server is the
query string: e.g. http://httpbin.org/get?key=val
. httr
allows you to provide these arguments as a named list with the
query
argument. For example, if you wanted to pass
key1=value1
and key2=value2
to
http://httpbin.org/get
you could do:
<- GET("http://httpbin.org/get",
r query = list(key1 = "value1", key2 = "value2")
)content(r)$args
#> $key1
#> [1] "value1"
#>
#> $key2
#> [1] "value2"
Any NULL
elements are automatically dropped from the
list, and both keys and values are escaped automatically.
<- GET("http://httpbin.org/get",
r query = list(key1 = "value 1", "key 2" = "value2", key2 = NULL))
content(r)$args
#> $`key 2`
#> [1] "value2"
#>
#> $key1
#> [1] "value 1"
You can add custom headers to a request with
add_headers()
:
<- GET("http://httpbin.org/get", add_headers(Name = "Hadley"))
r str(content(r)$headers)
#> List of 7
#> $ Accept : chr "application/json, text/xml, application/xml, */*"
#> $ Accept-Encoding: chr "deflate, gzip"
#> $ Cookie : chr "b=1; a=1"
#> $ Host : chr "httpbin.org"
#> $ Name : chr "Hadley"
#> $ User-Agent : chr "libcurl/7.79.1 r-curl/4.3.2 httr/1.4.3"
#> $ X-Amzn-Trace-Id: chr "Root=1-6271936d-6287e1ef0175380f7ac5233f"
(Note that content(r)$header
retrieves the headers that
httpbin received. headers(r)
gives the headers that it sent
back in its response.)