From 107d88d3667a6ecea1b9a23f509fee64faa1cfe0 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Wed, 4 Sep 2024 13:47:42 +0100 Subject: [PATCH 1/3] Implement `req_cookies_set()` Fixes #369 --- NAMESPACE | 1 + NEWS.md | 1 + R/req-cookies.R | 38 ++++++++++++++++++++++++------- R/url.R | 8 +++++-- man/req_cookie_preserve.Rd | 30 ++++++++++++++++++++---- tests/testthat/test-req-cookies.R | 18 +++++++++++++++ 6 files changed, 82 insertions(+), 14 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index e2f4ff2a..112fc391 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -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) diff --git a/NEWS.md b/NEWS.md index 4b086fa7..1224d314 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # httr2 (development version) +* New `req_cookie_set()` allows you to set client side cookies (#369). * `req_cache()` now re-caches the response if the body is hasn't been modified but the headers have changed (#442). * `req_cache()` works better when `req_perform()` sets a path (#442). * `req_body_*()` now give informative error if you attempt to change the body type (#451). diff --git a/R/req-cookies.R b/R/req-cookies.R index 29291f4b..83d13796 100644 --- a/R/req-cookies.R +++ b/R/req-cookies.R @@ -1,14 +1,24 @@ -#' 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 #' path <- tempfile() #' httpbin <- request(example_url()) |> #' req_cookie_preserve(path) @@ -30,8 +40,20 @@ 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) } diff --git a/R/url.R b/R/url.R index b4f3cf0c..8ffab225 100644 --- a/R/url.R +++ b/R/url.R @@ -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) @@ -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, diff --git a/man/req_cookie_preserve.Rd b/man/req_cookie_preserve.Rd index 5bbcb23b..598ef21b 100644 --- a/man/req_cookie_preserve.Rd +++ b/man/req_cookie_preserve.Rd @@ -2,22 +2,39 @@ % Please edit documentation in R/req-cookies.R \name{req_cookie_preserve} \alias{req_cookie_preserve} -\title{Preserve cookies across requests} +\alias{req_cookies_set} +\title{Set and preserve cookies} \usage{ req_cookie_preserve(req, path) + +req_cookies_set(req, ...) } \arguments{ \item{req}{A httr2 \link{request} object.} \item{path}{A path to a file where cookies will be read from before and updated after the request.} + +\item{...}{<\code{\link[rlang:dyn-dots]{dynamic-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 \code{I()}.} } \description{ +Use \code{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 +\code{req_cookie_preserve()} along with the path to cookie file that will be +read before and updated after each request. } \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 path <- tempfile() httpbin <- request(example_url()) |> req_cookie_preserve(path) @@ -33,6 +50,11 @@ httpbin |> req_perform() |> resp_body_json() +httpbin |> + req_template("/cookies/set/:name/:value", name = "oatmeal", value = "raisin") |> + req_perform() |> + resp_body_json() + # The cookie path has a straightforward format cat(readChar(path, nchars = 1e4)) } diff --git a/tests/testthat/test-req-cookies.R b/tests/testthat/test-req-cookies.R index 039362af..3f7d163b 100644 --- a/tests/testthat/test-req-cookies.R +++ b/tests/testthat/test-req-cookies.R @@ -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"))) +}) From 9a6662a66cea066c867dc349480c5f9705a40a4c Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Wed, 4 Sep 2024 14:33:26 +0100 Subject: [PATCH 2/3] magrittr pipes --- tests/testthat/test-req-cookies.R | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/testthat/test-req-cookies.R b/tests/testthat/test-req-cookies.R index 3f7d163b..85d3cda4 100644 --- a/tests/testthat/test-req-cookies.R +++ b/tests/testthat/test-req-cookies.R @@ -22,18 +22,18 @@ test_that("can read/write cookies", { }) test_that("can set cookies", { - resp <- request(example_url()) |> - req_cookies_set(a = 1, b = 1) |> - req_url_path("/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") |> + 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"))) From 891aababb62288973e3c376927a962da0cd52ff9 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Thu, 5 Sep 2024 13:48:54 +0100 Subject: [PATCH 3/3] Polish docs --- R/req-cookies.R | 21 +++++++++++++++------ man/req_cookie_preserve.Rd | 20 ++++++++++++-------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/R/req-cookies.R b/R/req-cookies.R index 83d13796..65e13159 100644 --- a/R/req-cookies.R +++ b/R/req-cookies.R @@ -18,22 +18,31 @@ #' req_cookies_set(a = 1, b = 1) |> #' req_dry_run() #' -#' # Use `req_cookie_preserve()` to preserve server-side cookies +#' # 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) { diff --git a/man/req_cookie_preserve.Rd b/man/req_cookie_preserve.Rd index 598ef21b..0ef9f237 100644 --- a/man/req_cookie_preserve.Rd +++ b/man/req_cookie_preserve.Rd @@ -34,24 +34,28 @@ request(example_url()) |> req_cookies_set(a = 1, b = 1) |> req_dry_run() -# Use `req_cookie_preserve()` to preserve server-side cookies +# 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() -httpbin |> - req_template("/cookies/set/:name/:value", name = "oatmeal", value = "raisin") |> +# 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()