request is DSL for http requests for R, and is inspired by the CLI tool httpie. It’s built on httr.

The following were driving principles for this package:

  • The web is increasingly a JSON world, so we assume applications/json by default, but give back other types if not
  • The workflow follows logically, or at least should, from, hey, I got this url, to i need to add some options, to execute request - and functions support piping so that you can execute functions in this order
  • Whenever possible, we transform output to data.frame’s - facilitating downstream manipulation via dplyr, etc.
  • We do GET requests by default. Specify a different type if you don’t want GET. Given GET by default, this client is optimized for consumption of data, rather than creating new things on servers
  • You can use non-standard evaluation to easily pass in query parameters without worrying about &’s, URL escaping, etc. (see api_query())
  • Same for body params (see api_body())

The following is a brief demo of some of the package functionality:

Install

From CRAN

install.packages("request")

Or from GitHub

devtools::install_github("sckott/request")
library("request")

Execute on last pipe

When using pipes (%>%) in request, we autodetect last piped command, and execute http() if it’s the last. If not the last, the output gets passed to the next command, and so on. This feature (and magrittr) were done by Stefan Milton Bache.

This feature is really nice because a) it’s one less thing you need to do, and b) you only need to care about the request itself.

You can escape auto-execution if you use the function peep(), which prints out a summary of the request you’ve created, but does not execute an HTTP request.

HTTP Requests

A high level function http() wraps a lower level R6 object RequestIterator, which holds a series of variables and functions to execute GET and POST requests, and will hold other HTTP verbs as well. In addition, it can hold state, which will allow us to do paging internally for you (see below). You have direct access to the R6 object if you call http_client() instead of http().

NSE and SE

Most if not all functions in request support non-standard evaluation (NSE) as well as standard evaluation (SE). If a function supports both, there’s a version without an underscore for NSE, while a version with an underscore is for SE. For example, here, we make a HTTP request by passing a base URL, then a series of paths that get combined together. First the NSE version

api('https://api.github.com/') %>%
  api_path(repos, ropensci, rgbif, issues)

Then the SE version

api('https://api.github.com/') %>%
  api_path_('repos', 'ropensci', 'rgbif', 'issues')

Building API routes

The first thing you’ll want to do is lay out the base URL for your request. The function api() is your friend.

api() works with full or partial URLs:

api('https://api.github.com/')
#> URL: https://api.github.com/
api('https://api.gbif.org/v1')
#> URL: https://api.gbif.org/v1
api('api.gbif.org/v1')
#> URL: api.gbif.org/v1

And works with ports, full or partial

api('http://localhost:9200')
#> URL: http://localhost:9200
api('localhost:9200')
#> URL: http://localhost:9200
api(':9200')
#> URL: http://localhost:9200
api('9200')
#> URL: http://localhost:9200
api('9200/stuff')
#> URL: http://localhost:9200/stuff

Make HTTP requests

The above examples with api() are not passed through a pipe, so only define a URL, but don’t do an HTTP request. To make an HTTP request, you can either pipe a url or partial url to e.g., api(), or call http() at the end of a string of function calls:

'https://api.github.com/' %>% api()
#> $current_user_url
#> [1] "https://api.github.com/user"
#>
#> $current_user_authorizations_html_url
#> [1] "https://github.com/settings/connections/applications{/client_id}"
#>
#> $authorizations_url
#> [1] "https://api.github.com/authorizations"
#>
#> $code_search_url
...

Or

api('https://api.github.com/') %>% http()
#> $current_user_url
#> [1] "https://api.github.com/user"
#>
#> $current_user_authorizations_html_url
#> [1] "https://github.com/settings/connections/applications{/client_id}"
#>
#> $authorizations_url
#> [1] "https://api.github.com/authorizations"
#>
#> $code_search_url
...

http() is called at the end of a chain of piped commands, so no need to invoke it. However, you can if you like.

Templating

repo_info <- list(username = 'craigcitro', repo = 'r-travis')
api('https://api.github.com/') %>%
  api_template(template = 'repos/{{username}}/{{repo}}/issues', data = repo_info)
#> [[1]]
#> [[1]]$url
#> [1] "https://api.github.com/repos/craigcitro/r-travis/issues/164"
#>
#> [[1]]$labels_url
#> [1] "https://api.github.com/repos/craigcitro/r-travis/issues/164/labels{/name}"
#>
#> [[1]]$comments_url
#> [1] "https://api.github.com/repos/craigcitro/r-travis/issues/164/comments"
#> ...

Set paths

api_path() adds paths to the base URL

api('https://api.github.com/') %>%
  api_path(repos, ropensci, rgbif, issues) %>%
  peep
#> <http request>
#>   url: https://api.github.com/
#>   paths: repos/ropensci/rgbif/issues
#>   query:
#>   body:
#>   paging:
#>   headers:
#>   rate limit:
#>   retry (n/delay (s)): /
#>   error handler:
#>   config:

Query

api("https://api.plos.org/search") %>%
  api_query(q = ecology, wt = json, fl = journal) %>%
  peep
#> <http request>
#>   url: https://api.plos.org/search
#>   paths:
#>   query: q=ecology, wt=json, fl=journal
#>   body:
#>   paging:
#>   headers:
#>   rate limit:
#>   retry (n/delay (s)): /
#>   error handler:
#>   config:

Headers

api('https://httpbin.org/headers') %>%
  api_headers(`X-FARGO-SEASON` = 3, `X-NARCOS-SEASON` = 5) %>%
  peep
#> <http request>
#>   url: https://httpbin.org/headers
#>   paths:
#>   query:
#>   body:
#>   paging:
#>   headers:
#>     X-FARGO-SEASON: 3
#>     X-NARCOS-SEASON: 5
#>   rate limit:
#>   retry (n/delay (s)): /
#>   error handler:
#>   config:

curl configuration

httr is exported in request, so you can use httr functions like verbose() to get verbose curl output

api('https://httpbin.org/headers') %>%
  api_config(verbose())
#> -> GET /headers HTTP/1.1
#> -> Host: httpbin.org
#> -> User-Agent: curl/7.43.0 curl/0.9.4 httr/1.0.0 request/0.1.0
#> -> Accept-Encoding: gzip, deflate
#> -> Accept: application/json, text/xml, application/xml, */*
#> ->
#> <- HTTP/1.1 200 OK
#> <- Server: nginx
#> <- Date: Sun, 03 Jan 2016 16:56:29 GMT
#> <- Content-Type: application/json
#> <- Content-Length: 227
#> <- Connection: keep-alive
#> <- Access-Control-Allow-Origin: *
#> <- Access-Control-Allow-Credentials: true
#> <-
#> $headers
#> $headers$Accept
#> [1] "application/json, text/xml, application/xml, */*"
#> ...

Coming soon

There’s a number of interesting features that should be coming soon to request.

  • Paging - a paging helper will make it easy to do paing, and will attempt to handle any parameters used for paging. Some user input will be required, like what parameter names are, and how many records you want returned sckott/request#2
  • Retry - a retry helper will make it easy to retry http requests on any failure, and execute a user defined function on failure sckott/request#6
  • Rate limit - a rate limit helper will add info to a set of many requests - still in early design stages sckott/request#5
  • Caching - a caching helper - may use in the background the in development vcr R client when on CRAN or perhaps storr sckott/request#4