Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement req_cookies_set() #540

Merged
merged 5 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)

Check warning on line 52 in R/req-cookies.R

View check run for this annotation

Codecov / codecov/patch

R/req-cookies.R#L52

Added line #L52 was not covered by tests
}

#' @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(...)))

Check warning on line 63 in R/req-cookies.R

View check run for this annotation

Codecov / codecov/patch

R/req-cookies.R#L63

Added line #L63 was not covered by tests
}

cookies_build <- function(x, error_call = caller_env()) {
elements_build(x, "Cookies", ";", error_call = error_call)

Check warning on line 67 in R/req-cookies.R

View check run for this annotation

Codecov / codecov/patch

R/req-cookies.R#L67

Added line #L67 was not covered by tests
}
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")))
})
Loading