From d17249ace64a29a629dce9ac09feab9d538e6c65 Mon Sep 17 00:00:00 2001 From: frans Date: Tue, 28 Feb 2017 21:25:02 -0600 Subject: [PATCH 1/8] Added support for feather files --- DESCRIPTION | 4 ++- R/content-types.R | 3 +- R/plumber.R | 11 ++++++-- R/post-body.R | 34 +++++++++++++++++++++-- tests/testthat/files/static/test.feather | Bin 0 -> 352 bytes tests/testthat/test-postbody.R | 8 +++++- 6 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 tests/testthat/files/static/test.feather diff --git a/DESCRIPTION b/DESCRIPTION index 4f1ead137..7fd1b8abd 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -18,7 +18,9 @@ Imports: R6 (>= 2.0.0), stringi (>= 0.3.0), jsonlite (>= 0.9.16), - httpuv (>= 1.2.3) + httpuv (>= 1.2.3), + feather, + webutils LazyData: TRUE Suggests: testthat (>= 0.11.0), diff --git a/R/content-types.R b/R/content-types.R index 9da895b67..3a8361937 100644 --- a/R/content-types.R +++ b/R/content-types.R @@ -41,7 +41,8 @@ knownContentTypes <- list( docx='application/vnd.openxmlformats-officedocument.wordprocessingml.document', dotx='application/vnd.openxmlformats-officedocument.wordprocessingml.template', xlam='application/vnd.ms-excel.addin.macroEnabled.12', - xlsb='application/vnd.ms-excel.sheet.binary.macroEnabled.12') + xlsb='application/vnd.ms-excel.sheet.binary.macroEnabled.12', + feather='application/feather') getContentType <- function(ext, defaultType='application/octet-stream') { ct <- knownContentTypes[[tolower(ext)]] diff --git a/R/plumber.R b/R/plumber.R index d893bc70d..1f3fb1876 100644 --- a/R/plumber.R +++ b/R/plumber.R @@ -1,5 +1,7 @@ #' @import R6 #' @import stringi +#' @import feather +#' @import webutils NULL verbs <- c("GET", "PUT", "POST", "DELETE") @@ -41,9 +43,12 @@ plumber <- R6Class( private$errorHandler <- defaultErrorHandler private$notFoundHandler <- default404Handler - self$filters <- c(self$filters, PlumberFilter$new("queryString", queryStringFilter, private$envir, private$defaultSerializer, NULL, NULL)) - self$filters <- c(self$filters, PlumberFilter$new("postBody", postBodyFilter, private$envir, private$defaultSerializer, NULL, NULL)) - self$filters <- c(self$filters, PlumberFilter$new("cookieParser", cookieFilter, private$envir, private$defaultSerializer, NULL, NULL)) + self$filters <- c(self$filters, PlumberFilter$new("queryString", + queryStringFilter, private$envir, private$defaultSerializer, NULL, NULL)) + self$filters <- c(self$filters, PlumberFilter$new("postBody", + postBodyFilter, private$envir, private$defaultSerializer, NULL, NULL)) + self$filters <- c(self$filters, PlumberFilter$new("cookieParser", + cookieFilter, private$envir, private$defaultSerializer, NULL, NULL)) private$filename <- file private$envir <- new.env() diff --git a/R/post-body.R b/R/post-body.R index c37905431..48370231d 100644 --- a/R/post-body.R +++ b/R/post-body.R @@ -1,6 +1,12 @@ postBodyFilter <- function(req){ - body <- req$rook.input$read_lines() - args <- parseBody(body) + + if (!is.null(req$CONTENT_TYPE) && grepl("multipart/form-data; boundary=", req$CONTENT_TYPE, fixed = TRUE)) { + body <- req$rook.input$read() + args <- parseMultipart(body) + } else { + body <- req$rook.input$read_lines() + args <- parseBody(body) + } req$postBody <- body req$args <- c(req$args, args) forward() @@ -24,3 +30,27 @@ parseBody <- function(body){ } ret } + +#' @importFrom utils URLdecode +#' @noRd +parseMultipart <- function(body){ + # This is not as generic as perhaps it should be, but should be relatively + # easy to extend if more binary formats are required + # Is there data in the request? + if (is.null(body) || length(body) == 0 || body == "") { + return(list()) + } + # This is something odd with webutils::parse_multipart. For some reason it + # does not work when the boundary is set to the content type as per the + # webutils docs. + boundary <- "-----------------" + parsed_binary <- webutils::parse_multipart(body, boundary) + file_name <- parsed_binary[[1]][[1]] + file_data <- parsed_binary[[1]][[2]] + + tmpfile <- tempfile() + writeBin(file_data, tmpfile) + + ret <- feather::read_feather(tmpfile) + ret +} diff --git a/tests/testthat/files/static/test.feather b/tests/testthat/files/static/test.feather new file mode 100644 index 0000000000000000000000000000000000000000..ade34ef66c84bdf4b96e3f3b7d616046843907ff GIT binary patch literal 352 zcmZ>Bbu?svgAevlrUQiLfYJg`S_DeVILw*}0t!%C1uAX~r41a6!9d^P>=_U+b`W6z z0X_y0&BMULzyanlFbDu?CLm@5Vh{ka3xJpfh?o8U|DOj)>i{uG9Y{?|YHng)3XlN> z0t{*l>_9dLScL?b1ai47pz<(t4WN7o#RpUeaRbOTd_X~Ddq8%FWag$KYY|{jz-kZ3 PA0U8k&j+BG8!&7EJs2Qs literal 0 HcmV?d00001 diff --git a/tests/testthat/test-postbody.R b/tests/testthat/test-postbody.R index 95056003c..8528163e9 100644 --- a/tests/testthat/test-postbody.R +++ b/tests/testthat/test-postbody.R @@ -4,5 +4,11 @@ test_that("JSON is consumed on POST", { test_that("Query strings on post are handled correctly", { expect_equivalent(parseBody("a="), list()) # It's technically a named list() - expect_equal(parseBody("a=1&b=&c&d=1"), list(a="1", d="1")) + expect_equal(parseBody("a=1&b=&c&d=1"), list(a = "1", d = "1")) +}) + +test_that("Multipart binary is consumed on POST", { +# TODO: I'm not sure how to read the file in such a way that it can be used as a +# multipart upload. I expected that copying the rawToChar(req$input.rook$read()) +# output would do the trick, but it did not. }) From 44be238bc130eb4149953b2b7d0bdbb2e00c1248 Mon Sep 17 00:00:00 2001 From: frans Date: Tue, 28 Feb 2017 21:25:02 -0600 Subject: [PATCH 2/8] Added support for feather files --- DESCRIPTION | 4 ++- R/content-types.R | 3 +- R/plumber.R | 11 ++++++-- R/post-body.R | 34 +++++++++++++++++++++-- tests/testthat/files/static/test.feather | Bin 0 -> 352 bytes tests/testthat/test-postbody.R | 8 +++++- 6 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 tests/testthat/files/static/test.feather diff --git a/DESCRIPTION b/DESCRIPTION index d8d4ac2b1..bff8eb0b8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -20,7 +20,9 @@ Imports: R6 (>= 2.0.0), stringi (>= 0.3.0), jsonlite (>= 0.9.16), - httpuv (>= 1.2.3) + httpuv (>= 1.2.3), + feather, + webutils LazyData: TRUE Suggests: testthat (>= 0.11.0), diff --git a/R/content-types.R b/R/content-types.R index 9da895b67..3a8361937 100644 --- a/R/content-types.R +++ b/R/content-types.R @@ -41,7 +41,8 @@ knownContentTypes <- list( docx='application/vnd.openxmlformats-officedocument.wordprocessingml.document', dotx='application/vnd.openxmlformats-officedocument.wordprocessingml.template', xlam='application/vnd.ms-excel.addin.macroEnabled.12', - xlsb='application/vnd.ms-excel.sheet.binary.macroEnabled.12') + xlsb='application/vnd.ms-excel.sheet.binary.macroEnabled.12', + feather='application/feather') getContentType <- function(ext, defaultType='application/octet-stream') { ct <- knownContentTypes[[tolower(ext)]] diff --git a/R/plumber.R b/R/plumber.R index db6b6b372..a7a1997fb 100644 --- a/R/plumber.R +++ b/R/plumber.R @@ -1,5 +1,7 @@ #' @import R6 #' @import stringi +#' @import feather +#' @import webutils NULL verbs <- c("GET", "PUT", "POST", "DELETE", "HEAD") @@ -42,9 +44,12 @@ plumber <- R6Class( private$errorHandler <- defaultErrorHandler private$notFoundHandler <- default404Handler - self$filters <- c(self$filters, PlumberFilter$new("queryString", queryStringFilter, private$envir, private$defaultSerializer, NULL, NULL)) - self$filters <- c(self$filters, PlumberFilter$new("postBody", postBodyFilter, private$envir, private$defaultSerializer, NULL, NULL)) - self$filters <- c(self$filters, PlumberFilter$new("cookieParser", cookieFilter, private$envir, private$defaultSerializer, NULL, NULL)) + self$filters <- c(self$filters, PlumberFilter$new("queryString", + queryStringFilter, private$envir, private$defaultSerializer, NULL, NULL)) + self$filters <- c(self$filters, PlumberFilter$new("postBody", + postBodyFilter, private$envir, private$defaultSerializer, NULL, NULL)) + self$filters <- c(self$filters, PlumberFilter$new("cookieParser", + cookieFilter, private$envir, private$defaultSerializer, NULL, NULL)) private$filename <- file private$envir <- new.env(parent=.GlobalEnv) diff --git a/R/post-body.R b/R/post-body.R index c37905431..48370231d 100644 --- a/R/post-body.R +++ b/R/post-body.R @@ -1,6 +1,12 @@ postBodyFilter <- function(req){ - body <- req$rook.input$read_lines() - args <- parseBody(body) + + if (!is.null(req$CONTENT_TYPE) && grepl("multipart/form-data; boundary=", req$CONTENT_TYPE, fixed = TRUE)) { + body <- req$rook.input$read() + args <- parseMultipart(body) + } else { + body <- req$rook.input$read_lines() + args <- parseBody(body) + } req$postBody <- body req$args <- c(req$args, args) forward() @@ -24,3 +30,27 @@ parseBody <- function(body){ } ret } + +#' @importFrom utils URLdecode +#' @noRd +parseMultipart <- function(body){ + # This is not as generic as perhaps it should be, but should be relatively + # easy to extend if more binary formats are required + # Is there data in the request? + if (is.null(body) || length(body) == 0 || body == "") { + return(list()) + } + # This is something odd with webutils::parse_multipart. For some reason it + # does not work when the boundary is set to the content type as per the + # webutils docs. + boundary <- "-----------------" + parsed_binary <- webutils::parse_multipart(body, boundary) + file_name <- parsed_binary[[1]][[1]] + file_data <- parsed_binary[[1]][[2]] + + tmpfile <- tempfile() + writeBin(file_data, tmpfile) + + ret <- feather::read_feather(tmpfile) + ret +} diff --git a/tests/testthat/files/static/test.feather b/tests/testthat/files/static/test.feather new file mode 100644 index 0000000000000000000000000000000000000000..ade34ef66c84bdf4b96e3f3b7d616046843907ff GIT binary patch literal 352 zcmZ>Bbu?svgAevlrUQiLfYJg`S_DeVILw*}0t!%C1uAX~r41a6!9d^P>=_U+b`W6z z0X_y0&BMULzyanlFbDu?CLm@5Vh{ka3xJpfh?o8U|DOj)>i{uG9Y{?|YHng)3XlN> z0t{*l>_9dLScL?b1ai47pz<(t4WN7o#RpUeaRbOTd_X~Ddq8%FWag$KYY|{jz-kZ3 PA0U8k&j+BG8!&7EJs2Qs literal 0 HcmV?d00001 diff --git a/tests/testthat/test-postbody.R b/tests/testthat/test-postbody.R index 51dc8c0cb..b907e7fa5 100644 --- a/tests/testthat/test-postbody.R +++ b/tests/testthat/test-postbody.R @@ -6,5 +6,11 @@ test_that("JSON is consumed on POST", { test_that("Query strings on post are handled correctly", { expect_equivalent(parseBody("a="), list()) # It's technically a named list() - expect_equal(parseBody("a=1&b=&c&d=1"), list(a="1", d="1")) + expect_equal(parseBody("a=1&b=&c&d=1"), list(a = "1", d = "1")) +}) + +test_that("Multipart binary is consumed on POST", { +# TODO: I'm not sure how to read the file in such a way that it can be used as a +# multipart upload. I expected that copying the rawToChar(req$input.rook$read()) +# output would do the trick, but it did not. }) From 98d6f22f3077e3cfe646b71b2fbeb7f90065e93c Mon Sep 17 00:00:00 2001 From: frans Date: Fri, 28 Jul 2017 15:36:07 -0600 Subject: [PATCH 3/8] Remove feather from imports and move to suggests --- DESCRIPTION | 4 ++-- R/plumber.R | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 72b117c68..e5df58442 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -24,7 +24,6 @@ Imports: stringi (>= 0.3.0), jsonlite (>= 0.9.16), httpuv (>= 1.2.3), - feather, webutils LazyData: TRUE Suggests: @@ -35,7 +34,8 @@ Suggests: base64enc, htmlwidgets, visNetwork, - analogsea + analogsea, + feather Collate: 'content-types.R' 'cookie-parser.R' diff --git a/R/plumber.R b/R/plumber.R index 796a8e124..52a963143 100644 --- a/R/plumber.R +++ b/R/plumber.R @@ -1,6 +1,5 @@ #' @import R6 #' @import stringi -#' @import feather #' @import webutils NULL From d2c04a9491cba63a8a7fc8b99fc067455eb071f0 Mon Sep 17 00:00:00 2001 From: frans Date: Fri, 28 Jul 2017 15:40:03 -0600 Subject: [PATCH 4/8] Remove webutils from imports and move to suggests --- DESCRIPTION | 6 +++--- R/plumber.R | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index e5df58442..e7769b00c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -23,8 +23,7 @@ Imports: R6 (>= 2.0.0), stringi (>= 0.3.0), jsonlite (>= 0.9.16), - httpuv (>= 1.2.3), - webutils + httpuv (>= 1.2.3) LazyData: TRUE Suggests: testthat (>= 0.11.0), @@ -35,7 +34,8 @@ Suggests: htmlwidgets, visNetwork, analogsea, - feather + feather, + webutils Collate: 'content-types.R' 'cookie-parser.R' diff --git a/R/plumber.R b/R/plumber.R index 52a963143..63d0e6bc5 100644 --- a/R/plumber.R +++ b/R/plumber.R @@ -1,6 +1,5 @@ #' @import R6 #' @import stringi -#' @import webutils NULL # used to identify annotation flags. From 2d7e59efa22a7fe037ca3883b84c2ec5646837fe Mon Sep 17 00:00:00 2001 From: frans Date: Fri, 28 Jul 2017 20:36:32 -0600 Subject: [PATCH 5/8] clean up input to and output from parse_multipart --- R/post-body.R | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/R/post-body.R b/R/post-body.R index 48370231d..3ee339e85 100644 --- a/R/post-body.R +++ b/R/post-body.R @@ -1,8 +1,11 @@ postBodyFilter <- function(req){ - if (!is.null(req$CONTENT_TYPE) && grepl("multipart/form-data; boundary=", req$CONTENT_TYPE, fixed = TRUE)) { + if (!is.null(req$CONTENT_TYPE) && grepl("multipart/form-data; boundary=", + req$CONTENT_TYPE, fixed = TRUE)) { + + boundary <- strsplit(req$CONTENT_TYPE, split = "=")[[1]][2] body <- req$rook.input$read() - args <- parseMultipart(body) + args <- parseMultipart(body, boundary) } else { body <- req$rook.input$read_lines() args <- parseBody(body) @@ -33,7 +36,7 @@ parseBody <- function(body){ #' @importFrom utils URLdecode #' @noRd -parseMultipart <- function(body){ +parseMultipart <- function(body, boundary){ # This is not as generic as perhaps it should be, but should be relatively # easy to extend if more binary formats are required # Is there data in the request? @@ -43,14 +46,17 @@ parseMultipart <- function(body){ # This is something odd with webutils::parse_multipart. For some reason it # does not work when the boundary is set to the content type as per the # webutils docs. - boundary <- "-----------------" parsed_binary <- webutils::parse_multipart(body, boundary) - file_name <- parsed_binary[[1]][[1]] - file_data <- parsed_binary[[1]][[2]] +# file_name = "test.feather" + file_name <- parsed_binary$myfile$filename + file_data <- parsed_binary$myfile$value tmpfile <- tempfile() writeBin(file_data, tmpfile) - ret <- feather::read_feather(tmpfile) - ret + # ret <- feather::read_feather(tmpfile) + ret <- NULL + ret$name <- file_name + ret$data <- tmpfile + return(ret) } From ebaba940480d8e13e2bb2501dca1a5539296350f Mon Sep 17 00:00:00 2001 From: frans Date: Fri, 28 Jul 2017 21:29:05 -0600 Subject: [PATCH 6/8] generic binary upload an example for feather file --- R/post-body.R | 3 +-- inst/examples/13-binary-upload/plumber.R | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 inst/examples/13-binary-upload/plumber.R diff --git a/R/post-body.R b/R/post-body.R index 3ee339e85..999de4a0c 100644 --- a/R/post-body.R +++ b/R/post-body.R @@ -54,9 +54,8 @@ parseMultipart <- function(body, boundary){ tmpfile <- tempfile() writeBin(file_data, tmpfile) - # ret <- feather::read_feather(tmpfile) ret <- NULL ret$name <- file_name ret$data <- tmpfile - return(ret) + ret } diff --git a/inst/examples/13-binary-upload/plumber.R b/inst/examples/13-binary-upload/plumber.R new file mode 100644 index 000000000..f89a94670 --- /dev/null +++ b/inst/examples/13-binary-upload/plumber.R @@ -0,0 +1,21 @@ +library(plumber) +library(feather) + +#* @post /upload +function(name, data) { + # work with binary files after upload + # in this example a feather file + content <- read_feather(data) + return(content) +} + +#* @post /inspect +function(name, data) { + file_info <- data.frame( + filename = name, + mtime = file.info(data)$mtime, + ctime = file.info(data)$ctime + ) + + return(file_info) +} From f115c217e6021d33e2c3e490089f175dc18d09d4 Mon Sep 17 00:00:00 2001 From: frans Date: Fri, 28 Jul 2017 21:39:03 -0600 Subject: [PATCH 7/8] Include example of usage --- inst/examples/13-binary-upload/plumber.R | 11 +++++++++++ inst/examples/13-binary-upload/test.feather | Bin 0 -> 14776 bytes 2 files changed, 11 insertions(+) create mode 100644 inst/examples/13-binary-upload/test.feather diff --git a/inst/examples/13-binary-upload/plumber.R b/inst/examples/13-binary-upload/plumber.R index f89a94670..b963bde88 100644 --- a/inst/examples/13-binary-upload/plumber.R +++ b/inst/examples/13-binary-upload/plumber.R @@ -1,6 +1,13 @@ library(plumber) library(feather) +# Illustration of binary uploads and post-processing of the file on receipt. +# As a proof of concept we use a feather file: test.feather + + +# To upload and use the content of the file in a function: +# curl -X POST http://localhost:9080/upload -F 'myfile=@test.feather' + #* @post /upload function(name, data) { # work with binary files after upload @@ -9,6 +16,10 @@ function(name, data) { return(content) } + +# To upload and use the properties of the file in a function: +# curl -X POST http://localhost:9080/inspect -F 'myfile=@test.feather' + #* @post /inspect function(name, data) { file_info <- data.frame( diff --git a/inst/examples/13-binary-upload/test.feather b/inst/examples/13-binary-upload/test.feather new file mode 100644 index 0000000000000000000000000000000000000000..6cdb393d20eb1d6ca4507e7b36a61e952eda3709 GIT binary patch literal 14776 zcmeHO>vB{@5T1xg1jJQQsmi{+yK)o9UORg04nQ9Y z+AKjI4ceZ-_n%|_b8;jxaxKFq0LXSk0u zm}9*G<_`E6tYf&&$5;pWJK#QmKSw`^m$nZp3Z4 zZX^EapZFXJ&)ZLfHpCr%J@{e~r~LtT7lZEnggJ2`KZy(c8{i)KN!-5-&pYhP*I{lk zC*EVcXWZ~>fRE!okOy4P@Vv=i+VfoZVILnNp2N`KjXj3A=pWBp`UP$VdBJmvyeBU3 zOFW2ojq^0XJNqWA=b;VqYJm9=c1_+GoU7qF%wNRqf%lL&gC`mM*Wl>@c}4%^y8}K3 z=WCI_%wvypanPI-Ki|NQ{>e+m?OD%=FXClB8N^wC7jQD*Wp^#i2hd~wi4W@z`N?=2 zpU3CH55^h!2!EU#)U`3*0x!cl)Zmi?pIBcl_~gN-0ru~i_lselZWlE34|R$4m3YQ; z;`v~GgY#hBiu~;Z$7cQ5A3l%!M4h64%Y5Pa(LeWx`si6_z(>P8M4m8i$GU-fzR|F{%BkLSqq!8u#R$-3cDH+V02)Tx^M>%$-NW(dtZH>@Ab58x5~ zgU=rOVIF4i%l%p8H}jl)qJPu4A6%2Sev# zkagzY{F_Gn8T>iq59{}kbrpS4hJ8f;#HVq-jGuYxSq~UD>bD2}YsiNs?$DID-{eb%?)&u${|C{|I?=#?20~Z7S#QX(5tTzsI%%iSk zz-0*BYuuLu)QdIv>t3`*2m6|e_?0l{eVu`)4(CFA*k{B(hV_f*OwD=(p0bXTm#h~S=THBv zZw~Q0)CbR)ha*+L7n`#s?IU)?XrR@3Hdo3Dz% z?f<=In^>nvN9>j6#Eo;PV7_U39(O+C&fNRwu^m= zJR|lg^0e5e$duTp$WvmUBF~C_iaaj%De|J&r^vL}r^xeSpCYe_eTuv)_9>EyeTuvw z_9^m`*r&)2u}_hg#Xd#e5c?GAihYW_F7_!hBlfo=uZdlXye0N2vQz9+Bp3S>c~k6D z^GsTH~}XYHiW{RE}#M zQ0^+fG^Z4Q<&N${W7GYYB0Z5gkp+==MBWp5Uu2iaqR3v6{UQfM4vHKSIV^HS=s2)7IVdXF#}lC&sY0L9eGh!s(<1s_ZTGA$UlJ9^q Date: Sat, 29 Jul 2017 08:16:15 -0600 Subject: [PATCH 8/8] Add check for the availability of webutils package --- R/post-body.R | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/R/post-body.R b/R/post-body.R index 999de4a0c..642aec1b8 100644 --- a/R/post-body.R +++ b/R/post-body.R @@ -15,6 +15,7 @@ postBodyFilter <- function(req){ forward() } + #' @importFrom utils URLdecode #' @noRd parseBody <- function(body){ @@ -34,28 +35,32 @@ parseBody <- function(body){ ret } + #' @importFrom utils URLdecode #' @noRd parseMultipart <- function(body, boundary){ - # This is not as generic as perhaps it should be, but should be relatively - # easy to extend if more binary formats are required - # Is there data in the request? - if (is.null(body) || length(body) == 0 || body == "") { - return(list()) + function(val, req, res, errorHandler){ + tryCatch({ + if (!requireNamespace("webutils", quietly = TRUE)) { + stop("The webutils package is not available but is required in order + to use the functionality to POST binary files", + call. = FALSE) + } + # Is there data in the request? + if (is.null(body) || length(body) == 0 || body == "") { + return(list()) + } + parsed_binary <- webutils::parse_multipart(body, boundary) + file_name <- parsed_binary$myfile$filename + file_data <- parsed_binary$myfile$value + + tmpfile <- tempfile() + writeBin(file_data, tmpfile) + + ret <- NULL + ret$name <- file_name + ret$data <- tmpfile + ret + }) } - # This is something odd with webutils::parse_multipart. For some reason it - # does not work when the boundary is set to the content type as per the - # webutils docs. - parsed_binary <- webutils::parse_multipart(body, boundary) -# file_name = "test.feather" - file_name <- parsed_binary$myfile$filename - file_data <- parsed_binary$myfile$value - - tmpfile <- tempfile() - writeBin(file_data, tmpfile) - - ret <- NULL - ret$name <- file_name - ret$data <- tmpfile - ret }