httr/0000755000176200001440000000000013522036552011236 5ustar liggesusershttr/NAMESPACE0000644000176200001440000000477013517044665012475 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method("$",insensitive) S3method("[",insensitive) S3method("[[",insensitive) S3method(as.character,form_file) S3method(as.character,response) S3method(as_field,default) S3method(as_field,logical) S3method(as_field,numeric) S3method(c,request) S3method(cookies,handle) S3method(cookies,response) S3method(headers,response) S3method(http_error,character) S3method(http_error,integer) S3method(http_error,response) S3method(print,cache_info) S3method(print,handle) S3method(print,oauth_app) S3method(print,oauth_endpoint) S3method(print,opts_list) S3method(print,request) S3method(print,response) S3method(status_code,numeric) S3method(status_code,response) export(BROWSE) export(DELETE) export(GET) export(HEAD) export(PATCH) export(POST) export(PUT) export(RETRY) export(Token) export(Token1.0) export(Token2.0) export(TokenServiceAccount) export(VERB) export(accept) export(accept_json) export(accept_xml) export(add_headers) export(authenticate) export(build_url) export(cache_info) export(config) export(content) export(content_type) export(content_type_json) export(content_type_xml) export(cookies) export(curl_docs) export(get_callback) export(guess_media) export(handle) export(handle_find) export(handle_reset) export(has_content) export(headers) export(hmac_sha1) export(http_condition) export(http_date) export(http_error) export(http_status) export(http_type) export(httr_dr) export(httr_options) export(init_oauth1.0) export(init_oauth2.0) export(insensitive) export(message_for_status) export(modify_url) export(oauth1.0_token) export(oauth2.0_access_token) export(oauth2.0_authorize_url) export(oauth2.0_token) export(oauth_app) export(oauth_callback) export(oauth_endpoint) export(oauth_endpoints) export(oauth_exchanger) export(oauth_header) export(oauth_listener) export(oauth_service_token) export(oauth_signature) export(parse_http_date) export(parse_media) export(parse_url) export(parsed_content) export(progress) export(rerequest) export(reset_config) export(revoke_all) export(set_callback) export(set_config) export(set_cookies) export(sha1_hash) export(sign_oauth1.0) export(sign_oauth2.0) export(status_code) export(stop_for_status) export(text_content) export(timeout) export(upload_file) export(url_ok) export(url_success) export(use_proxy) export(user_agent) export(verbose) export(warn_for_status) export(with_config) export(with_verbose) export(write_disk) export(write_function) export(write_memory) export(write_stream) importFrom(R6,R6Class) importFrom(utils,modifyList) httr/demo/0000755000176200001440000000000013520044656012164 5ustar liggesusershttr/demo/oauth1-nounproject.r0000644000176200001440000000317113401566431016114 0ustar liggesuserslibrary(httr) # Noun Project has an API secured with OAuth 1.0a One-legged. A client key and a # secret must be used to sign requests when accessing the API. This is an # exemple using OAuth 1.0a One legged auth mechanism # http://oauthbible.com/#oauth-10a-one-legged # 1. Register an application to get required keys: # https://thenounproject.com/accounts/login/?next=/developers/apps/ nouns_app <- oauth_app("noun_project", key = rstudioapi::askForPassword(), secret = rstudioapi::askForPassword() ) # 2. Each request must be signed using the app key and secret # see ?oauth_signature for more information on signature url <- "http://api.thenounproject.com/icon/15" signature <- oauth_signature(url, method = "GET", app = nouns_app) res <- GET(url, oauth_header(signature)) stop_for_status(res) content(res) # 3. Create a wrapper function to sign each request more easily get_nouns_api <- function(endpoint, baseurl = "http://api.thenounproject.com/", app = nouns_app, ...) { url <- modify_url(baseurl, path = endpoint) info <- oauth_signature(url, app = app) header_oauth <- oauth_header(info) GET(url, header_oauth, ...) } res <- get_nouns_api("collections") stop_for_status(res) content(res) # 4. Signing request requires the METHOD used. If the API has a POST for # example, you must take it into account. url <- "http://api.thenounproject.com/notify/publish?test=1" signature <- oauth_signature(url, method = "POST", app = nouns_app) res <- POST(url, oauth_header(signature), body = list(icons = 15), encode = "json") stop_for_status(res) content(res) httr/demo/oauth2-facebook.r0000644000176200001440000000315613401566431015323 0ustar liggesuserslibrary(httr) # Facebook requires a https redirect url, which there's no general way # to generate for a localhost url. Instead we'll use device flow: # https://developers.facebook.com/docs/facebook-login/for-devices/ # 1. Register an application at https://developers.facebook.com/apps/ # Make sure Product > Facebook login > Settings > Login from Devices = Yes. # Record your app_id and client token below. app_id <- "314363036052185" client_token <- "14b30b3ca83057c5a77ade469b496a26" access_token <- paste0(app_id, "|", client_token) # 2. Request a user code: device_url <- "https://graph.facebook.com/v2.6/device/login" resp <- POST(device_url, body = list( access_token = access_token, scope = "public_profile" # change this as needed )) stop_for_status(resp) request <- content(resp, type = "application/json") message("Go to <", request$verification_uri, "> and enter ", request$user_code) poll <- function(code) { poll_url <- "https://graph.facebook.com/v2.6/device/login_status" POST(poll_url, body = list( access_token = access_token, code = code )) } keep_waiting <- function(resp) { content <- content(resp, type = "application/json") if (!"error" %in% names(content)) { return(FALSE) } content$error$error_subcode == 1349174 } cat("Waiting for confirmation") while (keep_waiting(resp <- poll(request$code))) { cat(".") Sys.sleep(request$interval) } cat("\n") stop_for_status(resp) token <- content(resp)$access_token # 3. Use API req <- GET("https://graph.facebook.com/v2.3/me", query = list( fields = "name,picture", access_token = token )) stop_for_status(req) str(content(req)) httr/demo/oauth2-yelp.R0000644000176200001440000000212513401555653014462 0ustar liggesuserslibrary(httr) # This example demonstrate the use of client credentials grant # 1. Find OAuth settings for yelp: # https://www.yelp.ca/developers/documentation/v3/authentication # Set authorize url to NULL as we are not using Authorization code grant # but client credential grant yelp_endpoint <- oauth_endpoint( authorize = NULL, access = "https://api.yelp.com/oauth2/token" ) # 2. Register an application at https://www.yelp.com/developers/v3/manage_app # Replace key and secret below. yelp_app <- oauth_app( appname = "yelp", key = "bvmjj2EOBvOknQ", secret = "n8ueSvTNdlE0BDDJpLljvmgUGUw" ) # 3. Get OAuth credentials using client credential grant # Yelp do not use basic auth. Use `use_basic_auth = T` otherwise yelp_token <- oauth2.0_token( endpoint = yelp_endpoint, app = yelp_app, client_credentials = T ) # 4. Use API url <- modify_url( url = "https://api.yelp.com", path = c("v3", "businesses", "search"), query = list( term = "coffee", location = "Vancouver, BC", limit = 3 ) ) req <- GET(url, config(token = token)) stop_for_status(req) content(req) httr/demo/oauth1-withings.r0000644000176200001440000000146313401555650015405 0ustar liggesuserslibrary(httr) # 1. Create endpoint withings <- oauth_endpoint( base_url = "https://oauth.withings.com/account", "request_token", "authorize", "access_token" ) # 2. Register an application at https://oauth.withings.com/partner/add # Insert your key and secret below withingsapp <- oauth_app("withings", key = "e71b82e1d7fc2d2e3e2c2b398eeb617c16cee050c924c84df640e2e15eccb", secret = "3707510707116e836299c04f26f5ebbb51026d9d9cd758e36e4c7833dd7d" ) # 3. Get OAuth credentials withings_token <- oauth1.0_token(withings, withingsapp, as_header = FALSE) # 4. Use API req <- GET("http://wbsapi.withings.net/measure", query = list( action = "getmeas", userid = withings_token$credentials$userid ), config(token = withings_token) ) stop_for_status(req) content(req, type = "application/json") httr/demo/oauth2-yahoo.r0000644000176200001440000000136313401555653014673 0ustar liggesuserslibrary(httr) # 1. Find OAuth settings for yahoo: # https://developer.yahoo.com/oauth/guide/oauth-auth-flow.html oauth_endpoints("yahoo") # 2. Register an application at https://developer.apps.yahoo.com/projects # Replace key and secret below. myapp <- oauth_app("yahoo", key = "dj0yJmk9ZEp0d2J2MFRuakNQJmQ9WVdrOU0zaHRUMlJpTTJNbWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD00Nw--", secret = "82f339a41f71a3b4d9b840be427dde132e36d115" ) # 3. Get OAuth credentials # For OOB flows, Yahoo requires a value of "oob" for the callback: # https://developer.yahoo.com/oauth2/guide/flows_authcode/#step-2-get-an-authorization-url-and-authorize-access= yahoo_token <- oauth2.0_token(oauth_endpoints("yahoo"), myapp, use_oob = TRUE, oob_value = "oob" ) httr/demo/oauth2-reddit.R0000644000176200001440000000162113401555653014764 0ustar liggesuserslibrary(httr) # 1. Find OAuth settings for reddit: # https://github.com/reddit/reddit/wiki/OAuth2 reddit <- oauth_endpoint( authorize = "https://www.reddit.com/api/v1/authorize", access = "https://www.reddit.com/api/v1/access_token" ) # 2. Register an application at https://www.reddit.com/prefs/apps # Make sure to register http://localhost:1410/ as the "redirect uri". # (the trailing slash is important!) app <- oauth_app("reddit", "bvmjj2EOBvOknQ", "n8ueSvTNdlE0BDDJpLljvmgUGUw") # 3. Get OAuth credentials token <- oauth2.0_token(reddit, app, scope = c("read", "modposts"), use_basic_auth = TRUE ) # 3b. If get 429 too many requests, the default user_agent is overloaded. # If you have an application on Reddit then you can pass that using: token <- oauth2.0_token( reddit, app, scope = c("read", "modposts"), use_basic_auth = TRUE, config_init = user_agent("YOUR_USER_AGENT") ) httr/demo/oauth2-google.r0000644000176200001440000000124313401555652015024 0ustar liggesuserslibrary(httr) # 1. Find OAuth settings for google: # https://developers.google.com/accounts/docs/OAuth2InstalledApp oauth_endpoints("google") # 2. Register an application at https://cloud.google.com/console#/project # Replace key and secret below. myapp <- oauth_app("google", key = "16795585089.apps.googleusercontent.com", secret = "hlJNgK73GjUXILBQvyvOyurl" ) # 3. Get OAuth credentials google_token <- oauth2.0_token(oauth_endpoints("google"), myapp, scope = "https://www.googleapis.com/auth/userinfo.profile" ) # 4. Use API req <- GET( "https://www.googleapis.com/oauth2/v1/userinfo", config(token = google_token) ) stop_for_status(req) content(req) httr/demo/oauth2-vimeo.r0000644000176200001440000000116513401555653014673 0ustar liggesuserslibrary(httr) # 1. Find OAuth settings for vimeo: # http://vimeo.com/api/docs/authentication oauth_endpoints("vimeo") # 2. Register an application at https://developer.vimeo.com/apps # Replace key and secret below. myapp <- oauth_app("vimeo", key = "bd535bc38ed5caccd79330ff33075eb9", secret = "51ab8cb2cbb8b7eb" ) # 3. Get OAuth credentials vimeo_token <- oauth2.0_token( oauth_endpoints("vimeo"), myapp, as_header = TRUE, use_basic_auth = TRUE ) # 4. Use API req <- GET( "https://api.vimeo.com/me/videos", config(token = vimeo_token) ) stop_for_status(req) str(jsonlite::fromJSON(content(req, "text"))) httr/demo/oauth2-linkedin.r0000644000176200001440000000135113504410572015340 0ustar liggesuserslibrary(httr) # 1. Find OAuth settings for linkedin: # https://developer.linkedin.com/documents/linkedins-oauth-details endpoints <- oauth_endpoints("linkedin") # 2. Register an application at https://www.linkedin.com/secure/developer # Make sure to register http://localhost:1410/ as an "OAuth 2.0 Redirect URL". # (the trailing slash is important!) # # Replace key and secret below. myapp <- oauth_app("linkedin", key = "outmkw3859gy", secret = "n7vBr3lokGOCDKCd" ) # 3. Get OAuth credentials and specify a scope your app has permission for token <- oauth2.0_token(endpoints, myapp, scope = "r_liteprofile") # 4. Use API req <- GET("https://api.linkedin.com/v2/me", config(token = token)) stop_for_status(req) content(req) httr/demo/oauth1-twitter.r0000644000176200001440000000121613401555650015247 0ustar liggesuserslibrary(httr) # 1. Find OAuth settings for twitter: # https://dev.twitter.com/docs/auth/oauth oauth_endpoints("twitter") # 2. Register an application at https://apps.twitter.com/ # Make sure to set callback url to "http://127.0.0.1:1410/" # # Replace key and secret below myapp <- oauth_app("twitter", key = "TYrWFPkFAkn4G5BbkWINYw", secret = "qjOkmKYU9kWfUFWmekJuu5tztE9aEfLbt26WlhZL8" ) # 3. Get OAuth credentials twitter_token <- oauth1.0_token(oauth_endpoints("twitter"), myapp) # 4. Use API req <- GET( "https://api.twitter.com/1.1/statuses/home_timeline.json", config(token = twitter_token) ) stop_for_status(req) content(req) httr/demo/oauth2-azure.r0000644000176200001440000000445713401555651014707 0ustar liggesusers# !!! The special redirect URI "urn:ietf:wg:oauth:2.0:oob used # !!! by httr in case httuv is not installed is currently not # !!! supported by Azure Active Directory (AAD). # !!! Therefore it is required to install httpuv to make this work. # 1. Register an app app in AAD, e.g. as a "Native app", with # redirect URI . # 2. Insert the App name: app_name <- "myapp" # not important for authorization grant flow # 3. Insert the created apps client ID which was issued after app creation: client_id <- "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" # In case your app was registered as a web app instead of a native app, # you might have to add your secret key string here: client_secret <- NULL # API resource ID to request access for, e.g. Power BI: resource_uri <- "https://analysis.windows.net/powerbi/api" # Obtain OAuth2 endpoint settings for azure: # This uses the "common" endpoint. # To use a tenant url, create an # oauth_endpoint(authorize = "https://login.windows.net//oauth2/authorize", # access = "https://login.windows.net//oauth2/token") # with replaced by your endpoint ID. azure_endpoint <- oauth_endpoints("azure") # Create the app instance. myapp <- oauth_app( appname = app_name, key = client_id, secret = client_secret ) # Step through the authorization chain: # 1. You will be redirected to you authorization endpoint via web browser. # 2. Once you responded to the request, the endpoint will redirect you to # the local address specified by httr. # 3. httr will acquire the authorization code (or error) from the data # posted to the redirect URI. # 4. If a code was acquired, httr will contact your authorized token access # endpoint to obtain the token. mytoken <- oauth2.0_token(azure_endpoint, myapp, user_params = list(resource = resource_uri), use_oob = FALSE ) if (("error" %in% names(mytoken$credentials)) && (nchar(mytoken$credentials$error) > 0)) { errorMsg <- paste("Error while acquiring token.", paste("Error message:", mytoken$credentials$error), paste("Error description:", mytoken$credentials$error_description), paste("Error code:", mytoken$credentials$error_codes), sep = "\n" ) stop(errorMsg) } # Resource API can be accessed through "mytoken" at this point. httr/demo/oauth1-yahoo.r0000644000176200001440000000102313401555650014660 0ustar liggesuserslibrary(httr) # 1. Find OAuth settings for yahoo: # https://developer.yahoo.com/oauth/guide/oauth-auth-flow.html oauth_endpoints("yahoo") # 2. Register an application at https://developer.apps.yahoo.com/projects # Replace key and secret below. myapp <- oauth_app("yahoo", key = "dj0yJmk9ZEp0d2J2MFRuakNQJmQ9WVdrOU0zaHRUMlJpTTJNbWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD00Nw--", secret = "82f339a41f71a3b4d9b840be427dde132e36d115" ) # 3. Get OAuth credentials yahoo_token <- oauth1.0_token(oauth_endpoints("yahoo"), myapp) httr/demo/connection-sharing.r0000644000176200001440000000074013401555647016145 0ustar liggesuserstest_server <- "http://had.co.nz" # Create a new handle for every request - no connection sharing rowMeans(replicate( 20, GET(handle = handle(test_server), path = "index.html")$times )) test_handle <- handle(test_server) # Re use the same handle for multiple requests rowMeans(replicate( 20, GET(handle = test_handle, path = "index.html")$times )) # With httr, handles are automatically pooled rowMeans(replicate( 20, GET(test_server, path = "index.html")$times )) httr/demo/00Index0000644000176200001440000000157013401566431013317 0ustar liggesusersconnection-sharing Demonstration of how connection sharing saves time oauth1-nounproject Using noun project api with OAuth 1.0 one-legged oauth1-twitter Using twitter api with OAuth 1.0 oauth1-yahoo Using yahoo api with OAuth 1.0 oauth1-withings Using withings api with OAuth 1.0 oauth2-azure Using Azure apis with OAuth 2.0 oauth2-facebook Using the facebook api with OAuth 2.0 oauth2-github Using the github api with OAuth 2.0 oauth2-google Using the google api with OAuth 2.0 oauth2-reddit Using the reddit api with OAuth 2.0 oauth2-linkedin Using linkedin api with OAuth 1.0 oauth2-vimeo Using vimeo api with OAuth 2.0 oauth2-yahoo Using yahoo api with OAuth 2.0 and custom OOB redirect URI oauth2-yelp Using yelp api with OAuth 2.0 and Client Credentials Grant service-account Using Google service account httr/demo/service-account.R0000644000176200001440000000133213401555654015403 0ustar liggesusers # 1. Find OAuth settings for google: # https://developers.google.com/accounts/docs/OAuth2InstalledApp oauth_endpoints("google") # 2. Register an project at https://cloud.google.com/console#/project # 3. Navigate to API Manager, then credentials. Create a new # "service account key". This will generate a JSON file that you need to # save in a secure location. This file is equivalent to a username + # password pair. token <- oauth_service_token( oauth_endpoints("google"), jsonlite::fromJSON("demo/service-account.json"), "https://www.googleapis.com/auth/userinfo.profile" ) # 4. Use API req <- GET("https://www.googleapis.com/oauth2/v1/userinfo", config(token = token)) stop_for_status(req) content(req) httr/demo/oauth2-github.r0000644000176200001440000000150013401555652015026 0ustar liggesuserslibrary(httr) # 1. Find OAuth settings for github: # http://developer.github.com/v3/oauth/ oauth_endpoints("github") # 2. To make your own application, register at # https://github.com/settings/developers. Use any URL for the homepage URL # (http://github.com is fine) and http://localhost:1410 as the callback url # # Replace your key and secret below. myapp <- oauth_app("github", key = "56b637a5baffac62cad9", secret = "8e107541ae1791259e9987d544ca568633da2ebf" ) # 3. Get OAuth credentials github_token <- oauth2.0_token(oauth_endpoints("github"), myapp) # 4. Use API gtoken <- config(token = github_token) req <- GET("https://api.github.com/rate_limit", gtoken) stop_for_status(req) content(req) # OR: req <- with_config(gtoken, GET("https://api.github.com/rate_limit")) stop_for_status(req) content(req) httr/LICENSE0000644000176200001440000000006513376014735012252 0ustar liggesusersYEAR: 2014 COPYRIGHT HOLDER: Hadley Wickham; RStudio httr/README.md0000644000176200001440000000471113401566431012520 0ustar liggesusers# httr [![Travis build status](https://travis-ci.org/r-lib/httr.svg?branch=master)](https://travis-ci.org/r-lib/httr) [![Codecov test coverage](https://codecov.io/gh/r-lib/httr/branch/master/graph/badge.svg)](https://codecov.io/gh/r-lib/httr?branch=master) [![CRAN status](https://www.r-pkg.org/badges/version/httr)](https://cran.r-project.org/package=httr) The aim of httr is to provide a wrapper for the [curl](https://cran.r-project.org/package=curl) package, customised to the demands of modern web APIs. Key features: * Functions for the most important http verbs: `GET()`, `HEAD()`, `PATCH()`, `PUT()`, `DELETE()` and `POST()`. * Automatic connection sharing across requests to the same website (by default, curl handles are managed automatically), cookies are maintained across requests, and a up-to-date root-level SSL certificate store is used. * Requests return a standard reponse object that captures the http status line, headers and body, along with other useful information. * Response content is available with `content()` as a raw vector (`as = "raw"`), a character vector (`as = "text"`), or parsed into an R object (`as = "parsed"`), currently for html, xml, json, png and jpeg. * You can convert http errors into R errors with `stop_for_status()`. * Config functions make it easier to modify the request in common ways: `set_cookies()`, `add_headers()`, `authenticate()`, `use_proxy()`, `verbose()`, `timeout()`, `content_type()`, `accept()`, `progress()`. * Support for OAuth 1.0 and 2.0 with `oauth1.0_token()` and `oauth2.0_token()`. The demo directory has eight OAuth demos: four for 1.0 (twitter, vimeo, withings and yahoo) and four for 2.0 (facebook, github, google, linkedin). OAuth credentials are automatically cached within a project. `httr` wouldn't be possible without the hard work of the authors of [curl](https://cran.r-project.org/package=curl) and [libcurl](http://curl.haxx.se/). Thanks! `httr` is inspired by http libraries in other languages, such as [Resty](http://beders.github.com/Resty/Resty/Examples.html), [Requests](http://docs.python-requests.org/en/latest/index.html) and [httparty](http://github.com/jnunemaker/httparty/tree/master). ## Installation To get the current released version from CRAN: ```R install.packages("httr") ``` To get the current development version from github: ```R # install.packages("devtools") devtools::install_github("r-lib/httr") ``` httr/man/0000755000176200001440000000000013504410572012007 5ustar liggesusershttr/man/handle_pool.Rd0000644000176200001440000000111113376014735014564 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/handle-pool.r \docType{data} \name{handle_pool} \alias{handle_pool} \alias{handle_find} \alias{handle_reset} \title{Maintain a pool of handles.} \format{An environment.} \usage{ handle_pool handle_find(url) handle_reset(url) } \description{ The handle pool is used to automatically reuse Curl handles for the same scheme/host/port combination. This ensures that the http session is automatically reused, and cookies are maintained across requests to a site without user intervention. } \keyword{internal} httr/man/authenticate.Rd0000644000176200001440000000173713401555510014762 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/authenticate.r \name{authenticate} \alias{authenticate} \title{Use http authentication.} \usage{ authenticate(user, password, type = "basic") } \arguments{ \item{user}{user name} \item{password}{password} \item{type}{type of HTTP authentication. Should be one of the following types supported by Curl: basic, digest, digest_ie, gssnegotiate, ntlm, any. It defaults to "basic", the most common type.} } \description{ It's not obvious how to turn authentication off after using it, so I recommend using custom handles with authentication. } \examples{ GET("http://httpbin.org/basic-auth/user/passwd") GET( "http://httpbin.org/basic-auth/user/passwd", authenticate("user", "passwd") ) } \seealso{ Other config: \code{\link{add_headers}}, \code{\link{config}}, \code{\link{set_cookies}}, \code{\link{timeout}}, \code{\link{use_proxy}}, \code{\link{user_agent}}, \code{\link{verbose}} } \concept{config} httr/man/BROWSE.Rd0000644000176200001440000000277013401555510013303 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/http-browse.r \name{BROWSE} \alias{BROWSE} \title{Open specified url in browser.} \usage{ BROWSE(url = NULL, config = list(), ..., handle = NULL) } \arguments{ \item{url}{the url of the page to retrieve} \item{config}{All configuration options are ignored because the request is handled by the browser, not \pkg{RCurl}.} \item{...}{Further named parameters, such as \code{query}, \code{path}, etc, passed on to \code{\link[=modify_url]{modify_url()}}. Unnamed parameters will be combined with \code{\link[=config]{config()}}.} \item{handle}{The handle to use with this request. If not supplied, will be retrieved and reused from the \code{\link[=handle_pool]{handle_pool()}} based on the scheme, hostname and port of the url. By default \pkg{httr} requests to the same scheme/host/port combo. This substantially reduces connection time, and ensures that cookies are maintained over multiple requests to the same host. See \code{\link[=handle_pool]{handle_pool()}} for more details.} } \value{ A \code{\link[=response]{response()}} object. } \description{ (This isn't really a http verb, but it seems to follow the same format). } \details{ Only works in interactive sessions. } \examples{ BROWSE("http://google.com") BROWSE("http://had.co.nz") } \seealso{ Other http methods: \code{\link{DELETE}}, \code{\link{GET}}, \code{\link{HEAD}}, \code{\link{PATCH}}, \code{\link{POST}}, \code{\link{PUT}}, \code{\link{VERB}} } \concept{http methods} httr/man/DELETE.Rd0000644000176200001440000001002313401555510013232 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/http-delete.r \name{DELETE} \alias{DELETE} \title{Send a DELETE request.} \usage{ DELETE(url = NULL, config = list(), ..., body = NULL, encode = c("multipart", "form", "json", "raw"), handle = NULL) } \arguments{ \item{url}{the url of the page to retrieve} \item{config}{Additional configuration settings such as http authentication (\code{\link[=authenticate]{authenticate()}}), additional headers (\code{\link[=add_headers]{add_headers()}}), cookies (\code{\link[=set_cookies]{set_cookies()}}) etc. See \code{\link[=config]{config()}} for full details and list of helpers.} \item{...}{Further named parameters, such as \code{query}, \code{path}, etc, passed on to \code{\link[=modify_url]{modify_url()}}. Unnamed parameters will be combined with \code{\link[=config]{config()}}.} \item{body}{One of the following: \itemize{ \item \code{FALSE}: No body. This is typically not used with \code{POST}, \code{PUT}, or \code{PATCH}, but can be useful if you need to send a bodyless request (like \code{GET}) with \code{VERB()}. \item \code{NULL}: An empty body \item \code{""}: A length 0 body \item \code{upload_file("path/")}: The contents of a file. The mime type will be guessed from the extension, or can be supplied explicitly as the second argument to \code{upload_file()} \item A character or raw vector: sent as is in body. Use \code{\link[=content_type]{content_type()}} to tell the server what sort of data you are sending. \item A named list: See details for encode. }} \item{encode}{If the body is a named list, how should it be encoded? Can be one of form (application/x-www-form-urlencoded), multipart, (multipart/form-data), or json (application/json). For "multipart", list elements can be strings or objects created by \code{\link[=upload_file]{upload_file()}}. For "form", elements are coerced to strings and escaped, use \code{I()} to prevent double-escaping. For "json", parameters are automatically "unboxed" (i.e. length 1 vectors are converted to scalars). To preserve a length 1 vector as a vector, wrap in \code{I()}. For "raw", either a character or raw vector. You'll need to make sure to set the \code{\link[=content_type]{content_type()}} yourself.} \item{handle}{The handle to use with this request. If not supplied, will be retrieved and reused from the \code{\link[=handle_pool]{handle_pool()}} based on the scheme, hostname and port of the url. By default \pkg{httr} requests to the same scheme/host/port combo. This substantially reduces connection time, and ensures that cookies are maintained over multiple requests to the same host. See \code{\link[=handle_pool]{handle_pool()}} for more details.} } \value{ A \code{\link[=response]{response()}} object. } \description{ Send a DELETE request. } \section{RFC2616}{ The DELETE method requests that the origin server delete the resource identified by the Request-URI. This method MAY be overridden by human intervention (or other means) on the origin server. The client cannot be guaranteed that the operation has been carried out, even if the status code returned from the origin server indicates that the action has been completed successfully. However, the server SHOULD NOT indicate success unless, at the time the response is given, it intends to delete the resource or move it to an inaccessible location. A successful response SHOULD be 200 (OK) if the response includes an entity describing the status, 202 (Accepted) if the action has not yet been enacted, or 204 (No Content) if the action has been enacted but the response does not include an entity. If the request passes through a cache and the Request-URI identifies one or more currently cached entities, those entries SHOULD be treated as stale. Responses to this method are not cacheable. } \examples{ DELETE("http://httpbin.org/delete") POST("http://httpbin.org/delete") } \seealso{ Other http methods: \code{\link{BROWSE}}, \code{\link{GET}}, \code{\link{HEAD}}, \code{\link{PATCH}}, \code{\link{POST}}, \code{\link{PUT}}, \code{\link{VERB}} } \concept{http methods} httr/man/http_status.Rd0000644000176200001440000000316413401555510014662 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/response-status.r \name{http_status} \alias{http_status} \title{Give information on the status of a request.} \usage{ http_status(x) } \arguments{ \item{x}{a request object or a number.} } \value{ If the status code does not match a known status, an error. Otherwise, a list with components \item{category}{the broad category of the status} \item{message}{the meaning of the status code} } \description{ Extract the http status code and convert it into a human readable message. } \details{ http servers send a status code with the response to each request. This code gives information regarding the outcome of the execution of the request on the server. Roughly speaking, codes in the 100s and 200s mean the request was successfully executed; codes in the 300s mean the page was redirected; codes in the 400s mean there was a mistake in the way the client sent the request; codes in the 500s mean the server failed to fulfill an apparently valid request. More details on the codes can be found at \code{http://en.wikipedia.org/wiki/Http_error_codes}. } \examples{ http_status(100) http_status(404) x <- GET("http://httpbin.org/status/200") http_status(x) http_status(GET("http://httpbin.org/status/300")) http_status(GET("http://httpbin.org/status/301")) http_status(GET("http://httpbin.org/status/404")) # errors out on unknown status \dontrun{ http_status(GET("http://httpbin.org/status/320")) } } \seealso{ Other response methods: \code{\link{content}}, \code{\link{http_error}}, \code{\link{response}}, \code{\link{stop_for_status}} } \concept{response methods} httr/man/POST.Rd0000644000176200001440000000726513504410572013075 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/http-post.r \name{POST} \alias{POST} \title{POST file to a server.} \usage{ POST(url = NULL, config = list(), ..., body = NULL, encode = c("multipart", "form", "json", "raw"), handle = NULL) } \arguments{ \item{url}{the url of the page to retrieve} \item{config}{Additional configuration settings such as http authentication (\code{\link[=authenticate]{authenticate()}}), additional headers (\code{\link[=add_headers]{add_headers()}}), cookies (\code{\link[=set_cookies]{set_cookies()}}) etc. See \code{\link[=config]{config()}} for full details and list of helpers.} \item{...}{Further named parameters, such as \code{query}, \code{path}, etc, passed on to \code{\link[=modify_url]{modify_url()}}. Unnamed parameters will be combined with \code{\link[=config]{config()}}.} \item{body}{One of the following: \itemize{ \item \code{FALSE}: No body. This is typically not used with \code{POST}, \code{PUT}, or \code{PATCH}, but can be useful if you need to send a bodyless request (like \code{GET}) with \code{VERB()}. \item \code{NULL}: An empty body \item \code{""}: A length 0 body \item \code{upload_file("path/")}: The contents of a file. The mime type will be guessed from the extension, or can be supplied explicitly as the second argument to \code{upload_file()} \item A character or raw vector: sent as is in body. Use \code{\link[=content_type]{content_type()}} to tell the server what sort of data you are sending. \item A named list: See details for encode. }} \item{encode}{If the body is a named list, how should it be encoded? Can be one of form (application/x-www-form-urlencoded), multipart, (multipart/form-data), or json (application/json). For "multipart", list elements can be strings or objects created by \code{\link[=upload_file]{upload_file()}}. For "form", elements are coerced to strings and escaped, use \code{I()} to prevent double-escaping. For "json", parameters are automatically "unboxed" (i.e. length 1 vectors are converted to scalars). To preserve a length 1 vector as a vector, wrap in \code{I()}. For "raw", either a character or raw vector. You'll need to make sure to set the \code{\link[=content_type]{content_type()}} yourself.} \item{handle}{The handle to use with this request. If not supplied, will be retrieved and reused from the \code{\link[=handle_pool]{handle_pool()}} based on the scheme, hostname and port of the url. By default \pkg{httr} requests to the same scheme/host/port combo. This substantially reduces connection time, and ensures that cookies are maintained over multiple requests to the same host. See \code{\link[=handle_pool]{handle_pool()}} for more details.} } \value{ A \code{\link[=response]{response()}} object. } \description{ POST file to a server. } \examples{ b2 <- "http://httpbin.org/post" POST(b2, body = "A simple text string") POST(b2, body = list(x = "A simple text string")) POST(b2, body = list(y = upload_file(system.file("CITATION")))) POST(b2, body = list(x = "A simple text string"), encode = "json") # body can also be provided as a json string directly to deal # with specific case, like an empty element in the json string. # passing as string directly POST(b2, body = '{"a":1,"b":{}}', encode = "raw") # or building the json string before json_body <- jsonlite::toJSON(list(a = 1, b = NULL), auto_unbox = TRUE) POST(b2, body = json_body, encode = "raw") # Various types of empty body: POST(b2, body = NULL, verbose()) POST(b2, body = FALSE, verbose()) POST(b2, body = "", verbose()) } \seealso{ Other http methods: \code{\link{BROWSE}}, \code{\link{DELETE}}, \code{\link{GET}}, \code{\link{HEAD}}, \code{\link{PATCH}}, \code{\link{PUT}}, \code{\link{VERB}} } \concept{http methods} httr/man/httr_dr.Rd0000644000176200001440000000036213376014735013755 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/doctor.R \name{httr_dr} \alias{httr_dr} \title{Diagnose common configuration problems} \usage{ httr_dr() } \description{ Currently one check: that curl uses nss. } httr/man/PATCH.Rd0000644000176200001440000000573013401555510013140 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/http-patch.r \name{PATCH} \alias{PATCH} \title{Send PATCH request to a server.} \usage{ PATCH(url = NULL, config = list(), ..., body = NULL, encode = c("multipart", "form", "json", "raw"), handle = NULL) } \arguments{ \item{url}{the url of the page to retrieve} \item{config}{Additional configuration settings such as http authentication (\code{\link[=authenticate]{authenticate()}}), additional headers (\code{\link[=add_headers]{add_headers()}}), cookies (\code{\link[=set_cookies]{set_cookies()}}) etc. See \code{\link[=config]{config()}} for full details and list of helpers.} \item{...}{Further named parameters, such as \code{query}, \code{path}, etc, passed on to \code{\link[=modify_url]{modify_url()}}. Unnamed parameters will be combined with \code{\link[=config]{config()}}.} \item{body}{One of the following: \itemize{ \item \code{FALSE}: No body. This is typically not used with \code{POST}, \code{PUT}, or \code{PATCH}, but can be useful if you need to send a bodyless request (like \code{GET}) with \code{VERB()}. \item \code{NULL}: An empty body \item \code{""}: A length 0 body \item \code{upload_file("path/")}: The contents of a file. The mime type will be guessed from the extension, or can be supplied explicitly as the second argument to \code{upload_file()} \item A character or raw vector: sent as is in body. Use \code{\link[=content_type]{content_type()}} to tell the server what sort of data you are sending. \item A named list: See details for encode. }} \item{encode}{If the body is a named list, how should it be encoded? Can be one of form (application/x-www-form-urlencoded), multipart, (multipart/form-data), or json (application/json). For "multipart", list elements can be strings or objects created by \code{\link[=upload_file]{upload_file()}}. For "form", elements are coerced to strings and escaped, use \code{I()} to prevent double-escaping. For "json", parameters are automatically "unboxed" (i.e. length 1 vectors are converted to scalars). To preserve a length 1 vector as a vector, wrap in \code{I()}. For "raw", either a character or raw vector. You'll need to make sure to set the \code{\link[=content_type]{content_type()}} yourself.} \item{handle}{The handle to use with this request. If not supplied, will be retrieved and reused from the \code{\link[=handle_pool]{handle_pool()}} based on the scheme, hostname and port of the url. By default \pkg{httr} requests to the same scheme/host/port combo. This substantially reduces connection time, and ensures that cookies are maintained over multiple requests to the same host. See \code{\link[=handle_pool]{handle_pool()}} for more details.} } \value{ A \code{\link[=response]{response()}} object. } \description{ Send PATCH request to a server. } \seealso{ Other http methods: \code{\link{BROWSE}}, \code{\link{DELETE}}, \code{\link{GET}}, \code{\link{HEAD}}, \code{\link{POST}}, \code{\link{PUT}}, \code{\link{VERB}} } \concept{http methods} httr/man/HEAD.Rd0000644000176200001440000000477013401555510013005 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/http-head.r \name{HEAD} \alias{HEAD} \title{Get url HEADers.} \usage{ HEAD(url = NULL, config = list(), ..., handle = NULL) } \arguments{ \item{url}{the url of the page to retrieve} \item{config}{Additional configuration settings such as http authentication (\code{\link[=authenticate]{authenticate()}}), additional headers (\code{\link[=add_headers]{add_headers()}}), cookies (\code{\link[=set_cookies]{set_cookies()}}) etc. See \code{\link[=config]{config()}} for full details and list of helpers.} \item{...}{Further named parameters, such as \code{query}, \code{path}, etc, passed on to \code{\link[=modify_url]{modify_url()}}. Unnamed parameters will be combined with \code{\link[=config]{config()}}.} \item{handle}{The handle to use with this request. If not supplied, will be retrieved and reused from the \code{\link[=handle_pool]{handle_pool()}} based on the scheme, hostname and port of the url. By default \pkg{httr} requests to the same scheme/host/port combo. This substantially reduces connection time, and ensures that cookies are maintained over multiple requests to the same host. See \code{\link[=handle_pool]{handle_pool()}} for more details.} } \value{ A \code{\link[=response]{response()}} object. } \description{ Get url HEADers. } \section{RFC2616}{ The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response. The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied by the request without transferring the entity-body itself. This method is often used for testing hypertext links for validity, accessibility, and recent modification. The response to a HEAD request MAY be cacheable in the sense that the information contained in the response MAY be used to update a previously cached entity from that resource. If the new field values indicate that the cached entity differs from the current entity (as would be indicated by a change in Content-Length, Content-MD5, ETag or Last-Modified), then the cache MUST treat the cache entry as stale. } \examples{ HEAD("http://google.com") headers(HEAD("http://google.com")) } \seealso{ Other http methods: \code{\link{BROWSE}}, \code{\link{DELETE}}, \code{\link{GET}}, \code{\link{PATCH}}, \code{\link{POST}}, \code{\link{PUT}}, \code{\link{VERB}} } \concept{http methods} httr/man/config.Rd0000644000176200001440000000430413401555510013542 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/config.r \name{config} \alias{config} \title{Set curl options.} \usage{ config(..., token = NULL) } \arguments{ \item{...}{named Curl options.} \item{token}{An OAuth token (1.0 or 2.0)} } \description{ Generally you should only need to use this function to set CURL options directly if there isn't already a helpful wrapper function, like \code{\link[=set_cookies]{set_cookies()}}, \code{\link[=add_headers]{add_headers()}} or \code{\link[=authenticate]{authenticate()}}. To use this function effectively requires some knowledge of CURL, and CURL options. Use \code{\link[=httr_options]{httr_options()}} to see a complete list of available options. To see the libcurl documentation for a given option, use \code{\link[=curl_docs]{curl_docs()}}. } \details{ Unlike Curl (and RCurl), all configuration options are per request, not per handle. } \examples{ # There are a number of ways to modify the configuration of a request # * you can add directly to a request HEAD("https://www.google.com", verbose()) # * you can wrap with with_config() with_config(verbose(), HEAD("https://www.google.com")) # * you can set global with set_config() old <- set_config(verbose()) HEAD("https://www.google.com") # and re-establish the previous settings with set_config(old, override = TRUE) HEAD("https://www.google.com") # or reset_config() HEAD("https://www.google.com") # If available, you should use a friendly httr wrapper over RCurl # options. But you can pass Curl options (as listed in httr_options()) # in config HEAD("https://www.google.com/", config(verbose = TRUE)) } \seealso{ \code{\link[=set_config]{set_config()}} to set global config defaults, and \code{\link[=with_config]{with_config()}} to temporarily run code with set options. All known available options are listed in \code{\link[=httr_options]{httr_options()}} Other config: \code{\link{add_headers}}, \code{\link{authenticate}}, \code{\link{set_cookies}}, \code{\link{timeout}}, \code{\link{use_proxy}}, \code{\link{user_agent}}, \code{\link{verbose}} Other ways to set configuration: \code{\link{set_config}}, \code{\link{with_config}} } \concept{config} \concept{ways to set configuration} httr/man/verbose.Rd0000644000176200001440000000371313401555510013745 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verbose.r \name{verbose} \alias{verbose} \title{Give verbose output.} \usage{ verbose(data_out = TRUE, data_in = FALSE, info = FALSE, ssl = FALSE) } \arguments{ \item{data_out}{Show data sent to the server.} \item{data_in}{Show data recieved from the server.} \item{info}{Show informational text from curl. This is mainly useful for debugging https and auth problems, so is disabled by default.} \item{ssl}{Show even data sent/recieved over SSL connections?} } \description{ A verbose connection provides much more information about the flow of information between the client and server. } \section{Prefixes}{ \code{verbose()} uses the following prefixes to distinguish between different components of the http messages: \itemize{ \item \code{*} informative curl messages \item \code{->} headers sent (out) \item \code{>>} data sent (out) \item \code{*>} ssl data sent (out) \item \code{<-} headers received (in) \item \code{<<} data received (in) \item \code{<*} ssl data received (in) } } \examples{ GET("http://httpbin.org", verbose()) GET("http://httpbin.org", verbose(info = TRUE)) f <- function() { GET("http://httpbin.org") } with_verbose(f()) with_verbose(f(), info = TRUE) # verbose() makes it easy to see exactly what POST requests send POST_verbose <- function(body, ...) { POST("https://httpbin.org/post", body = body, verbose(), ...) invisible() } POST_verbose(list(x = "a", y = "b")) POST_verbose(list(x = "a", y = "b"), encode = "form") POST_verbose(FALSE) POST_verbose(NULL) POST_verbose("") POST_verbose("xyz") } \seealso{ \code{\link[=with_verbose]{with_verbose()}} makes it easier to use verbose mode even when the requests are buried inside another function call. Other config: \code{\link{add_headers}}, \code{\link{authenticate}}, \code{\link{config}}, \code{\link{set_cookies}}, \code{\link{timeout}}, \code{\link{use_proxy}}, \code{\link{user_agent}} } \concept{config} httr/man/cookies.Rd0000644000176200001440000000066513401555510013737 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cookies.r \name{cookies} \alias{cookies} \title{Access cookies in a response.} \usage{ cookies(x) } \arguments{ \item{x}{A response.} } \description{ Access cookies in a response. } \examples{ r <- GET("http://httpbin.org/cookies/set", query = list(a = 1, b = 2)) cookies(r) } \seealso{ \code{\link[=set_cookies]{set_cookies()}} to send cookies in request. } httr/man/content_type.Rd0000644000176200001440000000234213401555510015010 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/headers.r \name{content_type} \alias{content_type} \alias{content_type_json} \alias{content_type_xml} \alias{accept} \alias{accept_json} \alias{accept_xml} \title{Set content-type and accept headers.} \usage{ content_type(type) content_type_json() content_type_xml() accept(type) accept_json() accept_xml() } \arguments{ \item{type}{A mime type or a file extension. If a file extension (i.e. starts with \code{.}) will guess the mime type using \code{\link[mime:guess_type]{mime::guess_type()}}.} } \description{ These are convenient wrappers aroud \code{\link[=add_headers]{add_headers()}}. } \details{ \code{accept_json}/\code{accept_xml} and \code{content_type_json}/\code{content_type_xml} are useful shortcuts to ask for json or xml responses or tell the server you are sending json/xml. } \examples{ GET("http://httpbin.org/headers") GET("http://httpbin.org/headers", accept_json()) GET("http://httpbin.org/headers", accept("text/csv")) GET("http://httpbin.org/headers", accept(".doc")) GET("http://httpbin.org/headers", content_type_xml()) GET("http://httpbin.org/headers", content_type("text/csv")) GET("http://httpbin.org/headers", content_type(".xml")) } httr/man/oauth2.0_token.Rd0000644000176200001440000000654213401555510015043 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-token.r \name{oauth2.0_token} \alias{oauth2.0_token} \title{Generate an oauth2.0 token.} \usage{ oauth2.0_token(endpoint, app, scope = NULL, user_params = NULL, type = NULL, use_oob = getOption("httr_oob_default"), oob_value = NULL, as_header = TRUE, use_basic_auth = FALSE, cache = getOption("httr_oauth_cache"), config_init = list(), client_credentials = FALSE, credentials = NULL, query_authorize_extra = list()) } \arguments{ \item{endpoint}{An OAuth endpoint, created by \code{\link[=oauth_endpoint]{oauth_endpoint()}}} \item{app}{An OAuth consumer application, created by \code{\link[=oauth_app]{oauth_app()}}} \item{scope}{a character vector of scopes to request.} \item{user_params}{Named list holding endpoint specific parameters to pass to the server when posting the request for obtaining or refreshing the access token.} \item{type}{content type used to override incorrect server response} \item{use_oob}{if FALSE, use a local webserver for the OAuth dance. Otherwise, provide a URL to the user and prompt for a validation code. Defaults to the of the \code{"httr_oob_default"} default, or \code{TRUE} if \code{httpuv} is not installed.} \item{oob_value}{if provided, specifies the value to use for the redirect_uri parameter when retrieving an authorization URL. Defaults to "urn:ietf:wg:oauth:2.0:oob". Requires \code{use_oob = TRUE}.} \item{as_header}{If \code{TRUE}, the default, configures the token to add itself to the bearer header of subsequent requests. If \code{FALSE}, configures the token to add itself as a url parameter of subsequent requests.} \item{use_basic_auth}{if \code{TRUE} use http basic authentication to retrieve the token. Some authorization servers require this. If \code{FALSE}, the default, retrieve the token by including the app key and secret in the request body.} \item{cache}{A logical value or a string. \code{TRUE} means to cache using the default cache file \code{.httr-oauth}, \code{FALSE} means don't cache, and \code{NA} means to guess using some sensible heuristics. A string means use the specified path as the cache file.} \item{config_init}{Additional configuration settings sent to \code{\link[=POST]{POST()}}, e.g. \code{\link[=user_agent]{user_agent()}}.} \item{client_credentials}{Default to \code{FALSE}. Set to \code{TRUE} to use \emph{Client Credentials Grant} instead of \emph{Authorization Code Grant}. See \url{https://tools.ietf.org/html/rfc6749#section-4.4}.} \item{credentials}{Advanced use only: allows you to completely customise token generation.} \item{query_authorize_extra}{Default to \code{list()}. Set to named list holding query parameters to append to initial auth page query. Useful for some APIs.} } \value{ A \code{Token2.0} reference class (RC) object. } \description{ This is the final object in the OAuth dance - it encapsulates the app, the endpoint, other parameters and the received credentials. It is a reference class so that it can be seamlessly updated (e.g. using \code{$refresh()}) when access expires. } \details{ See \code{\link[=Token]{Token()}} for full details about the token object, and the caching policies used to store credentials across sessions. } \seealso{ Other OAuth: \code{\link{oauth1.0_token}}, \code{\link{oauth_app}}, \code{\link{oauth_endpoint}}, \code{\link{oauth_service_token}} } \concept{OAuth} httr/man/hmac_sha1.Rd0000644000176200001440000000053113376014735014131 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/sha1.r \name{hmac_sha1} \alias{hmac_sha1} \title{HMAC SHA1} \usage{ hmac_sha1(key, string) } \arguments{ \item{key}{secret key} \item{string}{data to securely hash} } \description{ As described in \url{http://datatracker.ietf.org/doc/rfc2104/}. } \keyword{internal} httr/man/oauth_app.Rd0000644000176200001440000000333113401521062014247 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-app.r \name{oauth_app} \alias{oauth_app} \title{Create an OAuth application.} \usage{ oauth_app(appname, key, secret = NULL, redirect_uri = oauth_callback()) } \arguments{ \item{appname}{name of the application. This is not used for OAuth, but is used to make it easier to identify different applications.} \item{key}{consumer key, also sometimes called the client ID} \item{secret}{consumer secret, also sometimes called the client secret. Despite its name, this does not necessarily need to be protected like a password, i.e. the user still has to authenticate themselves and grant the app permission to access resources on their behalf. For example, see Google's docs for \href{https://developers.google.com/identity/protocols/OAuth2#installed}{OAuth2 for installed applications}.} \item{redirect_uri}{The URL that user will be redirected to after authorisation is complete. You should generally leave this as the default unless you're using a non-standard auth flow (like with shiny).} } \description{ See the demos for instructions on how to create an OAuth app for linkedin, twitter, vimeo, facebook, github and google. When wrapping an API from a package, the author may want to include a default app to facilitate early and casual use and then provide a method for heavy or advanced users to supply their own app or key and secret. } \examples{ \dontrun{ google_app <- oauth_app( "google", key = "123456789.apps.googleusercontent.com", secret = "abcdefghijklmnopqrstuvwxyz" ) } } \seealso{ Other OAuth: \code{\link{oauth1.0_token}}, \code{\link{oauth2.0_token}}, \code{\link{oauth_endpoint}}, \code{\link{oauth_service_token}} } \concept{OAuth} httr/man/write_stream.Rd0000644000176200001440000000136113401555510015002 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/write-function.R \name{write_stream} \alias{write_stream} \title{Process output in a streaming manner.} \usage{ write_stream(f) } \arguments{ \item{f}{Callback function. It should have a single argument, a raw vector containing the bytes recieved from the server. This will usually be 16k or less. The return value of the function is ignored.} } \description{ This is the most general way of processing the response from the server - you receive the raw bytes as they come in, and you can do whatever you want with them. } \examples{ GET( "https://github.com/jeroen/data/raw/gh-pages/diamonds.json", write_stream(function(x) { print(length(x)) length(x) }) ) } httr/man/oauth_endpoint.Rd0000644000176200001440000000256113401555510015320 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-endpoint.r \name{oauth_endpoint} \alias{oauth_endpoint} \title{Describe an OAuth endpoint.} \usage{ oauth_endpoint(request = NULL, authorize, access, ..., base_url = NULL) } \arguments{ \item{request}{url used to request initial (unauthenticated) token. If using OAuth2.0, leave as \code{NULL}.} \item{authorize}{url to send client to for authorisation. Set to \code{NULL} if not needed} \item{access}{url used to exchange unauthenticated for authenticated token.} \item{...}{other additional endpoints.} \item{base_url}{option url to use as base for \code{request}, \code{authorize} and \code{access} urls.} } \description{ See \code{\link[=oauth_endpoints]{oauth_endpoints()}} for a list of popular OAuth endpoints baked into httr. } \examples{ linkedin <- oauth_endpoint("requestToken", "authorize", "accessToken", base_url = "https://api.linkedin.com/uas/oauth" ) github <- oauth_endpoint(NULL, "authorize", "access_token", base_url = "https://github.com/login/oauth" ) facebook <- oauth_endpoint( authorize = "https://www.facebook.com/dialog/oauth", access = "https://graph.facebook.com/oauth/access_token" ) oauth_endpoints } \seealso{ Other OAuth: \code{\link{oauth1.0_token}}, \code{\link{oauth2.0_token}}, \code{\link{oauth_app}}, \code{\link{oauth_service_token}} } \concept{OAuth} httr/man/write_function.Rd0000644000176200001440000000072613376014735015352 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/write-function.R \name{write_function} \alias{write_function} \title{S3 object to define response writer.} \usage{ write_function(subclass, ...) } \arguments{ \item{subclass, ...}{Class name and fields. Used in class constructors.} \item{x}{A \code{write_function} object to process.} } \description{ This S3 object allows you to control how the response body is saved. } \keyword{internal} httr/man/status_code.Rd0000644000176200001440000000044113376014735014622 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/response-status.r \name{status_code} \alias{status_code} \title{Extract status code from response.} \usage{ status_code(x) } \arguments{ \item{x}{A response} } \description{ Extract status code from response. } httr/man/oauth_exchanger.Rd0000644000176200001440000000142013401555510015435 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-exchanger.r \name{oauth_exchanger} \alias{oauth_exchanger} \title{Walk the user through the OAuth2 dance without a local webserver.} \usage{ oauth_exchanger(request_url) } \arguments{ \item{request_url}{the url to provide to the user} } \description{ This performs a similar function to \code{\link[=oauth_listener]{oauth_listener()}}, but without running a local webserver. This manual process can be useful in situations where the user is remotely accessing the machine outside a browser (say via ssh) or when it's not possible to successfully receive a callback (such as when behind a firewall). } \details{ This function should generally not be called directly by the user. } \keyword{internal} httr/man/parse_http_date.Rd0000644000176200001440000000211013401555510015434 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/date.R \name{parse_http_date} \alias{parse_http_date} \alias{http_date} \title{Parse and print http dates.} \usage{ parse_http_date(x, failure = structure(NA_real_, class = "Date")) http_date(x) } \arguments{ \item{x}{For \code{parse_http_date}, a character vector of strings to parse. All elements must be of the same type. For \code{http_date}, a \code{POSIXt} vector.} \item{failure}{What to return on failure?} } \value{ A POSIXct object if succesful, otherwise \code{failure} } \description{ As defined in RFC2616, \url{http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3}, there are three valid formats: \itemize{ \item Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 \item Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 \item Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format } } \examples{ parse_http_date("Sun, 06 Nov 1994 08:49:37 GMT") parse_http_date("Sunday, 06-Nov-94 08:49:37 GMT") parse_http_date("Sun Nov 6 08:49:37 1994") http_date(Sys.time()) } httr/man/init_oauth2.0.Rd0000644000176200001440000000515413401555510014664 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-init.R \name{init_oauth2.0} \alias{init_oauth2.0} \alias{oauth2.0_authorize_url} \alias{oauth2.0_access_token} \title{Retrieve OAuth 2.0 access token.} \usage{ init_oauth2.0(endpoint, app, scope = NULL, user_params = NULL, type = NULL, use_oob = getOption("httr_oob_default"), oob_value = NULL, is_interactive = interactive(), use_basic_auth = FALSE, config_init = list(), client_credentials = FALSE, query_authorize_extra = list()) oauth2.0_authorize_url(endpoint, app, scope, redirect_uri = app$redirect_uri, state = nonce(), query_extra = list()) oauth2.0_access_token(endpoint, app, code, user_params = NULL, type = NULL, use_basic_auth = FALSE, redirect_uri = app$redirect_uri, client_credentials = FALSE, config = list()) } \arguments{ \item{endpoint}{An OAuth endpoint, created by \code{\link[=oauth_endpoint]{oauth_endpoint()}}} \item{app}{An OAuth consumer application, created by \code{\link[=oauth_app]{oauth_app()}}} \item{scope}{a character vector of scopes to request.} \item{user_params}{Named list holding endpoint specific parameters to pass to the server when posting the request for obtaining or refreshing the access token.} \item{type}{content type used to override incorrect server response} \item{use_oob}{if FALSE, use a local webserver for the OAuth dance. Otherwise, provide a URL to the user and prompt for a validation code. Defaults to the of the \code{"httr_oob_default"} default, or \code{TRUE} if \code{httpuv} is not installed.} \item{oob_value}{if provided, specifies the value to use for the redirect_uri parameter when retrieving an authorization URL. Defaults to "urn:ietf:wg:oauth:2.0:oob". Requires \code{use_oob = TRUE}.} \item{is_interactive}{DEPRECATED} \item{use_basic_auth}{if \code{TRUE} use http basic authentication to retrieve the token. Some authorization servers require this. If \code{FALSE}, the default, retrieve the token by including the app key and secret in the request body.} \item{config_init}{Additional configuration settings sent to \code{\link[=POST]{POST()}}, e.g. \code{\link[=user_agent]{user_agent()}}.} \item{client_credentials}{Default to \code{FALSE}. Set to \code{TRUE} to use \emph{Client Credentials Grant} instead of \emph{Authorization Code Grant}. See \url{https://tools.ietf.org/html/rfc6749#section-4.4}.} \item{query_authorize_extra}{Default to \code{list()}. Set to named list holding query parameters to append to initial auth page query. Useful for some APIs.} \item{query_extra}{See \code{query_authorize_extra}} } \description{ See demos for use. } \keyword{internal} httr/man/cache_info.Rd0000644000176200001440000000211413376014735014362 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cache.R \name{cache_info} \alias{cache_info} \alias{rerequest} \title{Compute caching information for a response.} \usage{ cache_info(r) rerequest(r) } \arguments{ \item{r}{A response} } \description{ \code{cache_info()} gives details of cacheability of a response, \code{rerequest()} re-performs the original request doing as little work as possible (if not expired, returns response as is, or performs revalidation if Etag or Last-Modified headers are present). } \examples{ # Never cached, always causes redownload r1 <- GET("https://www.google.com") cache_info(r1) r1$date rerequest(r1)$date # Expires in a year r2 <- GET("https://www.google.com/images/srpr/logo11w.png") cache_info(r2) r2$date rerequest(r2)$date # Has last-modified and etag, so does revalidation r3 <- GET("http://httpbin.org/cache") cache_info(r3) r3$date rerequest(r3)$date # Expires after 5 seconds \dontrun{ r4 <- GET("http://httpbin.org/cache/5") cache_info(r4) r4$date rerequest(r4)$date Sys.sleep(5) cache_info(r4) rerequest(r4)$date } } httr/man/handle.Rd0000644000176200001440000000264113401555510013532 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/handle.r \name{handle} \alias{handle} \title{Create a handle tied to a particular host.} \usage{ handle(url, cookies = TRUE) } \arguments{ \item{url}{full url to site} \item{cookies}{DEPRECATED} } \description{ This handle preserves settings and cookies across multiple requests. It is the foundation of all requests performed through the httr package, although it will mostly be hidden from the user. } \note{ Because of the way argument dispatch works in R, using handle() in the http methods (See \code{\link[=GET]{GET()}}) will cause problems when trying to pass configuration arguments (See examples below). Directly specifying the handle when using http methods is not recommended in general, since the selection of the correct handle is taken care of when the user passes an url (See \code{\link[=handle_pool]{handle_pool()}}). } \examples{ handle("http://google.com") handle("https://google.com") h <- handle("http://google.com") GET(handle = h) # Should see cookies sent back to server GET(handle = h, config = verbose()) h <- handle("http://google.com", cookies = FALSE) GET(handle = h)$cookies \dontrun{ # Using the preferred way of configuring the http methods # will not work when using handle(): GET(handle = h, timeout(10)) # Passing named arguments will work properly: GET(handle = h, config = list(timeout(10), add_headers(Accept = ""))) } } httr/man/insensitive.Rd0000644000176200001440000000066513376014735014655 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/insensitive.r \name{insensitive} \alias{insensitive} \title{Create a vector with case insensitive name matching.} \usage{ insensitive(x) } \arguments{ \item{x}{vector to modify} } \description{ Create a vector with case insensitive name matching. } \examples{ x <- c("abc" = 1, "def" = 2) x["ABC"] y <- insensitive(x) y["ABC"] y[["ABC"]] } \keyword{internal} httr/man/stop_for_status.Rd0000644000176200001440000000340713401555510015536 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/response-status.r \name{stop_for_status} \alias{stop_for_status} \alias{warn_for_status} \alias{message_for_status} \title{Take action on http error.} \usage{ stop_for_status(x, task = NULL) warn_for_status(x, task = NULL) message_for_status(x, task = NULL) } \arguments{ \item{x}{a response, or numeric http code (or other object with \code{status_code} method)} \item{task}{The text of the message: either \code{NULL} or a character vector. If non-\code{NULL}, the error message will finish with "Failed to \code{task}".} } \value{ If request was successful, the response (invisibly). Otherwise, raised a classed http error or warning, as generated by \code{\link[=http_condition]{http_condition()}} } \description{ Converts http errors to R errors or warnings - these should always be used whenever you're creating requests inside a function, so that the user knows why a request has failed. } \examples{ x <- GET("http://httpbin.org/status/200") stop_for_status(x) # nothing happens warn_for_status(x) message_for_status(x) x <- GET("http://httpbin.org/status/300") \dontrun{ stop_for_status(x) } warn_for_status(x) message_for_status(x) x <- GET("http://httpbin.org/status/404") \dontrun{ stop_for_status(x) } warn_for_status(x) message_for_status(x) # You can provide more information with the task argument warn_for_status(x, "download spreadsheet") message_for_status(x, "download spreadsheet") } \seealso{ \code{\link[=http_status]{http_status()}} and \code{http://en.wikipedia.org/wiki/Http_status_codes} for more information on http status codes. Other response methods: \code{\link{content}}, \code{\link{http_error}}, \code{\link{http_status}}, \code{\link{response}} } \concept{response methods} httr/man/parse_url.Rd0000644000176200001440000000161113401555510014267 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/url.r \name{parse_url} \alias{parse_url} \alias{build_url} \title{Parse and build urls according to RFC1808.} \usage{ parse_url(url) build_url(url) } \arguments{ \item{url}{For \code{parse_url} a character vector (of length 1) to parse into components; for \code{build_url} a list of components to turn back into a string.} } \value{ a list containing: \itemize{ \item scheme \item hostname \item port \item path \item params \item fragment \item query, a list \item username \item password } } \description{ See \url{http://tools.ietf.org/html/rfc1808.html} for details of parsing algorithm. } \examples{ parse_url("http://google.com/") parse_url("http://google.com:80/") parse_url("http://google.com:80/?a=1&b=2") url <- parse_url("http://google.com/") url$scheme <- "https" url$query <- list(q = "hello") build_url(url) } httr/man/content.Rd0000644000176200001440000000626613401555510013760 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/content.r \name{content} \alias{content} \alias{text_content} \alias{parsed_content} \title{Extract content from a request.} \usage{ content(x, as = NULL, type = NULL, encoding = NULL, ...) } \arguments{ \item{x}{request object} \item{as}{desired type of output: \code{raw}, \code{text} or \code{parsed}. \code{content} attempts to automatically figure out which one is most appropriate, based on the content-type.} \item{type}{MIME type (aka internet media type) used to override the content type returned by the server. See \url{http://en.wikipedia.org/wiki/Internet_media_type} for a list of common types.} \item{encoding}{For text, overrides the charset or the Latin1 (ISO-8859-1) default, if you know that the server is returning the incorrect encoding as the charset in the content-type. Use for text and parsed outputs.} \item{...}{Other parameters parsed on to the parsing functions, if \code{as = "parsed"}} } \value{ For "raw", a raw vector. For "text", a character vector of length 1. The character vector is always re-encoded to UTF-8. If this encoding fails (usually because the page declares an incorrect encoding), \code{content()} will return \code{NA}. For "auto", a parsed R object. } \description{ There are currently three ways to retrieve the contents of a request: as a raw object (\code{as = "raw"}), as a character vector, (\code{as = "text"}), and as parsed into an R object where possible, (\code{as = "parsed"}). If \code{as} is not specified, \code{content} does its best to guess which output is most appropriate. } \details{ \code{content} currently knows about the following mime types: \itemize{ \item \code{text/html}: \code{\link[xml2:read_html]{xml2::read_html()}} \item \code{text/xml}: \code{\link[xml2:read_xml]{xml2::read_xml()}} \item \code{text/csv}: \code{\link[readr:read_csv]{readr::read_csv()}} \item \code{text/tab-separated-values}: \code{\link[readr:read_tsv]{readr::read_tsv()}} \item \code{application/json}: \code{\link[jsonlite:fromJSON]{jsonlite::fromJSON()}} \item \code{application/x-www-form-urlencoded}: \code{parse_query} \item \code{image/jpeg}: \code{\link[jpeg:readJPEG]{jpeg::readJPEG()}} \item \code{image/png}: \code{\link[png:readPNG]{png::readPNG()}} } \code{as = "parsed"} is provided as a convenience only: if the type you are trying to parse is not available, use \code{as = "text"} and parse yourself. } \section{WARNING}{ When using \code{content()} in a package, DO NOT use on \code{as = "parsed"}. Instead, check the mime-type is what you expect, and then parse yourself. This is safer, as you will fail informatively if the API changes, and you will protect yourself against changes to httr. } \examples{ r <- POST("http://httpbin.org/post", body = list(a = 1, b = 2)) content(r) # automatically parses JSON cat(content(r, "text"), "\\n") # text content content(r, "raw") # raw bytes from server rlogo <- content(GET("http://cran.r-project.org/Rlogo.jpg")) plot(0:1, 0:1, type = "n") rasterImage(rlogo, 0, 0, 1, 1) } \seealso{ Other response methods: \code{\link{http_error}}, \code{\link{http_status}}, \code{\link{response}}, \code{\link{stop_for_status}} } \concept{response methods} httr/man/progress.Rd0000644000176200001440000000136013401555510014140 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/progress.R \name{progress} \alias{progress} \title{Add a progress bar.} \usage{ progress(type = c("down", "up"), con = stdout()) } \arguments{ \item{type}{Type of progress to display: either number of bytes uploaded or downloaded.} \item{con}{Connection to send output too. Usually \code{stdout()} or \code{stderr}.} } \description{ Add a progress bar. } \examples{ cap_speed <- config(max_recv_speed_large = 10000) \donttest{ # If file size is known, you get a progress bar: x <- GET("http://httpbin.org/bytes/102400", progress(), cap_speed) # Otherwise you get the number of bytes downloaded: x <- GET("http://httpbin.org/stream-bytes/102400", progress(), cap_speed) } } httr/man/sha1_hash.Rd0000644000176200001440000000067213376014735014152 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/sha1.r \name{sha1_hash} \alias{sha1_hash} \title{SHA1 hash} \usage{ sha1_hash(key, string, method = "HMAC-SHA1") } \arguments{ \item{key}{The key to create the hash with} \item{string}{data to securely hash} \item{method}{The method to use, either HMAC-SHA1 or RSA-SHA1} } \description{ Creates a SHA1 hash of data using either HMAC or RSA. } \keyword{internal} httr/man/oauth_callback.Rd0000644000176200001440000000051713401555510015233 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-listener.r \name{oauth_callback} \alias{oauth_callback} \title{The oauth callback url.} \usage{ oauth_callback() } \description{ The url that \code{\link[=oauth_listener]{oauth_listener()}} expects that the client be referred to. } \keyword{internal} httr/man/VERB.Rd0000644000176200001440000000633513401555510013041 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/http-verb.R \name{VERB} \alias{VERB} \title{VERB a url.} \usage{ VERB(verb, url = NULL, config = list(), ..., body = NULL, encode = c("multipart", "form", "json", "raw"), handle = NULL) } \arguments{ \item{verb}{Name of verb to use.} \item{url}{the url of the page to retrieve} \item{config}{Additional configuration settings such as http authentication (\code{\link[=authenticate]{authenticate()}}), additional headers (\code{\link[=add_headers]{add_headers()}}), cookies (\code{\link[=set_cookies]{set_cookies()}}) etc. See \code{\link[=config]{config()}} for full details and list of helpers.} \item{...}{Further named parameters, such as \code{query}, \code{path}, etc, passed on to \code{\link[=modify_url]{modify_url()}}. Unnamed parameters will be combined with \code{\link[=config]{config()}}.} \item{body}{One of the following: \itemize{ \item \code{FALSE}: No body. This is typically not used with \code{POST}, \code{PUT}, or \code{PATCH}, but can be useful if you need to send a bodyless request (like \code{GET}) with \code{VERB()}. \item \code{NULL}: An empty body \item \code{""}: A length 0 body \item \code{upload_file("path/")}: The contents of a file. The mime type will be guessed from the extension, or can be supplied explicitly as the second argument to \code{upload_file()} \item A character or raw vector: sent as is in body. Use \code{\link[=content_type]{content_type()}} to tell the server what sort of data you are sending. \item A named list: See details for encode. }} \item{encode}{If the body is a named list, how should it be encoded? Can be one of form (application/x-www-form-urlencoded), multipart, (multipart/form-data), or json (application/json). For "multipart", list elements can be strings or objects created by \code{\link[=upload_file]{upload_file()}}. For "form", elements are coerced to strings and escaped, use \code{I()} to prevent double-escaping. For "json", parameters are automatically "unboxed" (i.e. length 1 vectors are converted to scalars). To preserve a length 1 vector as a vector, wrap in \code{I()}. For "raw", either a character or raw vector. You'll need to make sure to set the \code{\link[=content_type]{content_type()}} yourself.} \item{handle}{The handle to use with this request. If not supplied, will be retrieved and reused from the \code{\link[=handle_pool]{handle_pool()}} based on the scheme, hostname and port of the url. By default \pkg{httr} requests to the same scheme/host/port combo. This substantially reduces connection time, and ensures that cookies are maintained over multiple requests to the same host. See \code{\link[=handle_pool]{handle_pool()}} for more details.} } \value{ A \code{\link[=response]{response()}} object. } \description{ Use an arbitrary verb. } \examples{ r <- VERB( "PROPFIND", "http://svn.r-project.org/R/tags/", add_headers(depth = 1), verbose() ) stop_for_status(r) content(r) VERB("POST", url = "http://httpbin.org/post") VERB("POST", url = "http://httpbin.org/post", body = "foobar") } \seealso{ Other http methods: \code{\link{BROWSE}}, \code{\link{DELETE}}, \code{\link{GET}}, \code{\link{HEAD}}, \code{\link{PATCH}}, \code{\link{POST}}, \code{\link{PUT}} } \concept{http methods} httr/man/timeout.Rd0000644000176200001440000000125013401521062013753 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/timeout.r \name{timeout} \alias{timeout} \title{Set maximum request time.} \usage{ timeout(seconds) } \arguments{ \item{seconds}{number of seconds to wait for a response until giving up. Can not be less than 1 ms.} } \description{ Set maximum request time. } \examples{ \dontrun{ GET("http://httpbin.org/delay/3", timeout(1)) GET("http://httpbin.org/delay/1", timeout(2)) } } \seealso{ Other config: \code{\link{add_headers}}, \code{\link{authenticate}}, \code{\link{config}}, \code{\link{set_cookies}}, \code{\link{use_proxy}}, \code{\link{user_agent}}, \code{\link{verbose}} } \concept{config} httr/man/http_type.Rd0000644000176200001440000000105013401555510014310 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/response-type.R \name{http_type} \alias{http_type} \title{Extract the content type of a response} \usage{ http_type(x) } \arguments{ \item{x}{A response} } \value{ A string giving the complete mime type, with all parameters stripped off. } \description{ Extract the content type of a response } \examples{ r1 <- GET("http://httpbin.org/image/png") http_type(r1) headers(r1)[["Content-Type"]] r2 <- GET("http://httpbin.org/ip") http_type(r2) headers(r2)[["Content-Type"]] } httr/man/set_config.Rd0000644000176200001440000000162213401555510014415 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/config.r \name{set_config} \alias{set_config} \alias{reset_config} \title{Set (and reset) global httr configuration.} \usage{ set_config(config, override = FALSE) reset_config() } \arguments{ \item{config}{Settings as generated by \code{\link[=add_headers]{add_headers()}}, \code{\link[=set_cookies]{set_cookies()}} or \code{\link[=authenticate]{authenticate()}}.} \item{override}{if \code{TRUE}, ignore existing settings, if \code{FALSE}, combine new config with old.} } \value{ invisibility, the old global config. } \description{ Set (and reset) global httr configuration. } \examples{ GET("http://google.com") set_config(verbose()) GET("http://google.com") reset_config() GET("http://google.com") } \seealso{ Other ways to set configuration: \code{\link{config}}, \code{\link{with_config}} } \concept{ways to set configuration} httr/man/PUT.Rd0000644000176200001440000000632513401555510012752 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/http-put.r \name{PUT} \alias{PUT} \title{Send PUT request to server.} \usage{ PUT(url = NULL, config = list(), ..., body = NULL, encode = c("multipart", "form", "json", "raw"), handle = NULL) } \arguments{ \item{url}{the url of the page to retrieve} \item{config}{Additional configuration settings such as http authentication (\code{\link[=authenticate]{authenticate()}}), additional headers (\code{\link[=add_headers]{add_headers()}}), cookies (\code{\link[=set_cookies]{set_cookies()}}) etc. See \code{\link[=config]{config()}} for full details and list of helpers.} \item{...}{Further named parameters, such as \code{query}, \code{path}, etc, passed on to \code{\link[=modify_url]{modify_url()}}. Unnamed parameters will be combined with \code{\link[=config]{config()}}.} \item{body}{One of the following: \itemize{ \item \code{FALSE}: No body. This is typically not used with \code{POST}, \code{PUT}, or \code{PATCH}, but can be useful if you need to send a bodyless request (like \code{GET}) with \code{VERB()}. \item \code{NULL}: An empty body \item \code{""}: A length 0 body \item \code{upload_file("path/")}: The contents of a file. The mime type will be guessed from the extension, or can be supplied explicitly as the second argument to \code{upload_file()} \item A character or raw vector: sent as is in body. Use \code{\link[=content_type]{content_type()}} to tell the server what sort of data you are sending. \item A named list: See details for encode. }} \item{encode}{If the body is a named list, how should it be encoded? Can be one of form (application/x-www-form-urlencoded), multipart, (multipart/form-data), or json (application/json). For "multipart", list elements can be strings or objects created by \code{\link[=upload_file]{upload_file()}}. For "form", elements are coerced to strings and escaped, use \code{I()} to prevent double-escaping. For "json", parameters are automatically "unboxed" (i.e. length 1 vectors are converted to scalars). To preserve a length 1 vector as a vector, wrap in \code{I()}. For "raw", either a character or raw vector. You'll need to make sure to set the \code{\link[=content_type]{content_type()}} yourself.} \item{handle}{The handle to use with this request. If not supplied, will be retrieved and reused from the \code{\link[=handle_pool]{handle_pool()}} based on the scheme, hostname and port of the url. By default \pkg{httr} requests to the same scheme/host/port combo. This substantially reduces connection time, and ensures that cookies are maintained over multiple requests to the same host. See \code{\link[=handle_pool]{handle_pool()}} for more details.} } \description{ Send PUT request to server. } \examples{ POST("http://httpbin.org/put") PUT("http://httpbin.org/put") b2 <- "http://httpbin.org/put" PUT(b2, body = "A simple text string") PUT(b2, body = list(x = "A simple text string")) PUT(b2, body = list(y = upload_file(system.file("CITATION")))) PUT(b2, body = list(x = "A simple text string"), encode = "json") } \seealso{ Other http methods: \code{\link{BROWSE}}, \code{\link{DELETE}}, \code{\link{GET}}, \code{\link{HEAD}}, \code{\link{PATCH}}, \code{\link{POST}}, \code{\link{VERB}} } \concept{http methods} httr/man/response.Rd0000644000176200001440000000214013401555510014127 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/response.r \name{response} \alias{response} \title{The response object.} \description{ The response object captures all information from a request. It includes fields: \itemize{ \item \code{url} the url the request was actually sent to (after redirects) \item \code{handle} the handle associated with the url \item \code{status_code} the http status code \item \code{header} a named list of headers returned by the server \item \code{cookies} a named list of cookies returned by the server \item \code{content} the body of the response, as raw vector. See \code{\link[=content]{content()}} for various ways to access the content. \item \code{time} request timing information \item \code{config} configuration for the request } } \details{ For non-http(s) responses, some parts including the status and header may not be interpretable the same way as http responses. } \seealso{ Other response methods: \code{\link{content}}, \code{\link{http_error}}, \code{\link{http_status}}, \code{\link{stop_for_status}} } \concept{response methods} httr/man/add_headers.Rd0000644000176200001440000000232513401555510014521 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/headers.r \name{add_headers} \alias{add_headers} \title{Add additional headers to a request.} \usage{ add_headers(..., .headers = character()) } \arguments{ \item{...}{named header values. To stop an existing header from being set, pass an empty string: \code{""}.} \item{.headers}{a named character vector} } \description{ Wikipedia provides a useful list of common http headers: \url{http://en.wikipedia.org/wiki/List_of_HTTP_header_fields}. } \examples{ add_headers(a = 1, b = 2) add_headers(.headers = c(a = "1", b = "2")) GET("http://httpbin.org/headers") # Add arbitrary headers GET( "http://httpbin.org/headers", add_headers(version = version$version.string) ) # Override default headers with empty strings GET("http://httpbin.org/headers", add_headers(Accept = "")) } \seealso{ \code{\link[=accept]{accept()}} and \code{\link[=content_type]{content_type()}} for convenience functions for setting accept and content-type headers. Other config: \code{\link{authenticate}}, \code{\link{config}}, \code{\link{set_cookies}}, \code{\link{timeout}}, \code{\link{use_proxy}}, \code{\link{user_agent}}, \code{\link{verbose}} } \concept{config} httr/man/httr-package.Rd0000644000176200001440000000374713517044342014665 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/httr.r \docType{package} \name{httr-package} \alias{httr} \alias{httr-package} \title{\pkg{httr} makes http easy.} \description{ \code{httr} is organised around the six most common http verbs: \code{\link[=GET]{GET()}}, \code{\link[=PATCH]{PATCH()}}, \code{\link[=POST]{POST()}}, \code{\link[=HEAD]{HEAD()}}, \code{\link[=PUT]{PUT()}}, and \code{\link[=DELETE]{DELETE()}}. } \details{ Each request returns a \code{\link[=response]{response()}} object which provides easy access to status code, cookies, headers, timings, and other useful info. The content of the request is available as a raw vector (\code{\link[=content]{content()}}), character vector (\code{\link[=text_content]{text_content()}}), or parsed into an R object (\code{\link[=parsed_content]{parsed_content()}}), currently for html, xml, json, png and jpeg). Requests can be modified by various config options like \code{\link[=set_cookies]{set_cookies()}}, \code{\link[=add_headers]{add_headers()}}, \code{\link[=authenticate]{authenticate()}}, \code{\link[=use_proxy]{use_proxy()}}, \code{\link[=verbose]{verbose()}}, and \code{\link[=timeout]{timeout()}} httr supports OAuth 1.0 and 2.0. Use \code{\link[=oauth1.0_token]{oauth1.0_token()}} and \code{\link[=oauth2.0_token]{oauth2.0_token()}} to get user tokens, and \code{\link[=sign_oauth1.0]{sign_oauth1.0()}} and \code{\link[=sign_oauth2.0]{sign_oauth2.0()}} to sign requests. The demos directory has twelve demos of using OAuth: four for 1.0 (linkedin, twitter, vimeo, and yahoo) and eight for 2.0 (azure, facebook, github, google, linkedin, reddit, yahoo, and yelp). } \seealso{ Useful links: \itemize{ \item \url{https://httr.r-lib.org/} \item \url{https://github.com/r-lib/httr} \item Report bugs at \url{https://github.com/r-lib/httr/issues} } } \author{ \strong{Maintainer}: Hadley Wickham \email{hadley@rstudio.com} Other contributors: \itemize{ \item RStudio [copyright holder] } } \keyword{internal} httr/man/with_config.Rd0000644000176200001440000000202613401555510014574 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/config.r \name{with_config} \alias{with_config} \alias{with_verbose} \title{Execute code with configuration set.} \usage{ with_config(config = config(), expr, override = FALSE) with_verbose(expr, ...) } \arguments{ \item{config}{Settings as generated by \code{\link[=add_headers]{add_headers()}}, \code{\link[=set_cookies]{set_cookies()}} or \code{\link[=authenticate]{authenticate()}}.} \item{expr}{code to execute under specified configuration} \item{override}{if \code{TRUE}, ignore existing settings, if \code{FALSE}, combine new config with old.} \item{...}{Other arguments passed on to \code{\link[=verbose]{verbose()}}} } \description{ Execute code with configuration set. } \examples{ with_config(verbose(), { GET("http://had.co.nz") GET("http://google.com") }) # Or even easier: with_verbose(GET("http://google.com")) } \seealso{ Other ways to set configuration: \code{\link{config}}, \code{\link{set_config}} } \concept{ways to set configuration} httr/man/upload_file.Rd0000644000176200001440000000126213401555510014560 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/upload-file.r \name{upload_file} \alias{upload_file} \title{Upload a file with \code{\link[=POST]{POST()}} or \code{\link[=PUT]{PUT()}}.} \usage{ upload_file(path, type = NULL) } \arguments{ \item{path}{path to file} \item{type}{mime type of path. If not supplied, will be guess by \code{\link[mime:guess_type]{mime::guess_type()}} when needed.} } \description{ Upload a file with \code{\link[=POST]{POST()}} or \code{\link[=PUT]{PUT()}}. } \examples{ citation <- upload_file(system.file("CITATION")) POST("http://httpbin.org/post", body = citation) POST("http://httpbin.org/post", body = list(y = citation)) } httr/man/oauth_signature.Rd0000644000176200001440000000157113401555510015501 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-signature.r \name{oauth_signature} \alias{oauth_signature} \alias{oauth_header} \title{Generate oauth signature.} \usage{ oauth_signature(url, method = "GET", app, token = NULL, token_secret = NULL, private_key = NULL, other_params = NULL) oauth_header(info) } \arguments{ \item{url, method}{Url and http method of request.} \item{app}{\code{\link[=oauth_app]{oauth_app()}} object representing application.} \item{token, token_secret}{OAuth token and secret.} \item{other_params}{Named argument providing additional parameters (e.g. \code{oauth_callback} or \code{oauth_body_hash}).} } \value{ A list of oauth parameters. } \description{ For advanced use only. Occassionally needed for sites that use some components of the OAuth spec, but not all of them (e.g. 2-legged oauth) } \keyword{internal} httr/man/guess_media.Rd0000644000176200001440000000052713376014735014577 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/media-guess.r \name{guess_media} \alias{guess_media} \title{Guess the media type of a path from its extension.} \usage{ guess_media(x) } \arguments{ \item{x}{path to file} } \description{ DEPRECATED: please use \code{mime::guess_type} instead. } \keyword{internal} httr/man/oauth_listener.Rd0000644000176200001440000000165213401555510015325 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-listener.r \name{oauth_listener} \alias{oauth_listener} \title{Create a webserver to listen for OAuth callback.} \usage{ oauth_listener(request_url, is_interactive = interactive()) } \arguments{ \item{request_url}{the url to send the browser to} \item{is_interactive}{DEPRECATED} \item{host}{ip address for the listener} \item{port}{for the listener} } \description{ This opens a web browser pointing to \code{request_url}, and opens a webserver on port 1410 to listen to the reponse. The redirect url should either be set previously (during the OAuth authentication dance) or supplied as a parameter to the url. See \code{\link[=oauth1.0_token]{oauth1.0_token()}} and \code{\link[=oauth2.0_token]{oauth2.0_token()}} for examples of both techniques. } \details{ This function should not normally be called directly by the user. } \keyword{internal} httr/man/RETRY.Rd0000644000176200001440000001207213401555510013203 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/retry.R \name{RETRY} \alias{RETRY} \title{Retry a request until it succeeds.} \usage{ RETRY(verb, url = NULL, config = list(), ..., body = NULL, encode = c("multipart", "form", "json", "raw"), times = 3, pause_base = 1, pause_cap = 60, pause_min = 1, handle = NULL, quiet = FALSE, terminate_on = NULL, terminate_on_success = TRUE) } \arguments{ \item{verb}{Name of verb to use.} \item{url}{the url of the page to retrieve} \item{config}{Additional configuration settings such as http authentication (\code{\link[=authenticate]{authenticate()}}), additional headers (\code{\link[=add_headers]{add_headers()}}), cookies (\code{\link[=set_cookies]{set_cookies()}}) etc. See \code{\link[=config]{config()}} for full details and list of helpers.} \item{...}{Further named parameters, such as \code{query}, \code{path}, etc, passed on to \code{\link[=modify_url]{modify_url()}}. Unnamed parameters will be combined with \code{\link[=config]{config()}}.} \item{body}{One of the following: \itemize{ \item \code{FALSE}: No body. This is typically not used with \code{POST}, \code{PUT}, or \code{PATCH}, but can be useful if you need to send a bodyless request (like \code{GET}) with \code{VERB()}. \item \code{NULL}: An empty body \item \code{""}: A length 0 body \item \code{upload_file("path/")}: The contents of a file. The mime type will be guessed from the extension, or can be supplied explicitly as the second argument to \code{upload_file()} \item A character or raw vector: sent as is in body. Use \code{\link[=content_type]{content_type()}} to tell the server what sort of data you are sending. \item A named list: See details for encode. }} \item{encode}{If the body is a named list, how should it be encoded? Can be one of form (application/x-www-form-urlencoded), multipart, (multipart/form-data), or json (application/json). For "multipart", list elements can be strings or objects created by \code{\link[=upload_file]{upload_file()}}. For "form", elements are coerced to strings and escaped, use \code{I()} to prevent double-escaping. For "json", parameters are automatically "unboxed" (i.e. length 1 vectors are converted to scalars). To preserve a length 1 vector as a vector, wrap in \code{I()}. For "raw", either a character or raw vector. You'll need to make sure to set the \code{\link[=content_type]{content_type()}} yourself.} \item{times}{Maximum number of requests to attempt.} \item{pause_base, pause_cap}{This method uses exponential back-off with full jitter - this means that each request will randomly wait between 0 and \code{pause_base * 2 ^ attempt} seconds, up to a maximum of \code{pause_cap} seconds.} \item{pause_min}{Minimum time to wait in the backoff; generally only necessary if you need pauses less than one second (which may not be kind to the server, use with caution!).} \item{handle}{The handle to use with this request. If not supplied, will be retrieved and reused from the \code{\link[=handle_pool]{handle_pool()}} based on the scheme, hostname and port of the url. By default \pkg{httr} requests to the same scheme/host/port combo. This substantially reduces connection time, and ensures that cookies are maintained over multiple requests to the same host. See \code{\link[=handle_pool]{handle_pool()}} for more details.} \item{quiet}{If \code{FALSE}, will print a message displaying how long until the next request.} \item{terminate_on}{Optional vector of numeric HTTP status codes that if found on the response will terminate the retry process. If \code{NULL}, will keep retrying while \code{\link[=http_error]{http_error()}} is \code{TRUE} for the response.} \item{terminate_on_success}{If \code{TRUE}, the default, this will automatically terminate when the request is successful, regardless of the value of \code{terminate_on}.} } \value{ The last response. Note that if the request doesn't succeed after \code{times} times this will be a failed request, i.e. you still need to use \code{\link[=stop_for_status]{stop_for_status()}}. } \description{ Safely retry a request until it succeeds, as defined by the \code{terminate_on} parameter, which by default means a response for which \code{\link[=http_error]{http_error()}} is \code{FALSE}. Will also retry on error conditions raised by the underlying curl code, but if the last retry still raises one, \code{RETRY} will raise it again with \code{\link[=stop]{stop()}}. It is designed to be kind to the server: after each failure randomly waits up to twice as long. (Technically it uses exponential backoff with jitter, using the approach outlined in \url{https://www.awsarchitectureblog.com/2015/03/backoff.html}.) If the server returns status code 429 and specifies a \code{retry-after} value, that value will be used instead, unless it's smaller than \code{pause_min}. } \examples{ # Succeeds straight away RETRY("GET", "http://httpbin.org/status/200") # Never succeeds RETRY("GET", "http://httpbin.org/status/500") \donttest{ # Invalid hostname generates curl error condition and is retried but eventually # raises an error condition. RETRY("GET", "http://invalidhostname/") } } httr/man/write_disk.Rd0000644000176200001440000000173013401555510014441 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/write-function.R \name{write_disk} \alias{write_disk} \alias{write_memory} \title{Control where the response body is written.} \usage{ write_disk(path, overwrite = FALSE) write_memory() } \arguments{ \item{path}{Path to content to.} \item{overwrite}{Will only overwrite existing \code{path} if TRUE.} } \description{ The default behaviour is to use \code{write_memory()}, which caches the response locally in memory. This is useful when talking to APIs as it avoids a round-trip to disk. If you want to save a file that's bigger than memory, use \code{write_disk()} to save it to a known path. } \examples{ tmp <- tempfile() r1 <- GET("https://www.google.com", write_disk(tmp)) readLines(tmp) # The default r2 <- GET("https://www.google.com", write_memory()) # Save a very large file \dontrun{ GET( "http://www2.census.gov/acs2011_5yr/pums/csv_pus.zip", write_disk("csv_pus.zip"), progress() ) } } httr/man/http_error.Rd0000644000176200001440000000176713401555510014477 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/response-status.r \name{http_error} \alias{http_error} \alias{url_success} \alias{url_ok} \title{Check for an http error.} \usage{ http_error(x, ...) } \arguments{ \item{x}{Object to check. Default methods are provided for strings (which perform an \code{\link[=HEAD]{HEAD()}} request), responses, and integer status codes.} \item{...}{Other arguments passed on to methods.} } \value{ \code{TRUE} if the request fails (status code 400 or above), otherwise \code{FALSE}. } \description{ Check for an http error. } \examples{ # You can pass a url: http_error("http://www.google.com") http_error("http://httpbin.org/status/404") # Or a request r <- GET("http://httpbin.org/status/201") http_error(r) # Or an (integer) status code http_error(200L) http_error(404L) } \seealso{ Other response methods: \code{\link{content}}, \code{\link{http_status}}, \code{\link{response}}, \code{\link{stop_for_status}} } \concept{response methods} httr/man/GET.Rd0000644000176200001440000000671313401555510012722 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/http-get.r \name{GET} \alias{GET} \title{GET a url.} \usage{ GET(url = NULL, config = list(), ..., handle = NULL) } \arguments{ \item{url}{the url of the page to retrieve} \item{config}{Additional configuration settings such as http authentication (\code{\link[=authenticate]{authenticate()}}), additional headers (\code{\link[=add_headers]{add_headers()}}), cookies (\code{\link[=set_cookies]{set_cookies()}}) etc. See \code{\link[=config]{config()}} for full details and list of helpers.} \item{...}{Further named parameters, such as \code{query}, \code{path}, etc, passed on to \code{\link[=modify_url]{modify_url()}}. Unnamed parameters will be combined with \code{\link[=config]{config()}}.} \item{handle}{The handle to use with this request. If not supplied, will be retrieved and reused from the \code{\link[=handle_pool]{handle_pool()}} based on the scheme, hostname and port of the url. By default \pkg{httr} requests to the same scheme/host/port combo. This substantially reduces connection time, and ensures that cookies are maintained over multiple requests to the same host. See \code{\link[=handle_pool]{handle_pool()}} for more details.} } \value{ A \code{\link[=response]{response()}} object. } \description{ GET a url. } \section{RFC2616}{ The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI. If the Request-URI refers to a data-producing process, it is the produced data which shall be returned as the entity in the response and not the source text of the process, unless that text happens to be the output of the process. The semantics of the GET method change to a "conditional GET" if the request message includes an If-Modified-Since, If-Unmodified-Since, If-Match, If-None-Match, or If-Range header field. A conditional GET method requests that the entity be transferred only under the circumstances described by the conditional header field(s). The conditional GET method is intended to reduce unnecessary network usage by allowing cached entities to be refreshed without requiring multiple requests or transferring data already held by the client. The semantics of the GET method change to a "partial GET" if the request message includes a Range header field. A partial GET requests that only part of the entity be transferred, as described in \url{http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35} The partial GET method is intended to reduce unnecessary network usage by allowing partially-retrieved entities to be completed without transferring data already held by the client. } \examples{ GET("http://google.com/") GET("http://google.com/", path = "search") GET("http://google.com/", path = "search", query = list(q = "ham")) # See what GET is doing with httpbin.org url <- "http://httpbin.org/get" GET(url) GET(url, add_headers(a = 1, b = 2)) GET(url, set_cookies(a = 1, b = 2)) GET(url, add_headers(a = 1, b = 2), set_cookies(a = 1, b = 2)) GET(url, authenticate("username", "password")) GET(url, verbose()) # You might want to manually specify the handle so you can have multiple # independent logins to the same website. google <- handle("http://google.com") GET(handle = google, path = "/") GET(handle = google, path = "search") } \seealso{ Other http methods: \code{\link{BROWSE}}, \code{\link{DELETE}}, \code{\link{HEAD}}, \code{\link{PATCH}}, \code{\link{POST}}, \code{\link{PUT}}, \code{\link{VERB}} } \concept{http methods} httr/man/use_proxy.Rd0000644000176200001440000000167213401521062014332 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/proxy.r \name{use_proxy} \alias{use_proxy} \title{Use a proxy to connect to the internet.} \usage{ use_proxy(url, port = NULL, username = NULL, password = NULL, auth = "basic") } \arguments{ \item{url, port}{location of proxy} \item{username, password}{login details for proxy, if needed} \item{auth}{type of HTTP authentication to use. Should be one of the following: basic, digest, digest_ie, gssnegotiate, ntlm, any.} } \description{ Use a proxy to connect to the internet. } \examples{ # See http://www.hidemyass.com/proxy-list for a list of public proxies # to test with # GET("http://had.co.nz", use_proxy("64.251.21.73", 8080), verbose()) } \seealso{ Other config: \code{\link{add_headers}}, \code{\link{authenticate}}, \code{\link{config}}, \code{\link{set_cookies}}, \code{\link{timeout}}, \code{\link{user_agent}}, \code{\link{verbose}} } \concept{config} httr/man/oauth_endpoints.Rd0000644000176200001440000000064313376014735015514 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-endpoint.r \name{oauth_endpoints} \alias{oauth_endpoints} \title{Popular oauth endpoints.} \usage{ oauth_endpoints(name) } \arguments{ \item{name}{One of the following endpoints: linkedin, twitter, vimeo, google, facebook, github, azure.} } \description{ Provides some common OAuth endpoints. } \examples{ oauth_endpoints("twitter") } httr/man/httr_options.Rd0000644000176200001440000000255713401555510015041 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/config.r \name{httr_options} \alias{httr_options} \alias{curl_docs} \title{List available options.} \usage{ httr_options(matches) curl_docs(x) } \arguments{ \item{matches}{If not missing, this restricts the output so that either the httr or curl option matches this regular expression.} \item{x}{An option name (either short or full).} } \value{ A data frame with three columns: \item{httr}{The short name used in httr} \item{libcurl}{The full name used by libcurl} \item{type}{The type of R object that the option accepts} } \description{ This function lists all available options for \code{\link[=config]{config()}}. It provides both the short R name which you use with httr, and the longer Curl name, which is useful when searching the documentation. \code{curl_doc} opens a link to the libcurl documentation for an option in your browser. } \details{ RCurl and httr use slightly different names to libcurl: the initial \code{CURLOPT_} is removed, all underscores are converted to periods and the option is given in lower case. Thus "CURLOPT_SSLENGINE_DEFAULT" becomes "sslengine.default". } \examples{ httr_options() httr_options("post") # Use curl_docs to read the curl documentation for each option. # You can use either the httr or curl option name. curl_docs("userpwd") curl_docs("CURLOPT_USERPWD") } httr/man/oauth_service_token.Rd0000644000176200001440000000242413401555510016336 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-token.r \name{oauth_service_token} \alias{oauth_service_token} \title{Generate OAuth token for service accounts.} \usage{ oauth_service_token(endpoint, secrets, scope = NULL, sub = NULL) } \arguments{ \item{endpoint}{An OAuth endpoint, created by \code{\link[=oauth_endpoint]{oauth_endpoint()}}} \item{secrets}{Secrets loaded from JSON file, downloaded from console.} \item{scope}{a character vector of scopes to request.} \item{sub}{The email address of the user for which the application is requesting delegated access.} } \description{ Service accounts provide a way of using OAuth2 without user intervention. They instead assume that the server has access to a private key used to sign requests. The OAuth app is not needed for service accounts: that information is embedded in the account itself. } \examples{ \dontrun{ endpoint <- oauth_endpoints("google") secrets <- jsonlite::fromJSON("~/Desktop/httrtest-45693cbfac92.json") scope <- "https://www.googleapis.com/auth/bigquery.readonly" token <- oauth_service_token(endpoint, secrets, scope) } } \seealso{ Other OAuth: \code{\link{oauth1.0_token}}, \code{\link{oauth2.0_token}}, \code{\link{oauth_app}}, \code{\link{oauth_endpoint}} } \concept{OAuth} httr/man/oauth1.0_token.Rd0000644000176200001440000000312013401555510015027 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-token.r \name{oauth1.0_token} \alias{oauth1.0_token} \title{Generate an oauth1.0 token.} \usage{ oauth1.0_token(endpoint, app, permission = NULL, as_header = TRUE, private_key = NULL, cache = getOption("httr_oauth_cache")) } \arguments{ \item{endpoint}{An OAuth endpoint, created by \code{\link[=oauth_endpoint]{oauth_endpoint()}}} \item{app}{An OAuth consumer application, created by \code{\link[=oauth_app]{oauth_app()}}} \item{permission}{optional, a string of permissions to ask for.} \item{as_header}{If \code{TRUE}, the default, sends oauth in header. If \code{FALSE}, adds as parameter to url.} \item{private_key}{Optional, a key provided by \code{\link[openssl:read_key]{openssl::read_key()}}. Used for signed OAuth 1.0.} \item{cache}{A logical value or a string. \code{TRUE} means to cache using the default cache file \code{.httr-oauth}, \code{FALSE} means don't cache, and \code{NA} means to guess using some sensible heuristics. A string means use the specified path as the cache file.} } \value{ A \code{Token1.0} reference class (RC) object. } \description{ This is the final object in the OAuth dance - it encapsulates the app, the endpoint, other parameters and the received credentials. } \details{ See \code{\link[=Token]{Token()}} for full details about the token object, and the caching policies used to store credentials across sessions. } \seealso{ Other OAuth: \code{\link{oauth2.0_token}}, \code{\link{oauth_app}}, \code{\link{oauth_endpoint}}, \code{\link{oauth_service_token}} } \concept{OAuth} httr/man/revoke_all.Rd0000644000176200001440000000106613401555510014422 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-token-utils.R \name{revoke_all} \alias{revoke_all} \title{Revoke all OAuth tokens in the cache.} \usage{ revoke_all(cache_path = NA) } \arguments{ \item{cache_path}{Path to cache file. Defaults to \code{.httr-oauth} in current directory.} } \description{ Use this function if you think that your token may have been compromised, e.g. you accidentally uploaded the cache file to github. It's not possible to automatically revoke all tokens - this function will warn when it can't. } httr/man/get_callback.Rd0000644000176200001440000000526713401555510014701 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/callback.R \name{get_callback} \alias{get_callback} \alias{set_callback} \title{Install or uninstall a callback function} \usage{ get_callback(name) set_callback(name, new_callback = NULL) } \arguments{ \item{name}{Character scalar, name of the callback to query or set.} \item{new_callback}{The callback function to install, a function object; or \code{NULL} to remove the currently installed callback (if any).} } \value{ \code{get_callback} returns the currently installed callback, or \code{NULL} if none is installed. \code{set_callback} returns the previously installed callback, or \code{NULL} if none was installed. } \description{ Supported callback functions: \describe{ \item{\sQuote{request}}{This callback is called before an HTTP request is performed, with the \code{request} object as an argument. If the callback returns a value other than \code{NULL}, the HTTP request is not performed at all, and the return value of the callback is returned. This mechanism can be used to replay previously recorded HTTP responses. } \item{\sQuote{response}}{This callback is called after an HTTP request is performed. The callback is called with two arguments: the \code{request} object and the \code{response} object of the HTTP request. If this callback returns a value other than \code{NULL}, then this value is returned by \code{httr}.} } } \details{ Note that it is not possible to install multiple callbacks of the same type. The installed callback overwrites the previously intalled one. To uninstall a callback function, set it to \code{NULL} with \code{set_callback()}. See the \code{httrmock} package for a proper example that uses callbacks. } \examples{ \dontrun{ ## Log all HTTP requests to the screeen req_logger <- function(req) { cat("HTTP request to", sQuote(req$url), "\\n") } old <- set_callback("request", req_logger) g1 <- GET("https://httpbin.org") g2 <- GET("https://httpbin.org/ip") set_callback("request", old) ## Log all HTTP requests and response status codes as well req_logger2 <- function(req) { cat("HTTP request to", sQuote(req$url), "... ") } res_logger <- function(req, res) { cat(res$status_code, "\\n") } old_req <- set_callback("request", req_logger2) old_res <- set_callback("response", res_logger) g3 <- GET("https://httpbin.org") g4 <- GET("https://httpbin.org/ip") set_callback("request", old_req) set_callback("response", old_res) ## Return a recorded response, without performing the HTTP request replay <- function(req) { if (req$url == "https://httpbin.org") g3 } old_req <- set_callback("request", replay) grec <- GET("https://httpbin.org") grec$date == g3$date set_callback("request", old_req) } } httr/man/has_content.Rd0000644000176200001440000000063713376014735014621 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/content.r \name{has_content} \alias{has_content} \title{Does the request have content associated with it?} \usage{ has_content(x) } \description{ Does the request have content associated with it? } \examples{ has_content(POST("http://httpbin.org/post", body = FALSE)) has_content(HEAD("http://httpbin.org/headers")) } \keyword{internal} httr/man/http_condition.Rd0000644000176200001440000000354013401555510015323 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/response-status.r \name{http_condition} \alias{http_condition} \title{Generate a classed http condition.} \usage{ http_condition(x, type, task = NULL, call = sys.call(-1)) } \arguments{ \item{x}{a response, or numeric http code (or other object with \code{status_code} method)} \item{type}{type of condition to generate. Must be one of error, warning or message.} \item{task}{The text of the message: either \code{NULL} or a character vector. If non-\code{NULL}, the error message will finish with "Failed to \code{task}".} \item{call}{The call stored in the condition object.} } \value{ An S3 object that inherits from (e.g.) condition, \code{type}, http_error, http_400 and http_404. } \description{ This function generate S3 condition objects which are passed to \code{\link[=stop]{stop()}} or \code{\link[=warning]{warning()}} to generate classes warnings and error. These can be used in conjunction with \code{\link[=tryCatch]{tryCatch()}} to respond differently to different type of failure. } \examples{ # You can use tryCatch to take different actions based on the type # of error. Note that tryCatch will call the first handler that # matches any classes of the condition, not the best matching, so # always list handlers from most specific to least specific f <- function(url) { tryCatch(stop_for_status(GET(url)), http_404 = function(c) "That url doesn't exist", http_403 = function(c) "You need to authenticate!", http_400 = function(c) "You made a mistake!", http_500 = function(c) "The server screwed up" ) } f("http://httpbin.org/status/404") f("http://httpbin.org/status/403") f("http://httpbin.org/status/505") } \seealso{ \url{http://adv-r.had.co.nz/Exceptions-Debugging.html#condition-handling} for more details about R's condition handling model } \keyword{internal} httr/man/set_cookies.Rd0000644000176200001440000000142013401555510014600 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cookies.r \name{set_cookies} \alias{set_cookies} \title{Set cookies.} \usage{ set_cookies(..., .cookies = character(0)) } \arguments{ \item{...}{a named cookie values} \item{.cookies}{a named character vector} } \description{ Set cookies. } \examples{ set_cookies(a = 1, b = 2) set_cookies(.cookies = c(a = "1", b = "2")) GET("http://httpbin.org/cookies") GET("http://httpbin.org/cookies", set_cookies(a = 1, b = 2)) } \seealso{ \code{\link[=cookies]{cookies()}} to see cookies in response. Other config: \code{\link{add_headers}}, \code{\link{authenticate}}, \code{\link{config}}, \code{\link{timeout}}, \code{\link{use_proxy}}, \code{\link{user_agent}}, \code{\link{verbose}} } \concept{config} httr/man/headers.Rd0000644000176200001440000000065613401555510013716 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/headers.r \name{headers} \alias{headers} \title{Extract the headers from a response} \usage{ headers(x) } \arguments{ \item{x}{A request object} } \description{ Extract the headers from a response } \examples{ r <- GET("http://httpbin.org/get") headers(r) } \seealso{ \code{\link[=add_headers]{add_headers()}} to send additional headers in a request } httr/man/init_oauth1.0.Rd0000644000176200001440000000140513401555510014656 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-init.R \name{init_oauth1.0} \alias{init_oauth1.0} \title{Retrieve OAuth 1.0 access token.} \usage{ init_oauth1.0(endpoint, app, permission = NULL, is_interactive = interactive(), private_key = NULL) } \arguments{ \item{endpoint}{An OAuth endpoint, created by \code{\link[=oauth_endpoint]{oauth_endpoint()}}} \item{app}{An OAuth consumer application, created by \code{\link[=oauth_app]{oauth_app()}}} \item{permission}{optional, a string of permissions to ask for.} \item{is_interactive}{DEPRECATED} \item{private_key}{Optional, a key provided by \code{\link[openssl:read_key]{openssl::read_key()}}. Used for signed OAuth 1.0.} } \description{ See demos for use. } \keyword{internal} httr/man/jwt_signature.Rd0000644000176200001440000000273513401521062015163 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-server-side.R \name{jwt_signature} \alias{jwt_signature} \title{Generate a JWT signature given credentials.} \usage{ jwt_signature(credentials, scope, aud, sub = NULL, iat = as.integer(Sys.time()), exp = iat + duration, duration = 60L * 60L) } \arguments{ \item{credentials}{Parsed contents of the credentials file.} \item{scope}{A space-delimited list of the permissions that the application requests.} \item{aud}{A descriptor of the intended target of the assertion. This typically comes from the service auth file.} \item{sub}{The email address of the user for which the application is requesting delegated access.} \item{iat}{The time the assertion was issued, measured in seconds since 00:00:00 UTC, January 1, 1970.} \item{exp}{The expiration time of the assertion, measured in seconds since 00:00:00 UTC, January 1, 1970. This value has a maximum of 1 hour from the issued time.} \item{duration}{Duration of token, in seconds.} \item{iss}{Email address of the client_id of the application making the access token request.} \item{scope}{A space-delimited list of the permissions that the application requests.} } \description{ As described in \url{https://developers.google.com/accounts/docs/OAuth2ServiceAccount} } \examples{ \dontrun{ cred <- jsonlite::fromJSON("~/Desktop/httrtest-45693cbfac92.json") jwt_signature(cred, "https://www.googleapis.com/auth/userinfo.profile") } } \keyword{internal} httr/man/user_agent.Rd0000644000176200001440000000117013401521062014422 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/user-agent.r \name{user_agent} \alias{user_agent} \title{Set user agent.} \usage{ user_agent(agent) } \arguments{ \item{agent}{string giving user agent} } \description{ Override the default RCurl user agent of \code{NULL} } \examples{ GET("http://httpbin.org/user-agent") GET("http://httpbin.org/user-agent", user_agent("httr")) } \seealso{ Other config: \code{\link{add_headers}}, \code{\link{authenticate}}, \code{\link{config}}, \code{\link{set_cookies}}, \code{\link{timeout}}, \code{\link{use_proxy}}, \code{\link{verbose}} } \concept{config} httr/man/Token-class.Rd0000644000176200001440000000322213401555510014456 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-token.r \docType{class} \name{Token-class} \alias{Token-class} \alias{Token} \alias{Token1.0} \alias{Token2.0} \alias{TokenServiceAccount} \title{OAuth token objects.} \format{An R6 class object.} \usage{ Token Token1.0 Token2.0 TokenServiceAccount } \description{ These objects represent the complete set of data needed for OAuth access: an app, an endpoint, cached credentials and parameters. They should be created through their constructor functions \code{\link[=oauth1.0_token]{oauth1.0_token()}} and \code{\link[=oauth2.0_token]{oauth2.0_token()}}. } \section{Methods}{ \itemize{ \item \code{cache()}: caches token to disk \item \code{sign(method, url)}: returns list of url and config \item \code{refresh()}: refresh access token (if possible) \item \code{validate()}: TRUE if the token is still valid, FALSE otherwise } } \section{Caching}{ OAuth tokens are cached on disk in a file called \code{.httr-oauth} saved in the current working directory. Caching is enabled if: \itemize{ \item The session is interactive, and the user agrees to it, OR \item The \code{.httr-oauth} file is already present, OR \item \code{getOption("httr_oauth_cache")} is \code{TRUE} } You can suppress caching by setting the \code{httr_oauth_cache} option to \code{FALSE}. Tokens are cached based on their endpoint and parameters. The cache file should not be included in source code control or R packages (because it contains private information), so httr will automatically add the appropriate entries to \code{.gitignore} and \code{.Rbuildignore} if needed. } \keyword{datasets} \keyword{internal} httr/man/sign_oauth.Rd0000644000176200001440000000072513401521062014433 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/oauth-signature.r \name{sign_oauth} \alias{sign_oauth} \alias{sign_oauth1.0} \alias{sign_oauth2.0} \title{Sign an OAuth request} \usage{ sign_oauth1.0(app, token = NULL, token_secret = NULL, as_header = TRUE, ...) sign_oauth2.0(access_token, as_header = TRUE) } \description{ Deprecated. Instead create a config object directly using \code{config(token = my_token)}. } \keyword{internal} httr/man/modify_url.Rd0000644000176200001440000000112313376014735014454 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/url.r \name{modify_url} \alias{modify_url} \title{Modify a url.} \usage{ modify_url(url, scheme = NULL, hostname = NULL, port = NULL, path = NULL, query = NULL, params = NULL, fragment = NULL, username = NULL, password = NULL) } \arguments{ \item{url}{the url to modify} \item{scheme, hostname, port, path, query, params, fragment, username, password}{components of the url to change} } \description{ Modify a url by first parsing it and then replacing components with the non-NULL arguments of this function. } httr/man/parse_media.Rd0000644000176200001440000000165213401555510014551 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/media-parse.r \name{parse_media} \alias{parse_media} \title{Parse a media type.} \usage{ parse_media(x) } \arguments{ \item{x}{String to parse} } \description{ Parsed according to RFC 2616, as at \url{http://pretty-rfc.herokuapp.com/RFC2616#media.types}. } \details{ A simplified minimal EBNF is: \itemize{ \item media-type = \code{type "/" subtype *( ";" parameter )} \item type = \code{token} \item subtype = \code{token} \item parameter = \code{attribute "=" value} \item attribute = \code{token} \item value = \code{token | quoted-string} \item token = \code{1*@,;:\"/[]?=\{\}} } } \examples{ parse_media("text/plain") parse_media("text/plain; charset=utf-8") parse_media("text/plain; charset=\\"utf-8\\"") parse_media("text/plain; randomparam=\\";=;=\\"") } \keyword{internal} httr/DESCRIPTION0000644000176200001440000000212613522036552012745 0ustar liggesusersPackage: httr Title: Tools for Working with URLs and HTTP Version: 1.4.1 Authors@R: c(person(given = "Hadley", family = "Wickham", role = c("aut", "cre"), email = "hadley@rstudio.com"), person(given = "RStudio", role = "cph")) Description: Useful tools for working with HTTP organised by HTTP verbs (GET(), POST(), etc). Configuration functions make it easy to control additional request components (authenticate(), add_headers() and so on). License: MIT + file LICENSE URL: https://httr.r-lib.org/, https://github.com/r-lib/httr BugReports: https://github.com/r-lib/httr/issues Depends: R (>= 3.2) Imports: curl (>= 3.0.0), jsonlite, mime, openssl (>= 0.8), R6 Suggests: covr, httpuv, jpeg, knitr, png, readr, rmarkdown, testthat (>= 0.8.0), xml2 VignetteBuilder: knitr Encoding: UTF-8 RoxygenNote: 6.1.1 NeedsCompilation: no Packaged: 2019-07-30 13:44:18 UTC; hadley Author: Hadley Wickham [aut, cre], RStudio [cph] Maintainer: Hadley Wickham Repository: CRAN Date/Publication: 2019-08-05 14:30:02 UTC httr/build/0000755000176200001440000000000013520044655012336 5ustar liggesusershttr/build/vignette.rds0000644000176200001440000000043713520044655014701 0ustar liggesusersRMO0 2ډ?!&ākzm~UP6 b??Gy3Ʀ,4X` YV;_%w{j@jB`nIە]?*SKC5zHa+pE..FGY9_k/O`K Rpj qprW{lVkN j(z'l*m?(UӰg|ж# C'Eնߢ_4[httr/tests/0000755000176200001440000000000013376014735012406 5ustar liggesusershttr/tests/testthat/0000755000176200001440000000000013522036552014240 5ustar liggesusershttr/tests/testthat/test-content-parse.r0000644000176200001440000000120613401533030020146 0ustar liggesuserscontext("test-content-parse") test_that("default encoding is UTF-8", { expect_message(x <- guess_encoding(), "defaulting to UTF-8") expect_equal(x, "UTF-8") }) test_that("invalid encoding/type gets UTF-8", { expect_message(x <- guess_encoding("abcd"), "defaulting to UTF-8") expect_equal(x, "UTF-8") expect_message(x <- guess_encoding(NULL, "abcd"), "defaulting to UTF-8") expect_equal(x, "UTF-8") }) test_that("encoding guessed from type", { expect_equal(guess_encoding(NULL, "text/plain; charset=latin1"), "latin1") # But overridden by type expect_equal(guess_encoding("UTF-8", "text/plain; charset=latin1"), "UTF-8") }) httr/tests/testthat/test-header.r0000644000176200001440000000405413401555510016627 0ustar liggesuserscontext("Headers") # Setting --------------------------------------------------------------------- test_that("Only last duplicated header kept in add_headers", { expect_equal(add_headers(x = 1, x = 2)$headers, c(x = "2")) }) test_that("Only last duplicated header kept when combined", { out <- c(add_headers(x = 1), add_headers(x = 2)) expect_equal(out$headers, c(x = "2")) }) # Getting --------------------------------------------------------------------- test_that("All headers captures headers from redirects", { r1 <- GET("http://httpbin.org/redirect/1") expect_equal(length(r1$all_headers), 1 + 1) r3 <- GET("http://httpbin.org/redirect/3") expect_equal(length(r3$all_headers), 3 + 1) }) # Parsing --------------------------------------------------------------------- test_that("Trailing line breaks removed", { header <- charToRaw("HTTP/1.1 200 OK\r\nA: B\r\n") expect_equal(parse_http_headers(header)[[1]]$headers$A, "B") }) test_that("Invalid header raises error", { lines <- c( "HTTP/1.1 200 OK", "A: B", "Invalid" ) header <- charToRaw(paste(lines, collapse = "\n")) expect_warning(parse_http_headers(header), "Failed to parse headers") }) test_that("http status line only needs two components", { headers <- parse_http_headers(charToRaw("HTTP/1.1 200")) expect_equal(headers[[1]]$status, 200L) }) test_that("Key/value parsing tolerates multiple ':'", { lines <- c( "HTTP/1.1 200 OK", "A: B:C", "D:E:F" ) header <- charToRaw(paste(lines, collapse = "\n")) expect_equal(parse_http_headers(header)[[1]]$headers$A, "B:C") expect_equal(parse_http_headers(header)[[1]]$headers$D, "E:F") }) test_that("cache_info() handles flagless cache control", { response <- list(flags = "private", `max-age` = "0", `max-stale` = "0") expect_equal(response[1], parse_cache_control("private")) expect_equal(response[2], parse_cache_control("max-age=0")) expect_equal(response[1:2], parse_cache_control("private, max-age=0")) expect_equal( response, parse_cache_control("private, max-age=0, max-stale=0") ) }) httr/tests/testthat/test-oauth-server-side.R0000644000176200001440000000166013401555510020705 0ustar liggesuserscontext("OAuth: server side") # Reference values from # https://developers.google.com/accounts/docs/OAuth2ServiceAccount test_that("reference header yields expected base64", { expect_equal(jwt_base64(jwt_header()), "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9") }) test_that("reference claimset yields expected base64", { cs <- list( iss = "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com", scope = "https://www.googleapis.com/auth/prediction", aud = "https://accounts.google.com/o/oauth2/token", exp = 1328554385, iat = 1328550785 ) expected <- "eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ" expect_equal(jwt_base64(cs), expected) }) httr/tests/testthat/test-body.r0000644000176200001440000000576613401555510016347 0ustar liggesuserscontext("Body") round_trip <- function(...) { content(POST("http://httpbin.org/post", ...)) } data_path <- upload_file("data.txt") data <- readLines("data.txt") test_that("NULL body gives empty data element", { out <- round_trip(body = NULL) expect_equal(out$data, "") }) test_that("FALSE body doesn't send body", { out <- round_trip(body = FALSE) expect_equal(out$data, NULL) }) test_that("string/raw in body gives same string in data element", { out <- round_trip(body = "test") expect_equal(out$data, "test") out <- round_trip(body = charToRaw("test")) expect_equal(out$data, "test") }) test_that("string/raw in body doesn't lose content type", { body <- charToRaw("test") content_type <- "application/awesome" response <- content(POST("http://httpbin.org/post", body = body, add_headers("Content-Type" = content_type) )) expect_equal(response$headers$`Content-Type`, content_type) }) test_that("empty list gives empty body", { out <- round_trip(body = list(), encode = "form") expect_equal(out$data, "") out <- round_trip(body = list(), encode = "multipart") expect_equal(out$data, "") }) test_that("named list matches form results (encode = 'form')", { out <- round_trip(body = list(a = 1, b = 2), encode = "form") expect_equal(out$form$a, "1") expect_equal(out$form$b, "2") }) test_that("named list matches form results (encode = 'multipart')", { out <- round_trip(body = list(a = 1, b = 2), encode = "multipart") expect_equal(out$form$a, "1") expect_equal(out$form$b, "2") }) test_that("named list matches form results (encode = 'json')", { out <- round_trip(body = list(a = 1, b = 2), encode = "json") expect_equal(out$json$a[[1]], 1) expect_equal(out$json$b[[1]], 2) }) test_that("decimal precision is preserved (encode = 'json')", { out <- round_trip( body = list(a = 0.8675309867530986753098675309), encode = "json" ) expect_equal(out$data, "{\"a\":0.867530986753099}") }) test_that("can do own encoding", { out <- round_trip(body = '{"a":1,"b":2}', content_type_json(), encode = "raw") expect_equal(out$json, list(a = 1, b = 2)) }) test_that("NULL elements are automatically dropped", { out <- round_trip(body = list(x = 1, y = NULL), encode = "form") expect_equal(out$form, list(x = "1")) out <- round_trip(body = list(x = 1, y = NULL), encode = "multipart") expect_equal(out$form, list(x = "1")) }) test_that("file and form vals mixed give form and data elements", { out <- round_trip(body = list(y = data_path, a = 1)) expect_equal(out$form$a, "1") expect_equal(strsplit(out$files$y, "\r?\n")[[1]], data) }) test_that("single file matches contents on disk", { out <- round_trip(body = data_path) expect_equal(strsplit(out$data, "\r?\n")[[1]], data) expect_equal(out$headers$`Content-Type`, "text/plain") }) test_that("explicit content type overrides defaults", { out <- round_trip( body = jsonlite::toJSON(list(a = 1, b = 2)), content_type_json() ) expect_equal(out$headers$`Content-Type`, "application/json") }) httr/tests/testthat/test-ssl.R0000644000176200001440000000030313401533040016123 0ustar liggesuserscontext("SSL") test_that("SSL certificates verified as expected", { expect_equal(status_code(GET("https://google.com")), 200L) expect_equal(status_code(GET("https://firefox.com")), 200L) }) httr/tests/testthat/data.txt0000644000176200001440000000001413376014735015713 0ustar liggesusersabc def ghi httr/tests/testthat/test-callback.R0000644000176200001440000000054513401555510017074 0ustar liggesuserscontext("Callback") test_that("request callback", { f <- function(req) req$url old <- set_callback("request", f) on.exit(set_callback("request", old)) expect_identical(GET("foo.bar"), "foo.bar") expect_identical(f, get_callback("request")) expect_identical(f, set_callback("request", old)) expect_identical(old, get_callback("request")) }) httr/tests/testthat/test-config.r0000644000176200001440000000166313401555510016647 0ustar liggesuserscontext("Config") test_that("basic authentication works", { h <- handle("http://httpbin.org") path <- "basic-auth/user/passwd" r <- GET(path = path, handle = h) expect_equal(r$status_code, 401) r <- GET( path = path, handle = h, config = authenticate("user", "passwd", "basic") ) expect_equal(r$status_code, 200) # Authentication shouldn't persist r <- GET(path = path, handle = h) expect_equal(r$status_code, 401) }) test_that("digest authentication works", { h <- handle("http://httpbin.org") path <- "digest-auth/qop/user/passwd" r <- GET(path = path, handle = h) expect_equal(r$status_code, 401) r <- GET( path = path, handle = h, config = authenticate("user", "passwd", "digest") ) expect_equal(r$status_code, 200) }) test_that("timeout enforced", { skip_on_cran() expect_error( GET("http://httpbin.org/delay/1", timeout(0.5)), "(Timeout was reached)|(timed out)" ) }) httr/tests/testthat/test-oauth_signature.R0000644000176200001440000000067013401533032020533 0ustar liggesuserscontext("oauth_signature") # Url normalisation ------------------------------------------------------- test_that("protocol is lower-cased", { expect_equal(oauth_normalise_url("HTTP://google.com"), "http://google.com/") }) test_that("default ports are stripped", { expect_equal(oauth_normalise_url("http://google.com:80"), "http://google.com/") expect_equal(oauth_normalise_url("https://google.com:443"), "https://google.com/") }) httr/tests/testthat/test-http-head.r0000644000176200001440000000041213401533032017242 0ustar liggesuserscontext("test-http-head") test_that("HEAD succeeds regardless of wrapper", { expect_false(http_error(HEAD("https://httpbin.org"))) expect_false(http_error(VERB("HEAD", "https://httpbin.org"))) expect_false(http_error(RETRY("HEAD", "https://httpbin.org"))) }) httr/tests/testthat/test-oauth-cache.R0000644000176200001440000000433013401533033017511 0ustar liggesuserscontext("OAuth cache") tmp_dir <- function() { x <- tempfile() dir.create(x) x } test_that("use_cache() returns NULL or filepath", { old <- options() on.exit(options(old)) owd <- setwd(tmp_dir()) on.exit(setwd(owd), add = TRUE) expect_equal(use_cache(FALSE), NULL) expect_equal(use_cache(TRUE), ".httr-oauth") expect_equal(use_cache("xyz"), "xyz") }) test_that("use_cache() respects options", { old <- options() on.exit(options(old)) owd <- setwd(tmp_dir()) on.exit(setwd(owd), add = TRUE) options(httr_oauth_cache = FALSE) expect_equal(use_cache(), NULL) options(httr_oauth_cache = TRUE) expect_equal(use_cache(), ".httr-oauth") }) test_that("token saved to and restored from cache, scopes normalized in hash", { owd <- setwd(tmp_dir()) on.exit(setwd(owd)) token_a <- Token2.0$new( app = oauth_app("x", "y", "z"), endpoint = oauth_endpoints("google"), params = list(scope = c(a = "a", "b")), cache_path = ".tmp-cache", credentials = list(a = 1) ) token_a$cache() token_b <- Token2.0$new( app = oauth_app("x", "y", "z"), endpoint = oauth_endpoints("google"), params = list(scope = c(b = "b", "a")), cache_path = ".tmp-cache" ) expect_true(token_b$load_from_cache()) expect_equal(token_b$app, token_a$app) expect_equal(token_b$endpoint, token_a$endpoint) expect_equal(token_b$credentials, token_a$credentials) }) test_that("new caches are Rbuildignored and gitignored", { owd <- setwd(tmp_dir()) on.exit(setwd(owd)) file.create("DESCRIPTION") ## default: options("httr_oauth_cache" = NA) ## not tested ## if cache does not exist, will not be created if !interactive() old <- options("httr_oauth_cache" = TRUE) on.exit(options(old), add = TRUE) use_cache() expect_true(file.exists(".Rbuildignore")) expect_identical(readLines(".Rbuildignore"), "^\\.httr-oauth$") expect_true(file.exists(".gitignore")) expect_identical(readLines(".gitignore"), ".httr-oauth") unlink(c(".httr-oauth", ".Rbuildignore", ".gitignore")) use_cache("xyz") expect_true(file.exists(".Rbuildignore")) expect_identical(readLines(".Rbuildignore"), "^xyz$") expect_true(file.exists(".gitignore")) expect_identical(readLines(".gitignore"), "xyz") }) httr/tests/testthat/test-request.r0000644000176200001440000000141213401555510017062 0ustar liggesuserscontext("Request") test_that("c.request overwrites repeated options", { expect_equal( c(request(url = "a"), request(url = "b")), request(url = "b") ) }) test_that("c.request merges headers", { expect_equal( c(request(headers = c("a" = "a")), request(headers = c("b" = "b"))), request(headers = c("a" = "a", "b" = "b")) ) }) test_that("non-http methods don't parse headers", { # skip on travis to avoid hammering the FTP server, which doesn't # seem to be able to handle multiple simultaneous requests skip_on_travis() # Must not reuse or FTP connection is closed at the wrong time, # causing problems in final test r <- GET( "ftp://cran.r-project.org/incoming/", config(forbid_reuse = TRUE) ) expect_type(r$headers, "raw") }) httr/tests/testthat/test-response.r0000644000176200001440000000327413401555510017240 0ustar liggesuserscontext("Response") test_that("status codes returned as expected", { expect_equal(GET("http://httpbin.org/status/320")$status_code, 320) expect_equal(GET("http://httpbin.org/status/404")$status_code, 404) expect_equal(GET("http://httpbin.org/status/418")$status_code, 418) }) test_that("DELETE deletes", { expect_equal(GET("http://httpbin.org/delete")$status_code, 405) expect_equal(DELETE("http://httpbin.org/delete")$status_code, 200) }) test_that("POST posts", { expect_equal(GET("http://httpbin.org/post")$status_code, 405) expect_equal(POST("http://httpbin.org/post")$status_code, 200) }) test_that("PATCH patches", { expect_equal(GET("http://httpbin.org/patch")$status_code, 405) expect_equal(PATCH("http://httpbin.org/patch")$status_code, 200) }) test_that("PUT puts", { expect_equal(GET("http://httpbin.org/put")$status_code, 405) expect_equal(PUT("http://httpbin.org/put")$status_code, 200) }) test_that("headers returned as expected", { round_trip <- function(...) { req <- GET("http://httpbin.org/headers", add_headers(...)) headers <- content(req)$headers names(headers) <- tolower(names(headers)) headers } expect_equal(round_trip(a = 1)$a, "1") expect_equal(round_trip(a = "a + b")$a, "a + b") }) test_that("application/json responses parsed as lists", { test_user_agent <- function(user_agent = NULL) { response <- GET( "http://httpbin.org/user-agent", add_headers("user-agent" = user_agent) ) expect_equal(response$status_code, 200) expected_reply <- list("user-agent" = user_agent %||% default_ua()) expect_equal(expected_reply, content(response)) } test_user_agent() test_user_agent("gunicorn-client/0.1") }) httr/tests/testthat/test-retry.R0000644000176200001440000000113113504410572016477 0ustar liggesuserscontext("test-retry") test_that("sucessful requests terminate when terminate_on_success is true", { should_terminate <- function(status_code) { resp <- response(status_code = status_code) retry_should_terminate(1, 10, resp = resp, terminate_on = 500L, terminate_on_success = TRUE ) } expect_true(should_terminate(200L)) expect_false(should_terminate(400L)) }) test_that("if request_perform() throws an error, RETRY passes it on", { expect_error( RETRY("POST", "http://98d90a2a254647889e2e4c236fb576cd.com", times = 1), regexp = "resolve host" ) }) httr/tests/testthat/test-parse_media.R0000644000176200001440000000065413401533036017611 0ustar liggesuserscontext("parse_media") test_that("can parse bare media type", { out <- parse_media("text/plain") expect_equal(out$type, "text") expect_equal(out$subtype, "plain") expect_equal(out$params, list()) }) test_that("can parse params", { out1 <- parse_media("text/plain; a=1") out2 <- parse_media("text/plain; a=1; b=2") expect_equal(out1$params, list(a = "1")) expect_equal(out2$params, list(a = "1", b = "2")) }) httr/tests/testthat/test-oauth.R0000644000176200001440000000665013401555510016463 0ustar liggesuserscontext("OAuth") test_that("oauth2.0 signing works", { request_url <- "http://httpbin.org/headers" token <- Token2.0$new( app = oauth_app("x", "y", "z"), endpoint = oauth_endpoints("google"), credentials = list(access_token = "ofNoArms") ) token$params$as_header <- TRUE header_response <- GET(request_url, config(token = token)) response_content <- content(header_response)$headers expect_equal("Bearer ofNoArms", response_content$Authorization) expect_equal(request_url, header_response$url) token$params$as_header <- FALSE url_response <- GET(request_url, config(token = token)) response_content <- content(url_response)$headers expect_equal(NULL, response_content$Authorization) expect_equal( parse_url(url_response$url)$query, list(access_token = "ofNoArms") ) }) test_that("partial OAuth1 flow works", { skip_on_cran() # From rfigshare endpoint <- oauth_endpoint( base_url = "http://api.figshare.com/v1/pbl/oauth", "request_token", "authorize", "access_token" ) myapp <- oauth_app("rfigshare", key = "Kazwg91wCdBB9ggypFVVJg", secret = "izgO06p1ymfgZTsdsZQbcA" ) sig <- sign_oauth1.0(myapp, token = "xdBjcKOiunwjiovwkfTF2QjGhROeLMw0y0nSCSgvg3YQxdBjcKOiunwjiovwkfTF2Q", token_secret = "4mdM3pfekNGO16X4hsvZdg" ) r <- GET("http://api.figshare.com/v1/my_data/articles", sig) expect_equal(status_code(r), 200) }) test_that("oauth_encode1 works", { # chinese characters for "xaringan" orig_string <- "\u5199\u8f6e\u773c" restored_string <- URLdecode(oauth_encode1(orig_string)) Encoding(restored_string) <- "UTF-8" expect_equal(orig_string, restored_string) }) test_that("oauth2.0 authorize url appends query params", { app <- oauth_app("x", "y", "z") scope <- NULL query_extra <- list( foo = "bar" ) authURL <- oauth2.0_authorize_url( endpoint = oauth_endpoints("google"), app = app, scope = scope, query_extra = query_extra ) url <- parse_url(authURL) expect_equal(url$query$foo, "bar") }) test_that("oauth2.0 authorize url handles empty query_extra input", { # common constructor authorize_url_extra_params <- function(extra_params) { app <- oauth_app("x", "y", "z") scope <- NULL url_with_empty_input <- oauth2.0_authorize_url( endpoint = oauth_endpoints("google"), app = app, scope = scope, state = "testing-nonce", query_extra = extra_params ) parse_url(url_with_empty_input)$query } # expect NA (i.e. no) error expect_error(authorize_url_extra_params(list()), NA) # with empty list expect_error(authorize_url_extra_params(NULL), NA) # with NULL list }) # Parameter checking ------------------------------------------------------ test_that("scope must be character or NULL", { expect_equal(check_scope("a"), "a") expect_equal(check_scope(NULL), NULL) expect_error(check_scope(1), "character vector") }) test_that("multiple scopes collapsed with space", { expect_equal(check_scope(c("a", "b")), "a b") }) test_that("oob must be a flag", { expect_error(check_oob("a"), "logical vector") expect_error(check_oob(c(FALSE, FALSE)), "logical vector") }) test_that("can not use oob in non-interactive session", { expect_error(check_oob(TRUE), "interactive") }) test_that("can not use custom oob value without enabling oob", { with_mock( `httr:::is_interactive` = function() TRUE, expect_error(check_oob(FALSE, "custom_value"), "custom oob value") ) }) httr/tests/testthat/test-http-error.R0000644000176200001440000000100113401533032017425 0ustar liggesuserscontext("http_error") test_that("http_error works with urls", { expect_false(http_error("http://httpbin.org/status/200")) expect_true(http_error("http://httpbin.org/status/404")) }) test_that("http_error works with responses", { r200 <- GET("http://httpbin.org/status/200") expect_false(http_error(r200)) r404 <- GET("http://httpbin.org/status/404") expect_true(http_error(r404)) }) test_that("http_error works with integers", { expect_false(http_error(200L)) expect_true(http_error(404L)) }) httr/tests/testthat/test-http-condition.R0000644000176200001440000000124113504410572020277 0ustar liggesuserscontext("http_condition") test_that("non failures passed through as is", { expect_equal(stop_for_status(200), 200) }) test_that("status converted to errors", { expect_error(stop_for_status(300), "Multiple Choices (HTTP 300)", fixed = TRUE) expect_error(stop_for_status(404), "Not Found (HTTP 404)", fixed = TRUE) expect_error(stop_for_status(500), "Internal Server Error (HTTP 500)", fixed = TRUE) }) test_that("task adds informative message", { expect_error(stop_for_status(300, "download"), "Failed to download.") }) test_that("unknown status converted to error", { expect_error(stop_for_status(10000), "Unknown http status code: 10000", fixed = TRUE) }) httr/tests/testthat/test-content.R0000644000176200001440000000115513401533030017001 0ustar liggesuserscontext("Content") test_that("parse content is null if no body", { out <- content(HEAD("http://httpbin.org/headers")) expect_equal(out, NULL) }) # has_content ------------------------------------------------------------- test_that("POST result with empty body doesn't have content", { r <- POST("http://httpbin.org/post", body = FALSE) expect_false(has_content(r)) }) test_that("HEAD requests don't have body", { r <- HEAD("http://httpbin.org/headers") expect_false(has_content(r)) }) test_that("regular POST does have content", { r <- POST("http://httpbin.org/post") expect_true(has_content(r)) }) httr/tests/testthat/test-url.r0000644000176200001440000000573313401555510016206 0ustar liggesuserscontext("URL parsing and building") test_that("parse_url works as expected", { urls <- list( "http://google.com/", "http://google.com/path", "http://google.com/path?a=1&b=2", "http://google.com/path;param?a=1&b=2", "http://google.com:80/path;param?a=1&b=2", "http://google.com:80/path;param?a=1&b=2#frag", "http://user@google.com:80/path;param?a=1&b=2", "http://user:pass@google.com:80/path;param?a=1&b=2", "svn+ssh://my.svn.server/repo/trunk" ) expect_equal( lapply(urls, function(u) build_url(parse_url(u))), urls ) }) test_that("empty queries not converted to NA", { expect_equal(parse_url("http://x.com/?q=")$query, list(q = "")) expect_equal(parse_url("http://x.com/?q")$query, list(q = "")) expect_equal(parse_url("http://x.com/?a&q")$query, list(a = "", q = "")) }) test_that("query strings escaped and unescaped correctly", { url <- "http://x.com/?x%20y=a%20b" parsed <- parse_url(url) expect_equal(parsed$query, list("x y" = "a b")) expect_equal(build_url(parsed), url) }) test_that("password and no username is an error", { url <- "http://www.example.com/" parsed <- parse_url(url) expect_equal(build_url(parsed), url) parsed$password <- "secret" expect_error(build_url(parsed), "password without username") }) test_that("handle_url modifies url with named components", { hu <- handle_url(NULL, "http://google.com") expect_equal(hu$url, "http://google.com") hu <- handle_url(NULL, "http://google.com", path = "abc") expect_equal(hu$url, "http://google.com/abc") }) test_that("handle_url ignores unnamed arguments", { hu <- handle_url(NULL, "http://google.com", 1, 2, 3) expect_equal(hu$url, "http://google.com") }) test_that("build_url collapse path", { url <- modify_url("http://google.com", path = c("one", "two")) expect_equal(url, "http://google.com/one/two") }) test_that("build_url drops leading / in path", { url <- modify_url("http://google.com", path = "/one") expect_equal(url, "http://google.com/one") }) test_that("build_url drops null or empty query", { url <- modify_url("http://google.com", query = list(a = 1, b = NULL)) expect_equal(url, "http://google.com/?a=1") url <- modify_url("http://google.com", query = list(a = NULL)) expect_equal(url, "http://google.com/") url <- modify_url("http://google.com", query = list()) expect_equal(url, "http://google.com/") }) test_that("parse_url pulls off domain correctly given query without trailing '/'", { url <- modify_url("http://google.com?a=1", query = list(b = 2)) expect_equal(url, "http://google.com/?a=1&b=2") }) test_that("parse_url preserves leading / in path", { url <- parse_url("file:///tmp/foobar") expect_equal(url$path, "/tmp/foobar") }) # compose_query ----------------------------------------------------------- test_that("I() prevents escaping", { expect_equal(compose_query(list(x = I("&"))), "x=&") }) test_that("null elements are dropped", { expect_equal(compose_query(list(x = 1, y = NULL)), "x=1") }) httr/tests/testthat.R0000644000176200001440000000004513401533024014352 0ustar liggesuserslibrary(testthat) test_check("httr") httr/vignettes/0000755000176200001440000000000013520044655013247 5ustar liggesusershttr/vignettes/api-packages.Rmd0000644000176200001440000004407513401521062016240 0ustar liggesusers--- title: "Best practices for API packages" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Best practices for API packages} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} library(httr) knitr::opts_chunk$set(comment = "#>", collapse = TRUE) ``` So you want to write an R client for a web API? This document walks through the key issues involved in writing API wrappers in R. If you're new to working with web APIs, you may want to start by reading "[An introduction to APIs](https://zapier.com/learn/apis)" by zapier. ## Overall design APIs vary widely. Before starting to code, it is important to understand how the API you are working with handles important issues so that you can implement a complete and coherent R client for the API. The key features of any API are the structure of the requests and the structure of the responses. An HTTP request consists of the following parts: 1. HTTP verb (`GET`, `POST`, `DELETE`, etc.) 1. The base URL for the API 1. The URL path or endpoint 1. URL query arguments (e.g., `?foo=bar`) 1. Optional headers 1. An optional request body An API package needs to be able to generate these components in order to perform the desired API call, which will typically involve some sort of authentication. For example, to request that the GitHub API provides a list of all issues for the httr repo, we send an HTTP request that looks like: ``` -> GET /repos/hadley/httr HTTP/1.1 -> Host: api.github.com -> Accept: application/vnd.github.v3+json ``` Here we're using a `GET` request to the host `api.github.com`. The url is `/repos/hadley/httr`, and we send an accept header that tells GitHub what sort of data we want. In response to this request, the API will return an HTTP response that includes: 1. An HTTP status code. 1. Headers, key-value pairs. 1. A body typically consisting of XML, JSON, plain text, HTML, or some kind of binary representation. An API client needs to parse these responses, turning API errors into R errors, and return a useful object to the end user. For the previous HTTP request, GitHub returns: ``` <- HTTP/1.1 200 OK <- Server: GitHub.com <- Content-Type: application/json; charset=utf-8 <- X-RateLimit-Limit: 5000 <- X-RateLimit-Remaining: 4998 <- X-RateLimit-Reset: 1459554901 <- <- { <- "id": 2756403, <- "name": "httr", <- "full_name": "hadley/httr", <- "owner": { <- "login": "hadley", <- "id": 4196, <- "avatar_url": "https://avatars.githubusercontent.com/u/4196?v=3", <- ... <- }, <- "private": false, <- "html_url": "https://github.com/hadley/httr", <- "description": "httr: a friendly http package for R", <- "fork": false, <- "url": "https://api.github.com/repos/hadley/httr", <- ... <- "network_count": 1368, <- "subscribers_count": 64 <- } ``` Designing a good API client requires identifying how each of these API features is used to compose a request and what type of response is expected for each. It's best practice to insulate the end user from *how* the API works so they only need to understand how to use an R function, not the details of how APIs work. It's your job to suffer so that others don't have to! ## First steps ### Send a simple request First, find a simple API endpoint that doesn't require authentication: this lets you get the basics working before tackling the complexities of authentication. For this example, we'll use the list of httr issues which requires sending a GET request to `repos/hadley/httr`: ```{R} library(httr) github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) GET(url) } resp <- github_api("/repos/hadley/httr") resp ``` ### Parse the response Next, you need to take the response returned by the API and turn it into a useful object. Any API will return an HTTP response that consists of headers and a body. While the response can come in multiple forms (see above), two of the most common structured formats are XML and JSON. Note that while most APIs will return only one or the other, some, like the colour lovers API, allow you to choose which one with a url parameter: ```{r} GET("http://www.colourlovers.com/api/color/6B4106?format=xml") GET("http://www.colourlovers.com/api/color/6B4106?format=json") ``` Others use [content negotiation](http://en.wikipedia.org/wiki/Content_negotiation) to determine what sort of data to send back. If the API you're wrapping does this, then you'll need to include one of `accept_json()` and `accept_xml()` in your request. If you have a choice, choose json: it's usually much easier to work with than xml. Most APIs will return most or all useful information in the response body, which can be accessed using `content()`. To determine what type of information is returned, you can use `http_type()` ```{r} http_type(resp) ``` I recommend checking that the type is as you expect in your helper function. This will ensure that you get a clear error message if the API changes: ```{r} github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } resp } ``` NB: some poorly written APIs will say the content is type A, but it will actually be type B. In this case you should complain to the API authors, and until they fix the problem, simply drop the check for content type. Next we need to parse the output into an R object. httr provides some default parsers with `content(..., as = "auto")` but I don't recommend using them inside a package. Instead it's better to explicitly parse it yourself: 1. To parse json, use `jsonlite` package. 1. To parse xml, use the `xml2` package. ```{r} github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE) } ``` ### Return a helpful object Rather than simply returning the response as a list, I think it's a good practice to make a simple S3 object. That way you can return the response and parsed object, and provide a nice print method. This will make debugging later on much much much more pleasant. ```{r} github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE) structure( list( content = parsed, path = path, response = resp ), class = "github_api" ) } print.github_api <- function(x, ...) { cat("\n", sep = "") str(x$content) invisible(x) } github_api("/users/hadley") ``` The API might return invalid data, but this should be rare, so you can just rely on the parser to provide a useful error message. ### Turn API errors into R errors Next, you need to make sure that your API wrapper throws an error if the request failed. Using a web API introduces additional possible points of failure into R code aside from those occurring in R itself. These include: - Client-side exceptions - Network / communication exceptions - Server-side exceptions You need to make sure these are all converted into regular R errors. You can figure out if there's a problem with `http_error()`, which checks the HTTP status code. Status codes in the 400 range usually mean that you've done something wrong. Status codes in the 500 range typically mean that something has gone wrong on the server side. Often the API will provide information about the error in the body of the response: you should use this where available. If the API returns special errors for common problems, you might want to provide more detail in the error. For example, if you run out of requests and are [rate limited](https://developer.github.com/v3/#rate-limiting) you might want to tell the user how long to wait until they can make the next request (or even automatically wait that long!). ```{r, error = TRUE} github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE) if (http_error(resp)) { stop( sprintf( "GitHub API request failed [%s]\n%s\n<%s>", status_code(resp), parsed$message, parsed$documentation_url ), call. = FALSE ) } structure( list( content = parsed, path = path, response = resp ), class = "github_api" ) } github_api("/user/hadley") ``` > Some poorly written APIs will return different types of response based on > whether or not the request succeeded or failed. If your API does this you'll > need to make your request function check the `status_code()` before parsing > the response. For many APIs, the common approach is to retry API calls that return something in the 500 range. However, when doing this, it's **extremely** important to make sure to do this with some form of exponential backoff: if something's wrong on the server-side, hammering the server with retries may make things worse, and may lead to you exhausting quota (or hitting other sorts of rate limits). A common policy is to retry up to 5 times, starting at 1s, and each time doubling and adding a small amount of jitter (plus or minus up to, say, 5% of the current wait time). ### Set a user agent While we're in this function, there's one important header that you should set for every API wrapper: the user agent. The user agent is a string used to identify the client. This is most useful for the API owner as it allows them to see who is using the API. It's also useful for you if you have a contact on the inside as it often makes it easier for them to pull your requests from their logs and see what's going wrong. If you're hitting a commercial API, this also makes it easier for internal R advocates to see how many people are using their API via R and hopefully assign more resources. A good default for an R API package wrapper is to make it the URL to your GitHub repo: ```{r} ua <- user_agent("http://github.com/hadley/httr") ua github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url, ua) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE) if (status_code(resp) != 200) { stop( sprintf( "GitHub API request failed [%s]\n%s\n<%s>", status_code(resp), parsed$message, parsed$documentation_url ), call. = FALSE ) } structure( list( content = parsed, path = path, response = resp ), class = "github_api" ) } ``` ### Passing parameters Most APIs work by executing an HTTP method on a specified URL with some additional parameters. These parameters can be specified in a number of ways, including in the URL path, in URL query arguments, in HTTP headers, and in the request body itself. These parameters can be controlled using httr functions: 1. URL path: `modify_url()` 2. Query arguments: The `query` argument to `GET()`, `POST()`, etc. 3. HTTP headers: `add_headers()` 4. Request body: The `body` argument to `GET()`, `POST()`, etc. [RESTful APIs](https://en.wikipedia.org/wiki/Representational_state_transfer) also use the HTTP verb to communicate arguments (e.g., `GET` retrieves a file, `POST` adds a file, `DELETE` removes a file, etc.). We can use the helpful [httpbin service](http://httpbin.org) to show how to send arguments in each of these ways. ```{r, eval = FALSE} # modify_url POST(modify_url("https://httpbin.org", path = "/post")) # query arguments POST("http://httpbin.org/post", query = list(foo = "bar")) # headers POST("http://httpbin.org/post", add_headers(foo = "bar")) # body ## as form POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "form") ## as json POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "json") ``` Many APIs will use just one of these forms of argument passing, but others will use multiple of them in combination. Best practice is to insulate the user from how and where the various arguments are used by the API and instead simply expose relevant arguments via R function arguments, some of which might be used in the URL, in the headers, in the body, etc. If a parameter has a small fixed set of possible values that are allowed by the API, you can use list them in the default arguments and then use `match.arg()` to ensure that the caller only supplies one of those values. (This also allows the user to supply the short unique prefixes.) ```{r} f <- function(x = c("apple", "banana", "orange")) { match.arg(x) } f("a") ``` It is good practice to explicitly set default values for arguments that are not required to `NULL`. If there is a default value, it should be the first one listed in the vector of allowed arguments. ## Authentication Many APIs can be called without any authentication (just as if you called them in a web browser). However, others require authentication to perform particular requests or to avoid rate limits and other limitations. The most common forms of authentication are OAuth and HTTP basic authentication: 1. *"Basic" authentication*: This requires a username and password (or sometimes just a username). This is passed as part of the HTTP request. In httr, you can do: `GET("http://httpbin.org", authenticate("username", "password"))` 2. *Basic authentication with an API key*: An alternative provided by many APIs is an API "key" or "token" which is passed as part of the request. It is better than a username/password combination because it can be regenerated independent of the username and password. This API key can be specified in a number of different ways: in a URL query argument, in an HTTP header such as the `Authorization` header, or in an argument inside the request body. 3. *OAuth*: OAuth is a protocol for generating a user- or session-specific authentication token to use in subsequent requests. (An early standard, OAuth 1.0, is not terribly common any more. See `oauth1.0_token()` for details.) The current OAuth 2.0 standard is very common in modern web apps. It involves a round trip between the client and server to establish if the API client has the authority to access the data. See `oauth2.0_token()`. It's ok to publish the app ID and app "secret" - these are not actually important for security of user data. > Some APIs describe their authentication processes inaccurately, so > care needs to be taken to understand the true authentication mechanism > regardless of the label used in the API docs. It is possible to specify the key(s) or token(s) required for basic or OAuth authentication in a number of different ways. You may also need some way to preserve user credentials between function calls so that end users do not need to specify them each time. A good start is to use an environment variable. Here is an example of how to write a function that checks for the presence of a GitHub personal access token and errors otherwise: ```{r} github_pat <- function() { pat <- Sys.getenv('GITHUB_PAT') if (identical(pat, "")) { stop("Please set env var GITHUB_PAT to your github personal access token", call. = FALSE) } pat } ``` ## Pagination (handling multi-page responses) One particularly frustrating aspect of many APIs is dealing with paginated responses. This is common in APIs that offer search functionality and have the potential to return a very large number of responses. Responses might be paginated because there is a large number of response elements or because elements are updated frequently. Often they will be sorted by an explicit or implicit argument specified in the request. When a response is paginated, the API response will typically respond with a header or value specified in the body that contains one of the following: 1. The total number of pages of responses 2. The total number of response elements (with multiple elements per page) 3. An indicator for whether any further elements or pages are available. 4. A URL containing the next page These values can then be used to make further requests. This will either involve specifying a specific page of responses or specifying a "next page token" that returns the next page of results. How to deal with pagination is a difficult question and a client could implement any of the following: 1. Return one page only by default with an option to return additional specific pages 2. Return a specified page (defaulting to 1) and require the end user to handle pagination 3. Return all pages by writing an internal process of checking for further pages and combining the results The choice of which to use depends on your needs and goals and the rate limits of the API. ### Rate limiting Many APIs are rate limited, which means that you can only send a certain number of requests per hour. Often if your request is rate limited, the error message will tell you how long you should wait before performing another request. You might want to expose this to the user, or even include a call to `Sys.sleep()` that waits long enough. For example, we could implement a `rate_limit()` function that tells you how many calls against the github API are available to you. ```{r} rate_limit <- function() { github_api("/rate_limit") } rate_limit() ``` After getting the first version working, you'll often want to polish the output to be more user friendly. For this example, we can parse the unix timestamps into more useful date types. ```{r} rate_limit <- function() { req <- github_api("/rate_limit") core <- req$content$resources$core reset <- as.POSIXct(core$reset, origin = "1970-01-01") cat(core$remaining, " / ", core$limit, " (Resets at ", strftime(reset, "%H:%M:%S"), ")\n", sep = "") } rate_limit() ``` httr/vignettes/quickstart.Rmd0000644000176200001440000002256213376014735016121 0ustar liggesusers--- title: "Getting started with httr" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting started with httr} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, echo = FALSE} library(httr) knitr::opts_chunk$set(comment = "#>", collapse = TRUE) ``` # httr quickstart guide 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][http-tutorial]" or "[HTTP made really easy](http://www.jmarshall.com/easy/http/)". This vignette (and parts of the httr API) derived from the excellent "[Requests quickstart guide](http://docs.python-requests.org/en/latest/user/quickstart/)" 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. ## httr basics To make a request, first load httr, then call `GET()` with a url: ```{r} library(httr) r <- GET("http://httpbin.org/get") ``` 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} r ``` You can pull out important parts of the response with various helper methods, or dig directly into the object: ```{r} status_code(r) headers(r) str(content(r)) ``` 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 response 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 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()`: ```{r} r <- GET("http://httpbin.org/get") # Get an informative description: http_status(r) # Or just access the raw code: r$status_code ``` 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](https://www.flickr.com/photos/girliemac/sets/72157628409467125). You can automatically throw a warning or raise an error if a request did not succeed: ```{r} 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. ### The body There are three ways to access the body of the request, all using `content()`: * `content(r, "text")` accesses the body as a character vector: ```{r} r <- GET("http://httpbin.org/get") content(r, "text") ``` 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: ```{r, eval = FALSE} 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: ```{r} content(r, "raw") ``` This is exactly the sequence of bytes that the web server sent, so this is the highest fidelity way of saving files to disk: ```{r, eval = FALSE} bin <- content(r, "raw") writeBin(bin, "myfile.txt") ``` * httr provides a number of default parsers for common file types: ```{r} # JSON automatically parsed into named list str(content(r, "parsed")) ``` 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. ### The headers Access response headers with `headers()`: ```{r} headers(r) ``` This is basically a named list, but because http headers are case insensitive, indexing this object ignores case: ```{r} headers(r)$date headers(r)$DATE ``` ### Cookies You can access cookies in a similar way: ```{r} r <- GET("http://httpbin.org/cookies/set", query = list(a = 1)) cookies(r) ``` Cookies are automatically persisted between requests to the same domain: ```{r} r <- GET("http://httpbin.org/cookies/set", query = list(b = 1)) cookies(r) ``` ## The request 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. ### The url query string 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: ```{r} r <- GET("http://httpbin.org/get", query = list(key1 = "value1", key2 = "value2") ) content(r)$args ``` Any `NULL` elements are automatically dropped from the list, and both keys and values are escaped automatically. ```{r} r <- GET("http://httpbin.org/get", query = list(key1 = "value 1", "key 2" = "value2", key2 = NULL)) content(r)$args ``` ### Custom headers You can add custom headers to a request with `add_headers()`: ```{r} r <- GET("http://httpbin.org/get", add_headers(Name = "Hadley")) str(content(r)$headers) ``` (Note that `content(r)$header` retrieves the headers that httpbin received. `headers(r)` gives the headers that it sent back in its response.) ## Cookies Cookies are simple key-value pairs like the query string, but they persist across multiple requests in a session (because they're sent back and forth every time). To send your own cookies to the server, use `set_cookies()`: ```{r} r <- GET("http://httpbin.org/cookies", set_cookies("MeWant" = "cookies")) content(r)$cookies ``` Note that this response includes the `a` and `b` cookies that were added by the server earlier. ### Request body When `POST()`ing, you can include data in the `body` of the request. httr allows you to supply this in a number of different ways. The most common way is a named list: ```{r} r <- POST("http://httpbin.org/post", body = list(a = 1, b = 2, c = 3)) ``` You can use the `encode` argument to determine how this data is sent to the server: ```{r} url <- "http://httpbin.org/post" body <- list(a = 1, b = 2, c = 3) # Form encoded r <- POST(url, body = body, encode = "form") # Multipart encoded r <- POST(url, body = body, encode = "multipart") # JSON encoded r <- POST(url, body = body, encode = "json") ``` To see exactly what's being sent to the server, use `verbose()`. Unfortunately due to the way that `verbose()` works, knitr can't capture the messages, so you'll need to run these from an interactive console to see what's going on. ```{r, eval = FALSE} POST(url, body = body, encode = "multipart", verbose()) # the default POST(url, body = body, encode = "form", verbose()) POST(url, body = body, encode = "json", verbose()) ``` `PUT()` and `PATCH()` can also have request bodies, and they take arguments identically to `POST()`. You can also send files off disk: ```{r, eval = FALSE} POST(url, body = upload_file("mypath.txt")) POST(url, body = list(x = upload_file("mypath.txt"))) ``` (`upload_file()` will guess the mime-type from the extension - using the `type` argument to override/supply yourself.) These uploads stream the data to the server: the data will be loaded in R in chunks then sent to the remote server. This means that you can upload files that are larger than memory. See `POST()` for more details on the other types of thing that you can send: no body, empty body, and character and raw vectors. ##### Built with ```{r} sessionInfo() ``` [http-tutorial]:http://code.tutsplus.com/tutorials/http-the-protocol-every-web-developer-must-know-part-1--net-31177 httr/vignettes/secrets.Rmd0000644000176200001440000003025613401555510015364 0ustar liggesusers--- title: "Managing secrets" author: "Hadley Wickham" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Managing secrets} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, echo = FALSE} library(httr) knitr::opts_chunk$set(comment = "#>", collapse = TRUE) ``` ## Introduction This document gives you the basics on securely managing secrets. Most of this document is not directly related to httr, but it's common to have some secrets to manage whenever you are using an API. What is a secret? Some secrets are short alphanumeric sequences: * Passwords are clearly secrets, e.g. the second argument to `authenticate()`. Passwords are particularly important because people (ill-advisedly) often use the same password in multiple places. * Personal access tokens (e.g. [github][github-token]) should be kept secret: they are basically equivalent to a user name password combination, but are slightly safer because you can have multiple tokens for different purposes and it's easy to invalidate one token without affecting the others. Surprisingly, the "client secret" in an `oauth_app()` is __not__ a secret. It's not equivalent to a password, and if you are writing an API wrapper package, it should be included in the package. (If you don't believe me, here are [google's comments on the topic][google-secret].) Other secrets are files: * The JSON web token (jwt) used for server-to-server OAuth (e.g. [google][google-server]) is a secret because it's equivalent to a personal access token. * The `.httr-oauth` file is a secret because it stores OAuth access tokens. The goal of this vignette is to give you the tools to manage these secrets in a secure way. We'll start with best practices for managing secrets locally, then talk about sharing secrets with selected others (including travis), and finish with the challenges that CRAN presents. Here, I assume that the main threat is accidentally sharing your secrets when you don't want to. Protecting against a committed attacker is much harder. And if someone has already hacked your computer to the point where they can run code, there's almost nothing you can do. If you're concerned about those scenarios, you'll need to take a more comprehensive approach that's outside the scope of this document. ## Locally Working with secret files locally is straightforward because it's ok to store them in your project directory as long as you take three precautions: * Ensure the file is only readable by you, not by any other user on the system. You can use the R function `Sys.chmod()` to do so: ```{r, eval = FALSE} Sys.chmod("secret.file", mode = "0400") ``` It's good practice to verify this setting by examining the file metadata with your local filesystem GUI tools or commands. * If you use git: make sure the files are listed in `.gitignore` so they don't accidentally get included in a public repository. * If you're making a package: make sure they are listed in `.Rbuildignore` so they don't accidentally get included in a public R package. httr proactively takes all of these steps for you whenever it creates a `.httr-oauth` file. The main remaining risk is that you might share the entire directory (i.e. zipping and emailing, or in a public dropbox directory). If you're worried about this scenario, store your secret files outside of the project directory. If you do this, make sure to provide a helper function to locate the file and provide an informative message if it's missing. ```{r} my_secrets <- function() { path <- "~/secrets/secret.json" if (!file.exists(path)) { stop("Can't find secret file: '", path, "'") } jsonlite::read_json(path) } ``` Storing short secrets is harder because it's tempting to record them as a variable in your R script. This is a bad idea, because you end up with a file that contains a mix of secret and public code. Instead, you have three options: * Ask for the secret each time. * Store in an environment variable. * Use the keyring package. Regardless of how you store them, to use your secrets you will still need to read them into R variables. Be careful not to expose them by printing them or saving them to a file. ### Ask each time For scripts that you only use every now and then, a simple solution is to simply ask for the password each time the script is run. If you use RStudio an easy and secure way to request a password is with the rstudioapi package: ```{r, eval = FALSE} password <- rstudioapi::askForPassword() ``` If you don't use RStudio, use a more general solution like the [getPass](https://github.com/wrathematics/getPass) package. You should __never__ type your password into the R console: this will typically be stored in the `.Rhistory` file, and it's easy to accidentally share without realising it. ### Environment variables Asking each time is a hassle, so you might want to store the secret across sessions. One easy way to do that is with environment variables. Environment variables, or __envvars__ for short, are a cross platform way of passing information to processes. For passing envvars to R, you can list name-value pairs in a file called `.Renviron` in your home directory. The easiest way to edit it is to run: ```{r, eval = FALSE} file.edit("~/.Renviron") ``` The file looks something like ``` VAR1 = value1 VAR2 = value2 ``` ```{r, include = FALSE} Sys.setenv("VAR1" = "value1") ``` And you can access the values in R using `Sys.getenv()`: ```{r} Sys.getenv("VAR1") ``` Note that `.Renviron` is only processed on startup, so you'll need to restart R to see changes. These environment variables will be available in every running R process, and can easily be read by any other program on your computer to access that file directly. For more security, use the keyring package. ### Keyring The [keyring](https://github.com/r-lib/keyring) package provides a way to store (and retrieve) data in your OS's secure secret store. Keyring has a simple API: ```{r, eval = FALSE} keyring::key_set("MY_SECRET") keyring::key_get("MY_SECRET") ``` By default, keyring will use the system keyring. This is unlocked by default when you log in, which means while the password is stored securely pretty much any process can access it. If you want to be even more secure, you can create custom keyring and keep it locked. That will require you to enter a password every time you want to access your secret. ```{r, eval = FALSE} keyring::keyring_create("httr") keyring::key_set("MY_SECRET", keyring = "httr") ``` Note that accessing the key always unlocks the keyring, so if you're being really careful, make sure to lock it again afterwards. ```{r, eval = FALSE} keyring::keyring_lock("httr") ``` You might wonder if we've actually achieved anything here because we still need to enter a password! However, that one password lets you access every secret, and you can control how often you need to re-enter it by manually locking and unlocking the keyring. ## Sharing with others By and large, managing secrets on your own computer is straightforward. The challenge comes when you need to share them with selected others: * You may need to share a secret with me so that I can run your reprex and figure out what is wrong with httr. * You might want to share a secret amongst a group of developers all working on the same GitHub project. * You might want to automatically run authenticated tests on travis. To make this work, all the techniques in this section rely on __public key cryptography__. This is a type of asymmetric encryption where you use a public key to produce content that can only be decrypted by the holder of the matching private key. ### Reprexes The most common place you might need to share a secret is to generate a reprex. First, do everything you can do eliminate the need to share a secret: * If it is an http problem, make sure to run all requests with `verbose()`. * If you get an R error, make sure to include `traceback()`. If you're lucky, that will be sufficient information to fix the problem. Otherwise, you'll need to encrypt the secret so you can share it with me. The easiest way to do so is with the following snippet: ```{r, eval = FALSE} library(openssl) library(jsonlite) library(curl) encrypt <- function(secret, username) { url <- paste("https://api.github.com/users", username, "keys", sep = "/") resp <- httr::GET(url) httr::stop_for_status(resp) pubkey <- httr::content(resp)[[1]]$key opubkey <- openssl::read_pubkey(pubkey) cipher <- openssl::rsa_encrypt(charToRaw(secret), opubkey) jsonlite::base64_enc(cipher) } cipher <- encrypt("\n", "hadley") cat(cipher) ``` Then I can run the following code on my computer to access it: ```{r, eval = FALSE} decrypt <- function(cipher, key = openssl::my_key()) { cipherraw <- jsonlite::base64_dec(cipher) rawToChar(openssl::rsa_decrypt(cipherraw, key = key)) } decrypt(cipher) #> username #> password ``` Change your password before and after you share it with me or anyone else. ### GitHub If you want to share secrets with a group of other people on GitHub, use the [secret](https://github.com/gaborcsardi/secret) or [cyphr](https://github.com/richfitz/cyphr) packages. ### Travis The easiest way to handle short secrets is to use environment variables. You'll set in your `.Renviron` locally and in the settings pane on travis. That way you can use `Sys.getenv()` to access in both places. It's also possible to set encrypted env vars in your `.travis.yml`: see [the documentation][travis-envvar] for details. Regardless of how you set it, make sure you have a helper to retrieve the value. A good error message will save you a lot of time when debugging problems! ```{r} my_secret <- function() { val <- Sys.getenv("SECRET") if (identical(val, "")) { stop("`SECRET` env var has not been set") } val } ``` Note that encrypted data is not available in pull requests in forks. Typically you'll need to check PRs locally once you've confirmed that the code isn't actively malicious. To share secret files on travis, see . Basically you will encrypt the file locally and check it in to git. Then you'll add a decryption step to your `.travis.yml` which makes it decrypts it for each run. See [bigquery][travis-bigrquery] for an example. Be careful to not accidentally expose the secret on travis. An easy way to accidentally expose the secret is to print it out so that it's captured in the log. Don't do that! ## CRAN There is no way to securely share information with arbitrary R users, including CRAN. That means that if you're developing a package, you need to make sure that `R CMD check` passes cleanly even when authentication is not available. This tends to primarily affect the documentation, vignettes, and tests. ### Documentation Like any R package, an API client needs clear and complete documentation of all functions. Examples are particularly useful but may need to be wrapped in `\donttest{}` to avoid challenges of authentication, rate limiting, lack of network access, or occasional API server down time. ### Vignettes Vignettes pose additional challenges when an API requires authentication, because you don't want to bundle your own credentials with the package! However, you can take advantage of the fact that the vignette is built locally, and only checked by CRAN. In a setup chunk, do: ```{r} NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true") knitr::opts_chunk$set(purl = NOT_CRAN) ``` And then use `eval = NOT_CRAN` in any chunk that requires access to a secret. ### Testing Use `testthat::skip()` to automatically skip tests that require authentication. I typically will wrap this into a little helper function that I call at the start of every test requiring auth. ```{r} skip_if_no_auth <- function() { if (identical(Sys.getenv("MY_SECRET"), "")) { skip("No authentication available") } } ``` [google-secret]: https://developers.google.com/identity/protocols/OAuth2#installed [google-server]: https://developers.google.com/identity/protocols/OAuth2ServiceAccount [github-token]: https://github.com/blog/1509-personal-api-tokens [travis-envvar]: https://docs.travis-ci.com/user/environment-variables/ [travis-bigrquery]: https://github.com/rstats-db/bigrquery/blob/master/.travis.yml httr/R/0000755000176200001440000000000013517044334011440 5ustar liggesusershttr/R/url.r0000644000176200001440000001025613401555510012424 0ustar liggesusers# Good example for testing # http://stevenlevithan.com/demo/parseuri/js/ #' Parse and build urls according to RFC1808. #' #' See for details of parsing #' algorithm. #' #' @param url For `parse_url` a character vector (of length 1) to parse #' into components; for `build_url` a list of components to turn back #' into a string. #' @return a list containing: #' * scheme #' * hostname #' * port #' * path #' * params #' * fragment #' * query, a list #' * username #' * password #' @export #' @examples #' parse_url("http://google.com/") #' parse_url("http://google.com:80/") #' parse_url("http://google.com:80/?a=1&b=2") #' #' url <- parse_url("http://google.com/") #' url$scheme <- "https" #' url$query <- list(q = "hello") #' build_url(url) parse_url <- function(url) { if (is.url(url)) return(url) url <- as.character(url) stopifnot(length(url) == 1) pull_off <- function(pattern) { if (!str_detect(url, pattern)) return(NULL) piece <- str_match(url, pattern)[, 2] url <<- str_replace(url, pattern, "") piece } fragment <- pull_off("#(.*)$") scheme <- pull_off("^([[:alpha:]+.-]+):") netloc <- pull_off("^//([^/?]*)/?") if (identical(netloc, "")) { # corresponds to /// url <- paste0("/", url) port <- username <- password <- hostname <- NULL } else if (!is.null(netloc)) { pieces <- strsplit(netloc, "@")[[1]] if (length(pieces) == 1) { username <- NULL password <- NULL host <- pieces } else { user_pass <- strsplit(pieces[[1]], ":")[[1]] username <- user_pass[1] if (length(user_pass) == 1) { password <- NULL } else { password <- user_pass[2] } host <- pieces[2] } host_pieces <- str_split(host, ":")[[1]] hostname <- host_pieces[1] port <- if (length(host_pieces) > 1) host_pieces[2] } else { port <- username <- password <- hostname <- NULL } query <- pull_off("\\?(.*)$") if (!is.null(query)) { query <- parse_query(query) } params <- pull_off(";(.*)$") structure(list( scheme = scheme, hostname = hostname, port = port, path = url, query = query, params = params, fragment = fragment, username = username, password = password ), class = "url" ) } is.url <- function(x) inherits(x, "url") print.url <- function(x, ...) { cat("Url: ", build_url(x), "\n", sep = "") invisible(x) } "[.url" <- function(x, ...) { structure(NextMethod(), class = "url") } #' @export #' @rdname parse_url build_url <- function(url) { stopifnot(is.url(url)) scheme <- url$scheme hostname <- url$hostname if (!is.null(url$port)) { port <- paste0(":", url$port) } else { port <- NULL } path <- paste(gsub("^/", "", url$path), collapse = "/") if (!is.null(url$params)) { params <- paste0(";", url$params) } else { params <- NULL } if (is.list(url$query)) { query <- compose_query(compact(url$query)) } else { query <- url$query } if (!is.null(query) && nzchar(query)) { stopifnot(is.character(query), length(query) == 1) query <- paste0("?", query) } if (is.null(url$username) && !is.null(url$password)) { stop("Cannot set password without username") } paste0( scheme, "://", url$username, if (!is.null(url$password)) ":", url$password, if (!is.null(url$username)) "@", hostname, port, "/", path, params, query, if (!is.null(url$fragment)) "#", url$fragment ) } #' Modify a url. #' #' Modify a url by first parsing it and then replacing components with #' the non-NULL arguments of this function. #' #' @export #' @param url the url to modify #' @param scheme,hostname,port,path,query,params,fragment,username,password #' components of the url to change modify_url <- function(url, scheme = NULL, hostname = NULL, port = NULL, path = NULL, query = NULL, params = NULL, fragment = NULL, username = NULL, password = NULL) { old <- parse_url(url) new <- compact(list( scheme = scheme, hostname = hostname, port = port, path = path, query = query, params = params, fragment = fragment, username = username, password = password )) build_url(utils::modifyList(old, new)) } httr/R/body.R0000644000176200001440000000442413401555655012531 0ustar liggesusersbody_config <- function(body = NULL, encode = c("form", "json", "multipart", "raw"), type = NULL) { # No body if (identical(body, FALSE)) { return(config(post = TRUE, nobody = TRUE)) } # Send single file lazily if (inherits(body, "form_file")) { con <- file(body$path, "rb") size <- file.info(body$path)$size return(c( config( post = TRUE, readfunction = function(nbytes, ...) { if (is.null(con)) { return(raw()) } bin <- readBin(con, "raw", nbytes) if (length(bin) < nbytes) { close(con) con <<- NULL } bin }, postfieldsize_large = size ), content_type(body$type) )) } # For character/raw, send raw bytes if (is.character(body) || is.raw(body)) { return(body_raw(body, type = type)) } # Post with empty body if (is.null(body)) { return(body_raw(raw())) } if (!is.list(body)) { stop("Unknown type of `body`: must be NULL, FALSE, character, raw or list", call. = FALSE ) } body <- compact(body) # Deal with three ways to encode: form, multipart & json encode <- match.arg(encode) if (encode == "raw") { body_raw(body) } else if (encode == "form") { body_raw(compose_query(body), "application/x-www-form-urlencoded") } else if (encode == "json") { body_raw( jsonlite::toJSON(body, auto_unbox = TRUE, digits = 22), "application/json" ) } else if (encode == "multipart") { if (!all(has_name(body))) { stop("All components of body must be named", call. = FALSE) } request(fields = lapply(body, as_field)) } } as_field <- function(x) UseMethod("as_field") #' @export as_field.numeric <- function(x) as.character(x) #' @export as_field.logical <- function(x) as.character(x) #' @export as_field.default <- function(x) x # assume curl will handle body_raw <- function(body, type = NULL) { if (is.character(body)) { body <- charToRaw(paste(body, collapse = "\n")) } stopifnot(is.raw(body)) c( config( post = TRUE, postfieldsize = length(body), postfields = body ), # For raw bodies, override default POST content-type content_type(type %||% "") ) } httr/R/timeout.r0000644000176200001440000000071313401533014013300 0ustar liggesusers#' Set maximum request time. #' #' @param seconds number of seconds to wait for a response until giving up. #' Can not be less than 1 ms. #' @family config #' @export #' @examples #' \dontrun{ #' GET("http://httpbin.org/delay/3", timeout(1)) #' GET("http://httpbin.org/delay/1", timeout(2)) #' } timeout <- function(seconds) { if (seconds < 0.001) { stop("Timeout cannot be less than 1 ms", call. = FALSE) } config(timeout_ms = seconds * 1000) } httr/R/oauth-endpoint.r0000644000176200001440000000760213401555705014567 0ustar liggesusers#' Describe an OAuth endpoint. #' #' See [oauth_endpoints()] for a list of popular OAuth endpoints #' baked into httr. #' #' @param request url used to request initial (unauthenticated) token. #' If using OAuth2.0, leave as `NULL`. #' @param authorize url to send client to for authorisation. Set to `NULL` #' if not needed #' @param access url used to exchange unauthenticated for authenticated token. #' @param ... other additional endpoints. #' @param base_url option url to use as base for `request`, #' `authorize` and `access` urls. #' @family OAuth #' @export #' @examples #' linkedin <- oauth_endpoint("requestToken", "authorize", "accessToken", #' base_url = "https://api.linkedin.com/uas/oauth" #' ) #' github <- oauth_endpoint(NULL, "authorize", "access_token", #' base_url = "https://github.com/login/oauth" #' ) #' facebook <- oauth_endpoint( #' authorize = "https://www.facebook.com/dialog/oauth", #' access = "https://graph.facebook.com/oauth/access_token" #' ) #' #' oauth_endpoints oauth_endpoint <- function(request = NULL, authorize, access, ..., base_url = NULL) { urls <- list(request = request, authorize = authorize, access = access, ...) if (is.null(base_url)) { return(do.call(endpoint, urls)) } # If base_url provided, add it as a prefix path <- parse_url(base_url)$path add_base_url <- function(x) { if (is.null(x)) return(x) if (substr(x, 1, 4) == "http") return(x) modify_url(base_url, path = file.path(path, x)) } urls <- lapply(urls, add_base_url) do.call(endpoint, urls) } endpoint <- function(request, authorize, access, ...) { structure(list(request = request, authorize = authorize, access = access, ...), class = "oauth_endpoint" ) } is.oauth_endpoint <- function(x) inherits(x, "oauth_endpoint") #' @export print.oauth_endpoint <- function(x, ...) { x <- compact(x) cat("\n") cat(paste0(" ", format(paste0(names(x), ": ")), unlist(x), collapse = "\n")) cat("\n") invisible(x) } #' Popular oauth endpoints. #' #' Provides some common OAuth endpoints. #' #' @param name One of the following endpoints: linkedin, twitter, #' vimeo, google, facebook, github, azure. #' @export #' @examples #' oauth_endpoints("twitter") oauth_endpoints <- function(name) { switch(name, linkedin = oauth_endpoint( base_url = "https://www.linkedin.com/uas/oauth2", authorize = "authorization", access = "accessToken" ), twitter = oauth_endpoint( base_url = "https://api.twitter.com/oauth", request = "request_token", authorize = "authenticate", access = "access_token" ), vimeo = oauth_endpoint( base_url = "https://api.vimeo.com/oauth", request = "request_token", authorize = "authorize", access = "access_token" ), yahoo = oauth_endpoint( base_url = "https://api.login.yahoo.com/oauth2", request = "get_request_token", authorize = "request_auth", access = "get_token" ), google = oauth_endpoint( base_url = "https://accounts.google.com/o/oauth2", authorize = "auth", access = "token", validate = "https://www.googleapis.com/oauth2/v1/tokeninfo", revoke = "revoke" ), tumblr = oauth_endpoint( base_url = "http://www.tumblr.com/oauth/", request = "request_token", authorize = "authorize", access = "access_token" ), facebook = oauth_endpoint( authorize = "https://www.facebook.com/dialog/oauth", access = "https://graph.facebook.com/oauth/access_token" ), github = oauth_endpoint( base_url = "https://github.com/login/oauth", request = NULL, authorize = "authorize", access = "access_token" ), azure = oauth_endpoint( base_url = "https://login.windows.net/common/oauth2", authorize = "authorize", access = "token" ), stop("Unknown endpoint", call. = FALSE) ) } httr/R/http-delete.r0000644000176200001440000000314613401555675014055 0ustar liggesusers#' Send a DELETE request. #' #' @section RFC2616: #' #' The DELETE method requests that the origin server delete the resource #' identified by the Request-URI. This method MAY be overridden by human #' intervention (or other means) on the origin server. The client cannot be #' guaranteed that the operation has been carried out, even if the status code #' returned from the origin server indicates that the action has been #' completed successfully. However, the server SHOULD NOT indicate success #' unless, at the time the response is given, it intends to delete the #' resource or move it to an inaccessible location. #' #' A successful response SHOULD be 200 (OK) if the response includes an entity #' describing the status, 202 (Accepted) if the action has not yet been #' enacted, or 204 (No Content) if the action has been enacted but the #' response does not include an entity. #' #' If the request passes through a cache and the Request-URI identifies one or #' more currently cached entities, those entries SHOULD be treated as stale. #' Responses to this method are not cacheable. #' #' @inherit GET params return #' @inheritParams POST #' @family http methods #' @export #' @examples #' DELETE("http://httpbin.org/delete") #' POST("http://httpbin.org/delete") DELETE <- function(url = NULL, config = list(), ..., body = NULL, encode = c("multipart", "form", "json", "raw"), handle = NULL) { hu <- handle_url(handle, url, ...) req <- request_build( "DELETE", hu$url, body_config(body, match.arg(encode)), as.request(config), ... ) request_perform(req, hu$handle$handle) } httr/R/media-parse.r0000644000176200001440000000323413401555701014011 0ustar liggesusers#' Parse a media type. #' #' Parsed according to RFC 2616, as at #' . #' #' A simplified minimal EBNF is: #' #' * media-type = `type "/" subtype *( ";" parameter )` #' * type = `token` #' * subtype = `token` #' * parameter = `attribute "=" value` #' * attribute = `token` #' * value = `token | quoted-string` #' * token = `1*@@,;:\"/[]?=\{\}` #' @export #' @param x String to parse #' @keywords internal #' @examples #' parse_media("text/plain") #' parse_media("text/plain; charset=utf-8") #' parse_media("text/plain; charset=\"utf-8\"") #' parse_media("text/plain; randomparam=\";=;=\"") parse_media <- function(x) { stopifnot(is.character(x), length(x) == 1) # Use scan to deal with quoted strings. It loses the quotes, but it's # ok because the attribute can't be a quoted string so there's no ambiguity # about who the = belongs to. parse <- function(x, sep) { scan(text = x, what = character(), sep = sep, quiet = TRUE, quote = '"') } pieces <- str_trim(parse(tolower(x), ";")) types <- str_split_fixed(pieces[1], "/", 2)[1, ] type <- tolower(types[1]) subtype <- tolower(types[2]) if (length(pieces) > 1) { param_pieces <- lapply(pieces[-1], str_split_fixed, "=", 2) names <- vapply(param_pieces, "[", 1, FUN.VALUE = character(1)) values <- vapply(param_pieces, "[", 2, FUN.VALUE = character(1)) params <- stats::setNames(as.list(values), names) } else { params <- list() } list( complete = paste(type, "/", subtype, sep = ""), type = type, subtype = subtype, params = params ) } httr/R/http-put.r0000644000176200001440000000140013401555677013414 0ustar liggesusers#' Send PUT request to server. #' #' @inheritParams POST #' @family http methods #' @export #' @examples #' POST("http://httpbin.org/put") #' PUT("http://httpbin.org/put") #' #' b2 <- "http://httpbin.org/put" #' PUT(b2, body = "A simple text string") #' PUT(b2, body = list(x = "A simple text string")) #' PUT(b2, body = list(y = upload_file(system.file("CITATION")))) #' PUT(b2, body = list(x = "A simple text string"), encode = "json") PUT <- function(url = NULL, config = list(), ..., body = NULL, encode = c("multipart", "form", "json", "raw"), handle = NULL) { hu <- handle_url(handle, url, ...) req <- request_build("PUT", hu$url, body_config(body, match.arg(encode)), config, ...) request_perform(req, hu$handle$handle) } httr/R/httr.r0000644000176200001440000000212413401555700012577 0ustar liggesusers#' \pkg{httr} makes http easy. #' #' `httr` is organised around the six most common http verbs: #' [GET()], [PATCH()], #' [POST()], [HEAD()], #' [PUT()], and [DELETE()]. #' #' Each request returns a [response()] object which provides #' easy access to status code, cookies, headers, timings, and other useful #' info. The content of the request is available as a raw vector #' ([content()]), character vector ([text_content()]), #' or parsed into an R object ([parsed_content()]), currently for #' html, xml, json, png and jpeg). #' #' Requests can be modified by various config options like #' [set_cookies()], [add_headers()], #' [authenticate()], [use_proxy()], #' [verbose()], and [timeout()] #' #' httr supports OAuth 1.0 and 2.0. Use [oauth1.0_token()] and #' [oauth2.0_token()] to get user tokens, and #' [sign_oauth1.0()] and [sign_oauth2.0()] to sign #' requests. The demos directory has twelve demos of using OAuth: four for 1.0 #' (linkedin, twitter, vimeo, and yahoo) and eight for 2.0 (azure, facebook, #' github, google, linkedin, reddit, yahoo, and yelp). #' #' @keywords internal "_PACKAGE" httr/R/oauth-listener.r0000644000176200001440000000454113401555711014570 0ustar liggesusers#' Create a webserver to listen for OAuth callback. #' #' This opens a web browser pointing to `request_url`, and opens a #' webserver on port 1410 to listen to the reponse. The redirect url should #' either be set previously (during the OAuth authentication dance) or #' supplied as a parameter to the url. See [oauth1.0_token()] and #' [oauth2.0_token()] for examples of both techniques. #' #' This function should not normally be called directly by the user. #' #' @param request_url the url to send the browser to #' @param is_interactive DEPRECATED #' @param host ip address for the listener #' @param port for the listener #' @export #' @keywords internal oauth_listener <- function(request_url, is_interactive = interactive()) { if (!is_installed("httpuv")) { stop("httpuv package required to capture OAuth credentials.") } info <- NULL listen <- function(env) { if (!identical(env$PATH_INFO, "/")) { return(list( status = 404L, headers = list("Content-Type" = "text/plain"), body = "Not found" )) } query <- env$QUERY_STRING if (!is.character(query) || identical(query, "")) { info <<- NA } else { info <<- parse_query(gsub("^\\?", "", query)) } list( status = 200L, headers = list("Content-Type" = "text/plain"), body = "Authentication complete. Please close this page and return to R." ) } use <- listener_endpoint() server <- httpuv::startServer(use$host, use$port, list(call = listen)) on.exit(httpuv::stopServer(server)) message("Waiting for authentication in browser...") message("Press Esc/Ctrl + C to abort") BROWSE(request_url) while (is.null(info)) { httpuv::service() Sys.sleep(0.001) } httpuv::service() # to send text back to browser if (identical(info, NA)) { stop("Authentication failed.", call. = FALSE) } message("Authentication complete.") info } #' The oauth callback url. #' #' The url that [oauth_listener()] expects that the client be #' referred to. #' #' @keywords internal #' @export oauth_callback <- function() { paste0( "http://", Sys.getenv("HTTR_SERVER", "localhost"), ":", Sys.getenv("HTTR_SERVER_PORT", "1410"), "/" ) } listener_endpoint <- function() { list( host = Sys.getenv("HTTR_LOCALHOST", "127.0.0.1"), port = as.integer(Sys.getenv("HTTR_PORT", "1410")) ) } httr/R/zzz.R0000644000176200001440000000051113401555510012410 0ustar liggesusers.onLoad <- function(libname, pkgname) { op <- options() op.dplyr <- list( httr_oob_default = FALSE, httr_oauth_cache = NA ) toset <- !(names(op.dplyr) %in% names(op)) if (any(toset)) options(op.dplyr[toset]) invisible() } release_questions <- function() { c( "Have you run all the OAuth demos?" ) } httr/R/envvar.R0000644000176200001440000000533313401555671013073 0ustar liggesusers#' Get and set environment variables. #' #' This is a bad idea because it will leave it on the history #' #' @param Name of environment variable. #' @param Value of environment variable. Use `NA` to unset. #' @param Scope of change. If "session", the change is ephemeral and will #' disappear when you restart R. If "user", modifies your `~/.Renviron` #' so that it affects every project. If "site", modifies the site .Renviron #' so that it affects every user. #' @return Invisibly, the previous value of the environment variable. #' @noRd #' @examples #' \dontrun{ #' # Set locally #' set_envvar("HTTR", "true") #' #' # Set for every new session (and this session) #' set_envvar("HTTR", "false", "user") #' # Update existing value #' set_envvar("HTTR", "true", "user") #' } set_envvar <- function(name, value, scope = c("session", "user", "site")) { stopifnot(length(name) == 1, length(value) == 1) scope <- match.arg(scope) old <- get_envvar(value) path <- switch(scope, session = NULL, user = Sys.getenv("R_ENVIRON_USER", "~/.Renviron"), site = Sys.getenv("R_ENVIRON", file.path(R.home("etc"), "Renviron.site")) ) set_envvar_local(name, value) if (scope != "session") { set_envvar_renviron(name, value, path) } invisible(old) } #' @rdname set_envvar #' @noRd get_envvar <- function(name) { stopifnot(is.character(name)) Sys.getenv(name, unset = NA_character_) } set_envvar_local <- function(name, value) { if (is.na(value)) { Sys.unsetenv(name) } else { l <- stats::setNames(list(quote(value)), name) do.call("Sys.setenv", l) } } # @return \code{TRUE} if an existing env var was modified, \code{FALSE} # otherwise (invisibly). set_envvar_renviron <- function(name, value, path) { ev <- paste0(name, '="', value, '"') if (is.na(value)) value <- "" if (!file.exists(path)) { # Create if it doesn't already exist cat(ev, "\n", sep = "", file = path) return(invisible(FALSE)) } lines <- tryCatch(readLines(path), error = function(e) { stop("Failed to read ", path, " with error:\n", e$message, call. = FALSE) }) re <- paste0("^", name, "=") matches <- which(grepl(re, lines)) # No matches, so append to end of file if (length(matches) == 0) { message("Setting ", name, " in ", path) cat(ev, "\n", sep = "", file = path, append = TRUE) return(invisible(FALSE)) } message("Updating ", name, " in ", path) if (length(matches) == 1) { lines[matches] <- ev } else { lines[matches[1]] <- ev lines <- lines[-matches[-1]] } lines <- tryCatch(writeLines(lines, path), error = function(e) { stop("Failed to write ", path, " with error:\n", e$message, call. = FALSE) }) invisible(TRUE) } can_write <- function(x) file.access(x, 6) httr/R/http-browse.r0000644000176200001440000000125113401555675014107 0ustar liggesusers#' Open specified url in browser. #' #' (This isn't really a http verb, but it seems to follow the same format). #' #' Only works in interactive sessions. #' #' @param config All configuration options are ignored because the request #' is handled by the browser, not \pkg{RCurl}. #' @inherit GET params return #' @family http methods #' @export #' @examples #' BROWSE("http://google.com") #' BROWSE("http://had.co.nz") BROWSE <- function(url = NULL, config = list(), ..., handle = NULL) { hu <- handle_url(handle, url, ...) if (interactive()) { utils::browseURL(hu$url) } else { message("Please point your browser to the following url: ") message(hu$url) } } httr/R/insensitive.r0000644000176200001440000000112713401555701014161 0ustar liggesusers#' Create a vector with case insensitive name matching. #' #' @param x vector to modify #' @export #' @keywords internal #' @examples #' x <- c("abc" = 1, "def" = 2) #' x["ABC"] #' y <- insensitive(x) #' y["ABC"] #' y[["ABC"]] insensitive <- function(x) { names(x) <- tolower(names(x)) structure(x, class = c("insensitive", class(x))) } #' @export `[.insensitive` <- function(x, i, ...) { if (is.character(i)) { i <- tolower(i) } NextMethod() } #' @export `[[.insensitive` <- `[.insensitive` #' @export "$.insensitive" <- function(x, name) { name <- tolower(name) x[[name]] } httr/R/utils.r0000644000176200001440000000362313401555510012762 0ustar liggesusers"%||%" <- function(a, b) { if (length(a) > 0) a else b } timestamp <- function(x = Sys.time()) { format(x, "%Y-%m-%dT%H:%M:%SZ", tz = "UTC") } sort_names <- function(x) x[order(names(x))] nonce <- function(length = 10) { paste(sample(c(letters, LETTERS, 0:9), length, replace = TRUE), collapse = "" ) } has_env_var <- function(x) !identical(Sys.getenv(x), "") named <- function(x) x[has_name(x)] unnamed <- function(x) x[!has_name(x)] has_name <- function(x) { nms <- names(x) if (is.null(nms)) { return(rep(FALSE, length(x))) } !is.na(nms) & nms != "" } travis_encrypt <- function(vars) { values <- Sys.getenv(vars) cat(paste0("travis encrypt ", paste0(vars, "=", values, collapse = " "))) } is_installed <- function(pkg) { system.file(package = pkg) != "" } need_package <- function(pkg) { if (is_installed(pkg)) return(invisible()) stop("Please install ", pkg, " package", call. = FALSE) } last <- function(x) { if (length(x) < 1) return(x) x[[length(x)]] } compact <- function(x) { empty <- vapply(x, is_empty, logical(1)) x[!empty] } is_empty <- function(x) length(x) == 0 keep_last <- function(...) { x <- c(...) x[!duplicated(names(x), fromLast = TRUE)] } named_vector <- function(title, x) { if (length(x) == 0) return() cat(title, ":\n", sep = "") bullets <- paste0("* ", names(x), ": ", as.character(x)) cat(bullets, sep = "\n") } keep_last <- function(...) { x <- c(...) x[!duplicated(names(x), fromLast = TRUE)] } find_cert_bundle <- function() { if (.Platform$OS.type != "windows") { return() } env <- Sys.getenv("CURL_CA_BUNDLE") if (!identical(env, "")) { return(env) } bundled <- file.path(R.home("etc"), "curl-ca-bundle.crt") if (file.exists(bundled)) { return(bundled) } # Fall back to certificate bundle in openssl system.file("cacert.pem", package = "openssl") } isFALSE <- function(x) identical(x, FALSE) httr/R/oauth-server-side.R0000644000176200001440000000551513401555712015136 0ustar liggesusers init_oauth_service_account <- function(secrets, scope = NULL, sub = NULL) { signature <- jwt_signature( secrets, aud = secrets$token_uri, scope = scope, sub = sub ) res <- POST( secrets$token_uri, body = list( grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion = signature ), encode = "form" ) stop_for_status(res) content(res, type = "application/json") } #' Generate a JWT signature given credentials. #' #' As described in #' #' #' @param credentials Parsed contents of the credentials file. #' @param scope A space-delimited list of the permissions that the application #' requests. #' @param iss Email address of the client_id of the application making the #' access token request. #' @param scope A space-delimited list of the permissions that the application #' requests. #' @param sub The email address of the user for which the application is #' requesting delegated access. #' @param aud A descriptor of the intended target of the assertion. This #' typically comes from the service auth file. #' @param iat The time the assertion was issued, measured in seconds since #' 00:00:00 UTC, January 1, 1970. #' @param exp The expiration time of the assertion, measured in seconds since #' 00:00:00 UTC, January 1, 1970. This value has a maximum of 1 hour from #' the issued time. #' @param duration Duration of token, in seconds. #' @keywords internal #' @examples #' \dontrun{ #' cred <- jsonlite::fromJSON("~/Desktop/httrtest-45693cbfac92.json") #' jwt_signature(cred, "https://www.googleapis.com/auth/userinfo.profile") #' } jwt_signature <- function(credentials, scope, aud, sub = NULL, iat = as.integer(Sys.time()), exp = iat + duration, duration = 60L * 60L) { cs <- compact(list( iss = credentials$client_email, scope = scope, aud = aud, sub = sub, iat = iat, exp = exp )) jwt_sign(cs, credentials$private_key) } jwt_sign <- function(claimset, private_key, header = jwt_header()) { key <- openssl::read_key(private_key) to_sign_base64 <- paste0(jwt_base64(header), ".", jwt_base64(claimset)) to_sign <- charToRaw(to_sign_base64) sig <- openssl::signature_create(to_sign, openssl::sha256, key) sig_base64 <- base64url(sig) paste0(to_sign_base64, ".", sig_base64) } jwt_header <- function() { list( alg = "RS256", typ = "JWT" ) } jwt_base64 <- function(x) base64url(jwt_json(x)) jwt_json <- function(x) jsonlite::toJSON(x, auto_unbox = TRUE) base64url <- function(x) { if (is.character(x)) { x <- charToRaw(x) } out <- chartr("+/", "-_", openssl::base64_encode(x)) gsub("=+$", "", out) } httr/R/oauth-init.R0000644000176200001440000002021113401555710013635 0ustar liggesusers#' Retrieve OAuth 1.0 access token. #' #' See demos for use. #' #' @param endpoint An OAuth endpoint, created by [oauth_endpoint()] #' @param app An OAuth consumer application, created by #' [oauth_app()] #' @param permission optional, a string of permissions to ask for. #' @param is_interactive DEPRECATED #' @param private_key Optional, a key provided by [openssl::read_key()]. #' Used for signed OAuth 1.0. #' @export #' @keywords internal init_oauth1.0 <- function(endpoint, app, permission = NULL, is_interactive = interactive(), private_key = NULL) { oauth_sig <- function(url, method, token = NULL, token_secret = NULL, private_key = NULL, ...) { oauth_header(oauth_signature(url, method, app, token, token_secret, private_key, other_params = c(list(...), oauth_callback = oauth_callback()) )) } # 1. Get an unauthorized request token response <- POST(endpoint$request, oauth_sig(endpoint$request, "POST", private_key = private_key)) stop_for_status(response) params <- content(response, type = "application/x-www-form-urlencoded") token <- params$oauth_token secret <- params$oauth_token_secret # 2. Authorize the token authorize_url <- modify_url(endpoint$authorize, query = list( oauth_token = token, permission = "read" )) verifier <- oauth_listener(authorize_url, is_interactive) verifier <- verifier$oauth_verifier %||% verifier[[1]] # 3. Request access token response <- POST(endpoint$access, oauth_sig(endpoint$access, "POST", token, secret, oauth_verifier = verifier, private_key = private_key), body = "" ) stop_for_status(response) content(response, type = "application/x-www-form-urlencoded") } #' Retrieve OAuth 2.0 access token. #' #' See demos for use. #' #' @inheritParams init_oauth1.0 #' @param scope a character vector of scopes to request. #' @param user_params Named list holding endpoint specific parameters to pass to #' the server when posting the request for obtaining or refreshing the #' access token. #' @param type content type used to override incorrect server response #' @param use_oob if FALSE, use a local webserver for the OAuth dance. #' Otherwise, provide a URL to the user and prompt for a validation #' code. Defaults to the of the `"httr_oob_default"` default, #' or `TRUE` if `httpuv` is not installed. #' @param oob_value if provided, specifies the value to use for the redirect_uri #' parameter when retrieving an authorization URL. Defaults to "urn:ietf:wg:oauth:2.0:oob". #' Requires `use_oob = TRUE`. #' @param use_basic_auth if `TRUE` use http basic authentication to #' retrieve the token. Some authorization servers require this. #' If `FALSE`, the default, retrieve the token by including the #' app key and secret in the request body. #' @param config_init Additional configuration settings sent to #' [POST()], e.g. [user_agent()]. #' @param client_credentials Default to `FALSE`. Set to `TRUE` to use #' *Client Credentials Grant* instead of *Authorization #' Code Grant*. See . #' @param query_authorize_extra Default to `list()`. Set to named list #' holding query parameters to append to initial auth page query. Useful for #' some APIs. #' @export #' @keywords internal init_oauth2.0 <- function(endpoint, app, scope = NULL, user_params = NULL, type = NULL, use_oob = getOption("httr_oob_default"), oob_value = NULL, is_interactive = interactive(), use_basic_auth = FALSE, config_init = list(), client_credentials = FALSE, query_authorize_extra = list()) { scope <- check_scope(scope) use_oob <- check_oob(use_oob, oob_value) if (use_oob) { redirect_uri <- if (!is.null(oob_value)) oob_value else "urn:ietf:wg:oauth:2.0:oob" state <- NULL } else { redirect_uri <- app$redirect_uri state <- nonce() } # Some Oauth2 grant type not required an authorization request and code # (see https://tools.ietf.org/html/rfc6749#section-4.4) if (client_credentials) { code <- NULL } else { authorize_url <- oauth2.0_authorize_url( endpoint, app, scope = scope, redirect_uri = redirect_uri, state = state, query_extra = query_authorize_extra ) code <- oauth_authorize(authorize_url, use_oob) } # Use authorisation code to get (temporary) access token oauth2.0_access_token( endpoint, app, code = code, user_params = user_params, type = type, redirect_uri = redirect_uri, client_credentials = client_credentials, use_basic_auth = use_basic_auth, config = config_init ) } #' @export #' @importFrom utils modifyList #' @rdname init_oauth2.0 #' @param query_extra See `query_authorize_extra` oauth2.0_authorize_url <- function(endpoint, app, scope, redirect_uri = app$redirect_uri, state = nonce(), query_extra = list()) { # TODO might need to put some params before and some after... query_extra <- query_extra %||% list() # i.e. make list if query_extra is null default_query <- list( client_id = app$key, scope = scope, redirect_uri = redirect_uri, response_type = "code", state = state ) query <- compact(modifyList(default_query, query_extra)) modify_url( endpoint$authorize, query = query ) } #' @export #' @rdname init_oauth2.0 oauth2.0_access_token <- function(endpoint, app, code, user_params = NULL, type = NULL, use_basic_auth = FALSE, redirect_uri = app$redirect_uri, client_credentials = FALSE, config = list()) { req_params <- compact(list( client_id = app$key, redirect_uri = if (client_credentials) NULL else redirect_uri, grant_type = if (client_credentials) "client_credentials" else "authorization_code", code = code )) if (!is.null(user_params)) { req_params <- utils::modifyList(user_params, req_params) } # Send credentials using HTTP Basic or as parameters in the request body # See https://tools.ietf.org/html/rfc6749#section-2.3 (Client Authentication) if (isTRUE(use_basic_auth)) { req <- POST(endpoint$access, encode = "form", body = req_params, authenticate(app$key, app$secret, type = "basic"), config = config ) } else { req_params$client_secret <- app$secret req <- POST(endpoint$access, encode = "form", body = req_params, config = config ) } stop_for_status(req, task = "get an access token") content(req, type = type) } # Parameter checking ------------------------------------------------------ check_scope <- function(x) { if (is.null(x)) { return(NULL) } if (!is.character(x)) { stop("`scope` must be a character vector", call. = FALSE) } paste(x, collapse = " ") } # Wrap base::interactive in a non-primitive function so that the call can be mocked for testing is_interactive <- function() interactive() check_oob <- function(use_oob, oob_value = NULL) { if (!is.logical(use_oob) || length(use_oob) != 1) { stop("`use_oob` must be a length-1 logical vector", call. = FALSE) } if (!use_oob && !is_installed("httpuv")) { message("httpuv not installed, defaulting to out-of-band authentication") use_oob <- TRUE } if (use_oob) { if (!is_interactive()) { stop( "Can only use oob authentication in an interactive session", call. = FALSE ) } } if (!is.null(oob_value)) { if (!use_oob) { stop( "Can only use custom oob value if use_oob is enabled", call. = FALSE ) } } use_oob } oauth_authorize <- function(url, oob = FALSE) { if (oob) { oauth_exchanger(url)$code } else { oauth_listener(url)$code } } httr/R/retry.R0000644000176200001440000001114313504410572012725 0ustar liggesusers#' Retry a request until it succeeds. #' #' Safely retry a request until it succeeds, as defined by the `terminate_on` #' parameter, which by default means a response for which [http_error()] #' is `FALSE`. Will also retry on error conditions raised by the underlying curl code, #' but if the last retry still raises one, `RETRY` will raise it again with #' [stop()]. #' It is designed to be kind to the server: after each failure #' randomly waits up to twice as long. (Technically it uses exponential #' backoff with jitter, using the approach outlined in #' .) #' If the server returns status code 429 and specifies a `retry-after` value, that #' value will be used instead, unless it's smaller than `pause_min`. #' #' @inheritParams VERB #' @inherit GET params return #' @inheritParams POST #' @param times Maximum number of requests to attempt. #' @param pause_base,pause_cap This method uses exponential back-off with #' full jitter - this means that each request will randomly wait between 0 #' and `pause_base * 2 ^ attempt` seconds, up to a maximum of #' `pause_cap` seconds. #' @param pause_min Minimum time to wait in the backoff; generally #' only necessary if you need pauses less than one second (which may #' not be kind to the server, use with caution!). #' @param quiet If `FALSE`, will print a message displaying how long #' until the next request. #' @param terminate_on Optional vector of numeric HTTP status codes that if found #' on the response will terminate the retry process. If `NULL`, will keep #' retrying while [http_error()] is `TRUE` for the response. #' @param terminate_on_success If `TRUE`, the default, this will #' automatically terminate when the request is successful, regardless of the #' value of `terminate_on`. #' @return The last response. Note that if the request doesn't succeed after #' `times` times this will be a failed request, i.e. you still need #' to use [stop_for_status()]. #' @export #' @examples #' # Succeeds straight away #' RETRY("GET", "http://httpbin.org/status/200") #' # Never succeeds #' RETRY("GET", "http://httpbin.org/status/500") #' \donttest{ #' # Invalid hostname generates curl error condition and is retried but eventually #' # raises an error condition. #' RETRY("GET", "http://invalidhostname/") #' } RETRY <- function(verb, url = NULL, config = list(), ..., body = NULL, encode = c("multipart", "form", "json", "raw"), times = 3, pause_base = 1, pause_cap = 60, pause_min = 1, handle = NULL, quiet = FALSE, terminate_on = NULL, terminate_on_success = TRUE) { stopifnot(is.numeric(times), length(times) == 1L) stopifnot(is.numeric(pause_base), length(pause_base) == 1L) stopifnot(is.numeric(pause_cap), length(pause_cap) == 1L) stopifnot(is.numeric(terminate_on) || is.null(terminate_on)) stopifnot(is.logical(terminate_on_success), length(terminate_on_success) == 1) hu <- handle_url(handle, url, ...) req <- request_build(verb, hu$url, body_config(body, match.arg(encode)), config, ...) resp <- tryCatch(request_perform(req, hu$handle$handle), error = function(e) e) i <- 1 while (!retry_should_terminate(i, times, resp, terminate_on, terminate_on_success)) { backoff_full_jitter(i, resp, pause_base, pause_cap, pause_min, quiet = quiet) i <- i + 1 resp <- tryCatch(request_perform(req, hu$handle$handle), error = function(e) e) } if (inherits(resp, "error")) { stop(resp) } resp } retry_should_terminate <- function(i, times, resp, terminate_on, terminate_on_success) { if (i >= times) { TRUE } else if (inherits(resp, "error")) { FALSE } else if (terminate_on_success && !http_error(resp)) { TRUE } else if (!is.null(terminate_on)) { status_code(resp) %in% terminate_on } else { !http_error(resp) } } backoff_full_jitter <- function(i, resp, pause_base = 1, pause_cap = 60, pause_min = 1, quiet = FALSE) { length <- max(pause_min, stats::runif(1, max = min(pause_cap, pause_base * (2^i)))) if (!quiet) { if (inherits(resp, "error")) { error_description <- gsub("[\n\r]*$", "\n", as.character(resp)) status <- "ERROR" } else { error_description <- "" status <- status_code(resp) } if (status == 429) { retry_after <- resp$headers[["retry-after"]] if (!is.null(retry_after)) { length <- max(pause_min, as.numeric(retry_after)) } } message(error_description, "Request failed [", status, "]. Retrying in ", round(length, 1), " seconds...") } Sys.sleep(length) } httr/R/response.r0000644000176200001440000000461013401555731013462 0ustar liggesusers#' The response object. #' #' @description #' The response object captures all information from a request. It includes #' fields: #' #' * `url` the url the request was actually sent to (after redirects) #' * `handle` the handle associated with the url #' * `status_code` the http status code #' * `header` a named list of headers returned by the server #' * `cookies` a named list of cookies returned by the server #' * `content` the body of the response, as raw vector. See [content()] for various ways to access the content. #' * `time` request timing information #' * `config` configuration for the request #' #' @details For non-http(s) responses, some parts including the status and #' header may not be interpretable the same way as http responses. #' #' @name response #' @family response methods NULL response <- function(...) { structure(list(...), class = "response") } is.response <- function(x) { inherits(x, "response") } #' @export print.response <- function(x, ..., max.lines = 10, width = getOption("width")) { content_type <- x$headers$`content-type` cat("Response [", x$url, "]\n", sep = "") cat(" Date: ", format(x$date, "%Y-%m-%d %H:%M"), "\n", sep = "") cat(" Status: ", x$status_code, "\n", sep = "") cat(" Content-Type: ", content_type %||% "", "\n", sep = "") size <- length(x$content) if (size == 0) { cat("\n") return() } cat(" Size: ", bytes(size), "\n", sep = "") if (is.path(x$content)) { cat(" ", x$content) return() } if (!is_text(content_type)) { cat("\n") return() } # Content is text, so print up to `max.lines` lines, truncating each line to # at most `width` characters wide suppressMessages(text <- content(x, "text")) breaks <- gregexpr("\n", text, fixed = TRUE)[[1]] last_line <- breaks[min(length(breaks), max.lines)] lines <- strsplit(substr(text, 1, last_line), "\n")[[1]] too_wide <- nchar(lines) > width lines[too_wide] <- paste0(substr(lines[too_wide], 1, width - 3), "...") cat(lines, sep = "\n") if (max.lines < length(breaks)) cat("...\n") invisible(x) } is_text <- function(type) { if (is.null(type)) return(FALSE) media <- parse_media(type) if (media$type == "text") return(TRUE) if (media$type == "application" && media$subtype == "json") return(TRUE) FALSE } #' @export as.character.response <- function(x, ...) { content(x, "text") } httr/R/upload-file.r0000644000176200001440000000117713401555510014025 0ustar liggesusers#' Upload a file with [POST()] or [PUT()]. #' #' @param path path to file #' @param type mime type of path. If not supplied, will be guess by #' [mime::guess_type()] when needed. #' @export #' @examples #' citation <- upload_file(system.file("CITATION")) #' POST("http://httpbin.org/post", body = citation) #' POST("http://httpbin.org/post", body = list(y = citation)) upload_file <- function(path, type = NULL) { stopifnot(is.character(path), length(path) == 1, file.exists(path)) if (is.null(type)) { type <- mime::guess_type(path) } curl::form_file(path, type) } #' @export as.character.form_file <- function(x, ...) x httr/R/sha1.r0000644000176200001440000000205613401555510012455 0ustar liggesusers#' SHA1 hash #' #' Creates a SHA1 hash of data using either HMAC or RSA. #' #' @param key The key to create the hash with #' @param string data to securely hash #' @param method The method to use, either HMAC-SHA1 or RSA-SHA1 #' @keywords internal #' @export sha1_hash <- function(key, string, method = "HMAC-SHA1") { if (is.character(string)) { string <- charToRaw(paste(string, collapse = "\n")) } if (is.character(key)) { key <- charToRaw(paste(key, collapse = "\n")) } if (!method %in% c("HMAC-SHA1", "RSA-SHA1")) { stop(paste0("Unsupported hashing method: ", method), call. = FALSE) } if (method == "HMAC-SHA1") { hash <- openssl::sha1(string, key = key) } else { hash <- openssl::signature_create(string, openssl::sha1, key = key) } openssl::base64_encode(hash) } #' HMAC SHA1 #' #' As described in . #' #' @param key secret key #' @param string data to securely hash #' @keywords internal #' @export hmac_sha1 <- function(key, string) { sha1_hash(key, string, "HMAC-SHA1") } httr/R/http-verb.R0000644000176200001440000000142313401555700013472 0ustar liggesusers#' VERB a url. #' #' Use an arbitrary verb. #' #' @inherit GET params return #' @inheritParams POST #' @param verb Name of verb to use. #' @family http methods #' @export #' @examples #' r <- VERB( #' "PROPFIND", "http://svn.r-project.org/R/tags/", #' add_headers(depth = 1), verbose() #' ) #' stop_for_status(r) #' content(r) #' #' VERB("POST", url = "http://httpbin.org/post") #' VERB("POST", url = "http://httpbin.org/post", body = "foobar") VERB <- function(verb, url = NULL, config = list(), ..., body = NULL, encode = c("multipart", "form", "json", "raw"), handle = NULL) { hu <- handle_url(handle, url, ...) req <- request_build(verb, hu$url, body_config(body, match.arg(encode)), config, ...) request_perform(req, hu$handle$handle) } httr/R/media-guess.r0000644000176200001440000000042113401555701014020 0ustar liggesusers#' Guess the media type of a path from its extension. #' #' DEPRECATED: please use `mime::guess_type` instead. #' #' @param x path to file #' @keywords internal #' @export guess_media <- function(x) { .Deprecated("mime::guess_type") mime::guess_type(x, empty = NULL) } httr/R/http-post.r0000644000176200001440000000507613504410572013572 0ustar liggesusers#' POST file to a server. #' #' @inherit GET params return #' @param body One of the following: #' #' * `FALSE`: No body. This is typically not used with `POST`, #' `PUT`, or `PATCH`, but can be useful if you need to send a #' bodyless request (like `GET`) with `VERB()`. #' * `NULL`: An empty body #' * `""`: A length 0 body #' * `upload_file("path/")`: The contents of a file. The mime #' type will be guessed from the extension, or can be supplied explicitly #' as the second argument to `upload_file()` #' * A character or raw vector: sent as is in body. Use #' [content_type()] to tell the server what sort of data #' you are sending. #' * A named list: See details for encode. #' @param encode If the body is a named list, how should it be encoded? Can be #' one of form (application/x-www-form-urlencoded), multipart, #' (multipart/form-data), or json (application/json). #' #' For "multipart", list elements can be strings or objects created by #' [upload_file()]. For "form", elements are coerced to strings #' and escaped, use `I()` to prevent double-escaping. For "json", #' parameters are automatically "unboxed" (i.e. length 1 vectors are #' converted to scalars). To preserve a length 1 vector as a vector, #' wrap in `I()`. For "raw", either a character or raw vector. You'll #' need to make sure to set the [content_type()] yourself. #' @export #' @family http methods #' @examples #' b2 <- "http://httpbin.org/post" #' POST(b2, body = "A simple text string") #' POST(b2, body = list(x = "A simple text string")) #' POST(b2, body = list(y = upload_file(system.file("CITATION")))) #' POST(b2, body = list(x = "A simple text string"), encode = "json") #' #' # body can also be provided as a json string directly to deal #' # with specific case, like an empty element in the json string. #' # passing as string directly #' POST(b2, body = '{"a":1,"b":{}}', encode = "raw") #' # or building the json string before #' json_body <- jsonlite::toJSON(list(a = 1, b = NULL), auto_unbox = TRUE) #' POST(b2, body = json_body, encode = "raw") #' #' # Various types of empty body: #' POST(b2, body = NULL, verbose()) #' POST(b2, body = FALSE, verbose()) #' POST(b2, body = "", verbose()) POST <- function(url = NULL, config = list(), ..., body = NULL, encode = c("multipart", "form", "json", "raw"), handle = NULL) { encode <- match.arg(encode) hu <- handle_url(handle, url, ...) req <- request_build("POST", hu$url, body_config(body, match.arg(encode)), as.request(config), ...) request_perform(req, hu$handle$handle) } httr/R/config.r0000644000176200001440000001411613401555663013077 0ustar liggesusers#' Set curl options. #' #' Generally you should only need to use this function to set CURL options #' directly if there isn't already a helpful wrapper function, like #' [set_cookies()], [add_headers()] or #' [authenticate()]. To use this function effectively requires #' some knowledge of CURL, and CURL options. Use [httr_options()] to #' see a complete list of available options. To see the libcurl documentation #' for a given option, use [curl_docs()]. #' #' Unlike Curl (and RCurl), all configuration options are per request, not #' per handle. #' #' @seealso [set_config()] to set global config defaults, and #' [with_config()] to temporarily run code with set options. #' @param token An OAuth token (1.0 or 2.0) #' @family config #' @family ways to set configuration #' @seealso All known available options are listed in [httr_options()] #' @param ... named Curl options. #' @export #' @examples #' # There are a number of ways to modify the configuration of a request #' # * you can add directly to a request #' HEAD("https://www.google.com", verbose()) #' #' # * you can wrap with with_config() #' with_config(verbose(), HEAD("https://www.google.com")) #' #' # * you can set global with set_config() #' old <- set_config(verbose()) #' HEAD("https://www.google.com") #' # and re-establish the previous settings with #' set_config(old, override = TRUE) #' HEAD("https://www.google.com") #' # or #' reset_config() #' HEAD("https://www.google.com") #' #' # If available, you should use a friendly httr wrapper over RCurl #' # options. But you can pass Curl options (as listed in httr_options()) #' # in config #' HEAD("https://www.google.com/", config(verbose = TRUE)) config <- function(..., token = NULL) { request(options = list(...), auth_token = token) } #' List available options. #' #' This function lists all available options for [config()]. #' It provides both the short R name which you use with httr, and the longer #' Curl name, which is useful when searching the documentation. `curl_doc` #' opens a link to the libcurl documentation for an option in your browser. #' #' RCurl and httr use slightly different names to libcurl: the initial #' `CURLOPT_` is removed, all underscores are converted to periods and #' the option is given in lower case. Thus "CURLOPT_SSLENGINE_DEFAULT" #' becomes "sslengine.default". #' #' @param x An option name (either short or full). #' @param matches If not missing, this restricts the output so that either #' the httr or curl option matches this regular expression. #' @return A data frame with three columns: #' \item{httr}{The short name used in httr} #' \item{libcurl}{The full name used by libcurl} #' \item{type}{The type of R object that the option accepts} #' @export #' @examples #' httr_options() #' httr_options("post") #' #' # Use curl_docs to read the curl documentation for each option. #' # You can use either the httr or curl option name. #' curl_docs("userpwd") #' curl_docs("CURLOPT_USERPWD") httr_options <- function(matches) { constants <- curl::curl_options() constants <- constants[order(names(constants))] rcurl <- tolower(names(constants)) opts <- data.frame( httr = rcurl, libcurl = translate_curl(rcurl), type = curl_option_types(constants), stringsAsFactors = FALSE ) if (!missing(matches)) { sel <- grepl(matches, opts$httr, ignore.case = TRUE) | grepl(matches, opts$libcurl, ignore.case = TRUE) opts <- opts[sel, , drop = FALSE] } opts } curl_option_types <- function(opts = curl::curl_options()) { type_name <- c("integer", "string", "function", "number") type <- floor(opts / 10000) type_name[type + 1] } #' @export print.opts_list <- function(x, ...) { cat(paste0(format(names(x)), ": ", x, collapse = "\n"), "\n", sep = "") invisible(x) } translate_curl <- function(x) { paste0("CURLOPT_", gsub(".", "_", toupper(x), fixed = TRUE)) } #' @export #' @rdname httr_options curl_docs <- function(x) { stopifnot(is.character(x), length(x) == 1) opts <- httr_options() if (x %in% opts$httr) { x <- opts$libcurl[match(x, opts$httr)] } if (!(x %in% opts$libcurl)) { stop(x, " is not a known curl option", call. = FALSE) } url <- paste0("http://curl.haxx.se/libcurl/c/", x, ".html") BROWSE(url) } cache <- new.env(parent = emptyenv()) cache$default_ua <- NULL default_ua <- function() { if (is.null(cache$default_ua)) { versions <- c( libcurl = curl::curl_version()$version, `r-curl` = as.character(utils::packageVersion("curl")), httr = as.character(utils::packageVersion("httr")) ) cache$default_ua <- paste0(names(versions), "/", versions, collapse = " ") } cache$default_ua } #' Set (and reset) global httr configuration. #' #' @param config Settings as generated by [add_headers()], #' [set_cookies()] or [authenticate()]. #' @param override if `TRUE`, ignore existing settings, if `FALSE`, #' combine new config with old. #' @return invisibility, the old global config. #' @family ways to set configuration #' @export #' @examples #' GET("http://google.com") #' set_config(verbose()) #' GET("http://google.com") #' reset_config() #' GET("http://google.com") set_config <- function(config, override = FALSE) { stopifnot(is.request(config)) old <- getOption("httr_config") %||% config() if (!override) config <- c(old, config) options(httr_config = config) invisible(old) } #' @export #' @rdname set_config reset_config <- function() set_config(config(), TRUE) #' Execute code with configuration set. #' #' @family ways to set configuration #' @inheritParams set_config #' @param expr code to execute under specified configuration #' @export #' @examples #' with_config(verbose(), { #' GET("http://had.co.nz") #' GET("http://google.com") #' }) #' #' # Or even easier: #' with_verbose(GET("http://google.com")) with_config <- function(config = config(), expr, override = FALSE) { stopifnot(is.request(config)) old <- set_config(config, override) on.exit(set_config(old, override = TRUE)) force(expr) } #' @export #' @param ... Other arguments passed on to [verbose()] #' @rdname with_config with_verbose <- function(expr, ...) { with_config(verbose(...), expr) } httr/R/content.r0000644000176200001440000000734113401555666013311 0ustar liggesusers#' Extract content from a request. #' #' There are currently three ways to retrieve the contents of a request: #' as a raw object (`as = "raw"`), as a character vector, #' (`as = "text"`), and as parsed into an R object where possible, #' (`as = "parsed"`). If `as` is not specified, `content` #' does its best to guess which output is most appropriate. #' #' `content` currently knows about the following mime types: #' #' * `text/html`: [xml2::read_html()] #' * `text/xml`: [xml2::read_xml()] #' * `text/csv`: [readr::read_csv()] #' * `text/tab-separated-values`: [readr::read_tsv()] #' * `application/json`: [jsonlite::fromJSON()] #' * `application/x-www-form-urlencoded`: `parse_query` #' * `image/jpeg`: [jpeg::readJPEG()] #' * `image/png`: [png::readPNG()] #' #' `as = "parsed"` is provided as a convenience only: if the type you #' are trying to parse is not available, use `as = "text"` and parse #' yourself. #' #' @section WARNING: #' #' When using `content()` in a package, DO NOT use on `as = "parsed"`. #' Instead, check the mime-type is what you expect, and then parse yourself. #' This is safer, as you will fail informatively if the API changes, and #' you will protect yourself against changes to httr. #' #' @param x request object #' @param as desired type of output: `raw`, `text` or #' `parsed`. `content` attempts to automatically figure out #' which one is most appropriate, based on the content-type. #' @param type MIME type (aka internet media type) used to override #' the content type returned by the server. See #' for a list of #' common types. #' @param encoding For text, overrides the charset or the Latin1 (ISO-8859-1) #' default, if you know that the server is returning the incorrect encoding #' as the charset in the content-type. Use for text and parsed outputs. #' @param ... Other parameters parsed on to the parsing functions, if #' `as = "parsed"` #' @family response methods #' @return #' For "raw", a raw vector. #' #' For "text", a character vector of length 1. The character vector is always #' re-encoded to UTF-8. If this encoding fails (usually because the page #' declares an incorrect encoding), `content()` will return `NA`. #' #' For "auto", a parsed R object. #' @export #' @examples #' r <- POST("http://httpbin.org/post", body = list(a = 1, b = 2)) #' content(r) # automatically parses JSON #' cat(content(r, "text"), "\n") # text content #' content(r, "raw") # raw bytes from server #' #' rlogo <- content(GET("http://cran.r-project.org/Rlogo.jpg")) #' plot(0:1, 0:1, type = "n") #' rasterImage(rlogo, 0, 0, 1, 1) #' @aliases text_content parsed_content content <- function(x, as = NULL, type = NULL, encoding = NULL, ...) { stopifnot(is.response(x)) type <- type %||% x$headers[["Content-Type"]] %||% mime::guess_type(x$url, empty = "application/octet-stream") as <- as %||% parseability(type) as <- match.arg(as, c("raw", "text", "parsed")) if (is.path(x$content)) { raw <- readBin(x$content, "raw", file.info(x$content)$size) } else { raw <- x$content } switch(as, raw = raw, text = parse_text(raw, type, encoding), parsed = parse_auto(raw, type, encoding, ...) ) } #' @export text_content <- function(x) { message("text_content() deprecated. Use content(x, as = 'text')") content(x, as = "text") } #' @export parsed_content <- function(x, ...) { message("text_content() deprecated. Use parsed_content(x, as = 'parsed')") content(x, as = "parsed", ...) } #' Does the request have content associated with it? #' #' @keywords internal #' @export #' @examples #' has_content(POST("http://httpbin.org/post", body = FALSE)) #' has_content(HEAD("http://httpbin.org/headers")) has_content <- function(x) { length(x$content) > 0 } httr/R/url-query.r0000644000176200001440000000136113401555510013564 0ustar liggesusersparse_query <- function(query) { params <- vapply(strsplit(query, "&")[[1]], str_split_fixed, "=", 2, FUN.VALUE = character(2) ) values <- as.list(curl::curl_unescape(params[2, ])) names(values) <- curl::curl_unescape(params[1, ]) values } compose_query <- function(elements) { if (length(elements) == 0) { return("") } if (!all(has_name(elements))) { stop("All components of query must be named", call. = FALSE) } stopifnot(is.list(elements)) elements <- compact(elements) names <- curl::curl_escape(names(elements)) encode <- function(x) { if (inherits(x, "AsIs")) return(x) curl::curl_escape(x) } values <- vapply(elements, encode, character(1)) paste0(names, "=", values, collapse = "&") } httr/R/handle.r0000644000176200001440000000346513401555673013073 0ustar liggesusers#' Create a handle tied to a particular host. #' #' This handle preserves settings and cookies across multiple requests. It is #' the foundation of all requests performed through the httr package, although #' it will mostly be hidden from the user. #' #' @param url full url to site #' @param cookies DEPRECATED #' @export #' @note #' Because of the way argument dispatch works in R, using handle() in the #' http methods (See [GET()]) will cause problems when trying to #' pass configuration arguments (See examples below). Directly specifying the #' handle when using http methods is not recommended in general, since the #' selection of the correct handle is taken care of when the user passes an url #' (See [handle_pool()]). #' #' @examples #' handle("http://google.com") #' handle("https://google.com") #' #' h <- handle("http://google.com") #' GET(handle = h) #' # Should see cookies sent back to server #' GET(handle = h, config = verbose()) #' #' h <- handle("http://google.com", cookies = FALSE) #' GET(handle = h)$cookies #' \dontrun{ #' # Using the preferred way of configuring the http methods #' # will not work when using handle(): #' GET(handle = h, timeout(10)) #' # Passing named arguments will work properly: #' GET(handle = h, config = list(timeout(10), add_headers(Accept = ""))) #' } #' handle <- function(url, cookies = TRUE) { stopifnot(is.character(url), length(url) == 1) if (!missing(cookies)) { warning("Cookies argument is depcrated", call. = FALSE) } h <- curl::new_handle() structure(list(handle = h, url = url), class = "handle") } #' @export print.handle <- function(x, ...) { cat("Host: ", x$url, " <", ref(x$handle), ">\n", sep = "") invisible(x) } ref <- function(x) { str_extract(utils::capture.output(print(x))[1], "0x[0-9a-f]*") } is.handle <- function(x) inherits(x, "handle") httr/R/progress.R0000644000176200001440000000362413401555722013434 0ustar liggesusers#' Add a progress bar. #' #' @param type Type of progress to display: either number of bytes uploaded #' or downloaded. #' @param con Connection to send output too. Usually `stdout()` or #' `stderr`. #' @export #' @examples #' cap_speed <- config(max_recv_speed_large = 10000) #' \donttest{ #' # If file size is known, you get a progress bar: #' x <- GET("http://httpbin.org/bytes/102400", progress(), cap_speed) #' # Otherwise you get the number of bytes downloaded: #' x <- GET("http://httpbin.org/stream-bytes/102400", progress(), cap_speed) #' } progress <- function(type = c("down", "up"), con = stdout()) { type <- match.arg(type) request(options = list( noprogress = FALSE, progressfunction = progress_bar(type, con) )) } progress_bar <- function(type, con) { bar <- NULL show_progress <- function(down, up) { if (type == "down") { total <- down[[1]] now <- down[[2]] } else { total <- up[[1]] now <- up[[2]] } if (total == 0 && now == 0) { # Reset progress bar when seeing first byte bar <<- NULL } else if (total == 0) { cat("\rDownloading: ", bytes(now, digits = 2), " ", sep = "", file = con) utils::flush.console() # Can't automatically add newline on completion because there's no # way to tell when then the file has finished downloading } else { if (is.null(bar)) { bar <<- utils::txtProgressBar(max = total, style = 3, file = con) } utils::setTxtProgressBar(bar, now) if (now == total) close(bar) } TRUE } show_progress } bytes <- function(x, digits = 3, ...) { power <- min(floor(log(abs(x), 1000)), 4) if (power < 1) { unit <- "B" } else { unit <- c("kB", "MB", "GB", "TB")[[power]] x <- x / (1000^power) } formatted <- format(signif(x, digits = digits), big.mark = ",", scientific = FALSE ) paste0(formatted, " ", unit) } httr/R/handle-pool.r0000644000176200001440000000163713401555672014040 0ustar liggesusers#' Maintain a pool of handles. #' #' The handle pool is used to automatically reuse Curl handles for the same #' scheme/host/port combination. This ensures that the http session is #' automatically reused, and cookies are maintained across requests to a site #' without user intervention. #' #' @format An environment. #' @keywords internal handle_pool <- new.env(hash = TRUE, parent = emptyenv()) handle_name <- function(url) { build_url(parse_url(url)[c("scheme", "hostname", "port")]) } #' @export #' @rdname handle_pool handle_find <- function(url) { name <- handle_name(url) if (exists(name, handle_pool)) { handle <- handle_pool[[name]] } else { handle <- handle(name) handle_pool[[name]] <- handle } handle } #' @export #' @rdname handle_pool handle_reset <- function(url) { name <- handle_name(url) if (exists(name, envir = handle_pool)) { rm(list = name, envir = handle_pool) } } httr/R/oauth-refresh.R0000644000176200001440000000255013401555711014337 0ustar liggesusers# Refresh an OAuth 2.0 credential. # # Refreshes the given token, and returns a new credential with a # valid access_token. Based on: # https://developers.google.com/accounts/docs/OAuth2InstalledApp#refresh refresh_oauth2.0 <- function(endpoint, app, credentials, user_params = NULL, use_basic_auth = FALSE) { if (is.null(credentials$refresh_token)) { stop("Refresh token not available", call. = FALSE) } refresh_url <- endpoint$access req_params <- list( refresh_token = credentials$refresh_token, client_id = app$key, grant_type = "refresh_token" ) if (!is.null(user_params)) { req_params <- utils::modifyList(user_params, req_params) } if (isTRUE(use_basic_auth)) { response <- POST(refresh_url, body = req_params, encode = "form", authenticate(app$key, app$secret, type = "basic") ) } else { req_params$client_secret <- app$secret response <- POST(refresh_url, body = req_params, encode = "form") } err <- find_oauth2.0_error(response) if (!is.null(err)) { lines <- c( paste0("Unable to refresh token: ", err$error), err$error_description, err$error_uri ) warning(paste(lines, collapse = "\n"), call. = FALSE) return(NULL) } stop_for_status(response) refresh_data <- content(response) utils::modifyList(credentials, refresh_data) } httr/R/proxy.r0000644000176200001440000000163113401555722013005 0ustar liggesusers#' Use a proxy to connect to the internet. #' #' @param url,port location of proxy #' @param username,password login details for proxy, if needed #' @param auth type of HTTP authentication to use. Should be one of the #' following: basic, digest, digest_ie, gssnegotiate, ntlm, any. #' @family config #' @export #' @examples #' # See http://www.hidemyass.com/proxy-list for a list of public proxies #' # to test with #' # GET("http://had.co.nz", use_proxy("64.251.21.73", 8080), verbose()) use_proxy <- function(url, port = NULL, username = NULL, password = NULL, auth = "basic") { if (!is.null(username) || !is.null(password)) { proxyuserpwd <- paste0(username, ":", password) } else { proxyuserpwd <- NULL } if (!is.null(port)) stopifnot(is.numeric(port)) config( proxy = url, proxyuserpwd = proxyuserpwd, proxyport = port, proxyauth = auth_flags(auth) ) } httr/R/request.R0000644000176200001440000001176013517044334013260 0ustar liggesusersrequest <- function(method = NULL, url = NULL, headers = NULL, fields = NULL, options = NULL, auth_token = NULL, output = NULL) { if (!is.null(method)) { stopifnot(is.character(method), length(method) == 1) } if (!is.null(url)) { stopifnot(is.character(url), length(url) == 1) } if (!is.null(headers)) { stopifnot(is.character(headers)) } if (!is.null(fields)) { stopifnot(is.list(fields)) } if (!is.null(output)) { stopifnot(inherits(output, "write_function")) } structure( list( method = method, url = url, headers = keep_last(headers), fields = fields, options = compact(keep_last(options)), auth_token = auth_token, output = output ), class = "request" ) } is.request <- function(x) inherits(x, "request") request_default <- function() { c( request( options = list( useragent = default_ua() ), headers = c(Accept = "application/json, text/xml, application/xml, */*"), output = write_function("write_memory") ), getOption("httr_config") ) } #' @export c.request <- function(...) { Reduce(request_combine, list(...)) } as.request <- function(x) UseMethod("as.request") as.request.list <- function(x) structure(x, class = "request") as.request.request <- function(x) x as.request.NULL <- function(x) request() as.request.Token <- function(x) request(auth_token = x) request_build <- function(method, url, ...) { extra <- list(...) extra[has_name(extra)] <- NULL if (method == "HEAD") { extra <- c(extra, list(config(nobody = TRUE))) } req <- Reduce(request_combine, extra, init = request()) req$method <- method req$url <- url req } request_combine <- function(x, y) { if (length(x) == 0 && length(y) == 0) return(request()) if (length(x) == 0) return(y) if (length(y) == 0) return(x) stopifnot(is.request(x), is.request(y)) request( method = y$method %||% x$method, url = y$url %||% x$url, headers = keep_last(x$headers, y$headers), fields = c(x$fields, y$fields), options = keep_last(x$options, y$options), auth_token = y$auth_token %||% x$auth_token, output = y$output %||% x$output ) } #' @export print.request <- function(x, ...) { cat("\n") if (!is.null(x$method) && !is.null(x$url)) { cat(toupper(x$method), " ", x$url, "\n", sep = "") } if (!is.null(x$output)) { cat("Output: ", class(x$output)[[1]], "\n", sep = "") } named_vector("Options", x$options) named_vector("Headers", x$headers) named_vector("Fields", x$fields) if (!is.null(x$auth_token)) { cat("Auth token: ", class(x$auth_token)[[1]], "\n", sep = "") } invisible(x) } request_prepare <- function(req) { req <- request_combine(request_default(), req) # Use specific options for GET and POST; otherwise, perform a custom request. # The PUT/UPLOAD options don't appear to work, instead hanging forever. switch(req$method, GET = req$options$httpget <- TRUE, POST = req$options$post <- TRUE, req$options$customrequest <- req$method ) # Sign request, if needed token <- req$auth_token if (!is.null(token)) { signed_req <- token$sign(req$method, req$url) stopifnot(is.request(signed_req)) req <- c(req, signed_req) } req } request_perform <- function(req, handle, refresh = TRUE) { stopifnot(is.request(req), inherits(handle, "curl_handle")) req <- request_prepare(req) ## This callback can cancel the request if (!is.null(res <- perform_callback("request", req = req))) return(res) curl::handle_setopt(handle, .list = req$options) if (!is.null(req$fields)) { curl::handle_setform(handle, .list = req$fields) } curl::handle_setheaders(handle, .list = req$headers) on.exit(curl::handle_reset(handle), add = TRUE) resp <- request_fetch(req$output, req$url, handle) # If return 401 and have auth token, refresh it and then try again needs_refresh <- refresh && resp$status_code == 401L && !is.null(req$auth_token) && req$auth_token$can_refresh() if (needs_refresh) { message("Auto-refreshing stale OAuth token.") req$auth_token$refresh() return(request_perform(req, handle, refresh = FALSE)) } url_scheme <- parse_url(resp$url)$scheme is_http <- tolower(url_scheme) %in% c("http", "https") if (is_http) { all_headers <- parse_http_headers(resp$headers) headers <- last(all_headers)$headers } else { all_headers <- NULL headers <- resp$headers } if (is_http && !is.null(headers$date)) { date <- parse_http_date(headers$Date) } else { date <- Sys.time() } res <- response( url = resp$url, status_code = resp$status_code, headers = headers, all_headers = all_headers, cookies = curl::handle_cookies(handle), content = resp$content, date = date, times = resp$times, request = req, handle = handle ) ## If the callback provides a result, we return that if (!is.null(cbres <- perform_callback("response", req, res))) { return(cbres) } res } httr/R/oauth-exchanger.r0000644000176200001440000000135013401555705014705 0ustar liggesusers#' Walk the user through the OAuth2 dance without a local webserver. #' #' This performs a similar function to [oauth_listener()], #' but without running a local webserver. This manual process can be useful #' in situations where the user is remotely accessing the machine outside a #' browser (say via ssh) or when it's not possible to successfully receive a #' callback (such as when behind a firewall). #' #' This function should generally not be called directly by the user. #' #' @param request_url the url to provide to the user #' @export #' @keywords internal oauth_exchanger <- function(request_url) { BROWSE(request_url) authorization_code <- str_trim(readline("Enter authorization code: ")) list(code = authorization_code) } httr/R/write-function.R0000644000176200001440000000562213401555510014540 0ustar liggesusers#' S3 object to define response writer. #' #' This S3 object allows you to control how the response body is saved. #' #' @param subclass,... Class name and fields. Used in class constructors. #' @param x A `write_function` object to process. #' @keywords internal #' @export write_function <- function(subclass, ...) { structure(list(...), class = c(subclass, "write_function")) } #' Control where the response body is written. #' #' The default behaviour is to use `write_memory()`, which caches #' the response locally in memory. This is useful when talking to APIs as #' it avoids a round-trip to disk. If you want to save a file that's bigger #' than memory, use `write_disk()` to save it to a known path. #' #' @param path Path to content to. #' @param overwrite Will only overwrite existing `path` if TRUE. #' @export #' @examples #' tmp <- tempfile() #' r1 <- GET("https://www.google.com", write_disk(tmp)) #' readLines(tmp) #' #' # The default #' r2 <- GET("https://www.google.com", write_memory()) #' #' # Save a very large file #' \dontrun{ #' GET( #' "http://www2.census.gov/acs2011_5yr/pums/csv_pus.zip", #' write_disk("csv_pus.zip"), progress() #' ) #' } write_disk <- function(path, overwrite = FALSE) { if (!overwrite && file.exists(path)) { stop("Path exists and overwrite is FALSE", call. = FALSE) } request(output = write_function("write_disk", path = path, file = NULL)) } #' @rdname write_disk #' @export write_memory <- function() { request(output = write_function("write_memory")) } # Streaming ----------------------------------------------------------------------- #' Process output in a streaming manner. #' #' This is the most general way of processing the response from the server - #' you receive the raw bytes as they come in, and you can do whatever you want #' with them. #' #' @param f Callback function. It should have a single argument, a raw #' vector containing the bytes recieved from the server. This will usually #' be 16k or less. The return value of the function is ignored. #' @examples #' GET( #' "https://github.com/jeroen/data/raw/gh-pages/diamonds.json", #' write_stream(function(x) { #' print(length(x)) #' length(x) #' }) #' ) #' @export write_stream <- function(f) { stopifnot(is.function(f), length(formals(f)) == 1) request(output = write_function("write_stream", f = f)) } request_fetch <- function(x, url, handle) UseMethod("request_fetch") request_fetch.write_memory <- function(x, url, handle) { curl::curl_fetch_memory(url, handle = handle) } request_fetch.write_disk <- function(x, url, handle) { resp <- curl::curl_fetch_disk(url, x$path, handle = handle) resp$content <- path(resp$content) resp } request_fetch.write_stream <- function(x, url, handle) { curl::curl_fetch_stream(url, x$f, handle = handle) } path <- function(x) structure(x, class = "path") length.path <- function(x) file.info(x)$size is.path <- function(x) inherits(x, "path") httr/R/verbose.r0000644000176200001440000000475113401555510013272 0ustar liggesusers#' Give verbose output. #' #' A verbose connection provides much more information about the flow of #' information between the client and server. #' #' @section Prefixes: #' #' `verbose()` uses the following prefixes to distinguish between #' different components of the http messages: #' #' * `*` informative curl messages #' * `->` headers sent (out) #' * `>>` data sent (out) #' * `*>` ssl data sent (out) #' * `<-` headers received (in) #' * `<<` data received (in) #' * `<*` ssl data received (in) #' #' @family config #' @param data_out Show data sent to the server. #' @param info Show informational text from curl. This is mainly useful #' for debugging https and auth problems, so is disabled by default. #' @param data_in Show data recieved from the server. #' @param ssl Show even data sent/recieved over SSL connections? #' @seealso [with_verbose()] makes it easier to use verbose mode #' even when the requests are buried inside another function call. #' @export #' @examples #' GET("http://httpbin.org", verbose()) #' GET("http://httpbin.org", verbose(info = TRUE)) #' #' f <- function() { #' GET("http://httpbin.org") #' } #' with_verbose(f()) #' with_verbose(f(), info = TRUE) #' #' # verbose() makes it easy to see exactly what POST requests send #' POST_verbose <- function(body, ...) { #' POST("https://httpbin.org/post", body = body, verbose(), ...) #' invisible() #' } #' POST_verbose(list(x = "a", y = "b")) #' POST_verbose(list(x = "a", y = "b"), encode = "form") #' POST_verbose(FALSE) #' POST_verbose(NULL) #' POST_verbose("") #' POST_verbose("xyz") verbose <- function(data_out = TRUE, data_in = FALSE, info = FALSE, ssl = FALSE) { debug <- function(type, msg) { switch(type + 1, text = if (info) prefix_message("* ", msg), headerIn = prefix_message("<- ", msg), headerOut = prefix_message("-> ", msg), dataIn = if (data_in) prefix_message("<< ", msg, TRUE), dataOut = if (data_out) prefix_message(">> ", msg, TRUE), sslDataIn = if (ssl && data_in) prefix_message("*< ", msg, TRUE), sslDataOut = if (ssl && data_out) prefix_message("*> ", msg, TRUE) ) } config(debugfunction = debug, verbose = TRUE) } prefix_message <- function(prefix, x, blank_line = FALSE) { x <- readBin(x, character()) lines <- unlist(strsplit(x, "\n", fixed = TRUE, useBytes = TRUE)) out <- paste0(prefix, lines, collapse = "\n") message(out) if (blank_line) cat("\n") } httr/R/oauth-token-utils.R0000644000176200001440000000411213401555715015157 0ustar liggesusers#' Revoke all OAuth tokens in the cache. #' #' Use this function if you think that your token may have been compromised, #' e.g. you accidentally uploaded the cache file to github. It's not possible #' to automatically revoke all tokens - this function will warn when it can't. #' #' @param cache_path Path to cache file. Defaults to `.httr-oauth` in #' current directory. #' @export revoke_all <- function(cache_path = NA) { cache_path <- use_cache(cache_path) if (is.null(cache_path)) { stop("Can't find cache") } tokens <- load_cache(cache_path) cant_revoke <- vapply( tokens, function(x) is.null(x$endpoint$revoke), logical(1) ) if (any(cant_revoke)) { manual <- tokens[cant_revoke] apps <- vapply(manual, function(x) { paste0(x$app$appname, " (", x$app$key, ")") }, character(1), USE.NAMES = FALSE) warning("Can't revoke the following tokens automatically: ", paste0(apps, collapse = ", "), call. = FALSE ) } lapply(tokens, function(x) try(revoke_oauth2.0(x))) invisible(TRUE) } revoke_oauth2.0 <- function(endpoint, credentials) { if (is.null(endpoint$revoke)) { stop("No revoke endpoint", call. = FALSE) } url <- modify_url(endpoint$revoke, query = list(token = credentials$access_token) ) response <- GET(url, accept_json()) stop_for_status(response) invisible(TRUE) } validate_oauth2.0 <- function(endpoint, credentials) { validate_url <- endpoint_validation_url(endpoint, credentials) response <- GET(validate_url, accept_json()) status_code(response) == 200 } get_token_scopes <- function(endpoint, credentials) { validate_url <- endpoint_validation_url(endpoint, credentials) response <- GET(validate_url, accept_json()) if (response$status_code == 200) { strsplit(content(response)$scope, " ")[[1]] } } endpoint_validation_url <- function(endpoint, credentials) { if (is.null(endpoint$validate)) { stop("No validation endpoint", call. = FALSE) } base_url <- endpoint$validate url <- parse_url(base_url) url$query$access_token <- credentials$access_token build_url(url) } httr/R/http-get.r0000644000176200001440000000646313401555676013400 0ustar liggesusers#' GET a url. #' #' @section RFC2616: #' The GET method means retrieve whatever information (in the form of an #' entity) is identified by the Request-URI. If the Request-URI refers to a #' data-producing process, it is the produced data which shall be returned as #' the entity in the response and not the source text of the process, unless #' that text happens to be the output of the process. #' #' The semantics of the GET method change to a "conditional GET" if the #' request message includes an If-Modified-Since, If-Unmodified-Since, #' If-Match, If-None-Match, or If-Range header field. A conditional GET method #' requests that the entity be transferred only under the circumstances #' described by the conditional header field(s). The conditional GET method is #' intended to reduce unnecessary network usage by allowing cached entities to #' be refreshed without requiring multiple requests or transferring data #' already held by the client. #' #' The semantics of the GET method change to a "partial GET" if the request #' message includes a Range header field. A partial GET requests that only #' part of the entity be transferred, as described in #' The partial GET method is intended to reduce unnecessary network usage by #' allowing partially-retrieved entities to be completed without transferring #' data already held by the client. #' #' @param url the url of the page to retrieve #' @param ... Further named parameters, such as `query`, `path`, etc, #' passed on to [modify_url()]. Unnamed parameters will be combined #' with [config()]. #' @param config Additional configuration settings such as http #' authentication ([authenticate()]), additional headers #' ([add_headers()]), cookies ([set_cookies()]) etc. #' See [config()] for full details and list of helpers. #' @param handle The handle to use with this request. If not #' supplied, will be retrieved and reused from the [handle_pool()] #' based on the scheme, hostname and port of the url. By default \pkg{httr} # automatically reuses the same http connection (aka handle) for mulitple #' requests to the same scheme/host/port combo. This substantially reduces #' connection time, and ensures that cookies are maintained over multiple #' requests to the same host. See [handle_pool()] for more #' details. #' #' @return A [response()] object. #' #' @family http methods #' @export #' @examples #' GET("http://google.com/") #' GET("http://google.com/", path = "search") #' GET("http://google.com/", path = "search", query = list(q = "ham")) #' #' # See what GET is doing with httpbin.org #' url <- "http://httpbin.org/get" #' GET(url) #' GET(url, add_headers(a = 1, b = 2)) #' GET(url, set_cookies(a = 1, b = 2)) #' GET(url, add_headers(a = 1, b = 2), set_cookies(a = 1, b = 2)) #' GET(url, authenticate("username", "password")) #' GET(url, verbose()) #' #' # You might want to manually specify the handle so you can have multiple #' # independent logins to the same website. #' google <- handle("http://google.com") #' GET(handle = google, path = "/") #' GET(handle = google, path = "search") GET <- function(url = NULL, config = list(), ..., handle = NULL) { hu <- handle_url(handle, url, ...) req <- request_build("GET", hu$url, as.request(config), ...) request_perform(req, hu$handle$handle) } httr/R/authenticate.r0000644000176200001440000000216013401555654014304 0ustar liggesusers#' Use http authentication. #' #' It's not obvious how to turn authentication off after using it, so #' I recommend using custom handles with authentication. #' #' @param user user name #' @param password password #' @param type type of HTTP authentication. Should be one of the following #' types supported by Curl: basic, digest, digest_ie, gssnegotiate, #' ntlm, any. It defaults to "basic", the most common type. #' @export #' @family config #' @examples #' GET("http://httpbin.org/basic-auth/user/passwd") #' GET( #' "http://httpbin.org/basic-auth/user/passwd", #' authenticate("user", "passwd") #' ) authenticate <- function(user, password, type = "basic") { stopifnot(is.character(user), length(user) == 1) stopifnot(is.character(password), length(password) == 1) stopifnot(is.character(type), length(type) == 1) config(httpauth = auth_flags(type), userpwd = paste0(user, ":", password)) } auth_flags <- function(x = "basic") { constants <- c( basic = 1, digest = 2, gssnegotiate = 4, ntlm = 8, digest_ie = 16, any = -17 ) x <- match.arg(x, names(constants)) constants[[x]] } httr/R/doctor.R0000644000176200001440000000142113401555670013055 0ustar liggesusers#' Diagnose common configuration problems #' #' Currently one check: that curl uses nss. #' #' @export httr_dr <- function() { check_for_nss() } check_for_nss <- function() { if (!grepl("^NSS", curl::curl_version()$ssl_version)) return() warning(' ------------------------------------------------------------------------ Your installed RCurl is linked to the NSS library (`libcurl4-nss-dev`) which is likely to cause issues. To resolve the problem: 1. Quit R. 2. Install OpenSSL (`apt-get install libcurl4-openssl-dev`) or GnuTLS (`apt-get install libcurl4-gnutls-dev`) variants of libCurl. 3. Restart R. 4. Reinstall RCurl: `install.packages("RCurl")`. ------------------------------------------------------------------------ ', call. = FALSE) } httr/R/oauth-signature.r0000644000176200001440000001002113401555714014735 0ustar liggesusers#' Sign an OAuth request #' #' Deprecated. Instead create a config object directly using #' `config(token = my_token)`. #' #' @keywords internal #' @name sign_oauth NULL #' @export #' @rdname sign_oauth sign_oauth1.0 <- function(app, token = NULL, token_secret = NULL, as_header = TRUE, ...) { params <- list(as_header = as_header) credentials <- list(oauth_token = token, oauth_token_secret = token_secret) token <- Token1.0$new( endpoint = NULL, params = params, app = app, credentials = credentials ) request(auth_token = token) } #' @export #' @rdname sign_oauth sign_oauth2.0 <- function(access_token, as_header = TRUE) { stop("Deprecated: supply token object to config directly", call. = FALSE) } #' Generate oauth signature. #' #' For advanced use only. Occassionally needed for sites that use some #' components of the OAuth spec, but not all of them (e.g. 2-legged oauth) #' #' @param url,method Url and http method of request. #' @param app [oauth_app()] object representing application. #' @param token,token_secret OAuth token and secret. #' @param other_params Named argument providing additional parameters #' (e.g. `oauth_callback` or `oauth_body_hash`). #' @export #' @keywords internal #' @return A list of oauth parameters. oauth_signature <- function(url, method = "GET", app, token = NULL, token_secret = NULL, private_key = NULL, other_params = NULL) { if (!is.null(private_key)) { signature_method <- "RSA-SHA1" } else { signature_method <- "HMAC-SHA1" } oauth_params <- compact(list( oauth_consumer_key = app$key, oauth_nonce = nonce(), oauth_signature_method = signature_method, oauth_timestamp = as.integer(Sys.time()), oauth_version = "1.0", oauth_token = token )) if (length(other_params) > 0) { oauth_params <- c(oauth_params, other_params) } norm_method <- toupper(method) norm_url <- oauth_normalise_url(url) norm_params <- oauth_normalize_params(url, oauth_params) norm_req <- paste0( norm_method, "&", oauth_encode(norm_url), "&", oauth_encode(norm_params) ) # Generate signature if (signature_method == "HMAC-SHA1") { private_key <- paste0(oauth_encode(app$secret), "&", oauth_encode(token_secret)) } oauth_params$oauth_signature <- sha1_hash(private_key, norm_req, signature_method) sort_names(oauth_params) } #' @rdname oauth_signature #' @export oauth_header <- function(info) { oauth <- paste0( "OAuth ", paste0( oauth_encode(names(info)), "=\"", oauth_encode(info), "\"", collapse = ", " ) ) add_headers(Authorization = oauth) } oauth_encode <- function(x) vapply(x, oauth_encode1, character(1)) # As described in http://tools.ietf.org/html/rfc5849#page-29 oauth_encode1 <- function(x) { encode <- function(x) paste0("%", toupper(as.character(charToRaw(x))), collapse = "") x <- as.character(x) chars <- strsplit(x, "")[[1]] ok <- !str_detect(chars, "[^A-Za-z0-9_.~-]") if (all(ok)) return(x) chars[!ok] <- unlist(lapply(chars[!ok], encode)) paste0(chars, collapse = "") } oauth_normalise_url <- function(url) { url <- parse_url(url) # > Unless specified, URL scheme and authority MUST be lowercase and include # > the port number; http default port 80 and https default port 443 MUST be # > excluded. # > --- https://oauth.net/core/1.0/#anchor14 url$scheme <- tolower(url$scheme) if (url$scheme == "http" && identical(url$port, "80")) { url$port <- NULL } else if (url$scheme == "https" && identical(url$port, "443")) { url$port <- NULL } build_url(url[c("scheme", "hostname", "port", "url", "path")]) } oauth_normalize_params <- function(url, extra) { # Collect params, encode, sort and concatenate into a single string url <- parse_url(url) params <- c(url$query, extra) params_esc <- stats::setNames(oauth_encode(params), oauth_encode(names(params))) params_srt <- sort_names(params_esc) params_str <- paste0(names(params_srt), "=", params_srt, collapse = "&") params_str } httr/R/date.R0000644000176200001440000000327413401555670012510 0ustar liggesusers#' Parse and print http dates. #' #' @description #' As defined in RFC2616, #' , there are #' three valid formats: #' #' * Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 #' * Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 #' * Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format #' #' @param x For `parse_http_date`, a character vector of strings to parse. #' All elements must be of the same type. #' #' For `http_date`, a `POSIXt` vector. #' @param failure What to return on failure? #' @export #' @return A POSIXct object if succesful, otherwise `failure` #' @examples #' parse_http_date("Sun, 06 Nov 1994 08:49:37 GMT") #' parse_http_date("Sunday, 06-Nov-94 08:49:37 GMT") #' parse_http_date("Sun Nov 6 08:49:37 1994") #' #' http_date(Sys.time()) parse_http_date <- function(x, failure = structure(NA_real_, class = "Date")) { if (length(x) == 0) return(NULL) fmts <- c( "%a, %d %b %Y %H:%M:%S", "%A, %d-%b-%y %H:%M:%S", "%a %b %d %H:%M:%S %Y" ) # Need to set C locale to ensure that months/days are parsed in English for (fmt in fmts) { parsed <- c_time(as.POSIXct(strptime(x, fmt, tz = "GMT"))) if (all(!is.na(parsed))) return(parsed) } rep(failure, length(x)) } #' @export #' @rdname parse_http_date http_date <- function(x) { if (is.null(x)) return(NULL) stopifnot(inherits(x, "POSIXt")) c_time(strftime(x, "%a, %d %b %Y %H:%M:%S", tz = "GMT", usetz = TRUE)) } c_time <- function(code) { old <- Sys.getlocale(category = "LC_TIME") Sys.setlocale(category = "LC_TIME", locale = "C") on.exit(Sys.setlocale(category = "LC_TIME", locale = old)) force(code) } httr/R/cache.R0000644000176200001440000000722313401555657012641 0ustar liggesusers#' Compute caching information for a response. #' #' `cache_info()` gives details of cacheability of a response, #' `rerequest()` re-performs the original request doing as little work #' as possible (if not expired, returns response as is, or performs #' revalidation if Etag or Last-Modified headers are present). #' #' @param r A response #' @export #' @examples #' # Never cached, always causes redownload #' r1 <- GET("https://www.google.com") #' cache_info(r1) #' r1$date #' rerequest(r1)$date #' #' # Expires in a year #' r2 <- GET("https://www.google.com/images/srpr/logo11w.png") #' cache_info(r2) #' r2$date #' rerequest(r2)$date #' #' # Has last-modified and etag, so does revalidation #' r3 <- GET("http://httpbin.org/cache") #' cache_info(r3) #' r3$date #' rerequest(r3)$date #' #' # Expires after 5 seconds #' \dontrun{ #' r4 <- GET("http://httpbin.org/cache/5") #' cache_info(r4) #' r4$date #' rerequest(r4)$date #' Sys.sleep(5) #' cache_info(r4) #' rerequest(r4)$date #' } cache_info <- function(r) { stopifnot(is.response(r)) # If parsing fails using -Inf; If missing use NA expires <- parse_http_date(r$headers$expires, Inf) %||% NULL # Parse cache control control <- parse_cache_control(r$headers$`cache-control`) max_age <- as.integer(control$`max-age`) %||% NULL # Compute expiry if (!is.null(max_age)) { expires <- r$date + max_age } else if (!is.null(r$headers$expires)) { expires <- parse_http_date(r$headers$expires, -Inf) } else { expires <- NULL } # Is this cacheable? cacheable <- r$request$method %in% c("GET", "HEAD") && status_code(r) %in% c(200L, 203L, 300L, 301L, 410L) && (!is.null(expires) || !is.null(r$headers$etag) || !is.null(r$headers$`last-modified`)) && !any(c("no-store", "no-cache") %in% control$flags) # What impact should any(c("public", "private") %in% control$flags) have? structure( list( method = r$method, url = r$url, cacheable = cacheable, expires = expires, etag = r$headers$etag %||% NULL, modified = parse_http_date(r$headers$`last-modified`, NULL) ), class = "cache_info" ) } is.cache_info <- function(x) inherits(x, "cache_info") #' @export print.cache_info <- function(x, ...) { cat(" ", x$method, " ", x$url, "\n", sep = "") cat(" Cacheable: ", x$cacheable, "\n", sep = "") if (!is.null(x$expires)) { cat(" Expires: ", http_date(x$expires), sep = "") if (x$expires < Sys.time()) cat(" ") cat("\n") } cat(" Last-Modified: ", http_date(x$modified), "\n", sep = "") cat(" Etag: ", x$etag, "\n", sep = "") invisible(x) } parse_cache_control <- function(x) { if (is.null(x)) return(list()) pieces <- strsplit(x, ",")[[1]] pieces <- gsub("^\\s+|\\s+$", "", pieces) pieces <- tolower(pieces) is_value <- grepl("=", pieces) flags <- pieces[!is_value] keyvalues <- strsplit(pieces[is_value], "\\s*=\\s*") keys <- c(rep("flags", length(flags)), lapply(keyvalues, "[[", 1)) values <- c(flags, lapply(keyvalues, "[[", 2)) stats::setNames(values, keys) } #' @rdname cache_info #' @export rerequest <- function(r) { x <- cache_info(r) if (!x$cacheable) { return(reperform(r)) } # Cacheable, and hasn't expired if (!is.null(x$expires) && x$expires >= Sys.time()) { return(r) } # Requires validation req <- c( r$request, request(headers = c( `If-Modified-Since` = http_date(x$modified), `If-None-Match` = x$etag )) ) validated <- request_perform(req, r$handle) if (status_code(validated) == 304L) { r } else { validated } } reperform <- function(resp) { request_perform(resp$request, resp$handle) } httr/R/str.R0000644000176200001440000000330613401555510012370 0ustar liggesusers# These are drop in replacements for the stringr:: functions used in httr. They # do not retain all functionality from stringr, only that which is used in # httr. Notably they are generally not vectorized. str_trim <- function(x) { gsub("(^\\s+)|(\\s+$)", "", x) } str_split_fixed <- function(string, pattern, n) { if (length(string) == 0) return(matrix(character(), nrow = 1, ncol = n)) m <- gregexpr(pattern, string)[[1]] if (length(m) == 1 && m == -1) { res <- string } else { m_starts <- m m_ends <- m + attr(m, "match.length") - 1L starts <- c(1, m_ends + 1L)[seq_len(n)] ends <- c((m_starts - 1L)[seq_len(n - 1)], nchar(string)) res <- lapply(string, function(x) unlist(Map(substr, x, starts, ends, USE.NAMES = FALSE)) ) } mat <- matrix("", nrow = length(res), ncol = n, byrow = TRUE) mat[seq_along(unlist(res))] <- unlist(res) mat[, seq_len(n), drop = FALSE] } str_split <- function(string, pattern, n = Inf) { res <- strsplit(string, pattern) if (is.finite(n)) { res[seq_len(n)] } else { res } } str_detect <- function(string, pattern) { grepl(pattern, string) } str_match <- function(string, pattern) { m <- regexpr(pattern, string, perl = TRUE) cbind( substr(string, m, attr(m, "match.length") + m - 1L), substr( string, attr(m, "capture.start"), attr(m, "capture.length") + attr(m, "capture.start") - 1L ) ) } str_replace <- function(string, pattern, replace) { m <- regexpr(pattern, string) regmatches(string, m) <- replace string } str_extract <- function(string, pattern) { m <- regexpr(pattern, string) res <- substr(string, m, attr(m, "match.length") + m - 1L) res[m == -1] <- NA_character_ res } httr/R/callback.R0000644000176200001440000000656413401555661013334 0ustar liggesusers callback_env <- new.env(parent = emptyenv()) callback_env$request <- NULL callback_env$response <- NULL #' Install or uninstall a callback function #' #' Supported callback functions: \describe{ #' \item{\sQuote{request}}{This callback is called before an HTTP request #' is performed, with the `request` object as an argument. #' If the callback returns a value other than `NULL`, the HTTP #' request is not performed at all, and the return value of the callback #' is returned. This mechanism can be used to replay previously #' recorded HTTP responses. #' } #' \item{\sQuote{response}}{This callback is called after an HTTP request #' is performed. The callback is called with two arguments: the #' `request` object and the `response` object of the HTTP #' request. If this callback returns a value other than `NULL`, #' then this value is returned by `httr`.} #' } #' #' Note that it is not possible to install multiple callbacks of the same #' type. The installed callback overwrites the previously intalled one. #' To uninstall a callback function, set it to `NULL` with #' `set_callback()`. #' #' See the `httrmock` package for a proper example that uses #' callbacks. #' #' @param name Character scalar, name of the callback to query or set. #' @param new_callback The callback function to install, a function object; #' or `NULL` to remove the currently installed callback (if any). #' #' @return `get_callback` returns the currently installed #' callback, or `NULL` if none is installed. #' #' `set_callback` returns the previously installed callback, #' or `NULL` if none was installed. #' #' @export #' @examples #' \dontrun{ #' ## Log all HTTP requests to the screeen #' req_logger <- function(req) { #' cat("HTTP request to", sQuote(req$url), "\n") #' } #' #' old <- set_callback("request", req_logger) #' g1 <- GET("https://httpbin.org") #' g2 <- GET("https://httpbin.org/ip") #' set_callback("request", old) #' #' ## Log all HTTP requests and response status codes as well #' req_logger2 <- function(req) { #' cat("HTTP request to", sQuote(req$url), "... ") #' } #' res_logger <- function(req, res) { #' cat(res$status_code, "\n") #' } #' #' old_req <- set_callback("request", req_logger2) #' old_res <- set_callback("response", res_logger) #' g3 <- GET("https://httpbin.org") #' g4 <- GET("https://httpbin.org/ip") #' set_callback("request", old_req) #' set_callback("response", old_res) #' #' ## Return a recorded response, without performing the HTTP request #' replay <- function(req) { #' if (req$url == "https://httpbin.org") g3 #' } #' old_req <- set_callback("request", replay) #' grec <- GET("https://httpbin.org") #' grec$date == g3$date #' set_callback("request", old_req) #' } #' get_callback <- function(name) { stopifnot(is.character(name), length(name) == 1, !is.na(name)) if (!name %in% ls(callback_env)) stop("Unknown httr callback: ", name) callback_env[[name]] } #' @export #' @rdname get_callback set_callback <- function(name, new_callback = NULL) { stopifnot(is.character(name), length(name) == 1, !is.na(name)) if (!name %in% ls(callback_env)) stop("Unknown httr callback: ", name) old <- callback_env[[name]] stopifnot(is.null(new_callback) || is.function(new_callback)) callback_env[[name]] <- new_callback invisible(old) } perform_callback <- function(name, ...) { if (!is.null(callback <- get_callback(name))) { callback(...) } } httr/R/http-patch.r0000644000176200001440000000075613401555676013717 0ustar liggesusers#' Send PATCH request to a server. #' #' @inherit GET params return #' @inheritParams POST #' @family http methods #' @export PATCH <- function(url = NULL, config = list(), ..., body = NULL, encode = c("multipart", "form", "json", "raw"), handle = NULL) { encode <- match.arg(encode) hu <- handle_url(handle, url, ...) req <- request_build("PATCH", hu$url, body_config(body, match.arg(encode)), config, ...) request_perform(req, hu$handle$handle) } httr/R/user-agent.r0000644000176200001440000000056713401555510013700 0ustar liggesusers#' Set user agent. #' #' Override the default RCurl user agent of `NULL` #' #' @param agent string giving user agent #' @export #' @family config #' @examples #' GET("http://httpbin.org/user-agent") #' GET("http://httpbin.org/user-agent", user_agent("httr")) user_agent <- function(agent) { stopifnot(is.character(agent), length(agent) == 1) config(useragent = agent) } httr/R/http-head.r0000644000176200001440000000250613401555676013514 0ustar liggesusers#' Get url HEADers. #' #' @section RFC2616: #' The HEAD method is identical to GET except that the server MUST NOT return #' a message-body in the response. The metainformation contained in the HTTP #' headers in response to a HEAD request SHOULD be identical to the #' information sent in response to a GET request. This method can be used for #' obtaining metainformation about the entity implied by the request without #' transferring the entity-body itself. This method is often used for testing #' hypertext links for validity, accessibility, and recent modification. #' #' The response to a HEAD request MAY be cacheable in the sense that the #' information contained in the response MAY be used to update a previously #' cached entity from that resource. If the new field values indicate that the #' cached entity differs from the current entity (as would be indicated by a #' change in Content-Length, Content-MD5, ETag or Last-Modified), then the #' cache MUST treat the cache entry as stale. #' #' @inherit GET params return #' @family http methods #' @export #' @examples #' HEAD("http://google.com") #' headers(HEAD("http://google.com")) HEAD <- function(url = NULL, config = list(), ..., handle = NULL) { hu <- handle_url(handle, url, ...) req <- request_build("HEAD", hu$url, config, ...) request_perform(req, hu$handle$handle) } httr/R/oauth-cache.R0000644000176200001440000000473113401555704013751 0ustar liggesusersuse_cache <- function(cache = getOption("httr_oauth_cache")) { if (length(cache) != 1) { stop("cache should be length 1 vector", call. = FALSE) } if (!is.logical(cache) && !is.character(cache)) { stop("Cache must either be logical or string (file path)") } # If missing, see if it's ok to use one, and cache the results of # that check in a global option. if (is.na(cache)) { cache <- can_use_cache() options("httr_oauth_cache" = cache) } ## cache is now TRUE, FALSE or path if (isFALSE(cache)) { return(NULL) } if (isTRUE(cache)) { cache <- ".httr-oauth" } if (!file.exists(cache)) { create_cache(cache) } return(cache) } can_use_cache <- function(path = ".httr-oauth") { file.exists(path) || should_cache(path) } should_cache <- function(path = ".httr-oauth") { if (!interactive()) return(FALSE) cat("Use a local file ('", path, "'), to cache OAuth access credentials ", "between R sessions?\n", sep = "" ) utils::menu(c("Yes", "No")) == 1 } create_cache <- function(path = ".httr-oauth") { file.create(path, showWarnings = FALSE) if (!file.exists(path)) { stop("Failed to create local cache ('", path, "')", call. = FALSE) } # Protect cache as much as possible Sys.chmod(path, "0600") if (file.exists("DESCRIPTION")) { add_line(".Rbuildignore", paste0("^", gsub("\\.", "\\\\.", path), "$")) } add_line(".gitignore", path) TRUE } add_line <- function(path, line, quiet = FALSE) { if (file.exists(path)) { lines <- readLines(path, warn = FALSE) lines <- lines[lines != ""] } else { lines <- character() } if (line %in% lines) return(TRUE) if (!quiet) message("Adding ", line, " to ", path) lines <- c(lines, line) try(writeLines(lines, path)) TRUE } cache_token <- function(token, cache_path) { if (is.null(cache_path)) return() tokens <- load_cache(cache_path) tokens[[token$hash()]] <- token saveRDS(tokens, cache_path) } fetch_cached_token <- function(hash, cache_path) { if (is.null(cache_path)) return() load_cache(cache_path)[[hash]] } remove_cached_token <- function(token) { if (is.null(token$cache_path)) return() tokens <- load_cache(token$cache_path) tokens[[token$hash()]] <- NULL saveRDS(tokens, token$cache_path) } load_cache <- function(cache_path) { if (!file.exists(cache_path) || file_size(cache_path) == 0) { list() } else { readRDS(cache_path) } } file_size <- function(x) file.info(x, extra_cols = FALSE)$size httr/R/handle-url.r0000644000176200001440000000073613401555672013670 0ustar liggesusershandle_url <- function(handle = NULL, url = NULL, ...) { if (is.null(url) && is.null(handle)) { stop("Must specify at least one of url or handle") } if (is.null(handle)) handle <- handle_find(url) if (is.null(url)) url <- handle$url # If named components in ..., modify the url new <- named(list(...)) if (length(new) > 0 || is.url(url)) { old <- parse_url(url) url <- build_url(utils::modifyList(old, new)) } list(handle = handle, url = url) } httr/R/headers.r0000644000176200001440000001004613402227466013241 0ustar liggesusers#' Extract the headers from a response #' #' @param x A request object #' @seealso [add_headers()] to send additional headers in a #' request #' @export #' @examples #' r <- GET("http://httpbin.org/get") #' headers(r) headers <- function(x) UseMethod("headers") #' @export headers.response <- function(x) { x$headers } #' Add additional headers to a request. #' #' Wikipedia provides a useful list of common http headers: #' . #' #' @param ... named header values. To stop an existing header from being #' set, pass an empty string: `""`. #' @param .headers a named character vector #' @export #' @seealso [accept()] and [content_type()] for #' convenience functions for setting accept and content-type headers. #' @family config #' @examples #' add_headers(a = 1, b = 2) #' add_headers(.headers = c(a = "1", b = "2")) #' #' GET("http://httpbin.org/headers") #' #' # Add arbitrary headers #' GET( #' "http://httpbin.org/headers", #' add_headers(version = version$version.string) #' ) #' #' # Override default headers with empty strings #' GET("http://httpbin.org/headers", add_headers(Accept = "")) add_headers <- function(..., .headers = character()) { request(headers = c(..., .headers)) } #' Set content-type and accept headers. #' #' These are convenient wrappers aroud [add_headers()]. #' #' `accept_json`/`accept_xml` and #' `content_type_json`/`content_type_xml` are useful shortcuts to #' ask for json or xml responses or tell the server you are sending json/xml. #' #' @param type A mime type or a file extension. If a file extension (i.e. starts #' with `.`) will guess the mime type using [mime::guess_type()]. #' @export #' @examples #' GET("http://httpbin.org/headers") #' #' GET("http://httpbin.org/headers", accept_json()) #' GET("http://httpbin.org/headers", accept("text/csv")) #' GET("http://httpbin.org/headers", accept(".doc")) #' #' GET("http://httpbin.org/headers", content_type_xml()) #' GET("http://httpbin.org/headers", content_type("text/csv")) #' GET("http://httpbin.org/headers", content_type(".xml")) content_type <- function(type) { if (is.null(type)) return() if (substr(type, 1, 1) == ".") { type <- mime::guess_type(type, empty = NULL) } add_headers("Content-Type" = type) } #' @export #' @rdname content_type content_type_json <- function() content_type("application/json") #' @export #' @rdname content_type content_type_xml <- function() content_type("application/xml") #' @export #' @rdname content_type accept <- function(type) { if (substr(type, 1, 1) == ".") { type <- mime::guess_type(type, empty = NULL) } add_headers("Accept" = type) } #' @export #' @rdname content_type accept_json <- function() accept("application/json") #' @export #' @rdname content_type accept_xml <- function() accept("application/xml") # Parses a header lines as recieved from libcurl. Multiple responses # will be intermingled, each separated by an http status line. parse_http_headers <- function(raw) { lines <- strsplit(rawToChar(raw), "\r?\n")[[1]] new_response <- grepl("^HTTP", lines) grps <- cumsum(new_response) lapply(unname(split(lines, grps)), parse_single_header) } parse_single_header <- function(lines) { status <- parse_http_status(lines[[1]]) # Parse headers into name-value pairs header_lines <- lines[lines != ""][-1] pos <- regexec("^([^:]*):\\s*(.*)$", header_lines) pieces <- regmatches(header_lines, pos) n <- vapply(pieces, length, integer(1)) if (any(n != 3)) { bad <- header_lines[n != 3] pieces <- pieces[n == 3] warning("Failed to parse headers:\n", paste0(bad, "\n"), call. = FALSE) } names <- vapply(pieces, "[[", 2, FUN.VALUE = character(1)) values <- lapply(pieces, "[[", 3) headers <- insensitive(stats::setNames(values, names)) list(status = status$status, version = status$version, headers = headers) } parse_http_status <- function(x) { status <- as.list(strsplit(x, "\\s+")[[1]]) names(status) <- c("version", "status", "message")[seq_along(status)] status$status <- as.integer(status$status) status } httr/R/response-status.r0000644000176200001440000002415413504410572015005 0ustar liggesusers#' Extract status code from response. #' #' @param x A response #' @export status_code <- function(x) UseMethod("status_code") #' @export status_code.response <- function(x) x$status_code #' @export status_code.numeric <- function(x) x #' Give information on the status of a request. #' #' Extract the http status code and convert it into a human readable message. #' #' http servers send a status code with the response to each request. This code #' gives information regarding the outcome of the execution of the request #' on the server. Roughly speaking, codes in the 100s and 200s mean the request #' was successfully executed; codes in the 300s mean the page was redirected; #' codes in the 400s mean there was a mistake in the way the client sent the #' request; codes in the 500s mean the server failed to fulfill #' an apparently valid request. More details on the codes can be found at #' `http://en.wikipedia.org/wiki/Http_error_codes`. #' #' @param x a request object or a number. #' @return If the status code does not match a known status, an error. #' Otherwise, a list with components #' \item{category}{the broad category of the status} #' \item{message}{the meaning of the status code} #' @family response methods #' @examples #' http_status(100) #' http_status(404) #' #' x <- GET("http://httpbin.org/status/200") #' http_status(x) #' #' http_status(GET("http://httpbin.org/status/300")) #' http_status(GET("http://httpbin.org/status/301")) #' http_status(GET("http://httpbin.org/status/404")) #' #' # errors out on unknown status #' \dontrun{ #' http_status(GET("http://httpbin.org/status/320")) #' } #' @export http_status <- function(x) { status <- status_code(x) if (!status %in% names(http_statuses)) { stop("Unknown http status code: ", status, call. = FALSE) } status_desc <- http_statuses[[as.character(status)]] status_types <- c( "Information", "Success", "Redirection", "Client error", "Server error" ) status_type <- status_types[[status %/% 100]] # create the final information message message <- paste(status_type, ": (", status, ") ", status_desc, sep = "") list( category = status_type, reason = status_desc, message = message ) } http_statuses <- c( "100" = "Continue", "101" = "Switching Protocols", "102" = "Processing (WebDAV; RFC 2518)", "200" = "OK", "201" = "Created", "202" = "Accepted", "203" = "Non-Authoritative Information", "204" = "No Content", "205" = "Reset Content", "206" = "Partial Content", "207" = "Multi-Status (WebDAV; RFC 4918)", "208" = "Already Reported (WebDAV; RFC 5842)", "226" = "IM Used (RFC 3229)", "300" = "Multiple Choices", "301" = "Moved Permanently", "302" = "Found", "303" = "See Other", "304" = "Not Modified", "305" = "Use Proxy", "306" = "Switch Proxy", "307" = "Temporary Redirect", "308" = "Permanent Redirect (experimental Internet-Draft)", "400" = "Bad Request", "401" = "Unauthorized", "402" = "Payment Required", "403" = "Forbidden", "404" = "Not Found", "405" = "Method Not Allowed", "406" = "Not Acceptable", "407" = "Proxy Authentication Required", "408" = "Request Timeout", "409" = "Conflict", "410" = "Gone", "411" = "Length Required", "412" = "Precondition Failed", "413" = "Request Entity Too Large", "414" = "Request-URI Too Long", "415" = "Unsupported Media Type", "416" = "Requested Range Not Satisfiable", "417" = "Expectation Failed", "418" = "I'm a teapot (RFC 2324)", "420" = "Enhance Your Calm (Twitter)", "422" = "Unprocessable Entity (WebDAV; RFC 4918)", "423" = "Locked (WebDAV; RFC 4918)", "424" = "Failed Dependency (WebDAV; RFC 4918)", "424" = "Method Failure (WebDAV)", "425" = "Unordered Collection (Internet draft)", "426" = "Upgrade Required (RFC 2817)", "428" = "Precondition Required (RFC 6585)", "429" = "Too Many Requests (RFC 6585)", "431" = "Request Header Fields Too Large (RFC 6585)", "444" = "No Response (Nginx)", "449" = "Retry With (Microsoft)", "450" = "Blocked by Windows Parental Controls (Microsoft)", "451" = "Unavailable For Legal Reasons (Internet draft)", "499" = "Client Closed Request (Nginx)", "500" = "Internal Server Error", "501" = "Not Implemented", "502" = "Bad Gateway", "503" = "Service Unavailable", "504" = "Gateway Timeout", "505" = "HTTP Version Not Supported", "506" = "Variant Also Negotiates (RFC 2295)", "507" = "Insufficient Storage (WebDAV; RFC 4918)", "508" = "Loop Detected (WebDAV; RFC 5842)", "509" = "Bandwidth Limit Exceeded (Apache bw/limited extension)", "510" = "Not Extended (RFC 2774)", "511" = "Network Authentication Required (RFC 6585)", "598" = "Network read timeout error (Unknown)", "599" = "Network connect timeout error (Unknown)" ) #' Check for an http error. #' #' @param x Object to check. Default methods are provided for strings #' (which perform an [HEAD()] request), responses, and #' integer status codes. #' @param ... Other arguments passed on to methods. #' @return `TRUE` if the request fails (status code 400 or above), #' otherwise `FALSE`. #' @export #' @family response methods #' @examples #' # You can pass a url: #' http_error("http://www.google.com") #' http_error("http://httpbin.org/status/404") #' #' # Or a request #' r <- GET("http://httpbin.org/status/201") #' http_error(r) #' #' # Or an (integer) status code #' http_error(200L) #' http_error(404L) http_error <- function(x, ...) { UseMethod("http_error") } #' @export http_error.character <- function(x, ...) { http_error(HEAD(x, ...)) } #' @export http_error.response <- function(x, ...) { http_error(status_code(x)) } #' @export http_error.integer <- function(x, ...) { x >= 400L } #' @export #' @rdname http_error #' @usage NULL url_success <- function(x, ...) { warning( "`url_success(x)` is deprecated; please use `!http_error(x)` instead.", call. = FALSE ) !http_error(x, ...) } #' @export #' @rdname http_error #' @usage NULL url_ok <- function(x, ...) { warning( "`url_ok(x)` is deprecated; ", "please use `identical(status_code(x), 200L)` instead.", call. = FALSE ) identical(status_code(HEAD(x, ...)), 200L) } #' Take action on http error. #' #' Converts http errors to R errors or warnings - these should always #' be used whenever you're creating requests inside a function, so #' that the user knows why a request has failed. #' #' @return If request was successful, the response (invisibly). Otherwise, #' raised a classed http error or warning, as generated by #' [http_condition()] #' @inheritParams http_condition #' @seealso [http_status()] and #' `http://en.wikipedia.org/wiki/Http_status_codes` for more information #' on http status codes. #' @family response methods #' @examples #' x <- GET("http://httpbin.org/status/200") #' stop_for_status(x) # nothing happens #' warn_for_status(x) #' message_for_status(x) #' #' x <- GET("http://httpbin.org/status/300") #' \dontrun{ #' stop_for_status(x) #' } #' warn_for_status(x) #' message_for_status(x) #' #' x <- GET("http://httpbin.org/status/404") #' \dontrun{ #' stop_for_status(x) #' } #' warn_for_status(x) #' message_for_status(x) #' #' # You can provide more information with the task argument #' warn_for_status(x, "download spreadsheet") #' message_for_status(x, "download spreadsheet") #' @export stop_for_status <- function(x, task = NULL) { if (status_code(x) < 300) { return(invisible(x)) } call <- sys.call(-1) stop(http_condition(x, "error", task = task, call = call)) } #' @rdname stop_for_status #' @export warn_for_status <- function(x, task = NULL) { if (status_code(x) < 300) { return(invisible(x)) } call <- sys.call(-1) warning(http_condition(x, "warning", task = task, call = call)) } #' @rdname stop_for_status #' @export message_for_status <- function(x, task = NULL) { call <- sys.call(-1) message(http_condition(x, "message", task = task, call = call)) } #' Generate a classed http condition. #' #' This function generate S3 condition objects which are passed to #' [stop()] or [warning()] to generate classes warnings #' and error. These can be used in conjunction with [tryCatch()] #' to respond differently to different type of failure. #' #' @keywords internal #' @return An S3 object that inherits from (e.g.) condition, `type`, #' http_error, http_400 and http_404. #' @param x a response, or numeric http code (or other object with #' `status_code` method) #' @param type type of condition to generate. Must be one of error, #' warning or message. #' @param task The text of the message: either `NULL` or a #' character vector. If non-`NULL`, the error message will finish with #' "Failed to `task`". #' @param call The call stored in the condition object. #' @seealso #' #' for more details about R's condition handling model #' @export #' @examples #' # You can use tryCatch to take different actions based on the type #' # of error. Note that tryCatch will call the first handler that #' # matches any classes of the condition, not the best matching, so #' # always list handlers from most specific to least specific #' f <- function(url) { #' tryCatch(stop_for_status(GET(url)), #' http_404 = function(c) "That url doesn't exist", #' http_403 = function(c) "You need to authenticate!", #' http_400 = function(c) "You made a mistake!", #' http_500 = function(c) "The server screwed up" #' ) #' } #' f("http://httpbin.org/status/404") #' f("http://httpbin.org/status/403") #' f("http://httpbin.org/status/505") http_condition <- function(x, type, task = NULL, call = sys.call(-1)) { type <- match.arg(type, c("error", "warning", "message")) if (is.null(task)) { task <- "" } else if (is.character(task)) { task <- paste0(" Failed to ", task, ".") } else { stop("`task` must be NULL or a character vector", call. = FALSE) } status <- status_code(x) reason <- http_status(status)$reason message <- sprintf("%s (HTTP %d).%s", reason, status, task) status_type <- (status %/% 100) * 100 http_class <- paste0("http_", unique(c(status, status_type, "error"))) structure( list(message = message, call = call), class = c(http_class, type, "condition") ) } httr/R/cookies.r0000644000176200001440000000207413401555667013272 0ustar liggesusers#' Set cookies. #' #' @param ... a named cookie values #' @param .cookies a named character vector #' @export #' @family config #' @seealso [cookies()] to see cookies in response. #' @examples #' set_cookies(a = 1, b = 2) #' set_cookies(.cookies = c(a = "1", b = "2")) #' #' GET("http://httpbin.org/cookies") #' GET("http://httpbin.org/cookies", set_cookies(a = 1, b = 2)) set_cookies <- function(..., .cookies = character(0)) { cookies <- c(..., .cookies) stopifnot(is.character(cookies)) cookies_str <- vapply(cookies, curl::curl_escape, FUN.VALUE = character(1)) cookie <- paste(names(cookies), cookies_str, sep = "=", collapse = ";") config(cookie = cookie) } #' Access cookies in a response. #' #' @seealso [set_cookies()] to send cookies in request. #' @param x A response. #' @examples #' r <- GET("http://httpbin.org/cookies/set", query = list(a = 1, b = 2)) #' cookies(r) #' @export cookies <- function(x) UseMethod("cookies") #' @export cookies.response <- function(x) x$cookies #' @export cookies.handle <- function(x) { curl::handle_cookies(x$handle) } httr/R/response-type.R0000644000176200001440000000106413401555730014400 0ustar liggesusers#' Extract the content type of a response #' #' @param x A response #' @return A string giving the complete mime type, with all parameters #' stripped off. #' @export #' @examples #' r1 <- GET("http://httpbin.org/image/png") #' http_type(r1) #' headers(r1)[["Content-Type"]] #' #' r2 <- GET("http://httpbin.org/ip") #' http_type(r2) #' headers(r2)[["Content-Type"]] http_type <- function(x) { stopifnot(is.response(x)) type <- x$headers[["Content-Type"]] %||% mime::guess_type(x$url, empty = "application/octet-stream") parse_media(type)$complete } httr/R/oauth-app.r0000644000176200001440000000462713401555702013530 0ustar liggesusers#' Create an OAuth application. #' #' See the demos for instructions on how to create an OAuth app for linkedin, #' twitter, vimeo, facebook, github and google. When wrapping an API from a #' package, the author may want to include a default app to facilitate early and #' casual use and then provide a method for heavy or advanced users to supply #' their own app or key and secret. #' #' @param appname name of the application. This is not used for OAuth, but is #' used to make it easier to identify different applications. #' @param key consumer key, also sometimes called the client ID #' @param secret consumer secret, also sometimes called the client secret. #' Despite its name, this does not necessarily need to be protected like a #' password, i.e. the user still has to authenticate themselves and grant the #' app permission to access resources on their behalf. For example, see #' Google's docs for #' \href{https://developers.google.com/identity/protocols/OAuth2#installed}{OAuth2 for installed applications}. #' @param redirect_uri The URL that user will be redirected to after #' authorisation is complete. You should generally leave this as the default #' unless you're using a non-standard auth flow (like with shiny). #' @export #' @family OAuth #' @examples #' \dontrun{ #' google_app <- oauth_app( #' "google", #' key = "123456789.apps.googleusercontent.com", #' secret = "abcdefghijklmnopqrstuvwxyz" #' ) #' } oauth_app <- function(appname, key, secret = NULL, redirect_uri = oauth_callback()) { if (missing(secret)) { env_name <- paste0(toupper(appname), "_CONSUMER_SECRET") secret <- Sys.getenv(env_name) if (secret == "") { warning("Couldn't find secret in environment variable ", env_name, call. = FALSE ) secret <- NULL } else { message("Using secret stored in environment variable ", env_name) } } structure( list( appname = appname, secret = secret, key = key, redirect_uri = redirect_uri ), class = "oauth_app" ) } is.oauth_app <- function(x) inherits(x, "oauth_app") #' @export print.oauth_app <- function(x, ...) { cat(" ", x$appname, "\n", sep = "") cat(" key: ", x$key, "\n", sep = "") secret <- if (is.null(x$secret)) "" else "" cat(" secret: ", secret, "\n", sep = "") invisible(x) } httr/R/oauth-token.r0000644000176200001440000002561413401555740014071 0ustar liggesusers#' OAuth token objects. #' #' These objects represent the complete set of data needed for OAuth access: #' an app, an endpoint, cached credentials and parameters. They should be #' created through their constructor functions [oauth1.0_token()] #' and [oauth2.0_token()]. #' #' @section Methods: #' * `cache()`: caches token to disk #' * `sign(method, url)`: returns list of url and config #' * `refresh()`: refresh access token (if possible) #' * `validate()`: TRUE if the token is still valid, FALSE otherwise #' #' @section Caching: #' OAuth tokens are cached on disk in a file called `.httr-oauth` #' saved in the current working directory. Caching is enabled if: #' #' * The session is interactive, and the user agrees to it, OR #' * The `.httr-oauth` file is already present, OR #' * `getOption("httr_oauth_cache")` is `TRUE` #' #' You can suppress caching by setting the `httr_oauth_cache` option to #' `FALSE`. #' #' Tokens are cached based on their endpoint and parameters. #' #' The cache file should not be included in source code control or R packages #' (because it contains private information), so httr will automatically add #' the appropriate entries to `.gitignore` and `.Rbuildignore` if needed. #' #' @docType class #' @keywords internal #' @format An R6 class object. #' @importFrom R6 R6Class #' @export #' @name Token-class Token <- R6::R6Class("Token", list( endpoint = NULL, app = NULL, credentials = NULL, params = NULL, cache_path = FALSE, private_key = NULL, initialize = function(app, endpoint, params = list(), credentials = NULL, private_key = NULL, cache_path = getOption("httr_oauth_cache")) { stopifnot( is.oauth_endpoint(endpoint) || !is.null(credentials), is.oauth_app(app), is.list(params) ) self$app <- app self$endpoint <- endpoint self$params <- params self$cache_path <- use_cache(cache_path) self$private_key <- private_key if (!is.null(credentials)) { # Use credentials created elsewhere - usually for tests self$credentials <- credentials return(self) } # Are credentials cache already? if (self$load_from_cache()) { self } else { self$init_credentials() self$cache() } }, print = function(...) { cat("\n", sep = "") print(self$endpoint) print(self$app) cat(" ", paste0(names(self$credentials), collapse = ", "), "\n", sep = "" ) cat("---\n") }, cache = function(path = self$cache_path) { cache_token(self, path) self }, load_from_cache = function() { if (is.null(self$cache_path)) return(FALSE) cached <- fetch_cached_token(self$hash(), self$cache_path) if (is.null(cached)) return(FALSE) self$endpoint <- cached$endpoint self$app <- cached$app self$credentials <- cached$credentials self$params <- cached$params TRUE }, hash = function() { # endpoint = which site # app = client identification # params = scope msg <- serialize( list(self$endpoint, self$app, normalize_scopes(self$params$scope)), NULL ) # for compatibility with digest::digest() paste(openssl::md5(msg[-(1:14)]), collapse = "") }, sign = function() { stop("Must be implemented by subclass", call. = FALSE) }, refresh = function() { stop("Must be implemented by subclass", call. = FALSE) }, init_credentials = function() { stop("Must be implemented by subclass", call. = FALSE) } )) #' Generate an oauth1.0 token. #' #' This is the final object in the OAuth dance - it encapsulates the app, #' the endpoint, other parameters and the received credentials. #' #' See [Token()] for full details about the token object, and the #' caching policies used to store credentials across sessions. #' #' @inheritParams init_oauth1.0 #' @param as_header If `TRUE`, the default, sends oauth in header. #' If `FALSE`, adds as parameter to url. #' @param cache A logical value or a string. `TRUE` means to cache #' using the default cache file `.httr-oauth`, `FALSE` means #' don't cache, and `NA` means to guess using some sensible heuristics. #' A string means use the specified path as the cache file. #' @return A `Token1.0` reference class (RC) object. #' @family OAuth #' @export oauth1.0_token <- function(endpoint, app, permission = NULL, as_header = TRUE, private_key = NULL, cache = getOption("httr_oauth_cache")) { params <- list( permission = permission, as_header = as_header ) Token1.0$new( app = app, endpoint = endpoint, params = params, private_key = private_key, cache_path = cache ) } #' @export #' @rdname Token-class Token1.0 <- R6::R6Class("Token1.0", inherit = Token, list( init_credentials = function(force = FALSE) { self$credentials <- init_oauth1.0( self$endpoint, self$app, permission = self$params$permission, private_key = self$private_key ) }, can_refresh = function() { FALSE }, refresh = function() { stop("Not implemented") }, sign = function(method, url) { oauth <- oauth_signature( url, method, self$app, self$credentials$oauth_token, self$credentials$oauth_token_secret, self$private_key ) if (isTRUE(self$params$as_header)) { c(request(url = url), oauth_header(oauth)) } else { url <- parse_url(url) url$query <- c(url$query, oauth) request(url = build_url(url)) } } )) #' Generate an oauth2.0 token. #' #' This is the final object in the OAuth dance - it encapsulates the app, #' the endpoint, other parameters and the received credentials. It is a #' reference class so that it can be seamlessly updated (e.g. using #' `$refresh()`) when access expires. #' #' See [Token()] for full details about the token object, and the #' caching policies used to store credentials across sessions. #' #' @inheritParams init_oauth2.0 #' @param as_header If `TRUE`, the default, configures the token to add #' itself to the bearer header of subsequent requests. If `FALSE`, #' configures the token to add itself as a url parameter of subsequent #' requests. #' @param credentials Advanced use only: allows you to completely customise #' token generation. #' @inheritParams oauth1.0_token #' @return A `Token2.0` reference class (RC) object. #' @family OAuth #' @export oauth2.0_token <- function(endpoint, app, scope = NULL, user_params = NULL, type = NULL, use_oob = getOption("httr_oob_default"), oob_value = NULL, as_header = TRUE, use_basic_auth = FALSE, cache = getOption("httr_oauth_cache"), config_init = list(), client_credentials = FALSE, credentials = NULL, query_authorize_extra = list()) { params <- list( scope = scope, user_params = user_params, type = type, use_oob = use_oob, oob_value = oob_value, as_header = as_header, use_basic_auth = use_basic_auth, config_init = config_init, client_credentials = client_credentials, query_authorize_extra = query_authorize_extra ) Token2.0$new( app = app, endpoint = endpoint, params = params, credentials = credentials, cache_path = if (is.null(credentials)) cache else FALSE ) } #' @export #' @rdname Token-class Token2.0 <- R6::R6Class("Token2.0", inherit = Token, list( init_credentials = function() { self$credentials <- init_oauth2.0(self$endpoint, self$app, scope = self$params$scope, user_params = self$params$user_params, type = self$params$type, use_oob = self$params$use_oob, oob_value = self$params$oob_value, use_basic_auth = self$params$use_basic_auth, config_init = self$params$config_init, client_credentials = self$params$client_credentials, query_authorize_extra = self$params$query_authorize_extra ) }, can_refresh = function() { !is.null(self$credentials$refresh_token) }, refresh = function() { cred <- refresh_oauth2.0( self$endpoint, self$app, self$credentials, self$params$user_params, self$params$use_basic_auth ) if (is.null(cred)) { remove_cached_token(self) } else { self$credentials <- cred self$cache() } self }, sign = function(method, url) { if (self$params$as_header) { request(url = url, headers = c( Authorization = paste("Bearer", self$credentials$access_token) )) } else { url <- parse_url(url) url$query$access_token <- self$credentials$access_token request(url = build_url(url)) } }, validate = function() { validate_oauth2.0(self$endpoint, self$credentials) }, revoke = function() { revoke_oauth2.0(self$endpoint, self$credentials) } )) #' Generate OAuth token for service accounts. #' #' Service accounts provide a way of using OAuth2 without user intervention. #' They instead assume that the server has access to a private key used #' to sign requests. The OAuth app is not needed for service accounts: #' that information is embedded in the account itself. #' #' @inheritParams oauth2.0_token #' @inheritParams jwt_signature #' @param secrets Secrets loaded from JSON file, downloaded from console. #' @family OAuth #' @export #' @examples #' \dontrun{ #' endpoint <- oauth_endpoints("google") #' secrets <- jsonlite::fromJSON("~/Desktop/httrtest-45693cbfac92.json") #' scope <- "https://www.googleapis.com/auth/bigquery.readonly" #' #' token <- oauth_service_token(endpoint, secrets, scope) #' } oauth_service_token <- function(endpoint, secrets, scope = NULL, sub = NULL) { if (!is.oauth_endpoint(endpoint)) { stop("`endpoint` must be an OAuth endpoint", call. = FALSE) } if (!is.list(secrets)) { stop("`secrets` must be a list.", call. = FALSE) } scope <- check_scope(scope) TokenServiceAccount$new( endpoint = endpoint, secrets = secrets, params = list(scope = scope, sub = sub) ) } #' @export #' @rdname Token-class TokenServiceAccount <- R6::R6Class("TokenServiceAccount", inherit = Token2.0, list( secrets = NULL, initialize = function(endpoint, secrets, params) { self$endpoint <- endpoint self$secrets <- secrets self$params <- params self$refresh() }, can_refresh = function() { TRUE }, refresh = function() { self$credentials <- init_oauth_service_account( self$secrets, scope = self$params$scope, sub = self$params$sub ) self }, sign = function(method, url) { config <- add_headers( Authorization = paste("Bearer", self$credentials$access_token) ) request_build(method = method, url = url, config) }, # Never cache cache = function(path) self, load_from_cache = function() self )) normalize_scopes <- function(x) { stats::setNames(sort(unique(x)), NULL) } httr/R/oauth-error.r0000644000176200001440000000123713401555705014076 0ustar liggesusersoauth2.0_error_codes <- c( 400, 401 ) oauth2.0_errors <- c( "invalid_request", "invalid_client", "invalid_grant", "unauthorized_client", "unsupported_grant_type", "invalid_scope" ) # This implements error checking according to the OAuth2.0 # specification: https://tools.ietf.org/html/rfc6749#section-5.2 find_oauth2.0_error <- function(response) { if (!status_code(response) %in% oauth2.0_error_codes) { return(NULL) } content <- content(response) if (!content$error %in% oauth2.0_errors) { return(NULL) } list( error = content$error, error_description = content$error_description, error_uri = content$error_uri ) } httr/R/content-parse.r0000644000176200001440000000711113401555665014413 0ustar liggesuserscheck_encoding <- function(x) { if ((tolower(x) %in% tolower(iconvlist()))) { return(x) } message("Invalid encoding ", x, ": defaulting to UTF-8.") "UTF-8" } guess_encoding <- function(encoding = NULL, type = NULL) { # Try extracting from media type if (is.null(encoding)) { encoding <- tryCatch( error = function(err) NULL, parse_media(type)$params$charset ) } # Fall back to UTF-8 if (is.null(encoding)) { message("No encoding supplied: defaulting to UTF-8.") return("UTF-8") } check_encoding(encoding) } parse_text <- function(content, type = NULL, encoding = NULL) { encoding <- guess_encoding(encoding, type) iconv(readBin(content, character()), from = encoding, to = "UTF-8") } parse_auto <- function(content, type = NULL, encoding = NULL, ...) { if (length(content) == 0) { return(NULL) } if (is.null(type)) { stop("Unknown mime type: can't parse automatically. Use the `type` ", "argument to specify manually.", call. = FALSE ) } mt <- parse_media(type) parser <- parsers[[mt$complete]] if (is.null(parser)) { stop("No automatic parser available for ", mt$complete, ".", call. = FALSE ) } parser(x = content, type = type, encoding = encoding, ...) } parseability <- function(type) { if (is.null(type) || type == "") return("raw") mt <- parse_media(type) if (exists(mt$complete, parsers)) { "parsed" } else if (mt$type == "text") { "text" } else { "raw" } } parsers <- new.env(parent = emptyenv()) # Binary formats --------------------------------------------------------------- # http://www.ietf.org/rfc/rfc4627.txt - section 3. (encoding) parsers$`application/json` <- function(x, type = NULL, encoding = NULL, simplifyVector = FALSE, ...) { jsonlite::fromJSON(parse_text(x, encoding = "UTF-8"), simplifyVector = simplifyVector, ... ) } parsers$`application/x-www-form-urlencoded` <- function(x, encoding = NULL, type = NULL, ...) { parse_query(parse_text(x, encoding = "UTF-8")) } # Text formats ----------------------------------------------------------------- parsers$`image/jpeg` <- function(x, type = NULL, encoding = NULL, ...) { need_package("jpeg") jpeg::readJPEG(x) } parsers$`image/png` <- function(x, type = NULL, encoding = NULL, ...) { need_package("png") png::readPNG(x) } parsers$`text/plain` <- function(x, type = NULL, encoding = NULL, ...) { encoding <- guess_encoding(encoding, type) parse_text(x, type = type, encoding = encoding) } parsers$`text/html` <- function(x, type = NULL, encoding = NULL, ...) { need_package("xml2") encoding <- guess_encoding(encoding, type) xml2::read_html(x, encoding = encoding, ...) } parsers$`application/xml` <- function(x, type = NULL, encoding = NULL, ...) { need_package("xml2") encoding <- guess_encoding(encoding, type) xml2::read_xml(x, encoding = encoding, ...) } parsers$`text/xml` <- function(x, type = NULL, encoding = NULL, ...) { need_package("xml2") encoding <- guess_encoding(encoding, type) xml2::read_xml(x, encoding = encoding, ...) } parsers$`text/csv` <- function(x, type = NULL, encoding = NULL, ...) { need_package("readr") encoding <- guess_encoding(encoding, type) readr::read_csv(x, locale = readr::locale(encoding = encoding), ...) } parsers$`text/tab-separated-values` <- function(x, type = NULL, encoding = NULL, ...) { need_package("readr") encoding <- guess_encoding(encoding, type) readr::read_tsv(x, locale = readr::locale(encoding = encoding), ...) } httr/NEWS.md0000644000176200001440000006440313520044606012340 0ustar liggesusers# httr 1.4.1 * Remove the default `cainfo` option on Windows. Providing a CA bundle is not needed anymore because `curl` now uses the native schannel SSL backend. For recent versions of libcurl, overriding the CA bundle actually breaks custom trust certificates on corporate networks. (@jeroen, #603) * `http_status()` now throws the correct error message if http status code is not in the list of known codes (@Javdat, #567). * `POST()` gains an example on how to use `encode = "raw"` for specific json string body (@cderv, #563) * `RETRY()` now throws the correct error message if an error occurs during the request (@austin3dickey, #581). # httr 1.4.0 ## OAuth OAuth2.0 has been made somewhat more flexible in order to support more websites: * `init_oauth2.0()` passes `use_basic_auth` onwards, enabling basic authentication for OAuth 2.0 (@peterhartman, #484). * `oauth2.0_token()` (and `init_oauth2.0()`) gains a `oob_value` argument that allows arbitrary values to be sent for the `request_uri` parameter during OOB flows (@ctrombley, #493). * `oauth2.0_token()` (and `init_oauth2.0()`) gain a new `query_authorize_extra` parameter make it possible to add extra query parameters to the authorization URL. This is needed some APIs (e.g. fitbit) (@cosmomeese, #503). * `oauth_endpoints()` contains updated urls for Yahoo (@ctrombley, #493) and Vimeo (#491). * OAuth 2.0 token refresh gives a more informative error if it fails (#516). * Prior to token retrieval from on-disk cache, scopes are de-duplicated, sorted, and stripped of names before being hashed. This eliminates a source of hash mismatch that causes new tokens to be requested, even when existing tokens had the necessary scope. (@jennybc, #495) Updates to demos: * The Facebook OAuth demo now uses device flow (#510). This allows you to continue using the FB api from R under their new security policy. * A new Noun Project demo shows how to use one-legged OAuth1 (@cderv, #548). * The Vimeo demo has been updated from OAuth 1.0 to 2.0 (#491). ## Minor changes and improvements * `cache_info()` now handles un-named flags, as illustrated by "private" when the server returns "private, max-age = 0". * `parse_http_date()` gets a better default value for the `failure` argument so that reponses with unparseable dates can be printed without error (@shrektan, #544). * `POST()` now uses 22 digits of precision for `body` list elements by default (@jmwerner, #490) * `RETRY()` now terminates on any successful request, regardless of the value of `terminate_on`. To return to the previous behaviour, set `terminate_on_success = FALSE` (#522). * In `RETRY()` and `VERB()`, `HEAD` requests now succeed (#478, #499). * Encoding falls back to UTF-8 if not supplied and content-type parsing fails (#500). * Non-http(s) headers are no longer parsed (@billdenney, #537). This makes it possible to use httr with protocols other than http, although this is not advised, and you're own your own. # httr 1.3.1 * Re-enable on-disk caching (accidentally disabled in #457) (#475) # httr 1.3.0 ## API changes * Deprecated `safe_callback()` has been removed. * `is_interactive` argument to `init_oauth1.0()`, `init_oauth2.0()` and `oauth_listener()` has been deprecated, as the R session does not actually need to be interactive. ## New features * New `set_callback()` and `get_callback()` set and query callback functions that are called right before and after performing an HTTP request (@gaborcsardi, #409) * `RETRY()` now retries if an error occurs during the request (@asieira, #404), and gains two new arguments: * `terminate_on` gives you greater control over which status codes should it stop retrying. (@asieira, #404) * `pause_min` allows for sub-second delays. (Use with caution! Generally the default is preferred.) (@r2evans) * If the server returns HTTP status code 429 and specifies a `retry-after` value, that value will now be used instead of exponential backoff with jitter, unless it's smaller than `pause_min`. (@nielsoledam, #472) ## OAuth * New oauth cache files are always added to `.gitignore` and, if it exists, `.Rbuildignore`. Specifically, this now happens when option `httr_oauth_cache = TRUE` or user specifies cache file name explicitly. (@jennybc, #436) * `oauth_encode()` now handles UTF-8 characters correctly. (@yutannihilation, #424) * `oauth_app()` allows you to specify the `redirect_url` if you need to customise it. * `oauth_service_token()` gains a `sub` parameter so you can request access on behalf of another user (#410), and accepts a character vector of `scopes` as was described in the documentation (#389). * `oauth_signature()` now normalises the URL as described in the OAuth1.0a spec (@leeper, #435) * New `oauth2.0_authorize_url()` and `oauth2.0_access_token()` functions pull out parts of the OAuth process for reuse elsewhere (#457). * `oauth2.0_token()` gains three new arguments: * `config_init` allows you to supply additional config for the initial request. This is needed for some APIs (e.g. reddit) which rate limit based on `user_agent` (@muschellij2, #363). * `client_credentials`, allows you to use the OAauth2 *Client Credential Grant*. See [RFC 6749](https://tools.ietf.org/html/rfc6749#section-4) for details. (@cderv, #384) * A `credentials` argument that allows you to customise the auth flow. For advanced used only (#457) * `is_interactive` argument to `init_oauth1.0()`, `init_oauth2.0()` and `oauth_listener()` has been deprecated, as the R session does not need to be interactive. ## Minor bug fixes and improvements * `BROWSER()` prints a message telling you to browse to the URL if called in a non-interactive session. * `find_cert_bundle()` will now correctly find cert bundle in "R_HOME/etc" (@jiwalker-usgs, #386). * You can now send lists containing `curl::form_data()` in the `body` of requests with `encoding = "multipart". This makes it possible to specify the mime-type of individual components (#430). * `modify_url()` recognises more forms of empty queries. This eliminates a source of spurious trailing `?` and `?=` (@jennybc, #452). * The `length()` method of the internal `path` class is no longer exported (#395). # httr 1.2.1 * Fix bug with new cache creation code: need to check that cache isn't an empty file. # httr 1.2.0 ## New features * `oauth_signature()` no longer prepends 'oauth\_' to additional parameters. (@jimhester, #373) * All `print()` methods now invisibly return `x` (#355). * `DELETE()` gains a body parameter (#326). * New `encode = "raw"` allows you to do your own encoding for requests with bodies. * New `http_type()` returns the content/mime type of a request, sans parameters. ## Bug fixes and minor improvements * No longer uses use custom requests for standard `POST` requests (#356, #357). This has the side-effect of properly following redirects after `POST`, fixing some login issues (eg hadley/rvest#133). * Long deprecated `multipart` argument to `POST()`, `PUT()` and `PATCH()` has been removed. * The cross-session OAuth cache is now created with permission 0600, and should give a better error if it can't be created (#365). * New `RETRY()` function allows you to retry a request multiple times until it succeeds (#353). * The default user agent string is now computed once and cached. This is a small performance improvement, but important for local connections (#322, @richfitz). * `oauth_callback()` gains trailing slash for facebook compatibility (#324). * `progress()` gains `con` argument to control where progress bar is rendered (#359). * When `use_basic_auth` option is used to obtain a token, token refreshes will now use basic authentication too. * Suppress unhelpful "No encoding supplied: defaulting to UTF-8." when printing a response (#327). * All auto parser functions now have consistent arguments. This fixes problem where `...` is pass on to another function (#330). * `parse_media()` can once again parse multiple parameters (#362, #366). * Correctly cast `config` in `POST()`. * Fix in readfunction to close connection when done. # httr 1.1.0 ## New features * `stop_for_status()`, `warn_for_status()` and (new) `message_for_status()` replace `message` argument with new `task` argument that optionally describes the current task. This allows API wrappers to provide more informative error messages on failure (#277, #302). `stop_for_status()` and `warn_for_status()` return the response if there were no errors. This makes them easier to use in pipelines (#278). * `url_ok()` and `url_successful()` have been deprecated in favour of the more flexible `http_error()`, which works with urls, responses and integer status codes (#299). ## OAuth * `oauth1.0_token()` gains RSA-SHA1 signature support with the `private_key` argument (@nathangoulding, #316). * `oauth2.0_token()` throws an error if it fails to get an access token (#250) and gains two new arguments: * `user_params` allows you to pass arbitrary additional parameters to the token access endpoint when acquiring or refreshing a token (@cornf4ke, #312) * `use_basic_auth` allows you to pick use http authentication when getting a token (#310, @grahamrp). * `oauth_service_token()` checks that its arguments are the correct types (#282) and anways returns a `request` object (#313, @nathangoulding). * `refresh_oauth2.0()` checks for known OAuth2.0 errors and clears the locally cached token in the presense of any (@nathangoulding, #315). ## Bug fixes and minor improvements * httr no longer bundles `cacert.pem`, and instead it relies on the bundle in openssl. This bundle is only used a last-resort on windows with R <3.2.0. * Switch to 'openssl' package for hashing, hmac, signatures, and base64. * httr no longer depends on stringr (#285, @jimhester). * `build_url()` collapses vector `path` with `/` (#280, @artemklevtsov). * `content(x)` uses xml2 for XML documents and readr for csv and tsv. * `content(, type = "text")` defaults to UTF-8 encoding if not otherwise specified. * `has_content()` correctly tests for the presence/absence of body content (#91). * `parse_url()` correctly parses urls like `file:///a/b/c` work (#309). * `progress()` returns `TRUE` to fix for 'progress callback must return boolean' warning (@jeroenooms, #252). * `upload_file()` supports very large files (> 2.5 Gb) (@jeroenooms, #257). # httr 1.0.0 * httr no longer uses the RCurl package. Instead it uses the curl package, a modern binding to libcurl written by Jeroen Ooms (#172). This should make httr more reliable and prevent the "easy handle already used in multi handle" error. This change shouldn't affect any code that uses httr - all the changes have happened behind the scenes. * The `oauth_listener` can now listen on a custom IP address and port (the previously hardwired ip:port of `127.0.0.1:1410` is now just the default). This permits authentication to work under other settings, such as inside docker containers (which require localhost uses `0.0.0.0` instead). To configure, set the system environmental variables `HTTR_LOCALHOST` and `HTTR_PORT` respectively (@cboettig, #211). * `POST(encode = 'json')` now automatically turns length-1 vectors into json scalars. To prevent this automatic "unboxing", wrap the vector in `I()` (#187). * `POST()`, `PUT()` and `PATCH()` now drop `NULL` body elements. This is convenient and consistent with the behaviour for url query params. ## Minor improvements and bug fixes * `cookies` argument to `handle()` is deprecated - cookies are always turned on by default. * `brew_dr()` has been renamed to `httr_dr()` - that's what it should've been in the first place! * `content(type = "text")` compares encodings in a case-insensitive manner (#209). * `context(type = "auto")` uses a better strategy for text based formats (#209). This should allow the `encoding` argument to work more reliably. * `config()` now cleans up duplicated options (#213). * Uses `CURL_CA_BUNDLE` environment variable to look for cert bundle on Windows (#223). * `safe_callback()` is deprecated - it's no longer needed with curl. * `POST()` and `PUT()` now clean up after themselves when uploading a single file (@mtmorgan). * `proxy()` gains an `auth` argument which allows you to pick the type of http authentication used by the proxy (#216). * `VERB()` gains `body` and `encode` arguments so you can generate arbitrary requests with a body. * tumblr added as an `oauth_endpoint`. # httr 0.6.1 * Correctly parse headers with multiple `:`, thanks to @mmorgan (#180). * In `content()`, if no type is provided to function or specified in headers, and we can't guess the type from the extension, we now assume that it's `application/octet-stream` (#181). * Throw error if `timeout()` is less than 1 ms (#175). * Improved LinkedIn OAuth demo (#173). # httr 0.6.0 ## New features * New `write_stream()` allows you to process the response from a server as a stream of raw vectors (#143). * Suport for Google OAuth2 [service accounts](https://developers.google.com/accounts/docs/OAuth2ServiceAccount). (#119, thanks to help from @siddharthab). * `VERB()` allows to you use custom http verbs (#169). * New `handle_reset()` to allow you to reset the handle if you get the error "easy handle already used in multi handle" (#112). * Uses R6 instead of RC. This makes it possible to extend the OAuth classes from outside of httr (#113). * Now only set `capath` on Windows - system defaults on linux and mac ox seem to be adequate (and in some cases better). I've added a couple of tests to ensure that this continues to work in the future. ## Minor improvements and bug fixes * `vignette("api-packages")` gains more detailed instructions on setting environment variables, thanks to @jennybc. * Add `revoke_all()` to revoke all stored tokens (if possible) (#77). * Fix for OAuth 2 process when using `options(httr_oob_default = TRUE)` (#126, @WillemPaling). * New `brew_dr()` checks for common problems. Currently checks if your libCurl uses NSS. This is unlikely to work so it gives you some advice on how to fix the problem (thanks to @eddelbuettel for debugging this problem). * `Content-Type` set to title case to avoid errors in servers which do not correctly implement case insensitivity in header names. (#142, #146) thanks to Håkon Malmedal (@hmalmedal) and Jim Hester (@jimhester). * Correctly parse http status when it only contains two components (#162). * Correctly parse http headers when field name is followed by any amount (including none) of white space. * Default "Accepts" header set to `application/json, text/xml, application/xml, */*`: this should slightly increase the likelihood of getting xml back. `application/xml` is correctly converted to text before being parsed to `XML::xmlParse()` (#160). * Make it again possible to override the content type set up by `POST()` when sending data (#140). * New `safe_callback()` function operator that makes R functions safe for use as RCurl callbacks (#144). * Added support for passing oauth1 tokens in URL instead of the headers (#145, @bogstag). * Default to out-of-band credential exchange when `httpuv` isn't installed. (#168) ## Deprecated and deleted functions * `new_token()` has been removed - this was always an internal function so you should never have been using it. If you were, switch to creating the tokens directly. * Deprecate `guess_media()`, and instead use `mime::guess_type()` (#148). # httr 0.5 * You can now save response bodies directly to disk by using the `write_disk()` config. This is useful if you want to capture large files that don't fit in memory (#44). * Default accept header is now "application/json, text/xml, */*" - this should encourage servers to send json or xml if they know how. * `httr_options()` allows you to easily filter the options, e.g. `httr_options("post")` * `POST()` now specifies Curl options more precisely so that Curl know's that you're doing a POST and can respond appropriately to redirects. ## Caching * Preliminary and experimental support for caching with `cache_info()` and `rerequest()` (#129). Be aware that this API is likely to change in the future. * `parse_http_date()` parses http dates according RFC2616 spec. * Requests now print the time they were made. * Mime type `application/xml` is automatically parsed with ``XML::xmlParse()`. (#128) ## Minor improvements and bug fixes * Now possible to specify both handle and url when making a request. * `content(type = "text")` uses `readBin()` instead of `rawToChar()` so that strings with embedded NULLs (e.g. WINDOWS-1252) can be re-encoded to UTF-8. * `DELETE()` now returns body of request (#138). * `headers()` is now a generic with a method for response objects. * `parse_media()` failed to take into account that media types are case-insenstive - this lead to bad re-encoding for content-types like "text/html; Charset=UTF-8" * Typo which broke `set_cookies()` fixed by @hrbrmstr. * `url_ok()` works correctly now, instead of always returning `FALSE`, a bug since version 0.4 (#133). * Remove redundant arguments `simplifyDataFrame` and `simplifyMatrix` for json parser. # httr 0.4 ## New features * New `headers()` and `cookies()` functions to extract headers and cookies from responses. Previoulsy internal `status_code()` function now exported to extract `status_code()` from responses. * `POST()`, `PUT()`, and `PATCH()` now use `encode` argument to determine how list inputs are encoded. Valid values are "multiple", "form" or "json". The `multipart` argument is now deprecated (#103). You can stream a single file from disk with `upload_file("path/")`. The mime type will be guessed from the extension, or can be supplied explicitly as the second argument to `upload_file()`. * `progress()` will display a progress bar, useful if you're doing large uploads or downloads (#17). * `verbose()` now uses a custom debug function so that you can see exactly what data is sent to the server. Arguments control exactly what is included, and the defaults have been selected to be more helpful for the most common cases (#102). * `with_verbose()` makes it easier to see verbose information when http requests are made within other functions (#87). ## Documentation improvements * New `quickstart` vignette to help you get up and running with httr. * New `api-packages` vignette describes how best practices to follow when writing R packages that wrap web APIs. * `httr_options()` lists all known config options, translating between their short R names and the full libcurl names. The `curl_doc()` helper function allows you to jump directly to the online documentation for an option. ## Minor improvements * `authenticate()` now defaults to `type = "basic"` which is pretty much the only type of authentication anyone uses. * Updated `cacert.pem` to version at 2014-04-22 (#114). * `content_type()`, `content_type_xml()` and `content_type_json()` make it easier to set the content type for `POST` requests (and other requests with a body). * `has_content()` tells you if request has any content associated with it (#91). * Add `is_interactive()` parameter to `oauth_listener()`, `init_oauth1.0()` and `init_oauth2.0()` (#90). * `oauth_signature()` and `oauth_header()` now exported to make it easier to construct custom authentication for APIs that use only some components of the full OAuth process (e.g. 2 legged OAuth). * NULL `query` parameters are now dropped automatically. * When `print()`ing a response, httr will only attempt to print the first few lines if it's a text format (i.e. either the main type is text or is application/json). It will also truncate each line so that it fits on screen - this should hopefully make it easier to see a little bit of the content, without filling the screen with gibberish. * `new_bin()` has been removed: it's easier to see what's going on in examples with `httpbin.org`. ## Bug fixes * `user_agent()` once again overrides default (closes #97) * `parse(type = "auto")` returns NULL if no content associated with request (#91). * Better strategy for resetting Curl handles prevents carry-over of error status and other problems (#112). * `set_config()` and `with_config()` now work with `token`s (#111). # httr 0.3 ## OAuth improvements OAuth 2.0 has recieved a major overhaul in this version. The authentication dance now works in more environments (including RStudio), and is generally a little faster. When working on a remote server, or if R's internet connection is constrained in other ways, you can now use out-of-band authentication, copying and pasting from any browser to your R session. OAuth tokens from endpoints that regularly expire access tokens can now be refreshed, and will be refresh automatically on authentication failure. httr now uses project (working directory) based caching: every time you create or refresh a token, a copy of the credentials will be saved in `.httr-oauth`. You can override this default for individual tokens with the `cache` parameter, or globally with the `httr_oauth_cache` option. Supply either a logical vector (`TRUE` = always cache, `FALSE` = never cache, `NA` = ask), or a string (the path to the cache file). You should NOT include this cache file in source code control - if you do, delete it, and reset your access token through the corresponding web interface. To help, httr will automatically add appropriate entries to `.gitignore` and `.Rbuildignore`. These changes mean that you should only ever have to authenticate once per project, and you can authenticate from any environment in which you can run R. A big thanks go to Craig Citro (@craigcitro) from google, who contributed much code and many ideas to make this possible. * The OAuth token objects are now reference classes, which mean they can be updated in place, such as when an access token expires and needs to be refreshed. You can manually refresh by calling `$refresh()` on the object. You can force reinitialisation (to do the complete dance from scratch) by calling `$reinit(force = TRUE)`. * If a signed OAuth2 request fails with a 401 and the credentials have a `refresh_token`, then the OAuth token will be automatically refreshed (#74). * OAuth tokens are cached locally in a file called `.httr-oauth` (unless you opt out). This file should not be included in source code control, and httr will automatically add to `.gitignore` and `.Rbuildignore`. The caching policy is described in more detail in the help for the `Token` class. * The OAuth2 dance can now be performed without running a local webserver (#33, thanks to @craigcitro). To make that the default, set `options(httr_oob_default = TRUE)`. This is useful when running R remotely. * Add support for passing oauth2 tokens in headers instead of the URL, and make this the default (#34, thanks to @craigcitro). * OAuth endpoints can store arbitrary extra urls. * Use the httpuv webserver for the OAuth dance instead of the built-in httpd server (#32, thanks to @jdeboer). This makes the dance work in Rstudio, and also seems a little faster. Rook is no longer required. * `oauth_endpoints()` includes some popular OAuth endpoints. ## Other improvements * HTTP verbs (`GET()`, `POST()` etc) now pass unnamed arguments to `config()` and named arguments to `modify_url()` (#81). * The placement of `...` in `POST()`, `PATCH()` and `PUT()` has been tweaked so that you must always specify `body` and `multipart` arguments with their full name. This has always been recommended practice; now it is enforced. * `httr` includes its own copy of `cacert.pem`, which is more recent than the version included in RCurl (#67). * Added default user agent which includes versions of Curl, RCurl and httr. * Switched to jsonlite from rjson. * Content parsers no longer load packages on to search path. * `stop_for_status()` now raises errors with useful classes so that you can use `tryCatch()` to take different actions depending on the type of error. See `http_condition()` for more details. * httr now imports the methods package so that it works when called with Rscript. * New automatic parsers for mime types `text/tab-separated-values` and `text/csv` (#49) * Add support for `fragment` in url building/parsing (#70, thanks to @craigcitro). * You can suppress the body entirely in `POST()`, `PATCH()` and `PUT()` with `body = FALSE`. ## Bug fixes * If you supply multiple headers of the same name, the value of the most recently set header will always be used. * Urls with missing query param values (e.g. `http://x.com/?q=`) are now parsed correctly (#27). The names of query params are now also escaped and unescaped correctly when parsing and building urls. * Default html parser is now `XML::htmlParse()` which is easier to use with xpath (#66). # httr 0.2 * OAuth now uses custom escaping function which is guaranteed to work on all platforms (Fixes #21) * When concatenating configs, concatenate all the headers. (Fixes #19) * export `hmac_sha1` since so many authentication protocols need this * `content` will automatically guess what type of output (parsed, text or raw) based on the content-type header. It also automatically converts text content to UTF-8 (using the charset in the media type) and can guess at mime type from extension if server doesn't supply one. Media type and encoding can be overridden with the `type` and `encoding` arguments respectively. * response objects automatically print content type to aid debugging. * `text_content` has become `context(, "text")` and `parsed_content` `content(, "parsed")`. The previous calls are deprecated and will be removed in a future version. * In `oauth_listener`, use existing httpd port if help server has already been started. This allows the ouath authentication dance to work if you're in RStudio. (Fixes #15). * add several functions related to checking the status of an http request. Those are : `status`, `url_ok` and `url_success` as well as `stop_for_status` and `warn_for_status`. * `build_url`: correctly add params back into full url. # httr 0.1.1 * Add new default config: use the standard SSL certificate * Add recommendation to use custom handles with `authenticate` httr/MD50000644000176200001440000002233413522036552011552 0ustar liggesusersaad0ef8cdfdc17fceaba904f6cc3a814 *DESCRIPTION d8d2f9bd4836cd6c839888295d3b1da5 *LICENSE b2112c66260fe60ed628f0d32b588465 *NAMESPACE 354187698505e05e8fb25fec3fec6d5e *NEWS.md 095d06eb37728e22cfa7330cb9a73273 *R/authenticate.r e344120746e0dd97fe1c103f2d9b66ab *R/body.R 1577a7ff38f57c57a377b5c595afaf31 *R/cache.R d729e75fb887a34a9cc8425331882472 *R/callback.R 79787db0aaea5e3d880c96badc01533e *R/config.r 5b8946ab7c742331f0e49fe9dc808888 *R/content-parse.r 3cd594e278642eb61b0ca9a27ce06b07 *R/content.r 4b6bb808c342d8f699704fa8e846c4f0 *R/cookies.r 99f6e22eda9d223351557f07ac96cfc2 *R/date.R 9e376216ca3c78e2af4d590f91b5354f *R/doctor.R 4f00ea0f8f69f87b584237c504cdbde1 *R/envvar.R e3e79952d1831087d2354bd6c3648846 *R/handle-pool.r 839bfa06d6355bac3ebf4d5acf94acfc *R/handle-url.r 0aefe5c53d0a5f2609fb46ea0099fd73 *R/handle.r 274dc9928bc389077d2ef26141c95887 *R/headers.r e9ef5cb10499b1259734c631a407d546 *R/http-browse.r b57c28ca0a62a8a0a0dd58e6c52bb432 *R/http-delete.r 190396658eae2a928d6f4512c7fc6bfa *R/http-get.r 016b3fd2b6e218caa8fd203ae37e0538 *R/http-head.r 7775a01a88975932132d24a25b6cff1c *R/http-patch.r cc945117e4b7c8a05a5c4421fbe0d644 *R/http-post.r 574701554b46a22b8a602895f089698b *R/http-put.r 54ed0bb61ca47f97b1a6453841b3bab2 *R/http-verb.R c7d086d4d69233eec198be92f7f842e2 *R/httr.r 50c8c1f9754dabfeb57f0fb52f45abf9 *R/insensitive.r 855788f5633ef4cd3a90ef716acf802c *R/media-guess.r 3163581746db8edb1098b54304d85a41 *R/media-parse.r c21022d0589b1cd9dc7e7f46e852c9af *R/oauth-app.r 9b1a9816d2023ef9c92490560fe70b57 *R/oauth-cache.R 950cec8027e56342d0c9bbfe0cb69973 *R/oauth-endpoint.r 1c064f53a740131a7f30c7e15faa2628 *R/oauth-error.r a63345353ba170b7ff19279627329fe3 *R/oauth-exchanger.r 93610520258e956ba7f9db3413c092f2 *R/oauth-init.R f0fb944354056ef757fed6076878244c *R/oauth-listener.r aef318b141722143c8efbd539778f75d *R/oauth-refresh.R d3654b7777e44218fdc27feae6bb0ce6 *R/oauth-server-side.R da0153352e39868415ce76e1c97ec413 *R/oauth-signature.r ad05a11f1df847ce028d5eab9bdaf93a *R/oauth-token-utils.R 9ca851eb7fb1307c4f3723cc977c42a1 *R/oauth-token.r b8837c66b8a90f840d0c23ec8d6082d4 *R/progress.R b7c20c3a06ebed28783313c861aba09c *R/proxy.r 208f19e37281c74179af77a99bbda7b5 *R/request.R 182f3efbf8d2c7a32c62de35cef4310c *R/response-status.r 24089c64f5519a0090564414adb4ffb0 *R/response-type.R 050f06abfed21c689f5d6dc542c09bf7 *R/response.r d7051bc346878bfafd1ff92203810e7c *R/retry.R 5556eedb2fcf1d77c6327d39a11dfd91 *R/sha1.r ba93b5bf14ffd468c9ec697e6df87458 *R/str.R c27933cf5f645f5c7468693f45bd63c1 *R/timeout.r 1ed99674ef523a55ef242c76fc570e7b *R/upload-file.r 88a3f07546ab1a9b6c7fa57fb2451e2a *R/url-query.r 753920671e81cc5339853d5ce1b459c3 *R/url.r a687b6525071dbded6b131a1ff6ca234 *R/user-agent.r 135e4cfdf51eaecf64967cae12639581 *R/utils.r a9647d01537f226366fc8c79a827240e *R/verbose.r f268f8f669ebd408c5f7c22146cdf93b *R/write-function.R 50cd23b90e9cf9f072b1c9ace29e70d5 *R/zzz.R 8275a9b731f05cae096c70b6343eaa95 *README.md de24c30f9ed3bf0ef9167fbb93352351 *build/vignette.rds 204416c6aa8abb07b6a44f25f275d465 *demo/00Index ca37b48d2321fc58961dcdf7fcb6391b *demo/connection-sharing.r 8af6a39a4e8504f4689970ee3e8cc9df *demo/oauth1-nounproject.r 885033896db61b0ef46e8879faabd915 *demo/oauth1-twitter.r f33f8adad43227cf0858cafc6723871c *demo/oauth1-withings.r 3a82d7f7f92a6ce4c4b15e1f171074b8 *demo/oauth1-yahoo.r 75ab0e49b3d3511aa28dd1b14370fc11 *demo/oauth2-azure.r 89ed345a9d728e6cfb9f715f304f5b96 *demo/oauth2-facebook.r 5802c74d3c2460cc04daf4f89256309b *demo/oauth2-github.r a4c4bf74978a440a8d1aac362147292f *demo/oauth2-google.r 800b670312f847b93ff13b4e53fb3b33 *demo/oauth2-linkedin.r 31d6b36d5f725b134e87f662ae7e07d1 *demo/oauth2-reddit.R ad99b8f83d30e029a7d170bdc3653636 *demo/oauth2-vimeo.r b5e705bbd3517a82ac2791f0ee7eea40 *demo/oauth2-yahoo.r dae3272f618a947f5a3ad00ba40c3b27 *demo/oauth2-yelp.R 56b0c883a1b838991459f7eecc14bbc5 *demo/service-account.R 76d3c415e63c23cd74d89dc6448f5d40 *inst/doc/api-packages.R f1cd5d58909e0e4c26cb53df243b07a6 *inst/doc/api-packages.Rmd 17f476836be8231ad4b02583bd07e7f9 *inst/doc/api-packages.html 18601f685970936f29efcc7180d9833b *inst/doc/quickstart.R fc892815cee67ed566f68e0751d244a3 *inst/doc/quickstart.Rmd 0e1a4bb29f78a4aa826a6d654e8da6cb *inst/doc/quickstart.html b0da2a255d268bb4f5c523568431e9ea *inst/doc/secrets.R 5f739568d4290ca2d02e542f5f170112 *inst/doc/secrets.Rmd 7ff65b55abcaf86fcd8bd36ca9242274 *inst/doc/secrets.html cc56bdddbbef79270e5f059fc9a92a33 *man/BROWSE.Rd 0ca672294e6d10142952dc33adcf30dd *man/DELETE.Rd 53552b44f2ec59a48612bc7e74b1a847 *man/GET.Rd 0c62b3d4e97b545cfb970dea53c7b8a6 *man/HEAD.Rd a01199a2cfbae025191b7d0bfc56fcb1 *man/PATCH.Rd 118fb73643f927fda9fd65cf3e132ca9 *man/POST.Rd 581fc98213766b12b4f0577f5c0c20da *man/PUT.Rd 17a08483418c2fc472d281417cdb09b9 *man/RETRY.Rd 5ad1aa08b04fd61455816df5440994c6 *man/Token-class.Rd d06ea76bb047b224d5e858ac2fb0e74f *man/VERB.Rd 305bc8ece4fd7f38e490f602308924d5 *man/add_headers.Rd b9e8724f8cdafa241fbe6e102926c41f *man/authenticate.Rd 562421c2c203be5971b6ceb3ee40f1e8 *man/cache_info.Rd 012d68f433ce277848ab424fc83a75d2 *man/config.Rd f8628960c1a607ba3dc29a5bb819f5b0 *man/content.Rd 8dfaa81891c28ef404366e175ebd88c4 *man/content_type.Rd 853a1a200b2c9ee3348fe7790d3812de *man/cookies.Rd cd84ed7d06ec4a7c9f185fd2007927eb *man/get_callback.Rd ad1faa46b07c9a66cc38908fcdf1b7c6 *man/guess_media.Rd 8c2e882e702294670bb3db25d997d3b1 *man/handle.Rd 8ba5cde89845df95cf4156c9dc2ed8b4 *man/handle_pool.Rd caca67bf710251fecc3244ed46fecc7b *man/has_content.Rd 4e498413c57240c0e98569103593f370 *man/headers.Rd 300c19506c669d0d8c3101a7b919f3ae *man/hmac_sha1.Rd 05919888defd27ebbc0859d2d030cc1f *man/http_condition.Rd cc160440d89fb63fec3305e2eb784b8b *man/http_error.Rd 718cf7bca895496d0f5f59934b043100 *man/http_status.Rd b5dd646b00696e45af48efc30bafbb4d *man/http_type.Rd ddbfaf0dc267794481df0772ece06599 *man/httr-package.Rd 30dffc7b44899d9e080ff819162771aa *man/httr_dr.Rd 7939b106aea4de638f3c2abadd9d1b81 *man/httr_options.Rd adf4d9bd15eabf7d3ad69b6cf78184ae *man/init_oauth1.0.Rd a5f0d438d6f8fd8460a14f364ed056d8 *man/init_oauth2.0.Rd 1ba4167747a3d89733e2cb1ab33eef0e *man/insensitive.Rd 21c9707a0320f3bbbe23f68da9b6e52a *man/jwt_signature.Rd e3bee35191682ddf090bbd73d9cd9592 *man/modify_url.Rd ca95a5af4c9a025cb19505ff71809862 *man/oauth1.0_token.Rd 0da59f0d6e05275da1631db3c3dddd54 *man/oauth2.0_token.Rd 6aad308aed47a1fee73ec63148e10089 *man/oauth_app.Rd 9005a02e317a8542563b89706da4cd1b *man/oauth_callback.Rd 76a696fba9d78d8b19fdb51abd9ab428 *man/oauth_endpoint.Rd 7555dfc425fc22adfee7f8e330006434 *man/oauth_endpoints.Rd a635c01b3a5f7c8771ce13f43fd6ec19 *man/oauth_exchanger.Rd bc2726723e2f21aa5e6c31f4e13d0560 *man/oauth_listener.Rd 9786f7c8c1362d0fb71f3e8395a21305 *man/oauth_service_token.Rd 93a25d785e256fd1a83ff49691c779bb *man/oauth_signature.Rd 25d8a57e0a970ed67bc51b901575d0d9 *man/parse_http_date.Rd 16e9c0f796ad1cc4927d5d798e8de51c *man/parse_media.Rd 6983ca24e36a2a2e5eb5d3d5923bed63 *man/parse_url.Rd 2e08923691d33b9c194fd98b53e3716b *man/progress.Rd bfb24cf84c909886ae976234ce4702cb *man/response.Rd 5c08aec9826a8463e5d8e4877a9c7dbb *man/revoke_all.Rd 264fb1052d36bb6c4da7e82a53157e59 *man/set_config.Rd fce616d10d3f0520b5cbd5211105d3c4 *man/set_cookies.Rd fe7d33df28253b823a835c602d26fbb3 *man/sha1_hash.Rd 4c968239cd01acc8ebdaac21d41df720 *man/sign_oauth.Rd a7df331a37986e730a86a03f1001f72d *man/status_code.Rd 09793f5c9587f0d29b442b7ce44a578b *man/stop_for_status.Rd d7278506cee5340b420559015fea044e *man/timeout.Rd cf5dc62d8ec3965e92b1bf269609984d *man/upload_file.Rd b6938eb2f122021f8c79498a028f4e8b *man/use_proxy.Rd 37605c686a6b00b81e1629d2e47e1659 *man/user_agent.Rd 1c3c0af4ea31ebe577e3133a4550bccc *man/verbose.Rd 85e88592a110ffede6a9c139985e48f7 *man/with_config.Rd 3c524e55020ebfb6b5e9b9d0bc9dd0ff *man/write_disk.Rd 10a960266264d74c53d32ee662df802d *man/write_function.Rd 6e94a182bd6e152d1d58b405fa8448b7 *man/write_stream.Rd 24f3564620367449ffc825c5bafb0964 *tests/testthat.R 7fe4b98fb68caf83fa31905435f08da0 *tests/testthat/data.txt 4b2442a66bf41b07ec25264bb2c2f6e2 *tests/testthat/test-body.r d973c444ac4e4830fef5ccc7a5836cef *tests/testthat/test-callback.R d80563292c1aba419e0d8aa10663da4a *tests/testthat/test-config.r 45c6590689c27f621a6a6677db45e36a *tests/testthat/test-content-parse.r fffa5d36bd98d50813a328cb5def6008 *tests/testthat/test-content.R 3fbf91d9b536038592f1c18d02062fdf *tests/testthat/test-header.r a692f3a2f3ea68b98b54fc56f057c7be *tests/testthat/test-http-condition.R aa1471215f9852500a963cd11b8dfdec *tests/testthat/test-http-error.R 3347e27c652eea48a1509d6311ef4ed5 *tests/testthat/test-http-head.r 2ef85dee3b24ddbb54f318b822e8f9ee *tests/testthat/test-oauth-cache.R 8226c0da585721329996960f12ec383d *tests/testthat/test-oauth-server-side.R 9cd43deb46b8dd6237207f23ed8b617f *tests/testthat/test-oauth.R 3c599f3ebfdccb3206bf73013a3804dd *tests/testthat/test-oauth_signature.R bf515db445fb665849227fe5ff91aaba *tests/testthat/test-parse_media.R 06b5efa1fcd3da58a8c58b321c54ff9a *tests/testthat/test-request.r c528f51e6d9e6fef4364b6d73eebe1a9 *tests/testthat/test-response.r 877653ae32507a95eb93a53c170833e5 *tests/testthat/test-retry.R f645a25ca4d13891743ea5e4104bad91 *tests/testthat/test-ssl.R 27fb1ffcec0869c96495d7192b604f7a *tests/testthat/test-url.r f1cd5d58909e0e4c26cb53df243b07a6 *vignettes/api-packages.Rmd fc892815cee67ed566f68e0751d244a3 *vignettes/quickstart.Rmd 5f739568d4290ca2d02e542f5f170112 *vignettes/secrets.Rmd httr/inst/0000755000176200001440000000000013520044655012214 5ustar liggesusershttr/inst/doc/0000755000176200001440000000000013520044655012761 5ustar liggesusershttr/inst/doc/secrets.R0000644000176200001440000000514213520044655014556 0ustar liggesusers## ---- echo = FALSE------------------------------------------------------- library(httr) knitr::opts_chunk$set(comment = "#>", collapse = TRUE) ## ---- eval = FALSE------------------------------------------------------- # Sys.chmod("secret.file", mode = "0400") ## ------------------------------------------------------------------------ my_secrets <- function() { path <- "~/secrets/secret.json" if (!file.exists(path)) { stop("Can't find secret file: '", path, "'") } jsonlite::read_json(path) } ## ---- eval = FALSE------------------------------------------------------- # password <- rstudioapi::askForPassword() ## ---- eval = FALSE------------------------------------------------------- # file.edit("~/.Renviron") ## ---- include = FALSE---------------------------------------------------- Sys.setenv("VAR1" = "value1") ## ------------------------------------------------------------------------ Sys.getenv("VAR1") ## ---- eval = FALSE------------------------------------------------------- # keyring::key_set("MY_SECRET") # keyring::key_get("MY_SECRET") ## ---- eval = FALSE------------------------------------------------------- # keyring::keyring_create("httr") # keyring::key_set("MY_SECRET", keyring = "httr") ## ---- eval = FALSE------------------------------------------------------- # keyring::keyring_lock("httr") ## ---- eval = FALSE------------------------------------------------------- # library(openssl) # library(jsonlite) # library(curl) # # encrypt <- function(secret, username) { # url <- paste("https://api.github.com/users", username, "keys", sep = "/") # # resp <- httr::GET(url) # httr::stop_for_status(resp) # pubkey <- httr::content(resp)[[1]]$key # # opubkey <- openssl::read_pubkey(pubkey) # cipher <- openssl::rsa_encrypt(charToRaw(secret), opubkey) # jsonlite::base64_enc(cipher) # } # # cipher <- encrypt("\n", "hadley") # cat(cipher) ## ---- eval = FALSE------------------------------------------------------- # decrypt <- function(cipher, key = openssl::my_key()) { # cipherraw <- jsonlite::base64_dec(cipher) # rawToChar(openssl::rsa_decrypt(cipherraw, key = key)) # } # # decrypt(cipher) # #> username # #> password ## ------------------------------------------------------------------------ my_secret <- function() { val <- Sys.getenv("SECRET") if (identical(val, "")) { stop("`SECRET` env var has not been set") } val } ## ------------------------------------------------------------------------ NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true") knitr::opts_chunk$set(purl = NOT_CRAN) httr/inst/doc/api-packages.Rmd0000644000176200001440000004407513401521062015752 0ustar liggesusers--- title: "Best practices for API packages" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Best practices for API packages} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} library(httr) knitr::opts_chunk$set(comment = "#>", collapse = TRUE) ``` So you want to write an R client for a web API? This document walks through the key issues involved in writing API wrappers in R. If you're new to working with web APIs, you may want to start by reading "[An introduction to APIs](https://zapier.com/learn/apis)" by zapier. ## Overall design APIs vary widely. Before starting to code, it is important to understand how the API you are working with handles important issues so that you can implement a complete and coherent R client for the API. The key features of any API are the structure of the requests and the structure of the responses. An HTTP request consists of the following parts: 1. HTTP verb (`GET`, `POST`, `DELETE`, etc.) 1. The base URL for the API 1. The URL path or endpoint 1. URL query arguments (e.g., `?foo=bar`) 1. Optional headers 1. An optional request body An API package needs to be able to generate these components in order to perform the desired API call, which will typically involve some sort of authentication. For example, to request that the GitHub API provides a list of all issues for the httr repo, we send an HTTP request that looks like: ``` -> GET /repos/hadley/httr HTTP/1.1 -> Host: api.github.com -> Accept: application/vnd.github.v3+json ``` Here we're using a `GET` request to the host `api.github.com`. The url is `/repos/hadley/httr`, and we send an accept header that tells GitHub what sort of data we want. In response to this request, the API will return an HTTP response that includes: 1. An HTTP status code. 1. Headers, key-value pairs. 1. A body typically consisting of XML, JSON, plain text, HTML, or some kind of binary representation. An API client needs to parse these responses, turning API errors into R errors, and return a useful object to the end user. For the previous HTTP request, GitHub returns: ``` <- HTTP/1.1 200 OK <- Server: GitHub.com <- Content-Type: application/json; charset=utf-8 <- X-RateLimit-Limit: 5000 <- X-RateLimit-Remaining: 4998 <- X-RateLimit-Reset: 1459554901 <- <- { <- "id": 2756403, <- "name": "httr", <- "full_name": "hadley/httr", <- "owner": { <- "login": "hadley", <- "id": 4196, <- "avatar_url": "https://avatars.githubusercontent.com/u/4196?v=3", <- ... <- }, <- "private": false, <- "html_url": "https://github.com/hadley/httr", <- "description": "httr: a friendly http package for R", <- "fork": false, <- "url": "https://api.github.com/repos/hadley/httr", <- ... <- "network_count": 1368, <- "subscribers_count": 64 <- } ``` Designing a good API client requires identifying how each of these API features is used to compose a request and what type of response is expected for each. It's best practice to insulate the end user from *how* the API works so they only need to understand how to use an R function, not the details of how APIs work. It's your job to suffer so that others don't have to! ## First steps ### Send a simple request First, find a simple API endpoint that doesn't require authentication: this lets you get the basics working before tackling the complexities of authentication. For this example, we'll use the list of httr issues which requires sending a GET request to `repos/hadley/httr`: ```{R} library(httr) github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) GET(url) } resp <- github_api("/repos/hadley/httr") resp ``` ### Parse the response Next, you need to take the response returned by the API and turn it into a useful object. Any API will return an HTTP response that consists of headers and a body. While the response can come in multiple forms (see above), two of the most common structured formats are XML and JSON. Note that while most APIs will return only one or the other, some, like the colour lovers API, allow you to choose which one with a url parameter: ```{r} GET("http://www.colourlovers.com/api/color/6B4106?format=xml") GET("http://www.colourlovers.com/api/color/6B4106?format=json") ``` Others use [content negotiation](http://en.wikipedia.org/wiki/Content_negotiation) to determine what sort of data to send back. If the API you're wrapping does this, then you'll need to include one of `accept_json()` and `accept_xml()` in your request. If you have a choice, choose json: it's usually much easier to work with than xml. Most APIs will return most or all useful information in the response body, which can be accessed using `content()`. To determine what type of information is returned, you can use `http_type()` ```{r} http_type(resp) ``` I recommend checking that the type is as you expect in your helper function. This will ensure that you get a clear error message if the API changes: ```{r} github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } resp } ``` NB: some poorly written APIs will say the content is type A, but it will actually be type B. In this case you should complain to the API authors, and until they fix the problem, simply drop the check for content type. Next we need to parse the output into an R object. httr provides some default parsers with `content(..., as = "auto")` but I don't recommend using them inside a package. Instead it's better to explicitly parse it yourself: 1. To parse json, use `jsonlite` package. 1. To parse xml, use the `xml2` package. ```{r} github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE) } ``` ### Return a helpful object Rather than simply returning the response as a list, I think it's a good practice to make a simple S3 object. That way you can return the response and parsed object, and provide a nice print method. This will make debugging later on much much much more pleasant. ```{r} github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE) structure( list( content = parsed, path = path, response = resp ), class = "github_api" ) } print.github_api <- function(x, ...) { cat("\n", sep = "") str(x$content) invisible(x) } github_api("/users/hadley") ``` The API might return invalid data, but this should be rare, so you can just rely on the parser to provide a useful error message. ### Turn API errors into R errors Next, you need to make sure that your API wrapper throws an error if the request failed. Using a web API introduces additional possible points of failure into R code aside from those occurring in R itself. These include: - Client-side exceptions - Network / communication exceptions - Server-side exceptions You need to make sure these are all converted into regular R errors. You can figure out if there's a problem with `http_error()`, which checks the HTTP status code. Status codes in the 400 range usually mean that you've done something wrong. Status codes in the 500 range typically mean that something has gone wrong on the server side. Often the API will provide information about the error in the body of the response: you should use this where available. If the API returns special errors for common problems, you might want to provide more detail in the error. For example, if you run out of requests and are [rate limited](https://developer.github.com/v3/#rate-limiting) you might want to tell the user how long to wait until they can make the next request (or even automatically wait that long!). ```{r, error = TRUE} github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE) if (http_error(resp)) { stop( sprintf( "GitHub API request failed [%s]\n%s\n<%s>", status_code(resp), parsed$message, parsed$documentation_url ), call. = FALSE ) } structure( list( content = parsed, path = path, response = resp ), class = "github_api" ) } github_api("/user/hadley") ``` > Some poorly written APIs will return different types of response based on > whether or not the request succeeded or failed. If your API does this you'll > need to make your request function check the `status_code()` before parsing > the response. For many APIs, the common approach is to retry API calls that return something in the 500 range. However, when doing this, it's **extremely** important to make sure to do this with some form of exponential backoff: if something's wrong on the server-side, hammering the server with retries may make things worse, and may lead to you exhausting quota (or hitting other sorts of rate limits). A common policy is to retry up to 5 times, starting at 1s, and each time doubling and adding a small amount of jitter (plus or minus up to, say, 5% of the current wait time). ### Set a user agent While we're in this function, there's one important header that you should set for every API wrapper: the user agent. The user agent is a string used to identify the client. This is most useful for the API owner as it allows them to see who is using the API. It's also useful for you if you have a contact on the inside as it often makes it easier for them to pull your requests from their logs and see what's going wrong. If you're hitting a commercial API, this also makes it easier for internal R advocates to see how many people are using their API via R and hopefully assign more resources. A good default for an R API package wrapper is to make it the URL to your GitHub repo: ```{r} ua <- user_agent("http://github.com/hadley/httr") ua github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url, ua) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE) if (status_code(resp) != 200) { stop( sprintf( "GitHub API request failed [%s]\n%s\n<%s>", status_code(resp), parsed$message, parsed$documentation_url ), call. = FALSE ) } structure( list( content = parsed, path = path, response = resp ), class = "github_api" ) } ``` ### Passing parameters Most APIs work by executing an HTTP method on a specified URL with some additional parameters. These parameters can be specified in a number of ways, including in the URL path, in URL query arguments, in HTTP headers, and in the request body itself. These parameters can be controlled using httr functions: 1. URL path: `modify_url()` 2. Query arguments: The `query` argument to `GET()`, `POST()`, etc. 3. HTTP headers: `add_headers()` 4. Request body: The `body` argument to `GET()`, `POST()`, etc. [RESTful APIs](https://en.wikipedia.org/wiki/Representational_state_transfer) also use the HTTP verb to communicate arguments (e.g., `GET` retrieves a file, `POST` adds a file, `DELETE` removes a file, etc.). We can use the helpful [httpbin service](http://httpbin.org) to show how to send arguments in each of these ways. ```{r, eval = FALSE} # modify_url POST(modify_url("https://httpbin.org", path = "/post")) # query arguments POST("http://httpbin.org/post", query = list(foo = "bar")) # headers POST("http://httpbin.org/post", add_headers(foo = "bar")) # body ## as form POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "form") ## as json POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "json") ``` Many APIs will use just one of these forms of argument passing, but others will use multiple of them in combination. Best practice is to insulate the user from how and where the various arguments are used by the API and instead simply expose relevant arguments via R function arguments, some of which might be used in the URL, in the headers, in the body, etc. If a parameter has a small fixed set of possible values that are allowed by the API, you can use list them in the default arguments and then use `match.arg()` to ensure that the caller only supplies one of those values. (This also allows the user to supply the short unique prefixes.) ```{r} f <- function(x = c("apple", "banana", "orange")) { match.arg(x) } f("a") ``` It is good practice to explicitly set default values for arguments that are not required to `NULL`. If there is a default value, it should be the first one listed in the vector of allowed arguments. ## Authentication Many APIs can be called without any authentication (just as if you called them in a web browser). However, others require authentication to perform particular requests or to avoid rate limits and other limitations. The most common forms of authentication are OAuth and HTTP basic authentication: 1. *"Basic" authentication*: This requires a username and password (or sometimes just a username). This is passed as part of the HTTP request. In httr, you can do: `GET("http://httpbin.org", authenticate("username", "password"))` 2. *Basic authentication with an API key*: An alternative provided by many APIs is an API "key" or "token" which is passed as part of the request. It is better than a username/password combination because it can be regenerated independent of the username and password. This API key can be specified in a number of different ways: in a URL query argument, in an HTTP header such as the `Authorization` header, or in an argument inside the request body. 3. *OAuth*: OAuth is a protocol for generating a user- or session-specific authentication token to use in subsequent requests. (An early standard, OAuth 1.0, is not terribly common any more. See `oauth1.0_token()` for details.) The current OAuth 2.0 standard is very common in modern web apps. It involves a round trip between the client and server to establish if the API client has the authority to access the data. See `oauth2.0_token()`. It's ok to publish the app ID and app "secret" - these are not actually important for security of user data. > Some APIs describe their authentication processes inaccurately, so > care needs to be taken to understand the true authentication mechanism > regardless of the label used in the API docs. It is possible to specify the key(s) or token(s) required for basic or OAuth authentication in a number of different ways. You may also need some way to preserve user credentials between function calls so that end users do not need to specify them each time. A good start is to use an environment variable. Here is an example of how to write a function that checks for the presence of a GitHub personal access token and errors otherwise: ```{r} github_pat <- function() { pat <- Sys.getenv('GITHUB_PAT') if (identical(pat, "")) { stop("Please set env var GITHUB_PAT to your github personal access token", call. = FALSE) } pat } ``` ## Pagination (handling multi-page responses) One particularly frustrating aspect of many APIs is dealing with paginated responses. This is common in APIs that offer search functionality and have the potential to return a very large number of responses. Responses might be paginated because there is a large number of response elements or because elements are updated frequently. Often they will be sorted by an explicit or implicit argument specified in the request. When a response is paginated, the API response will typically respond with a header or value specified in the body that contains one of the following: 1. The total number of pages of responses 2. The total number of response elements (with multiple elements per page) 3. An indicator for whether any further elements or pages are available. 4. A URL containing the next page These values can then be used to make further requests. This will either involve specifying a specific page of responses or specifying a "next page token" that returns the next page of results. How to deal with pagination is a difficult question and a client could implement any of the following: 1. Return one page only by default with an option to return additional specific pages 2. Return a specified page (defaulting to 1) and require the end user to handle pagination 3. Return all pages by writing an internal process of checking for further pages and combining the results The choice of which to use depends on your needs and goals and the rate limits of the API. ### Rate limiting Many APIs are rate limited, which means that you can only send a certain number of requests per hour. Often if your request is rate limited, the error message will tell you how long you should wait before performing another request. You might want to expose this to the user, or even include a call to `Sys.sleep()` that waits long enough. For example, we could implement a `rate_limit()` function that tells you how many calls against the github API are available to you. ```{r} rate_limit <- function() { github_api("/rate_limit") } rate_limit() ``` After getting the first version working, you'll often want to polish the output to be more user friendly. For this example, we can parse the unix timestamps into more useful date types. ```{r} rate_limit <- function() { req <- github_api("/rate_limit") core <- req$content$resources$core reset <- as.POSIXct(core$reset, origin = "1970-01-01") cat(core$remaining, " / ", core$limit, " (Resets at ", strftime(reset, "%H:%M:%S"), ")\n", sep = "") } rate_limit() ``` httr/inst/doc/quickstart.Rmd0000644000176200001440000002256213376014735015633 0ustar liggesusers--- title: "Getting started with httr" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting started with httr} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, echo = FALSE} library(httr) knitr::opts_chunk$set(comment = "#>", collapse = TRUE) ``` # httr quickstart guide 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][http-tutorial]" or "[HTTP made really easy](http://www.jmarshall.com/easy/http/)". This vignette (and parts of the httr API) derived from the excellent "[Requests quickstart guide](http://docs.python-requests.org/en/latest/user/quickstart/)" 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. ## httr basics To make a request, first load httr, then call `GET()` with a url: ```{r} library(httr) r <- GET("http://httpbin.org/get") ``` 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} r ``` You can pull out important parts of the response with various helper methods, or dig directly into the object: ```{r} status_code(r) headers(r) str(content(r)) ``` 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 response 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 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()`: ```{r} r <- GET("http://httpbin.org/get") # Get an informative description: http_status(r) # Or just access the raw code: r$status_code ``` 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](https://www.flickr.com/photos/girliemac/sets/72157628409467125). You can automatically throw a warning or raise an error if a request did not succeed: ```{r} 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. ### The body There are three ways to access the body of the request, all using `content()`: * `content(r, "text")` accesses the body as a character vector: ```{r} r <- GET("http://httpbin.org/get") content(r, "text") ``` 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: ```{r, eval = FALSE} 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: ```{r} content(r, "raw") ``` This is exactly the sequence of bytes that the web server sent, so this is the highest fidelity way of saving files to disk: ```{r, eval = FALSE} bin <- content(r, "raw") writeBin(bin, "myfile.txt") ``` * httr provides a number of default parsers for common file types: ```{r} # JSON automatically parsed into named list str(content(r, "parsed")) ``` 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. ### The headers Access response headers with `headers()`: ```{r} headers(r) ``` This is basically a named list, but because http headers are case insensitive, indexing this object ignores case: ```{r} headers(r)$date headers(r)$DATE ``` ### Cookies You can access cookies in a similar way: ```{r} r <- GET("http://httpbin.org/cookies/set", query = list(a = 1)) cookies(r) ``` Cookies are automatically persisted between requests to the same domain: ```{r} r <- GET("http://httpbin.org/cookies/set", query = list(b = 1)) cookies(r) ``` ## The request 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. ### The url query string 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: ```{r} r <- GET("http://httpbin.org/get", query = list(key1 = "value1", key2 = "value2") ) content(r)$args ``` Any `NULL` elements are automatically dropped from the list, and both keys and values are escaped automatically. ```{r} r <- GET("http://httpbin.org/get", query = list(key1 = "value 1", "key 2" = "value2", key2 = NULL)) content(r)$args ``` ### Custom headers You can add custom headers to a request with `add_headers()`: ```{r} r <- GET("http://httpbin.org/get", add_headers(Name = "Hadley")) str(content(r)$headers) ``` (Note that `content(r)$header` retrieves the headers that httpbin received. `headers(r)` gives the headers that it sent back in its response.) ## Cookies Cookies are simple key-value pairs like the query string, but they persist across multiple requests in a session (because they're sent back and forth every time). To send your own cookies to the server, use `set_cookies()`: ```{r} r <- GET("http://httpbin.org/cookies", set_cookies("MeWant" = "cookies")) content(r)$cookies ``` Note that this response includes the `a` and `b` cookies that were added by the server earlier. ### Request body When `POST()`ing, you can include data in the `body` of the request. httr allows you to supply this in a number of different ways. The most common way is a named list: ```{r} r <- POST("http://httpbin.org/post", body = list(a = 1, b = 2, c = 3)) ``` You can use the `encode` argument to determine how this data is sent to the server: ```{r} url <- "http://httpbin.org/post" body <- list(a = 1, b = 2, c = 3) # Form encoded r <- POST(url, body = body, encode = "form") # Multipart encoded r <- POST(url, body = body, encode = "multipart") # JSON encoded r <- POST(url, body = body, encode = "json") ``` To see exactly what's being sent to the server, use `verbose()`. Unfortunately due to the way that `verbose()` works, knitr can't capture the messages, so you'll need to run these from an interactive console to see what's going on. ```{r, eval = FALSE} POST(url, body = body, encode = "multipart", verbose()) # the default POST(url, body = body, encode = "form", verbose()) POST(url, body = body, encode = "json", verbose()) ``` `PUT()` and `PATCH()` can also have request bodies, and they take arguments identically to `POST()`. You can also send files off disk: ```{r, eval = FALSE} POST(url, body = upload_file("mypath.txt")) POST(url, body = list(x = upload_file("mypath.txt"))) ``` (`upload_file()` will guess the mime-type from the extension - using the `type` argument to override/supply yourself.) These uploads stream the data to the server: the data will be loaded in R in chunks then sent to the remote server. This means that you can upload files that are larger than memory. See `POST()` for more details on the other types of thing that you can send: no body, empty body, and character and raw vectors. ##### Built with ```{r} sessionInfo() ``` [http-tutorial]:http://code.tutsplus.com/tutorials/http-the-protocol-every-web-developer-must-know-part-1--net-31177 httr/inst/doc/api-packages.R0000644000176200001440000001161413520044653015432 0ustar liggesusers## ----setup, include = FALSE---------------------------------------------- library(httr) knitr::opts_chunk$set(comment = "#>", collapse = TRUE) ## ------------------------------------------------------------------------ library(httr) github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) GET(url) } resp <- github_api("/repos/hadley/httr") resp ## ------------------------------------------------------------------------ GET("http://www.colourlovers.com/api/color/6B4106?format=xml") GET("http://www.colourlovers.com/api/color/6B4106?format=json") ## ------------------------------------------------------------------------ http_type(resp) ## ------------------------------------------------------------------------ github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } resp } ## ------------------------------------------------------------------------ github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE) } ## ------------------------------------------------------------------------ github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE) structure( list( content = parsed, path = path, response = resp ), class = "github_api" ) } print.github_api <- function(x, ...) { cat("\n", sep = "") str(x$content) invisible(x) } github_api("/users/hadley") ## ---- error = TRUE------------------------------------------------------- github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE) if (http_error(resp)) { stop( sprintf( "GitHub API request failed [%s]\n%s\n<%s>", status_code(resp), parsed$message, parsed$documentation_url ), call. = FALSE ) } structure( list( content = parsed, path = path, response = resp ), class = "github_api" ) } github_api("/user/hadley") ## ------------------------------------------------------------------------ ua <- user_agent("http://github.com/hadley/httr") ua github_api <- function(path) { url <- modify_url("https://api.github.com", path = path) resp <- GET(url, ua) if (http_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) } parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE) if (status_code(resp) != 200) { stop( sprintf( "GitHub API request failed [%s]\n%s\n<%s>", status_code(resp), parsed$message, parsed$documentation_url ), call. = FALSE ) } structure( list( content = parsed, path = path, response = resp ), class = "github_api" ) } ## ---- eval = FALSE------------------------------------------------------- # # modify_url # POST(modify_url("https://httpbin.org", path = "/post")) # # # query arguments # POST("http://httpbin.org/post", query = list(foo = "bar")) # # # headers # POST("http://httpbin.org/post", add_headers(foo = "bar")) # # # body # ## as form # POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "form") # ## as json # POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "json") ## ------------------------------------------------------------------------ f <- function(x = c("apple", "banana", "orange")) { match.arg(x) } f("a") ## ------------------------------------------------------------------------ github_pat <- function() { pat <- Sys.getenv('GITHUB_PAT') if (identical(pat, "")) { stop("Please set env var GITHUB_PAT to your github personal access token", call. = FALSE) } pat } ## ------------------------------------------------------------------------ rate_limit <- function() { github_api("/rate_limit") } rate_limit() ## ------------------------------------------------------------------------ rate_limit <- function() { req <- github_api("/rate_limit") core <- req$content$resources$core reset <- as.POSIXct(core$reset, origin = "1970-01-01") cat(core$remaining, " / ", core$limit, " (Resets at ", strftime(reset, "%H:%M:%S"), ")\n", sep = "") } rate_limit() httr/inst/doc/secrets.html0000644000176200001440000006774613520044655015343 0ustar liggesusers Managing secrets

Managing secrets

Hadley Wickham

Introduction

This document gives you the basics on securely managing secrets. Most of this document is not directly related to httr, but it’s common to have some secrets to manage whenever you are using an API.

What is a secret? Some secrets are short alphanumeric sequences:

  • Passwords are clearly secrets, e.g. the second argument to authenticate(). Passwords are particularly important because people (ill-advisedly) often use the same password in multiple places.

  • Personal access tokens (e.g. github) should be kept secret: they are basically equivalent to a user name password combination, but are slightly safer because you can have multiple tokens for different purposes and it’s easy to invalidate one token without affecting the others.

Surprisingly, the “client secret” in an oauth_app() is not a secret. It’s not equivalent to a password, and if you are writing an API wrapper package, it should be included in the package. (If you don’t believe me, here are google’s comments on the topic.)

Other secrets are files:

  • The JSON web token (jwt) used for server-to-server OAuth (e.g. google) is a secret because it’s equivalent to a personal access token.

  • The .httr-oauth file is a secret because it stores OAuth access tokens.

The goal of this vignette is to give you the tools to manage these secrets in a secure way. We’ll start with best practices for managing secrets locally, then talk about sharing secrets with selected others (including travis), and finish with the challenges that CRAN presents.

Here, I assume that the main threat is accidentally sharing your secrets when you don’t want to. Protecting against a committed attacker is much harder. And if someone has already hacked your computer to the point where they can run code, there’s almost nothing you can do. If you’re concerned about those scenarios, you’ll need to take a more comprehensive approach that’s outside the scope of this document.

Locally

Working with secret files locally is straightforward because it’s ok to store them in your project directory as long as you take three precautions:

  • Ensure the file is only readable by you, not by any other user on the system. You can use the R function Sys.chmod() to do so:

    Sys.chmod("secret.file", mode = "0400")

    It’s good practice to verify this setting by examining the file metadata with your local filesystem GUI tools or commands.

  • If you use git: make sure the files are listed in .gitignore so they don’t accidentally get included in a public repository.

  • If you’re making a package: make sure they are listed in .Rbuildignore so they don’t accidentally get included in a public R package.

httr proactively takes all of these steps for you whenever it creates a .httr-oauth file.

The main remaining risk is that you might share the entire directory (i.e. zipping and emailing, or in a public dropbox directory). If you’re worried about this scenario, store your secret files outside of the project directory. If you do this, make sure to provide a helper function to locate the file and provide an informative message if it’s missing.

my_secrets <- function() {
  path <- "~/secrets/secret.json"
  if (!file.exists(path)) {
    stop("Can't find secret file: '", path, "'")
  }
  
  jsonlite::read_json(path)
}

Storing short secrets is harder because it’s tempting to record them as a variable in your R script. This is a bad idea, because you end up with a file that contains a mix of secret and public code. Instead, you have three options:

  • Ask for the secret each time.
  • Store in an environment variable.
  • Use the keyring package.

Regardless of how you store them, to use your secrets you will still need to read them into R variables. Be careful not to expose them by printing them or saving them to a file.

Ask each time

For scripts that you only use every now and then, a simple solution is to simply ask for the password each time the script is run. If you use RStudio an easy and secure way to request a password is with the rstudioapi package:

password <- rstudioapi::askForPassword()

If you don’t use RStudio, use a more general solution like the getPass package.

You should never type your password into the R console: this will typically be stored in the .Rhistory file, and it’s easy to accidentally share without realising it.

Environment variables

Asking each time is a hassle, so you might want to store the secret across sessions. One easy way to do that is with environment variables. Environment variables, or envvars for short, are a cross platform way of passing information to processes.

For passing envvars to R, you can list name-value pairs in a file called .Renviron in your home directory. The easiest way to edit it is to run:

file.edit("~/.Renviron")

The file looks something like

VAR1 = value1
VAR2 = value2

And you can access the values in R using Sys.getenv():

Sys.getenv("VAR1")
#> [1] "value1"

Note that .Renviron is only processed on startup, so you’ll need to restart R to see changes.

These environment variables will be available in every running R process, and can easily be read by any other program on your computer to access that file directly. For more security, use the keyring package.

Keyring

The keyring package provides a way to store (and retrieve) data in your OS’s secure secret store. Keyring has a simple API:

keyring::key_set("MY_SECRET")
keyring::key_get("MY_SECRET")

By default, keyring will use the system keyring. This is unlocked by default when you log in, which means while the password is stored securely pretty much any process can access it.

If you want to be even more secure, you can create custom keyring and keep it locked. That will require you to enter a password every time you want to access your secret.

keyring::keyring_create("httr")
keyring::key_set("MY_SECRET", keyring = "httr")

Note that accessing the key always unlocks the keyring, so if you’re being really careful, make sure to lock it again afterwards.

keyring::keyring_lock("httr")

You might wonder if we’ve actually achieved anything here because we still need to enter a password! However, that one password lets you access every secret, and you can control how often you need to re-enter it by manually locking and unlocking the keyring.

Sharing with others

By and large, managing secrets on your own computer is straightforward. The challenge comes when you need to share them with selected others:

  • You may need to share a secret with me so that I can run your reprex and figure out what is wrong with httr.

  • You might want to share a secret amongst a group of developers all working on the same GitHub project.

  • You might want to automatically run authenticated tests on travis.

To make this work, all the techniques in this section rely on public key cryptography. This is a type of asymmetric encryption where you use a public key to produce content that can only be decrypted by the holder of the matching private key.

Reprexes

The most common place you might need to share a secret is to generate a reprex. First, do everything you can do eliminate the need to share a secret:

  • If it is an http problem, make sure to run all requests with verbose().
  • If you get an R error, make sure to include traceback().

If you’re lucky, that will be sufficient information to fix the problem.

Otherwise, you’ll need to encrypt the secret so you can share it with me. The easiest way to do so is with the following snippet:

library(openssl)
library(jsonlite)
library(curl)

encrypt <- function(secret, username) {
  url <- paste("https://api.github.com/users", username, "keys", sep = "/")

  resp <- httr::GET(url)
  httr::stop_for_status(resp)
  pubkey <- httr::content(resp)[[1]]$key

  opubkey <- openssl::read_pubkey(pubkey)
  cipher <- openssl::rsa_encrypt(charToRaw(secret), opubkey)
  jsonlite::base64_enc(cipher)
}
  
cipher <- encrypt("<username>\n<password>", "hadley")
cat(cipher)

Then I can run the following code on my computer to access it:

decrypt <- function(cipher, key = openssl::my_key()) {
  cipherraw <- jsonlite::base64_dec(cipher)
  rawToChar(openssl::rsa_decrypt(cipherraw, key = key))
}

decrypt(cipher)
#> username
#> password

Change your password before and after you share it with me or anyone else.

GitHub

If you want to share secrets with a group of other people on GitHub, use the secret or cyphr packages.

Travis

The easiest way to handle short secrets is to use environment variables. You’ll set in your .Renviron locally and in the settings pane on travis. That way you can use Sys.getenv() to access in both places. It’s also possible to set encrypted env vars in your .travis.yml: see the documentation for details.

Regardless of how you set it, make sure you have a helper to retrieve the value. A good error message will save you a lot of time when debugging problems!

my_secret <- function() {
  val <- Sys.getenv("SECRET")
  if (identical(val, "")) {
    stop("`SECRET` env var has not been set")
  }
  val
}

Note that encrypted data is not available in pull requests in forks. Typically you’ll need to check PRs locally once you’ve confirmed that the code isn’t actively malicious.

To share secret files on travis, see https://docs.travis-ci.com/user/encrypting-files/. Basically you will encrypt the file locally and check it in to git. Then you’ll add a decryption step to your .travis.yml which makes it decrypts it for each run. See bigquery for an example.

Be careful to not accidentally expose the secret on travis. An easy way to accidentally expose the secret is to print it out so that it’s captured in the log. Don’t do that!

CRAN

There is no way to securely share information with arbitrary R users, including CRAN. That means that if you’re developing a package, you need to make sure that R CMD check passes cleanly even when authentication is not available. This tends to primarily affect the documentation, vignettes, and tests.

Documentation

Like any R package, an API client needs clear and complete documentation of all functions. Examples are particularly useful but may need to be wrapped in \donttest{} to avoid challenges of authentication, rate limiting, lack of network access, or occasional API server down time.

Vignettes

Vignettes pose additional challenges when an API requires authentication, because you don’t want to bundle your own credentials with the package! However, you can take advantage of the fact that the vignette is built locally, and only checked by CRAN. In a setup chunk, do:

NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true")
knitr::opts_chunk$set(purl = NOT_CRAN)

And then use eval = NOT_CRAN in any chunk that requires access to a secret.

Testing

Use testthat::skip() to automatically skip tests that require authentication. I typically will wrap this into a little helper function that I call at the start of every test requiring auth.

skip_if_no_auth <- function() {
  if (identical(Sys.getenv("MY_SECRET"), "")) {
    skip("No authentication available")
  }
}
httr/inst/doc/api-packages.html0000644000176200001440000015731313520044653016204 0ustar liggesusers Best practices for API packages

Best practices for API packages

So you want to write an R client for a web API? This document walks through the key issues involved in writing API wrappers in R. If you’re new to working with web APIs, you may want to start by reading “An introduction to APIs” by zapier.

Overall design

APIs vary widely. Before starting to code, it is important to understand how the API you are working with handles important issues so that you can implement a complete and coherent R client for the API.

The key features of any API are the structure of the requests and the structure of the responses. An HTTP request consists of the following parts:

  1. HTTP verb (GET, POST, DELETE, etc.)
  2. The base URL for the API
  3. The URL path or endpoint
  4. URL query arguments (e.g., ?foo=bar)
  5. Optional headers
  6. An optional request body

An API package needs to be able to generate these components in order to perform the desired API call, which will typically involve some sort of authentication.

For example, to request that the GitHub API provides a list of all issues for the httr repo, we send an HTTP request that looks like:

-> GET /repos/hadley/httr HTTP/1.1
-> Host: api.github.com
-> Accept: application/vnd.github.v3+json

Here we’re using a GET request to the host api.github.com. The url is /repos/hadley/httr, and we send an accept header that tells GitHub what sort of data we want.

In response to this request, the API will return an HTTP response that includes:

  1. An HTTP status code.
  2. Headers, key-value pairs.
  3. A body typically consisting of XML, JSON, plain text, HTML, or some kind of binary representation.

An API client needs to parse these responses, turning API errors into R errors, and return a useful object to the end user. For the previous HTTP request, GitHub returns:

<- HTTP/1.1 200 OK
<- Server: GitHub.com
<- Content-Type: application/json; charset=utf-8
<- X-RateLimit-Limit: 5000
<- X-RateLimit-Remaining: 4998
<- X-RateLimit-Reset: 1459554901
<- 
<- {
<-   "id": 2756403,
<-   "name": "httr",
<-   "full_name": "hadley/httr",
<-   "owner": {
<-     "login": "hadley",
<-     "id": 4196,
<-     "avatar_url": "https://avatars.githubusercontent.com/u/4196?v=3",
<-     ...
<-   },
<-   "private": false,
<-   "html_url": "https://github.com/hadley/httr",
<-   "description": "httr: a friendly http package for R",
<-   "fork": false,
<-   "url": "https://api.github.com/repos/hadley/httr",
<-   ...
<-   "network_count": 1368,
<-   "subscribers_count": 64
<- }

Designing a good API client requires identifying how each of these API features is used to compose a request and what type of response is expected for each. It’s best practice to insulate the end user from how the API works so they only need to understand how to use an R function, not the details of how APIs work. It’s your job to suffer so that others don’t have to!

First steps

Send a simple request

First, find a simple API endpoint that doesn’t require authentication: this lets you get the basics working before tackling the complexities of authentication. For this example, we’ll use the list of httr issues which requires sending a GET request to repos/hadley/httr:

library(httr)
github_api <- function(path) {
  url <- modify_url("https://api.github.com", path = path)
  GET(url)
}

resp <- github_api("/repos/hadley/httr")
resp
#> Response [https://api.github.com/repositories/2756403]
#>   Date: 2019-07-30 13:44
#>   Status: 200
#>   Content-Type: application/json; charset=utf-8
#>   Size: 6.04 kB
#> {
#>   "id": 2756403,
#>   "node_id": "MDEwOlJlcG9zaXRvcnkyNzU2NDAz",
#>   "name": "httr",
#>   "full_name": "r-lib/httr",
#>   "private": false,
#>   "owner": {
#>     "login": "r-lib",
#>     "id": 22618716,
#>     "node_id": "MDEyOk9yZ2FuaXphdGlvbjIyNjE4NzE2",
#> ...

Parse the response

Next, you need to take the response returned by the API and turn it into a useful object. Any API will return an HTTP response that consists of headers and a body. While the response can come in multiple forms (see above), two of the most common structured formats are XML and JSON.

Note that while most APIs will return only one or the other, some, like the colour lovers API, allow you to choose which one with a url parameter:

GET("http://www.colourlovers.com/api/color/6B4106?format=xml")
#> Response [http://www.colourlovers.com/api/color/6B4106?format=xml]
#>   Date: 2019-07-30 13:44
#>   Status: 200
#>   Content-Type: text/xml; charset=utf-8
#>   Size: 1.8 kB
#> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
#> <colors numResults="1" totalResults="10010720">
#>  <color>
#>      <id>903893</id>
#>      <title><![CDATA[wet dirt]]></title>
#>      <userName><![CDATA[jessicabrown]]></userName>
#>      <numViews>565</numViews>
#>      <numVotes>1</numVotes>
#>      <numComments>0</numComments>
#>      <numHearts>0</numHearts>
#> ...
GET("http://www.colourlovers.com/api/color/6B4106?format=json")
#> Response [http://www.colourlovers.com/api/color/6B4106?format=json]
#>   Date: 2019-07-30 13:44
#>   Status: 200
#>   Content-Type: application/json; charset=utf-8
#>   Size: 1.43 kB

Others use content negotiation to determine what sort of data to send back. If the API you’re wrapping does this, then you’ll need to include one of accept_json() and accept_xml() in your request.

If you have a choice, choose json: it’s usually much easier to work with than xml.

Most APIs will return most or all useful information in the response body, which can be accessed using content(). To determine what type of information is returned, you can use http_type()

http_type(resp)
#> [1] "application/json"

I recommend checking that the type is as you expect in your helper function. This will ensure that you get a clear error message if the API changes:

github_api <- function(path) {
  url <- modify_url("https://api.github.com", path = path)
  
  resp <- GET(url)
  if (http_type(resp) != "application/json") {
    stop("API did not return json", call. = FALSE)
  }
  
  resp
}

NB: some poorly written APIs will say the content is type A, but it will actually be type B. In this case you should complain to the API authors, and until they fix the problem, simply drop the check for content type.

Next we need to parse the output into an R object. httr provides some default parsers with content(..., as = "auto") but I don’t recommend using them inside a package. Instead it’s better to explicitly parse it yourself:

  1. To parse json, use jsonlite package.
  2. To parse xml, use the xml2 package.
github_api <- function(path) {
  url <- modify_url("https://api.github.com", path = path)
  
  resp <- GET(url)
  if (http_type(resp) != "application/json") {
    stop("API did not return json", call. = FALSE)
  }
  
  jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
}

Return a helpful object

Rather than simply returning the response as a list, I think it’s a good practice to make a simple S3 object. That way you can return the response and parsed object, and provide a nice print method. This will make debugging later on much much much more pleasant.

github_api <- function(path) {
  url <- modify_url("https://api.github.com", path = path)
  
  resp <- GET(url)
  if (http_type(resp) != "application/json") {
    stop("API did not return json", call. = FALSE)
  }
  
  parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
  
  structure(
    list(
      content = parsed,
      path = path,
      response = resp
    ),
    class = "github_api"
  )
}

print.github_api <- function(x, ...) {
  cat("<GitHub ", x$path, ">\n", sep = "")
  str(x$content)
  invisible(x)
}

github_api("/users/hadley")
#> <GitHub /users/hadley>
#> List of 31
#>  $ login              : chr "hadley"
#>  $ id                 : int 4196
#>  $ node_id            : chr "MDQ6VXNlcjQxOTY="
#>  $ avatar_url         : chr "https://avatars3.githubusercontent.com/u/4196?v=4"
#>  $ gravatar_id        : chr ""
#>  $ url                : chr "https://api.github.com/users/hadley"
#>  $ html_url           : chr "https://github.com/hadley"
#>  $ followers_url      : chr "https://api.github.com/users/hadley/followers"
#>  $ following_url      : chr "https://api.github.com/users/hadley/following{/other_user}"
#>  $ gists_url          : chr "https://api.github.com/users/hadley/gists{/gist_id}"
#>  $ starred_url        : chr "https://api.github.com/users/hadley/starred{/owner}{/repo}"
#>  $ subscriptions_url  : chr "https://api.github.com/users/hadley/subscriptions"
#>  $ organizations_url  : chr "https://api.github.com/users/hadley/orgs"
#>  $ repos_url          : chr "https://api.github.com/users/hadley/repos"
#>  $ events_url         : chr "https://api.github.com/users/hadley/events{/privacy}"
#>  $ received_events_url: chr "https://api.github.com/users/hadley/received_events"
#>  $ type               : chr "User"
#>  $ site_admin         : logi FALSE
#>  $ name               : chr "Hadley Wickham"
#>  $ company            : chr "@rstudio "
#>  $ blog               : chr "http://hadley.nz"
#>  $ location           : chr "Houston, TX"
#>  $ email              : NULL
#>  $ hireable           : NULL
#>  $ bio                : chr "Chief Scientist at @RStudio"
#>  $ public_repos       : int 219
#>  $ public_gists       : int 169
#>  $ followers          : int 16388
#>  $ following          : int 6
#>  $ created_at         : chr "2008-04-01T14:47:36Z"
#>  $ updated_at         : chr "2019-07-30T12:00:45Z"

The API might return invalid data, but this should be rare, so you can just rely on the parser to provide a useful error message.

Turn API errors into R errors

Next, you need to make sure that your API wrapper throws an error if the request failed. Using a web API introduces additional possible points of failure into R code aside from those occurring in R itself. These include:

  • Client-side exceptions
  • Network / communication exceptions
  • Server-side exceptions

You need to make sure these are all converted into regular R errors. You can figure out if there’s a problem with http_error(), which checks the HTTP status code. Status codes in the 400 range usually mean that you’ve done something wrong. Status codes in the 500 range typically mean that something has gone wrong on the server side.

Often the API will provide information about the error in the body of the response: you should use this where available. If the API returns special errors for common problems, you might want to provide more detail in the error. For example, if you run out of requests and are rate limited you might want to tell the user how long to wait until they can make the next request (or even automatically wait that long!).

github_api <- function(path) {
  url <- modify_url("https://api.github.com", path = path)
  
  resp <- GET(url)
  if (http_type(resp) != "application/json") {
    stop("API did not return json", call. = FALSE)
  }
  
  parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
  
  if (http_error(resp)) {
    stop(
      sprintf(
        "GitHub API request failed [%s]\n%s\n<%s>", 
        status_code(resp),
        parsed$message,
        parsed$documentation_url
      ),
      call. = FALSE
    )
  }
  
  structure(
    list(
      content = parsed,
      path = path,
      response = resp
    ),
    class = "github_api"
  )
}
github_api("/user/hadley")
#> Error: GitHub API request failed [404]
#> Not Found
#> <https://developer.github.com/v3/users/#get-a-single-user>

Some poorly written APIs will return different types of response based on whether or not the request succeeded or failed. If your API does this you’ll need to make your request function check the status_code() before parsing the response.

For many APIs, the common approach is to retry API calls that return something in the 500 range. However, when doing this, it’s extremely important to make sure to do this with some form of exponential backoff: if something’s wrong on the server-side, hammering the server with retries may make things worse, and may lead to you exhausting quota (or hitting other sorts of rate limits). A common policy is to retry up to 5 times, starting at 1s, and each time doubling and adding a small amount of jitter (plus or minus up to, say, 5% of the current wait time).

Set a user agent

While we’re in this function, there’s one important header that you should set for every API wrapper: the user agent. The user agent is a string used to identify the client. This is most useful for the API owner as it allows them to see who is using the API. It’s also useful for you if you have a contact on the inside as it often makes it easier for them to pull your requests from their logs and see what’s going wrong. If you’re hitting a commercial API, this also makes it easier for internal R advocates to see how many people are using their API via R and hopefully assign more resources.

A good default for an R API package wrapper is to make it the URL to your GitHub repo:

ua <- user_agent("http://github.com/hadley/httr")
ua
#> <request>
#> Options:
#> * useragent: http://github.com/hadley/httr

github_api <- function(path) {
  url <- modify_url("https://api.github.com", path = path)
  
  resp <- GET(url, ua)
  if (http_type(resp) != "application/json") {
    stop("API did not return json", call. = FALSE)
  }
  
  parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
  
  if (status_code(resp) != 200) {
    stop(
      sprintf(
        "GitHub API request failed [%s]\n%s\n<%s>", 
        status_code(resp),
        parsed$message,
        parsed$documentation_url
      ),
      call. = FALSE
    )
  }
  
  structure(
    list(
      content = parsed,
      path = path,
      response = resp
    ),
    class = "github_api"
  )
}

Passing parameters

Most APIs work by executing an HTTP method on a specified URL with some additional parameters. These parameters can be specified in a number of ways, including in the URL path, in URL query arguments, in HTTP headers, and in the request body itself. These parameters can be controlled using httr functions:

  1. URL path: modify_url()
  2. Query arguments: The query argument to GET(), POST(), etc.
  3. HTTP headers: add_headers()
  4. Request body: The body argument to GET(), POST(), etc.

RESTful APIs also use the HTTP verb to communicate arguments (e.g., GET retrieves a file, POST adds a file, DELETE removes a file, etc.). We can use the helpful httpbin service to show how to send arguments in each of these ways.

# modify_url
POST(modify_url("https://httpbin.org", path = "/post"))

# query arguments
POST("http://httpbin.org/post", query = list(foo = "bar"))

# headers
POST("http://httpbin.org/post", add_headers(foo = "bar"))

# body
## as form
POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "form")
## as json
POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "json")

Many APIs will use just one of these forms of argument passing, but others will use multiple of them in combination. Best practice is to insulate the user from how and where the various arguments are used by the API and instead simply expose relevant arguments via R function arguments, some of which might be used in the URL, in the headers, in the body, etc.

If a parameter has a small fixed set of possible values that are allowed by the API, you can use list them in the default arguments and then use match.arg() to ensure that the caller only supplies one of those values. (This also allows the user to supply the short unique prefixes.)

f <- function(x = c("apple", "banana", "orange")) {
  match.arg(x)
}
f("a")
#> [1] "apple"

It is good practice to explicitly set default values for arguments that are not required to NULL. If there is a default value, it should be the first one listed in the vector of allowed arguments.

Authentication

Many APIs can be called without any authentication (just as if you called them in a web browser). However, others require authentication to perform particular requests or to avoid rate limits and other limitations. The most common forms of authentication are OAuth and HTTP basic authentication:

  1. “Basic” authentication: This requires a username and password (or sometimes just a username). This is passed as part of the HTTP request. In httr, you can do: GET("http://httpbin.org", authenticate("username", "password"))

  2. Basic authentication with an API key: An alternative provided by many APIs is an API “key” or “token” which is passed as part of the request. It is better than a username/password combination because it can be regenerated independent of the username and password.

    This API key can be specified in a number of different ways: in a URL query argument, in an HTTP header such as the Authorization header, or in an argument inside the request body.

  3. OAuth: OAuth is a protocol for generating a user- or session-specific authentication token to use in subsequent requests. (An early standard, OAuth 1.0, is not terribly common any more. See oauth1.0_token() for details.) The current OAuth 2.0 standard is very common in modern web apps. It involves a round trip between the client and server to establish if the API client has the authority to access the data. See oauth2.0_token(). It’s ok to publish the app ID and app “secret” - these are not actually important for security of user data.

Some APIs describe their authentication processes inaccurately, so care needs to be taken to understand the true authentication mechanism regardless of the label used in the API docs.

It is possible to specify the key(s) or token(s) required for basic or OAuth authentication in a number of different ways. You may also need some way to preserve user credentials between function calls so that end users do not need to specify them each time. A good start is to use an environment variable. Here is an example of how to write a function that checks for the presence of a GitHub personal access token and errors otherwise:

github_pat <- function() {
  pat <- Sys.getenv('GITHUB_PAT')
  if (identical(pat, "")) {
    stop("Please set env var GITHUB_PAT to your github personal access token",
      call. = FALSE)
  }

  pat
}

Pagination (handling multi-page responses)

One particularly frustrating aspect of many APIs is dealing with paginated responses. This is common in APIs that offer search functionality and have the potential to return a very large number of responses. Responses might be paginated because there is a large number of response elements or because elements are updated frequently. Often they will be sorted by an explicit or implicit argument specified in the request.

When a response is paginated, the API response will typically respond with a header or value specified in the body that contains one of the following:

  1. The total number of pages of responses
  2. The total number of response elements (with multiple elements per page)
  3. An indicator for whether any further elements or pages are available.
  4. A URL containing the next page

These values can then be used to make further requests. This will either involve specifying a specific page of responses or specifying a “next page token” that returns the next page of results. How to deal with pagination is a difficult question and a client could implement any of the following:

  1. Return one page only by default with an option to return additional specific pages
  2. Return a specified page (defaulting to 1) and require the end user to handle pagination
  3. Return all pages by writing an internal process of checking for further pages and combining the results

The choice of which to use depends on your needs and goals and the rate limits of the API.

Rate limiting

Many APIs are rate limited, which means that you can only send a certain number of requests per hour. Often if your request is rate limited, the error message will tell you how long you should wait before performing another request. You might want to expose this to the user, or even include a call to Sys.sleep() that waits long enough.

For example, we could implement a rate_limit() function that tells you how many calls against the github API are available to you.

rate_limit <- function() {
  github_api("/rate_limit")
}
rate_limit()
#> <GitHub /rate_limit>
#> List of 2
#>  $ resources:List of 4
#>   ..$ core                :List of 3
#>   .. ..$ limit    : int 60
#>   .. ..$ remaining: int 40
#>   .. ..$ reset    : int 1564497208
#>   ..$ search              :List of 3
#>   .. ..$ limit    : int 10
#>   .. ..$ remaining: int 10
#>   .. ..$ reset    : int 1564494311
#>   ..$ graphql             :List of 3
#>   .. ..$ limit    : int 0
#>   .. ..$ remaining: int 0
#>   .. ..$ reset    : int 1564497851
#>   ..$ integration_manifest:List of 3
#>   .. ..$ limit    : int 5000
#>   .. ..$ remaining: int 5000
#>   .. ..$ reset    : int 1564497851
#>  $ rate     :List of 3
#>   ..$ limit    : int 60
#>   ..$ remaining: int 40
#>   ..$ reset    : int 1564497208

After getting the first version working, you’ll often want to polish the output to be more user friendly. For this example, we can parse the unix timestamps into more useful date types.

rate_limit <- function() {
  req <- github_api("/rate_limit")
  core <- req$content$resources$core

  reset <- as.POSIXct(core$reset, origin = "1970-01-01")
  cat(core$remaining, " / ", core$limit,
    " (Resets at ", strftime(reset, "%H:%M:%S"), ")\n", sep = "")
}

rate_limit()
#> 40 / 60 (Resets at 09:33:28)
httr/inst/doc/secrets.Rmd0000644000176200001440000003025613401555510015076 0ustar liggesusers--- title: "Managing secrets" author: "Hadley Wickham" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Managing secrets} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, echo = FALSE} library(httr) knitr::opts_chunk$set(comment = "#>", collapse = TRUE) ``` ## Introduction This document gives you the basics on securely managing secrets. Most of this document is not directly related to httr, but it's common to have some secrets to manage whenever you are using an API. What is a secret? Some secrets are short alphanumeric sequences: * Passwords are clearly secrets, e.g. the second argument to `authenticate()`. Passwords are particularly important because people (ill-advisedly) often use the same password in multiple places. * Personal access tokens (e.g. [github][github-token]) should be kept secret: they are basically equivalent to a user name password combination, but are slightly safer because you can have multiple tokens for different purposes and it's easy to invalidate one token without affecting the others. Surprisingly, the "client secret" in an `oauth_app()` is __not__ a secret. It's not equivalent to a password, and if you are writing an API wrapper package, it should be included in the package. (If you don't believe me, here are [google's comments on the topic][google-secret].) Other secrets are files: * The JSON web token (jwt) used for server-to-server OAuth (e.g. [google][google-server]) is a secret because it's equivalent to a personal access token. * The `.httr-oauth` file is a secret because it stores OAuth access tokens. The goal of this vignette is to give you the tools to manage these secrets in a secure way. We'll start with best practices for managing secrets locally, then talk about sharing secrets with selected others (including travis), and finish with the challenges that CRAN presents. Here, I assume that the main threat is accidentally sharing your secrets when you don't want to. Protecting against a committed attacker is much harder. And if someone has already hacked your computer to the point where they can run code, there's almost nothing you can do. If you're concerned about those scenarios, you'll need to take a more comprehensive approach that's outside the scope of this document. ## Locally Working with secret files locally is straightforward because it's ok to store them in your project directory as long as you take three precautions: * Ensure the file is only readable by you, not by any other user on the system. You can use the R function `Sys.chmod()` to do so: ```{r, eval = FALSE} Sys.chmod("secret.file", mode = "0400") ``` It's good practice to verify this setting by examining the file metadata with your local filesystem GUI tools or commands. * If you use git: make sure the files are listed in `.gitignore` so they don't accidentally get included in a public repository. * If you're making a package: make sure they are listed in `.Rbuildignore` so they don't accidentally get included in a public R package. httr proactively takes all of these steps for you whenever it creates a `.httr-oauth` file. The main remaining risk is that you might share the entire directory (i.e. zipping and emailing, or in a public dropbox directory). If you're worried about this scenario, store your secret files outside of the project directory. If you do this, make sure to provide a helper function to locate the file and provide an informative message if it's missing. ```{r} my_secrets <- function() { path <- "~/secrets/secret.json" if (!file.exists(path)) { stop("Can't find secret file: '", path, "'") } jsonlite::read_json(path) } ``` Storing short secrets is harder because it's tempting to record them as a variable in your R script. This is a bad idea, because you end up with a file that contains a mix of secret and public code. Instead, you have three options: * Ask for the secret each time. * Store in an environment variable. * Use the keyring package. Regardless of how you store them, to use your secrets you will still need to read them into R variables. Be careful not to expose them by printing them or saving them to a file. ### Ask each time For scripts that you only use every now and then, a simple solution is to simply ask for the password each time the script is run. If you use RStudio an easy and secure way to request a password is with the rstudioapi package: ```{r, eval = FALSE} password <- rstudioapi::askForPassword() ``` If you don't use RStudio, use a more general solution like the [getPass](https://github.com/wrathematics/getPass) package. You should __never__ type your password into the R console: this will typically be stored in the `.Rhistory` file, and it's easy to accidentally share without realising it. ### Environment variables Asking each time is a hassle, so you might want to store the secret across sessions. One easy way to do that is with environment variables. Environment variables, or __envvars__ for short, are a cross platform way of passing information to processes. For passing envvars to R, you can list name-value pairs in a file called `.Renviron` in your home directory. The easiest way to edit it is to run: ```{r, eval = FALSE} file.edit("~/.Renviron") ``` The file looks something like ``` VAR1 = value1 VAR2 = value2 ``` ```{r, include = FALSE} Sys.setenv("VAR1" = "value1") ``` And you can access the values in R using `Sys.getenv()`: ```{r} Sys.getenv("VAR1") ``` Note that `.Renviron` is only processed on startup, so you'll need to restart R to see changes. These environment variables will be available in every running R process, and can easily be read by any other program on your computer to access that file directly. For more security, use the keyring package. ### Keyring The [keyring](https://github.com/r-lib/keyring) package provides a way to store (and retrieve) data in your OS's secure secret store. Keyring has a simple API: ```{r, eval = FALSE} keyring::key_set("MY_SECRET") keyring::key_get("MY_SECRET") ``` By default, keyring will use the system keyring. This is unlocked by default when you log in, which means while the password is stored securely pretty much any process can access it. If you want to be even more secure, you can create custom keyring and keep it locked. That will require you to enter a password every time you want to access your secret. ```{r, eval = FALSE} keyring::keyring_create("httr") keyring::key_set("MY_SECRET", keyring = "httr") ``` Note that accessing the key always unlocks the keyring, so if you're being really careful, make sure to lock it again afterwards. ```{r, eval = FALSE} keyring::keyring_lock("httr") ``` You might wonder if we've actually achieved anything here because we still need to enter a password! However, that one password lets you access every secret, and you can control how often you need to re-enter it by manually locking and unlocking the keyring. ## Sharing with others By and large, managing secrets on your own computer is straightforward. The challenge comes when you need to share them with selected others: * You may need to share a secret with me so that I can run your reprex and figure out what is wrong with httr. * You might want to share a secret amongst a group of developers all working on the same GitHub project. * You might want to automatically run authenticated tests on travis. To make this work, all the techniques in this section rely on __public key cryptography__. This is a type of asymmetric encryption where you use a public key to produce content that can only be decrypted by the holder of the matching private key. ### Reprexes The most common place you might need to share a secret is to generate a reprex. First, do everything you can do eliminate the need to share a secret: * If it is an http problem, make sure to run all requests with `verbose()`. * If you get an R error, make sure to include `traceback()`. If you're lucky, that will be sufficient information to fix the problem. Otherwise, you'll need to encrypt the secret so you can share it with me. The easiest way to do so is with the following snippet: ```{r, eval = FALSE} library(openssl) library(jsonlite) library(curl) encrypt <- function(secret, username) { url <- paste("https://api.github.com/users", username, "keys", sep = "/") resp <- httr::GET(url) httr::stop_for_status(resp) pubkey <- httr::content(resp)[[1]]$key opubkey <- openssl::read_pubkey(pubkey) cipher <- openssl::rsa_encrypt(charToRaw(secret), opubkey) jsonlite::base64_enc(cipher) } cipher <- encrypt("\n", "hadley") cat(cipher) ``` Then I can run the following code on my computer to access it: ```{r, eval = FALSE} decrypt <- function(cipher, key = openssl::my_key()) { cipherraw <- jsonlite::base64_dec(cipher) rawToChar(openssl::rsa_decrypt(cipherraw, key = key)) } decrypt(cipher) #> username #> password ``` Change your password before and after you share it with me or anyone else. ### GitHub If you want to share secrets with a group of other people on GitHub, use the [secret](https://github.com/gaborcsardi/secret) or [cyphr](https://github.com/richfitz/cyphr) packages. ### Travis The easiest way to handle short secrets is to use environment variables. You'll set in your `.Renviron` locally and in the settings pane on travis. That way you can use `Sys.getenv()` to access in both places. It's also possible to set encrypted env vars in your `.travis.yml`: see [the documentation][travis-envvar] for details. Regardless of how you set it, make sure you have a helper to retrieve the value. A good error message will save you a lot of time when debugging problems! ```{r} my_secret <- function() { val <- Sys.getenv("SECRET") if (identical(val, "")) { stop("`SECRET` env var has not been set") } val } ``` Note that encrypted data is not available in pull requests in forks. Typically you'll need to check PRs locally once you've confirmed that the code isn't actively malicious. To share secret files on travis, see . Basically you will encrypt the file locally and check it in to git. Then you'll add a decryption step to your `.travis.yml` which makes it decrypts it for each run. See [bigquery][travis-bigrquery] for an example. Be careful to not accidentally expose the secret on travis. An easy way to accidentally expose the secret is to print it out so that it's captured in the log. Don't do that! ## CRAN There is no way to securely share information with arbitrary R users, including CRAN. That means that if you're developing a package, you need to make sure that `R CMD check` passes cleanly even when authentication is not available. This tends to primarily affect the documentation, vignettes, and tests. ### Documentation Like any R package, an API client needs clear and complete documentation of all functions. Examples are particularly useful but may need to be wrapped in `\donttest{}` to avoid challenges of authentication, rate limiting, lack of network access, or occasional API server down time. ### Vignettes Vignettes pose additional challenges when an API requires authentication, because you don't want to bundle your own credentials with the package! However, you can take advantage of the fact that the vignette is built locally, and only checked by CRAN. In a setup chunk, do: ```{r} NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true") knitr::opts_chunk$set(purl = NOT_CRAN) ``` And then use `eval = NOT_CRAN` in any chunk that requires access to a secret. ### Testing Use `testthat::skip()` to automatically skip tests that require authentication. I typically will wrap this into a little helper function that I call at the start of every test requiring auth. ```{r} skip_if_no_auth <- function() { if (identical(Sys.getenv("MY_SECRET"), "")) { skip("No authentication available") } } ``` [google-secret]: https://developers.google.com/identity/protocols/OAuth2#installed [google-server]: https://developers.google.com/identity/protocols/OAuth2ServiceAccount [github-token]: https://github.com/blog/1509-personal-api-tokens [travis-envvar]: https://docs.travis-ci.com/user/environment-variables/ [travis-bigrquery]: https://github.com/rstats-db/bigrquery/blob/master/.travis.yml httr/inst/doc/quickstart.R0000644000176200001440000000710313520044654015276 0ustar liggesusers## ---- echo = FALSE------------------------------------------------------- library(httr) knitr::opts_chunk$set(comment = "#>", collapse = TRUE) ## ------------------------------------------------------------------------ library(httr) r <- GET("http://httpbin.org/get") ## ------------------------------------------------------------------------ r ## ------------------------------------------------------------------------ status_code(r) headers(r) str(content(r)) ## ------------------------------------------------------------------------ r <- GET("http://httpbin.org/get") # Get an informative description: http_status(r) # Or just access the raw code: r$status_code ## ------------------------------------------------------------------------ warn_for_status(r) stop_for_status(r) ## ------------------------------------------------------------------------ r <- GET("http://httpbin.org/get") content(r, "text") ## ---- eval = FALSE------------------------------------------------------- # content(r, "text", encoding = "ISO-8859-1") ## ------------------------------------------------------------------------ content(r, "raw") ## ---- eval = FALSE------------------------------------------------------- # bin <- content(r, "raw") # writeBin(bin, "myfile.txt") ## ------------------------------------------------------------------------ # JSON automatically parsed into named list str(content(r, "parsed")) ## ------------------------------------------------------------------------ headers(r) ## ------------------------------------------------------------------------ headers(r)$date headers(r)$DATE ## ------------------------------------------------------------------------ r <- GET("http://httpbin.org/cookies/set", query = list(a = 1)) cookies(r) ## ------------------------------------------------------------------------ r <- GET("http://httpbin.org/cookies/set", query = list(b = 1)) cookies(r) ## ------------------------------------------------------------------------ r <- GET("http://httpbin.org/get", query = list(key1 = "value1", key2 = "value2") ) content(r)$args ## ------------------------------------------------------------------------ r <- GET("http://httpbin.org/get", query = list(key1 = "value 1", "key 2" = "value2", key2 = NULL)) content(r)$args ## ------------------------------------------------------------------------ r <- GET("http://httpbin.org/get", add_headers(Name = "Hadley")) str(content(r)$headers) ## ------------------------------------------------------------------------ r <- GET("http://httpbin.org/cookies", set_cookies("MeWant" = "cookies")) content(r)$cookies ## ------------------------------------------------------------------------ r <- POST("http://httpbin.org/post", body = list(a = 1, b = 2, c = 3)) ## ------------------------------------------------------------------------ url <- "http://httpbin.org/post" body <- list(a = 1, b = 2, c = 3) # Form encoded r <- POST(url, body = body, encode = "form") # Multipart encoded r <- POST(url, body = body, encode = "multipart") # JSON encoded r <- POST(url, body = body, encode = "json") ## ---- eval = FALSE------------------------------------------------------- # POST(url, body = body, encode = "multipart", verbose()) # the default # POST(url, body = body, encode = "form", verbose()) # POST(url, body = body, encode = "json", verbose()) ## ---- eval = FALSE------------------------------------------------------- # POST(url, body = upload_file("mypath.txt")) # POST(url, body = list(x = upload_file("mypath.txt"))) ## ------------------------------------------------------------------------ sessionInfo() httr/inst/doc/quickstart.html0000644000176200001440000013663313520044655016055 0ustar liggesusers Getting started with httr

Getting started with httr

httr quickstart guide

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.

httr basics

To make a request, first load httr, then call GET() with a url:

library(httr)
r <- GET("http://httpbin.org/get")

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: 2019-07-30 13:44
#>   Status: 200
#>   Content-Type: application/json
#>   Size: 315 B
#> {
#>   "args": {}, 
#>   "headers": {
#>     "Accept": "application/json, text/xml, application/xml, */*", 
#>     "Accept-Encoding": "gzip, deflate", 
#>     "Host": "httpbin.org", 
#>     "User-Agent": "libcurl/7.54.0 r-curl/3.3 httr/1.4.1"
#>   }, 
#>   "origin": "73.243.104.136, 73.243.104.136", 
#>   "url": "https://httpbin.org/get"
#> ...

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)
#> $`access-control-allow-credentials`
#> [1] "true"
#> 
#> $`access-control-allow-origin`
#> [1] "*"
#> 
#> $`content-encoding`
#> [1] "gzip"
#> 
#> $`content-type`
#> [1] "application/json"
#> 
#> $date
#> [1] "Tue, 30 Jul 2019 13:44:11 GMT"
#> 
#> $`referrer-policy`
#> [1] "no-referrer-when-downgrade"
#> 
#> $server
#> [1] "nginx"
#> 
#> $`x-content-type-options`
#> [1] "nosniff"
#> 
#> $`x-frame-options`
#> [1] "DENY"
#> 
#> $`x-xss-protection`
#> [1] "1; mode=block"
#> 
#> $`content-length`
#> [1] "217"
#> 
#> $connection
#> [1] "keep-alive"
#> 
#> attr(,"class")
#> [1] "insensitive" "list"
str(content(r))
#> List of 4
#>  $ args   : Named list()
#>  $ headers:List of 4
#>   ..$ Accept         : chr "application/json, text/xml, application/xml, */*"
#>   ..$ Accept-Encoding: chr "gzip, deflate"
#>   ..$ Host           : chr "httpbin.org"
#>   ..$ User-Agent     : chr "libcurl/7.54.0 r-curl/3.3 httr/1.4.1"
#>  $ origin : chr "73.243.104.136, 73.243.104.136"
#>  $ url    : chr "https://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 response

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

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():

r <- GET("http://httpbin.org/get")
# Get an informative description:
http_status(r)
#> $category
#> [1] "Success"
#> 
#> $reason
#> [1] "OK"
#> 
#> $message
#> [1] "Success: (200) OK"

# Or just access the raw code:
r$status_code
#> [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.

The body

There are three ways to access the body of the request, all using content():

  • content(r, "text") accesses the body as a character vector:

    r <- GET("http://httpbin.org/get")
    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\": \"gzip, deflate\", \n    \"Host\": \"httpbin.org\", \n    \"User-Agent\": \"libcurl/7.54.0 r-curl/3.3 httr/1.4.1\"\n  }, \n  \"origin\": \"73.243.104.136, 73.243.104.136\", \n  \"url\": \"https://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
    #>  [24] 64 65 72 73 22 3a 20 7b 0a 20 20 20 20 22 41 63 63 65 70 74 22 3a 20
    #>  [47] 22 61 70 70 6c 69 63 61 74 69 6f 6e 2f 6a 73 6f 6e 2c 20 74 65 78 74
    #>  [70] 2f 78 6d 6c 2c 20 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 6d 6c 2c 20
    #>  [93] 2a 2f 2a 22 2c 20 0a 20 20 20 20 22 41 63 63 65 70 74 2d 45 6e 63 6f
    #> [116] 64 69 6e 67 22 3a 20 22 67 7a 69 70 2c 20 64 65 66 6c 61 74 65 22 2c
    #> [139] 20 0a 20 20 20 20 22 48 6f 73 74 22 3a 20 22 68 74 74 70 62 69 6e 2e
    #> [162] 6f 72 67 22 2c 20 0a 20 20 20 20 22 55 73 65 72 2d 41 67 65 6e 74 22
    #> [185] 3a 20 22 6c 69 62 63 75 72 6c 2f 37 2e 35 34 2e 30 20 72 2d 63 75 72
    #> [208] 6c 2f 33 2e 33 20 68 74 74 72 2f 31 2e 34 2e 31 22 0a 20 20 7d 2c 20
    #> [231] 0a 20 20 22 6f 72 69 67 69 6e 22 3a 20 22 37 33 2e 32 34 33 2e 31 30
    #> [254] 34 2e 31 33 36 2c 20 37 33 2e 32 34 33 2e 31 30 34 2e 31 33 36 22 2c
    #> [277] 20 0a 20 20 22 75 72 6c 22 3a 20 22 68 74 74 70 73 3a 2f 2f 68 74 74
    #> [300] 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:

    bin <- content(r, "raw")
    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 4
    #>   ..$ Accept         : chr "application/json, text/xml, application/xml, */*"
    #>   ..$ Accept-Encoding: chr "gzip, deflate"
    #>   ..$ Host           : chr "httpbin.org"
    #>   ..$ User-Agent     : chr "libcurl/7.54.0 r-curl/3.3 httr/1.4.1"
    #>  $ origin : chr "73.243.104.136, 73.243.104.136"
    #>  $ url    : chr "https://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.

The headers

Access response headers with headers():

headers(r)
#> $`access-control-allow-credentials`
#> [1] "true"
#> 
#> $`access-control-allow-origin`
#> [1] "*"
#> 
#> $`content-encoding`
#> [1] "gzip"
#> 
#> $`content-type`
#> [1] "application/json"
#> 
#> $date
#> [1] "Tue, 30 Jul 2019 13:44:11 GMT"
#> 
#> $`referrer-policy`
#> [1] "no-referrer-when-downgrade"
#> 
#> $server
#> [1] "nginx"
#> 
#> $`x-content-type-options`
#> [1] "nosniff"
#> 
#> $`x-frame-options`
#> [1] "DENY"
#> 
#> $`x-xss-protection`
#> [1] "1; mode=block"
#> 
#> $`content-length`
#> [1] "217"
#> 
#> $connection
#> [1] "keep-alive"
#> 
#> 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, 30 Jul 2019 13:44:11 GMT"
headers(r)$DATE
#> [1] "Tue, 30 Jul 2019 13:44:11 GMT"

Cookies

You can access cookies in a similar way:

r <- GET("http://httpbin.org/cookies/set", query = list(a = 1))
cookies(r)
#>        domain  flag path secure expiration name value
#> 1 httpbin.org FALSE    /  FALSE       <NA>    a     1

Cookies are automatically persisted between requests to the same domain:

r <- GET("http://httpbin.org/cookies/set", query = list(b = 1))
cookies(r)
#>        domain  flag path secure expiration name value
#> 1 httpbin.org FALSE    /  FALSE       <NA>    a     1
#> 2 httpbin.org FALSE    /  FALSE       <NA>    b     1

The request

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.

The url query string

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:

r <- GET("http://httpbin.org/get", 
  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.

r <- GET("http://httpbin.org/get", 
  query = list(key1 = "value 1", "key 2" = "value2", key2 = NULL))
content(r)$args
#> $`key 2`
#> [1] "value2"
#> 
#> $key1
#> [1] "value 1"

Custom headers

You can add custom headers to a request with add_headers():

r <- GET("http://httpbin.org/get", add_headers(Name = "Hadley"))
str(content(r)$headers)
#> List of 6
#>  $ Accept         : chr "application/json, text/xml, application/xml, */*"
#>  $ Accept-Encoding: chr "gzip, deflate"
#>  $ Cookie         : chr "a=1; b=1"
#>  $ Host           : chr "httpbin.org"
#>  $ Name           : chr "Hadley"
#>  $ User-Agent     : chr "libcurl/7.54.0 r-curl/3.3 httr/1.4.1"

(Note that content(r)$header retrieves the headers that httpbin received. headers(r) gives the headers that it sent back in its response.)

Cookies

Cookies are simple key-value pairs like the query string, but they persist across multiple requests in a session (because they’re sent back and forth every time). To send your own cookies to the server, use set_cookies():

r <- GET("http://httpbin.org/cookies", set_cookies("MeWant" = "cookies"))
content(r)$cookies
#> $MeWant
#> [1] "cookies"
#> 
#> $a
#> [1] "1"
#> 
#> $b
#> [1] "1"

Note that this response includes the a and b cookies that were added by the server earlier.

Request body

When POST()ing, you can include data in the body of the request. httr allows you to supply this in a number of different ways. The most common way is a named list:

r <- POST("http://httpbin.org/post", body = list(a = 1, b = 2, c = 3))

You can use the encode argument to determine how this data is sent to the server:

url <- "http://httpbin.org/post"
body <- list(a = 1, b = 2, c = 3)

# Form encoded
r <- POST(url, body = body, encode = "form")
# Multipart encoded
r <- POST(url, body = body, encode = "multipart")
# JSON encoded
r <- POST(url, body = body, encode = "json")

To see exactly what’s being sent to the server, use verbose(). Unfortunately due to the way that verbose() works, knitr can’t capture the messages, so you’ll need to run these from an interactive console to see what’s going on.

POST(url, body = body, encode = "multipart", verbose()) # the default
POST(url, body = body, encode = "form", verbose())
POST(url, body = body, encode = "json", verbose())

PUT() and PATCH() can also have request bodies, and they take arguments identically to POST().

You can also send files off disk:

POST(url, body = upload_file("mypath.txt"))
POST(url, body = list(x = upload_file("mypath.txt")))

(upload_file() will guess the mime-type from the extension - using the type argument to override/supply yourself.)

These uploads stream the data to the server: the data will be loaded in R in chunks then sent to the remote server. This means that you can upload files that are larger than memory.

See POST() for more details on the other types of thing that you can send: no body, empty body, and character and raw vectors.

Built with
sessionInfo()
#> R version 3.6.0 (2019-04-26)
#> Platform: x86_64-apple-darwin15.6.0 (64-bit)
#> Running under: macOS Mojave 10.14.5
#> 
#> Matrix products: default
#> BLAS:   /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRblas.0.dylib
#> LAPACK: /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRlapack.dylib
#> 
#> locale:
#> [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] httr_1.4.1
#> 
#> loaded via a namespace (and not attached):
#>  [1] compiler_3.6.0  R6_2.4.0        magrittr_1.5    htmltools_0.3.6
#>  [5] tools_3.6.0     curl_3.3        yaml_2.2.0      Rcpp_1.0.1     
#>  [9] stringi_1.4.3   rmarkdown_1.13  knitr_1.23      jsonlite_1.6   
#> [13] stringr_1.4.0   digest_0.6.20   xfun_0.8        evaluate_0.14