Skip to content

Commit c01d63f

Browse files
committed
Rewrite HTTP options handling
Now all HTTP honors the pkgcache_* and pkg_* HTTP options. Also, the default is now HTTP 1.1, everywhere. Until I figure out why HTTP/2 crashes occasionally. Closes #140.
1 parent c13b035 commit c01d63f

8 files changed

Lines changed: 205 additions & 104 deletions

File tree

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# pkgcache (development version)
22

3+
* All HTTP requests now honor the `pkgcache_http_version` option and
4+
`PKGCACHE_HTTP_VERSION` environment variable. Closes
5+
https://github.com/r-lib/pkgcache/issues/140.
6+
37
* New `PKG_USE_BIOCONDUCTOR` environment variable and new
48
`pkg.use_bioconductor` option to opt out from automatic Bioconductor
59
support.

R/aaa-async.R

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3165,6 +3165,16 @@ http_delete <- function(
31653165

31663166
http_delete <- mark_as_async(http_delete)
31673167

3168+
default_http_version <- function() {
3169+
# We default to HTTP/1.1 on every platform, because HTTP/2 has caused a
3170+
# number of transport-level failures (connection resets, `GOAWAY` frames,
3171+
# etc.) with various libcurl and server combinations, see e.g.
3172+
# https://github.com/r-lib/pak/issues/358
3173+
# https://github.com/r-lib/actions/issues/483
3174+
# https://github.com/r-lib/actions/issues/1091
3175+
2L # HTTP/1.1
3176+
}
3177+
31683178
#' @importFrom utils modifyList
31693179

31703180
get_default_curl_options <- function(options) {
@@ -3185,6 +3195,9 @@ get_default_curl_options <- function(options) {
31853195
connecttimeout = as.integer(getopt("connecttimeout") %||% 300),
31863196
low_speed_time = as.integer(getopt("low_speed_time") %||% 0),
31873197
low_speed_limit = as.integer(getopt("low_speed_limit") %||% 0),
3198+
http_version = as.integer(
3199+
getopt("http_version") %||% default_http_version()
3200+
),
31883201
cainfo = getopt("cainfo")
31893202
))
31903203
)

R/async-http.R

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,64 @@
1-
default_http_version <- function() {
2-
os <- Sys.info()["sysname"]
3-
if (!is.na(os) && os %in% c("Darwin", "Linux")) {
4-
# FIXME: when is it safe to remove this? Does it depend on the OS
5-
# version? The libcurl version?
6-
# UPDATE: HTTP/2 now also causes issues on Linux:
7-
# https://github.com/r-lib/pak/issues/358
8-
# https://github.com/r-lib/actions/issues/483
9-
# So this will be here for now. :(
10-
2 # HTTP 1.1
11-
} else {
12-
0 # whatever curl chooses
13-
}
14-
}
1+
# The embedded async `http_get()` etc. only look at the `async_http_*` options
2+
# (via `get_default_curl_options()`). `set_pkgcache_curl_options()` reads the
3+
# pkgcache-level options and environment variables and sets the corresponding
4+
# curl options, so that pkgcache's HTTP requests honor them. For each curl
5+
# option it looks, in decreasing priority, at the `pkgcache_*` option, the
6+
# `PKGCACHE_*` environment variable, the `pkg_http_*` option and the
7+
# `PKG_HTTP_*` environment variable.
8+
#
9+
# It only sets an option when it is actually configured. An unset option must
10+
# not be turned into an explicit curl option, otherwise it would override the
11+
# `async_http_*` option and the async default further downstream in
12+
# `get_default_curl_options()`.
1513

