Skip to content

fix: Disable httr2 for shinyapps.io bundle uploads#1298

Open
ChaitaC wants to merge 2 commits intomainfrom
fix/shinyapps-httr2-bad-checksum
Open

fix: Disable httr2 for shinyapps.io bundle uploads#1298
ChaitaC wants to merge 2 commits intomainfrom
fix/shinyapps-httr2-bad-checksum

Conversation

@ChaitaC
Copy link

@ChaitaC ChaitaC commented Mar 9, 2026

Summary

  • Disables httr2 for the shinyapps.io bundle upload flow by setting rsconnect.httr2 = FALSE in uploadShinyappsBundle()
  • The shinyapps.io API returns a 303 redirect during updateBundleStatus, and httr2 drops the request body on redirect while keeping the X-Content-Checksum header, causing a "bad checksum" HTTP 400 error
  • The option is restored after the upload completes via on.exit()

Fixes #1297

The shinyapps.io API returns a 303 redirect during updateBundleStatus,
and httr2 drops the request body on redirect while keeping the
X-Content-Checksum header, causing a "bad checksum" HTTP 400 error.

Work around this by using the legacy libcurl backend for the entire
shinyapps bundle upload flow.

Fixes #1297

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ChaitaC
Copy link
Author

ChaitaC commented Mar 9, 2026

Verification:
Without this fix

✔ Deploying "shinyapp-2-1" using "server: shinyapps.io / username: chaitatest"
ℹ Creating application on server...
Error in `POST()`:
! <https://api.shinyapps.io/v1/applications/> failed with HTTP status 409
Application exists with name: shinyapp-2-1
Run `rlang::last_trace()` to see where the error occurred.
With this fix bundle deployed successfully ``` > rsconnect::deployApp(appDir = "/Users/chaitamacpro/Desktop/shinyappIO/shinyapps-1", appName = "shinyapp-2-1") ── Preparing for deployment ────────────────────────────────────────────────────────────── ✔ Re-deploying "shinyapp-2-1" using "server: shinyapps.io / username: chaitatest" ℹ Looking up application with id "16852238"... ✔ Found application ℹ Bundling 1 file: app.R ℹ Capturing R dependencies ✔ Found 29 dependencies ✔ Created 18,708b bundle ℹ Uploading bundle... ✔ Uploaded bundle with id 11685212 ── Deploying to server ─────────────────────────────────────────────────────────────────── Waiting for task: 1661471050 building: Building image: 14512737 building: Fetching packages building: Installing packages building: Installing files building: Pushing image: 14512737 deploying: Starting instances terminating: Stopping old instances ── Deployment complete ─────────────────────────────────────────────────────────────────── ✔ Successfully deployed to ```

Copy link
Contributor

@karawoo karawoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if instead we could prevent httr2 from following redirects (httr2::req_options(req, followlocation = FALSE)). We already have code to handle redirects ourselves in httpRequestWithBody, we could potentially move this outside the if:

rsconnect/R/http.R

Lines 131 to 146 in 591e652

