Skip to content

Commit

Permalink
Implement req_cookies_set() (#540)
Browse files Browse the repository at this point in the history
Fixes #369
  • Loading branch information
hadley authored Sep 5, 2024
1 parent 88f0932 commit a241b04
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 24 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export(req_body_multipart)
export(req_body_raw)
export(req_cache)
export(req_cookie_preserve)
export(req_cookies_set)
export(req_dry_run)
export(req_error)
export(req_headers)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# httr2 (development version)

* New `req_cookie_set()` allows you to set client side cookies (#369).
* `req_body_file()` no longer leaks a connection if the response doesn't complete succesfully (#534).
* `req_perform()` no longer displays a progress bar when sleeping during tests. You can override this behaviour by setting the option `httr2_progress`.
* `req_cache()` now re-caches the response if the body is hasn't been modified but the headers have changed (#442).
Expand Down
57 changes: 44 additions & 13 deletions R/req-cookies.R
Original file line number Diff line number Diff line change
@@ -1,37 +1,68 @@
#' Preserve cookies across requests
#' Set and preserve cookies
#'
#' @description
#' Use `req_cookie_set()` to set client side cookies that are sent to the
#' server.
#'
#' By default, httr2 uses a clean slate for every request meaning that cookies
#' are not automatically preserved across requests. To preserve cookies, you
#' must set a cookie file which will be read before and updated after each
#' request.
#' are not automatically preserved across requests. To preserve cookies, use
#' `req_cookie_preserve()` along with the path to cookie file that will be
#' read before and updated after each request.
#'
#' @inheritParams req_perform
#' @param path A path to a file where cookies will be read from before and updated after the request.
#' @export
#' @examples
#' # Use `req_cookies_set()` to set client-side cookies
#' request(example_url()) |>
#' req_cookies_set(a = 1, b = 1) |>
#' req_dry_run()
#'
#' # Use `req_cookie_preserve()` to preserve server-side cookies across requests
#' path <- tempfile()
#' httpbin <- request(example_url()) |>
#' req_cookie_preserve(path)
#'
#' # Manually set two cookies
#' httpbin |>
#' # Set a server-side cookie
#' request(example_url()) |>
#' req_cookie_preserve(path) |>
#' req_template("/cookies/set/:name/:value", name = "chocolate", value = "chip") |>
#' req_perform() |>
#' resp_body_json()
#'
#' httpbin |>
#' # Set another sever-side cookie
#' request(example_url()) |>
#' req_cookie_preserve(path) |>
#' req_template("/cookies/set/:name/:value", name = "oatmeal", value = "raisin") |>
#' req_perform() |>
#' resp_body_json()
#'
#' # Add a client side cookie
#' request(example_url()) |>
#' req_url_path("/cookies/set") |>
#' req_cookie_preserve(path) |>
#' req_cookies_set(snicker = "doodle") |>
#' req_perform() |>
#' resp_body_json()
#'
#' # The cookie path has a straightforward format
#' cat(readChar(path, nchars = 1e4))
req_cookie_preserve <- function(req, path) {
check_request(req)
check_string(path, allow_empty = FALSE)

req_options(req,
cookiejar = path,
cookiefile = path
)
req_options(req, cookiejar = path, cookiefile = path)
}

#' @export
#' @rdname req_cookie_preserve
#' @param ... <[`dynamic-dots`][rlang::dyn-dots]>
#' Name-value pairs that define query parameters. Each value must be
#' an atomic vector, which is automatically escaped. To opt-out of escaping,
#' wrap strings in `I()`.
req_cookies_set <- function(req, ...) {
check_request(req)
req_options(req, cookie = cookies_build(list2(...)))
}

cookies_build <- function(x, error_call = caller_env()) {
elements_build(x, "Cookies", ";", error_call = error_call)
}
8 changes: 6 additions & 2 deletions R/url.R
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,12 @@ query_parse <- function(x) {
}

query_build <- function(x, error_call = caller_env()) {
elements_build(x, "Query", "&", error_call = error_call)
}

elements_build <- function(x, name, collapse, error_call = caller_env()) {
if (!is_list(x) || (!is_named(x) && length(x) > 0)) {
cli::cli_abort("Query must be a named list.", call = error_call)
cli::cli_abort("{name} must be a named list.", call = error_call)
}

x <- compact(x)
Expand All @@ -177,7 +181,7 @@ query_build <- function(x, error_call = caller_env()) {
values <- map2_chr(x, names(x), format_query_param, error_call = error_call)
names <- curl::curl_escape(names(x))

paste0(names, "=", values, collapse = "&")
paste0(names, "=", values, collapse = collapse)
}

format_query_param <- function(x,
Expand Down
44 changes: 35 additions & 9 deletions man/req_cookie_preserve.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions tests/testthat/test-req-cookies.R
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,21 @@ test_that("can read/write cookies", {
expect_mapequal(cookies, list(x = "a", y = "b", z = "c"))

})

test_that("can set cookies", {
resp <- request(example_url()) %>%
req_cookies_set(a = 1, b = 1) %>%
req_url_path("/cookies") %>%
req_perform()

expect_equal(resp_body_json(resp), list(cookies = list(a = "1", b = "1")))
})

test_that("cookie values are usually escaped", {
resp <- request(example_url()) %>%
req_cookies_set(a = I("%20"), b = "%") %>%
req_url_path("/cookies") %>%
req_perform()

expect_equal(resp_body_json(resp), list(cookies = list(a = "%20", b = "%25")))
})

0 comments on commit a241b04

Please sign in to comment.