Trying to understand what's wrong with my api

Dear all,

I am trying to build an api that allows me to post an RMD file, and which returns me a pdf.

This is what my code looks like:

#* compile pdf with parser
#* @param format The output format
#* @parser text
#* @parser multi
#* @serializer pdf
#* @post /compile3
function(req, res, format, data){ #data is my Rmd file
        str(data)
        rmd_path <- tempfile()
        output_path <- "/srv/plumber/output_file.pdf"
        write(data[[1]], rmd_path)
        rmarkdown::render(rmd_path, output_path, output_format = format)
        readBin(output_path, "raw", n = file.info(output_path)$size)
}

I realise writing this that I actually don't need the format argument, since only will be getting pdfs, but this doesn't matter for my issues.

This is heavily inspired by the code found on this repo.
I can see that my markdown file gets written on my server, and that the output pdf file gets written as well. Opening the pdf file from the server shows me a nice looking pdf. However, the one I get back, client-side, is empty. Here is the query I run:

library(httr)
library(magrittr)

res <- 
  POST(
 "http://shinybrodriguesco.duckdns.org:8000/compile3?format=pdf_document",
    body = list(
      data = upload_file("testmark.Rmd", "text/plain")
    )
  ) %>%
  content()

output_filename <- file("output_file.pdf", "wb")
writeBin(object = res, con = output_filename)
close(output_filename)

I have several questions actually:

  • First of all, would this be the best, most elegant way to write the api?
  • I actually don't really understand why I need the two parsers, #* @parser text and #* @parser multi. I found this on some stackoverflow thread, but can't find it back. I kinda get @parser text, since my Rmd file is pure text, but why @parser multi?
  • Finally, what am I doing wrong?

I'm running R 4.1.0 with plumber version 1.1.0 via docker on my raspberry pi.

Thank you very much in advance for your help!

1 Like
#* compile pdf with parser
#* @param data:file The Rmd file
#* @post /compile3
# We use serializer contentType, the pdf serializer is the plot output from grDevices
# Since the content is already in the right format from render, we just need to set
# the content-type
#* @serializer contentType list(type = "application/pdf")
function(data) { #data is my Rmd file
  # save the binary blob to a temp location
  rmd_doc <- file.path(tempdir(), names(data))
  writeBin(data[[1]], rmd_doc)
  # render document to pdf (file will be saved side by side with source but with pdf extension)
  pdf_output <- rmarkdown::render(rmd_doc, "pdf_document")
  # remove files on exit
  on.exit({file.remove(rmd_doc, pdf_output)}, add = TRUE)
  # Include file in response as attachment
  value <- readBin(pdf_output, "raw", file.info(pdf_output)$size)
  plumber::as_attachment(value, basename(pdf_output))
}

You are fine with the default parsers. If you want to know more about parsers : Plumber Parsers — parser_form • plumber (rplumber.io). "multi" parser is used to parse multipart content. Each part is then parsed using individual parsers like "text".

Thank you very much for your help! I got it to work, and modified it like so to be able to get documents in different formats:

#* Knit Rmarkdown document
#* @param data:file The Rmd file
#* @param string The output format
#* @post /compile4
# We use serializer contentType, the pdf serializer is the plot output from grDevices
# Since the content is already in the right format from render, we just need to set
# the content-type
#* @serializer contentType list(type = "application/gzip")
function(data, output_format) { #data is my Rmd file
  # save the binary blob to a temp location
  rmd_doc <- file.path(tempdir(), names(data))
  writeBin(data[[1]], rmd_doc)
  # render document to pdf (file will be saved side by side with source but with pdf extension)
  output <- rmarkdown::render(rmd_doc, output_format)
  tar("output.tar.gz", normalizePath(output), compression = "gzip", tar = "tar")
  # remove files on exit
  on.exit({file.remove(rmd_doc, output, "output.tar.gz")}, add = TRUE)
  # Include file in response as attachment
  value <- readBin("output.tar.gz", "raw", file.info("output.tar.gz")$size)
  plumber::as_attachment(value, basename("output.tar.gz"))
}

I have one further question though: when should we use include_rmd or include_file instead of as_attachment?

When you want to serve content as html (like a dynamic webpage). rmd, md are rendered before being served to client.

Thanks for the reply, but I’m not sure I get it. Why would I not want to simply use rmarkdown::render with the right format (html_document) for this? It is not clear to me what problem include_rmd solves when compared to rmarkdown::render.

It is basically a render. You can check the original PR Add helper functions for including · Issue #4 · rstudio/plumber (github.com). Those were added very early in the plumber life, I think version 0.1.0.

This topic was automatically closed 21 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.