16-
#' @importFrom utils modifyList
17-
18-
update_async_timeouts <- function(options) {
14+
set_pkgcache_curl_options <- function(options) {
15+
nms <- c(
16+
"timeout",
17+
"connecttimeout",
18+
"low_speed_time",
19+
"low_speed_limit",
20+
"http_version"
21+
)
22+
prefixes <- c("pkgcache_", "pkg_http_")
1923
getopt <- function(nm) {
20-
if (!is.null(v <- options[[nm]])) {
21-
return(v)
24+
for (prefix in prefixes) {
25+
anm <- paste0(prefix, nm)
26+
if (!is.null(v <- getOption(anm))) {
27+
return(v)
28+
}
29+
if (!is.na(v <- Sys.getenv(toupper(anm), NA_character_))) {
30+
return(v)
31+
}
32+
}
33+
NULL
34+
}
35+
for (nm in nms) {
36+
# An option passed explicitly to the request always wins.
37+
if (!is.null(options[[nm]])) {
38+
next
2239
}
23-
anm <- paste0("pkgcache_", nm)
24-
if (!is.null(v <- getOption(anm))) {
25-
return(v)
40+
if (!is.null(v <- getopt(nm))) {
41+
options[[nm]] <- as.integer(v)
2642
}
27-
if (!is.na(v <- Sys.getenv(toupper(anm), NA_character_))) return(v)
2843
}
29-
utils::modifyList(
30-
options,
31-
list(
32-
timeout = as.integer(getopt("timeout") %||% 0),
33-
connecttimeout = as.integer(getopt("connecttimeout") %||% 300),
34-
low_speed_time = as.integer(getopt("low_speed_time") %||% 0),
35-
low_speed_limit = as.integer(getopt("low_speed_limit") %||% 0),
36-
http_version = as.integer(
37-
getopt("http_version") %||% default_http_version()
38-
)
39-
)
40-
)
44+
options
4145
}
4246

47+
# Wrap an embedded async HTTP function so that it applies the pkgcache-level
48+
# HTTP options and environment variables (see `set_pkgcache_curl_options()`)
49+
# to every request.
50+
wrap_pkgcache_http <- function(fun) {
51+
force(fun)
52+
mark_as_async(function(url, ..., options = list()) {
53+
fun(url, ..., options = set_pkgcache_curl_options(options))
54+
})
55+
}
56+
57+
http_get <- wrap_pkgcache_http(http_get)
58+
http_head <- wrap_pkgcache_http(http_head)
59+
http_post <- wrap_pkgcache_http(http_post)
60+
http_delete <- wrap_pkgcache_http(http_delete)
61+
4362
add_auth_header <- function(url, headers) {
4463
c(headers, repo_auth_headers(url)$headers)
4564
}
@@ -127,7 +146,6 @@ download_file <- function(
127146
)
128147
force(list(...))
129148

130-
options <- update_async_timeouts(options)
131149
destfile <- normalizePath(destfile, mustWork = FALSE)
132150
tmp_destfile <- normalizePath(tmp_destfile, mustWork = FALSE)
133151
mkdirp(dirname(tmp_destfile))
@@ -259,7 +277,6 @@ download_if_newer <- function(
259277
)
260278
force(list(...))
261279

262-
options <- update_async_timeouts(options)
263280
etag_old <- get_etag_header_from_file(destfile, etag_file)
264281
headers <- c(headers, etag_old)
265282
headers <- add_auth_header(url, headers)
@@ -390,7 +407,6 @@ download_one_of <- function(
390407
)
391408
force(list(...))
392409

393-
options <- update_async_timeouts(options)
394410
tmps <- paste0(destfile, ".tmp.", seq_along(urls))
395411
dls <- mapply(
396412
download_if_newer,
@@ -428,7 +444,6 @@ download_files <- function(
428444
)
429445
}
430446

431-
options <- update_async_timeouts(options)
432447
bar <- create_progress_bar(data)
433448
prog_cb <- function(upd) update_progress_bar_progress(bar, upd)
434449

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,12 @@ configure Bioconductor support.
198198
and
199199
[`LOW_SPEED_TIME`](https://curl.se/libcurl/c/CURLOPT_LOW_SPEED_TIME.html)
200200
curl options.
201+
- `pkgcache_http_version` selects the HTTP version to use for HTTP requests.
202+
It corresponds to the [`HTTP_VERSION` libcurl
203+
option](https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html), so e.g.
204+
`2` forces HTTP/1.1 and `0` lets libcurl choose. It defaults to HTTP/1.1,
205+
because HTTP/2 has caused transport-level failures with some client and
206+
server combinations.
201207

202208
## Package environment variables
203209

@@ -239,6 +245,13 @@ configure Bioconductor support.
239245
curl options. The `pkgcache_low_speed_time` and
240246
`pkgcache_low_speed_limit` options have priority over these
241247
environment variables, if they are set.
248+
- `PKGCACHE_HTTP_VERSION` selects the HTTP version to use for HTTP requests.
249+
It corresponds to the [`HTTP_VERSION` libcurl
250+
option](https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html), so e.g.
251+
`2` forces HTTP/1.1 and `0` lets libcurl choose. It defaults to HTTP/1.1,
252+
because HTTP/2 has caused transport-level failures with some client and
253+
server combinations. The `pkgcache_http_version` option has priority over
254+
this, if set.
242255
- `R_PKG_CACHE_DIR` is used for the cache directory, if set. (Otherwise
243256
`tools::R_user_dir("pkgcache", "cache")` is used, see also
244257
`meta_cache_summary()` and `pkg_cache_summary()`).

man/pkgcache-package.Rd

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/async/test-http.R

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,34 @@ test_that("more sophisticated timeouts", {
235235
expect_true(toc - tic < as.difftime(5, units = "secs"))
236236
})
237237

238+
test_that("http_version is set from options and env vars", {
239+
# explicit option wins
240+
expect_equal(
241+
get_default_curl_options(list(http_version = 3))$http_version,
242+
3L
243+
)
244+
245+
# async_http_http_version option
246+
withr::local_options(async_http_http_version = 3)
247+
expect_equal(get_default_curl_options(list())$http_version, 3L)
248+
249+
# ASYNC_HTTP_HTTP_VERSION env var (option has priority)
250+
withr::local_options(async_http_http_version = NULL)
251+
withr::local_envvar(ASYNC_HTTP_HTTP_VERSION = "3")
252+
expect_equal(get_default_curl_options(list())$http_version, 3L)
253+
254+
# default is used when nothing is set
255+
withr::local_envvar(ASYNC_HTTP_HTTP_VERSION = NA_character_)
256+
expect_equal(
257+
get_default_curl_options(list())$http_version,
258+
default_http_version()
259+
)
260+
})
261+
262+
test_that("default_http_version", {
263+
expect_equal(default_http_version(), 2L)
264+
})
265+
238266
test_that("errors contain the response", {
239267
do <- function() {
240268
http_get(http$url("/status/418"))$then(http_stop_for_status)

0 commit comments

Comments
 (0)