是否可以从服务器端 R Shiny 应用更改本地文件?

Is it possible to make changes to a local file from a server-side R Shiny app?

提问人:ddm_ingram 提问时间:7/27/2023 更新时间:7/27/2023 访问量:67

问:

问题

我正在制作一个应用程序,允许人们将数据添加到 csv 文件中。为了避免我的同事看到任何代码,我希望这个应用程序是服务器端的,例如在 shinyapps.io 上。目前,我已经尝试过 和 :当我的应用程序在我的本地 PC 上时,这些工作(请参阅 MWE),但当我将其部署到 shinyapps.io 时则不起作用。fileinput()shinyFiles

这能起作用吗,或者从服务器写入本地目录总是有问题吗?

MWE 用于fileinput()

library(shiny)
library(shinyWidgets)
library(dplyr) # For bind_rows

# Define UI
ui = fluidPage(
    fileInput("my_file", "Add to Master Sheet", accept = ".csv"), # File upload
    actionButton("submit_btn", "Submit data") # Submit button
)

# Define Server Logic
server = function(input, output) {
    observeEvent(input$submit_btn, {
        my_data = read.csv(input$my_file$datapath) # Read in my data
        my_new_data = bind_rows(my_data, data.frame(5)) # Add new data
        write.csv(my_new_data, input$my_file$name) # Write the updated file
        stopApp() # Close app
    })
    
}

# Run the application
shinyApp(ui, server)

此示例在应用位于本地计算机上时有效,但即便如此,它也只会将新的.csv文件保存到与应用相同的位置。使用不起作用。当应用程序处于 shinyapps.io 状态时,不会更新任何文件。input$my_file$datapath

MWE 用于shinyFiles()

library(shiny)
library(shinyFiles)
library(shinyWidgets)
library(dplyr)

# Define UI
ui = fluidPage(
    shinyFilesButton("file_upload", "Choose a CSV file", title = "Upload", multiple = FALSE),
    verbatimTextOutput("file_status", placeholder = TRUE),  # Placeholder text will be shown until a file is uploaded
    actionButton("submit_btn", "Submit data"),
)

server <- function(input, output, session) {
    
    volumes <- c(Home = fs::path_home(), "R Installation" = R.home(), getVolumes()())
    shinyFileChoose(input, "file_upload", roots = volumes, filetypes = c('', 'csv'), session = session)
    observe({
        if (!is.null(input$file_upload)) {
            file_info <- parseFilePaths(volumes, input$file_upload)
            output$file_status <- renderText(paste("Selected file:", file_info$name))
        }
    })

    observeEvent(input$submit_btn, {
        infile = parseFilePaths(volumes, input$file_upload)
        my_data = read.csv(infile$datapath) # Read in my data
        my_new_data = bind_rows(my_data, data.frame(5)) # Add new data
        write.csv(my_new_data, infile$datapath) # Write the updated file
        stopApp() # Close app
    })
}

shinyApp(ui, server)

同样,这在我的本地 PC 上有效,但在 shinyapps.io 上,当我单击“文件上传”时,我的大多数系统文件都不会显示。

R 闪亮 的服务器端 ShinyApps

评论

1赞 Limey 7/27/2023
我很确定这行不通。允许 Web 应用免费访问本地文件具有非常严重的安全隐患。
2赞 r2evans 7/27/2023
你永远不应该尝试从一个闪亮的应用程序“写入本地文件”:随便这样做会是一个安全问题。但是,可以提供用户在您的闪亮 mastering-shiny.org/action-transfer.html 应用程序更改后上传文件()和下载文件()。虽然这对用户来说是一个两步,但在两个明确的用户发起的操作中完成它要安全得多。fileInputdownloadHandler
1赞 MrFlick 7/27/2023
远程 Web 服务器永远不会对本地文件系统具有写入访问权限。这将是一个很大的安全问题:您需要使用下载处理程序将更新文件返回给客户端。
0赞 ddm_ingram 7/27/2023
@r2evans 谢谢!所以从理论上讲,我可以让用户上传一个文件,让我的应用程序对其进行更改(例如添加新的数据行),然后再次下载修改后的新文件?
1赞 ddm_ingram 7/27/2023
@r2evans 明白了,谢谢。如果您将您的信息作为答案发布(带有有用的链接),我很乐意接受。

答:

2赞 r2evans 7/27/2023 #1

服务器可以访问用户的本地文件系统/驱动器的概念与大多数安全态势背道而驰,即使服务器支持它,大多数浏览器也不会(不应该!)允许它。相反,让用户上传他们的数据(一个或多个文件),你对它做一些事情(也许是交互式的),然后让他们下载结果。

数据设置:

write.csv(mtcars, "mt.csv", row.names=FALSE)

从这里开始,一个简单的应用程序:

library(shiny)
ui <- fluidPage(
  shinyjs::useShinyjs(),  # Set up shinyjs
  titlePanel("Hello Shiny!"),
  sidebarLayout(
    sidebarPanel(
      fileInput("infile", "Upload something!"),
      actionButton("act", "Do something to it!"),
      downloadButton("dwnld", "Get it back!")
    ),
    mainPanel(
      DT::DTOutput("tbl")
    )
  )
)
server <- function(input, output, session) {
  shinyjs::disable("act")
  shinyjs::disable("dwnld")
  userdata <- reactiveVal(NULL)
  observeEvent(input$infile, {
    req(file.exists(input$infile$datapath))
    tmp <- tryCatch(
      read.csv(input$infile$datapath),
      error = function(e) e)
    validate(
      need(!inherits(tmp, "error"), "Error reading file")
    )
    shinyjs::enable("act")
    shinyjs::enable("dwnld")
    userdata(tmp)
  })
  observeEvent(input$act, {
    req(userdata())
    dat <- userdata()
    dat[[1]] <- dat[[1]] + 100
    userdata(dat)
  })
  output$tbl <- DT::renderDT({
    req(userdata())
  })
  output$dwnld <- downloadHandler(
    filename = function() {
      sprintf("%s_updated_%s.csv", tools::file_path_sans_ext(input$infile$name),
              format(Sys.Date(), format = "%Y%m%d"))
    },
    content = function(file) {
      write.csv(userdata(), file, row.names = FALSE)
    }
  )
}
shinyApp(ui, server)

我仅用于禁用“做某事”和“下载”按钮;不是必需的,但我发现建议我可以在上传某些内容之前下载某些内容的界面可能会有问题。shinyjs

从这里开始,我们从:

shiny, start

点击“浏览”并从上面上传我们的,mt.csv

shiny, data uploaded

我点击了几次“做某事”(这只是为了在这里显示,不是严格要求的),请注意,这是增加的:mpg

shiny, something done

单击下载按钮,您将获得一个新文件名。我费了九牛二虎之力“巧妙地”重命名文件,您可以随心所欲地命名它。Get it back!

评论

1赞 ddm_ingram 7/27/2023
感谢您如此详尽的解释和示例。非常有意义,让我可以做我想做的事!