while (isRedirect(httpResponse$status)) {
# This is a simplification of the spec, since we should preserve
# the method for 307 and 308, but that's unlikely to arise for our apps
# https://www.rfc-editor.org/rfc/rfc9110.html#name-redirection-3xx
service <- redirectService(service, httpResponse$location)
authed_headers <- c(headers, authHeaders(authInfo, "GET", service$path))
httpResponse <- httpLibCurl(
protocol = service$protocol,
host = service$host,
port = service$port,
method = "GET",
path = service$path,
headers = authed_headers,
certificate = certificate
)
httpResponse

Move the httr2 workaround from uploadShinyappsBundle to
clientForAccount. The uploadShinyappsBundle-only fix was insufficient
because on.exit() restored rsconnect.httr2 = TRUE before
deployApplication ran. deployApplication also gets a 303 redirect
(to /v1/tasks/{id}), causing a 405 "Method Not Allowed" error.

Disabling httr2 in clientForAccount covers the entire shinyapps.io
deployment flow: bundle upload, deploy, task polling, and terminate.

Fixes #1297

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ChaitaC
Copy link
Author

ChaitaC commented Mar 9, 2026

I wonder if instead we could prevent httr2 from following redirects (httr2::req_options(req, followlocation = FALSE)). We already have code to handle redirects ourselves in httpRequestWithBody:

rsconnect/R/http.R

Lines 131 to 146 in 591e652

while (isRedirect(httpResponse$status)) {
# This is a simplification of the spec, since we should preserve
# the method for 307 and 308, but that's unlikely to arise for our apps
# https://www.rfc-editor.org/rfc/rfc9110.html#name-redirection-3xx
service <- redirectService(service, httpResponse$location)
authed_headers <- c(headers, authHeaders(authInfo, "GET", service$path))
httpResponse <- httpLibCurl(
protocol = service$protocol,
host = service$host,
port = service$port,
method = "GET",
path = service$path,
headers = authed_headers,
certificate = certificate
)
httpResponse

Thanks, @karawoo, for taking a look! I'm not super familiar with the httr2 internals to be confident making deeper changes to the redirect handling. I've updated the PR with a broader workaround that disables httr2 for all shinyapps.io API calls, and the integration tests pass locally.

A couple of things I found while testing:

  • isRedirect() checks for 301, 302, 307, 308, but not 303, which is what shinyapps.io returns.
  • The issue isn't just updateBundleStatus — deployApplication also gets a 303 redirect and fails with 405. So the fix needs to cover all shinyapps.io POST endpoints.

@nealrichardson I've added the workaround for shinyapps.io and have integration tests passing locally. When we're ready to run them in CI, we'll need someone with admin access to add the shinyapps.io credentials as repository secrets. Also, Kara's suggestion of using followlocation = FALSE could be a cleaner long-term fix — let me know if you'd like to go that route instead.

@karawoo
Copy link
Contributor

karawoo commented Mar 9, 2026

Thanks, @karawoo, for taking a look! I'm not super familiar with the httr2 internals to be confident making deeper changes to the redirect handling. I've updated the PR with a broader workaround that disables httr2 for all shinyapps.io API calls, and the integration tests pass locally.

As currently written, httr2 gets disabled globally in the R session when a shinyapps.io client is set up, which will also affect API calls to Connect and Connect Cloud if done in the same R session after a shinyapps.io client is created. I think this isn't quite what we want. If we want to disable htt2 for POST requests to shinyapps.io across the board, then I think we could add logic here:

if (isTRUE(getOption("rsconnect.httr2", TRUE))) {

if (isTRUE(getOption("rsconnect.httr2", TRUE)) &&  !isTRUE(grepl("shinyapps\\.io$", service$host))) {

@nealrichardson
Copy link
Contributor

@karawoo we discussed disabling the httr2 redirect following and keeping the manual logic, but since this is just the behavior of one endpoint on one legacy system, I wasn't sure it was worth keeping that around.

@ChaitaC I was thinking of a more narrow setting, similar to what you originally did, but you can probably do this:

diff --git a/R/client-shinyapps.R b/R/client-shinyapps.R
index a54a7516..4b068fc9 100644
--- a/R/client-shinyapps.R
+++ b/R/client-shinyapps.R
@@ -73,6 +73,8 @@ shinyAppsClient <- function(service, authInfo) {
       json$content_type <- content_type
       json$content_length <- content_length
       json$checksum <- checksum
+      old <- options(rsconnect.httr2 = FALSE)
+      on.exit(options(old))
       POST_JSON(service, authInfo, "/bundles", json)
     },

and only affect that one request. Did something not work when you had the original commit, such that you needed to make it broader.

You can DM me the secret and I can add it to the actions. You can push the test whenever, just make it skipped if the env var is not set, and then we can wire it up with the secret once I add it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

deployApp() fails with "bad checksum" when uploading bundle to shinyapps.io

3 participants