From 1b19e3ac1fcf96932ccef3a848512fca7e3159c8 Mon Sep 17 00:00:00 2001 From: shrektan Date: Fri, 12 Oct 2018 09:45:57 +0800 Subject: [PATCH 1/2] The source files used in plumber must use the UTF-8 encoding if they contain non-ASCII characters --- NEWS.md | 2 ++ R/plumber.R | 16 ++++++++++++---- tests/testthat/files/multibytes.R | 4 ++++ tests/testthat/test-multibytes.R | 12 ++++++++++++ 4 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 tests/testthat/files/multibytes.R create mode 100644 tests/testthat/test-multibytes.R diff --git a/NEWS.md b/NEWS.md index 6c8682125..3d5d9b5f3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,8 @@ plumber 0.4.7 -------------------------------------------------------------------------------- * Add support for swagger for mounted routers (@bradleyhd, [#274](https://github.com/trestletech/plumber/issues/274)). * BUGFIX: A multiline POST body is now collapsed to a single line ([#270](https://github.com/trestletech/plumber/issues/270)). +* The source files used in plumber must use the UTF-8 encoding if they contain +non-ASCII characters (@shrektan, [#312](https://github.com/trestletech/plumber/pull/312)). plumber 0.4.6 diff --git a/R/plumber.R b/R/plumber.R index 2a5db5132..441e2e091 100644 --- a/R/plumber.R +++ b/R/plumber.R @@ -2,6 +2,11 @@ #' @import stringi NULL +# Hard code UTF-8 file encoding +# Removes encoding headache at minor cost of setting encoding in editor +# https://github.com/trestletech/plumber/pull/312 +utf8Encoding <- "UTF-8" + # used to identify annotation flags. verbs <- c("GET", "PUT", "POST", "DELETE", "HEAD", "OPTIONS", "PATCH") enumerateVerbs <- function(v){ @@ -50,7 +55,7 @@ plumb <- function(file, dir="."){ on.exit(setwd(old)) # Expect that entrypoint will provide us with the router - x <- source(entrypoint) + x <- source(entrypoint, encoding=utf8Encoding) # source returns a list with value and visible elements, we want the (visible) value object. pr <- x$value @@ -177,10 +182,13 @@ plumber <- R6Class( private$notFoundHandler <- default404Handler if (!is.null(file)){ - private$lines <- readLines(file) - private$parsed <- parse(file, keep.source=TRUE) + con <- file(file, encoding=utf8Encoding) + on.exit(close(con), add=TRUE) - source(file, local=private$envir, echo=FALSE, keep.source=TRUE) + private$lines <- readLines(con) + srcfile <- srcfilecopy(file, private$lines, isFile=TRUE) + private$parsed <- parse(text=private$lines, srcfile=srcfile, keep.source=TRUE) + source(con, local=private$envir, echo=FALSE, keep.source=TRUE) for (i in 1:length(private$parsed)){ e <- private$parsed[i] diff --git a/tests/testthat/files/multibytes.R b/tests/testthat/files/multibytes.R new file mode 100644 index 000000000..2f1563baf --- /dev/null +++ b/tests/testthat/files/multibytes.R @@ -0,0 +1,4 @@ +#* @get /echo +function() { + "中文消息" +} diff --git a/tests/testthat/test-multibytes.R b/tests/testthat/test-multibytes.R new file mode 100644 index 000000000..ed25363fa --- /dev/null +++ b/tests/testthat/test-multibytes.R @@ -0,0 +1,12 @@ +context("multibytes source file") + +test_that("support files with multibytes", { + # on Windows, the default encoding is not UTF-8. So, plumber has to + # tell R to use UTF-8 encoding when reading the source file. + r <- plumber$new("files/multibytes.R") + req <- make_req("GET", "/echo") + res <- PlumberResponse$new() + out <- r$serve(req, res)$body + expect_identical(out, jsonlite::toJSON("\u4e2d\u6587\u6d88\u606f")) + expect_equal(Encoding(out), utf8Encoding) +}) From 0b82e792617e346f61c7bf516f0640ddf77321e2 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Mon, 29 Oct 2018 15:48:12 -0400 Subject: [PATCH 2/2] comments --- R/plumber.R | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/R/plumber.R b/R/plumber.R index 441e2e091..bcdd7f67f 100644 --- a/R/plumber.R +++ b/R/plumber.R @@ -185,9 +185,15 @@ plumber <- R6Class( con <- file(file, encoding=utf8Encoding) on.exit(close(con), add=TRUE) + # Read lines directly private$lines <- readLines(con) + # "...produces an object of the descendant class ‘srcfilecopy’, + # which saves the source lines in a character vector" (?srcfilecopy) srcfile <- srcfilecopy(file, private$lines, isFile=TRUE) + # "When ‘keep.source’ is ‘TRUE’, if ‘text’ is used, + # ‘srcfile’ will be set to a ‘srcfilecopy’ containing the text" (?parse) private$parsed <- parse(text=private$lines, srcfile=srcfile, keep.source=TRUE) + source(con, local=private$envir, echo=FALSE, keep.source=TRUE) for (i in 1:length(private$parsed)){