Code Monkey home page Code Monkey logo

fiery's Introduction

R-CMD-check CRAN_Status_Badge CRAN_Download_Badge Codecov test coverage

Fiery is a flexible and lightweight framework for building web servers in R. It is relatively unopinionated about how you chose to build your server logic and supports many use cases, from serving static files to being used as a base for a model-view-controller based setup.

The shiny elephant in the room

Before going any further I will briefly address what most people are thinking of when they think R+web: Shiny:

Is this a competing framework to Shiny?

In a way, yes. Any package that provides functionality for creating web applications in R will be competing for the developers who wish to make web apps. This is of course reinforced by the name of the package, which is a gently jab at Shiny. But mostly no. I believe Shiny and Fiery will appeal to vastly different use cases, in the same way as automakers and motorbike makers are in theory competing for the customers who wish to acquire the means to transport themselves, but would never be seen as truly competing.

So what is so different about Fiery?

Without falling too much into the trap of defining a project by how it differs from another, there are some very clear differences in approach between Fiery and Shiny.

  • Shiny uses magic to make everything work from R, Fiery lets you do all the hard work.
  • Shiny wants the main app-logic to be server-side, Fiery don’t care what you do.
  • Shiny uses a reactive model to define the app-logic, Fiery doesn’t care about what you do (see a pattern emerge).
  • Shiny wants you to use htmltools to build the html, Fiery really doesn’t care about what you use.

From the above it is quite clear that Fiery to a higher degree gives you the choice and responsibility of building up your app at the cost of higher complexity, but with the goal of giving you more power over what you can do.

So how is this different from httpuv?

Now we’re getting somewhere! httpuv is sitting in the bottom of the stack for both Shiny and Fiery, but where Shiny builds an elaborate, opinionated and complete framework on top of httpuv, Fiery “merely” adds a lot of convenience to running a httpuv based web server. You could say that Fiery sits between httpuv and Shiny, and that Shiny (or an alternative framework) could in theory be built on top of Fiery.

How to install this

Install the release from CRAN using install.packages('fiery') or get the development version directly from GitHub using pak:

# install.packages('pak')
pak::pak('thomasp85/fiery')

Design

Fiery is designed around a clear server life-cycle with events being triggered at specific points during the life-cycle that will call the handlers attached to these events. In addition to the life-cycle events, it is possible to trigger custom events and attach handlers to these as well. Fiery is designed with modularity in mind so that plugins can be developed for different tasks and mixed and matched to suit the specific project.

While the intro might indicate that fiery is difficult to use, this is not the case. Much of the hard work of handling http requests has been encapsulated in the reqres that fiery uses to handle http requests and responses. Further, A plugin that will often be used is routr, which provides powerful routing of HTTP requests, thus simplifying the server logic even more.

A minimal example

Following is a very Hello World-ish example of a fiery app (sans routr), that showcases some of the different life-cycle events:

library(fiery)

# Create a New App
app <- Fire$new()

# Setup the data every time it starts
app$on('start', function(server, ...) {
    server$set_data('visits', 0)
    server$set_data('cycles', 0)
})

# Count the number of cycles (internal loops)
app$on('cycle-start', function(server, ...) {
    server$set_data('cycles', server$get_data('cycles') + 1)
})

# Count the number of requests
app$on('before-request', function(server, ...) {
    server$set_data('visits', server$get_data('visits') + 1)
})

# Handle requests
app$on('request', function(server, request, ...) {
    response <- request$respond()
    response$status <- 200L
    response$body <- paste0('<h1>This is indeed a test. You are number ', server$get_data('visits'), '</h1>')
    response$type <- 'html'
})

# Show number of requests in the console
app$on('after-request', function(server, ...) {
    message(server$get_data('visits'))
    flush.console()
})

# Terminate the server after 50 cycles
app$on('cycle-end', function(server, ...) {
    if (server$get_data('cycles') > 50) {
        message('Ending...')
        flush.console()
        server$extinguish()
    }
})

# Be polite
app$on('end', function(server) {
    message('Goodbye')
    flush.console()
})

app$ignite(showcase = TRUE)
#> Fire started at 127.0.0.1:8080
#> message: 1
#>  from message(server$get_data("visits"))
#> message: Goodbye
#>  from message("Goodbye")

In general much of the logic will happen in the request and message handlers and you are free to ignore the other life-cycle events if they are not needed.

Feedback

I would love some feedback on this - open an issue or reach out to me on twitter.

Code of Conduct

Please note that the ‘fiery’ project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

fiery's People

Contributors

cpsievert avatar espinielli avatar henrikbengtsson avatar thomasp85 avatar ucg8j avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fiery's Issues

Add option to run a non-blocking server?

We're considering DashR's eventual support inside Jupyter notebooks (DashR uses Fiery as its web server). Can Fiery run in a background process / thread, so that the Jupyter REPL isn't blocked?

(Sister issue for Dash for Julia ("Dash.Jl"): plotly/Dash.jl#48)

Cannot set_logger of a cloned server

library(fiery)

server <- Fire$new()
server2 <- server$clone()
server2$set_logger(fiery::logger_console("{event}: {message}"))
Error in private$logger <- logger : 
  cannot change value of locked binding for 'logger'

Infrastructure for package based apps

Recognise the fireplace DESCRIPTION entry that defines an expression returning a Fire object

Add firestarter() function to start package based servers

Example linking fiery and vue.js (or react)?

I was looking at some of the tutorials at https://vuejs.org and was very interested to see that vue.js is reactive. Reactivity is, of course, one of Shiny's killer features. I started looking around to see if anyone had tried to link vue.js and R (or fiery).

@hrbrmstr's fiery example provides a nice illustration of using fiery but it would be great to see how to connect that to a basic user front-end, e.g., using vue.js. @timelyportfolio has some interesting work on embedding vue.js in R (https://github.com/timelyportfolio/vueR) but I haven't seen anything that uses R server-side and vue.js on the client side.

If anyone has an example of using R / fiery with vue.js I'd be very interested to see it.

In general, I think some (simple) examples using fiery to show how to generate plots and tables in a browser would make this very promising tool much more accessible.

Stack trace reporting

It seems as though fiery/routr/reqres based apps don't currently have a mechanism for reporting trace stacks (of errors), which makes debugging complex applications difficult. Here's a minimal example of the information reported in a request error (BTW, any idea why the error is being reported twice?):

library(fiery)
library(routr)

app <- Fire$new()

route <- Route$new()
route$add_handler('get', '*', function(request, response, keys, ...) {
  response$type <- 'html'
  response$status <- 200L
  response$body <- '<h1>All your AI are belong to us</h1>'
  1 + "a"
  FALSE
})

router <- RouteStack$new()
router$add_route(route, "main")
app$attach(router)
app$ignite(showcase = TRUE)
Fire started at 127.0.0.1:8080
error: non-numeric argument to binary operator
error: non-numeric argument to binary operator

With shiny, if you run an app that is saved to disk, it reports the line at which the error occurs -- would it be possible to add something similar to the fiery/routr/reqres stack?

library(shiny)

ui <- fluidPage(
  plotOutput("p")
)

server <- function(input, output, ...) {
  
  output$p <- renderPlot({
    1+"a"
    qplot(1:10)
  })
  
}

shinyApp(ui, server)
runApp('test-app-shiny.R')
Listening on http://127.0.0.1:5017
Warning: Error in +: non-numeric argument to binary operator
  161: renderPlot [~/test-app-shiny.R#10]
  159: func
  119: drawPlot
  105: <reactive:plotObj>
   92: drawReactive
   79: origRenderFunc
   78: output$p
    1: runApp

RStudio crashes when on("request") doesn't return the expected HTTP response list

Consider the following simple GET listener

library(fiery)
app <- Fire$new()
app$port <- 9000
app$host <- "127.0.0.1"

app$on('request', function(server, id, request, ...) {
  if (request$REQUEST_METHOD == "GET") {
    list(
      status = 200L,
      headers = list('Content-Type' = 'text/html'),
      body = paste('Get it!')
    )
  }
})

app$start(block = FALSE)

If you ping this URL with any request that is not a GET, my RStudio crashes. I'm on Windows 7.

a newcomer's question about Cycle, Message and Block

I just read the fiery reference manual from CRAN. I have several questions (maybe silly).

Cycle

The "minimal example" shutdown automatically after several seconds. It seems the following code does it.

# Count the number of cycles (internal loops)
app$on('cycle-start', function(server, ...) {
    server$set_data('cycles', server$get_data('cycles') + 1)
})

# Terminate the server after 50 cycles
app$on('cycle-end', function(server, ...) {
    if (server$get_data('cycles') > 50) {
        message('Ending...')
        flush.console()
        server$extinguish()
    }
})

What is the block "Count the number of cycles (internal loops)" does? i.e., what does internal loops mean? How the cycles becomes greater than 50? What's cycle in cycle-start and cycle-end?

Message

In the Life Cycle Events, there are before-message, message, after-message. I don't know what is websocket message. Is there any explanation about it? Or I should read some document/book (any suggestion?)

Block

What does the block argument in app$ignite(block = TRUE) do? How this argument affect the server? i.e., Does it mean a new process/session start when two or more users request the same resource in the same time?

Secure by default

Interested in a hand from someone far too entrenched in infosec?

I just started looking at this and will keep this list up to date but for starters:

  • host should default to 127.0.0.1 and absolutely not 0.0.0.0. I can go into "why" but this is pretty much the standard best practice (hate that term) and the only way to help users not shoot themselves in the foot.

FYI: parallelly::supportsMulticore()

FYI, instead of:

can_fork <- NULL
#' @importFrom parallelly availableCores
on_load({can_fork <- availableCores("multicore") > 1L})

you could use parallelly::supportsMulticore(). Other than being more clear, the advantage would be to that it returns TRUE also when there's a single core available.

where to start debugging for crashing sessions?

Playing around with the routr/fiery doc examples, and I'm successfully crashing as soon as I hit the hello/anything/ endpoint

image

it did work once, but then I pinged from localhost:8080 once and it crashed, and now on subsequent times it is crashing every time from http://127.0.0.1:8080/hello/mars/

I thought it might have been a port conflict so changed that but still no good.

Error in get(“PATHINFO”, envir = request) : object ‘PATHINFO’ not found

hi,
I follow your blog A Simple Prediction Web Service Using the New fiery Package

run the script , return an error: error: object 'PATH_INFO' not found

how to fix it ?

thank lots~

`

sessionInfo()
R version 3.4.3 (2017-11-30)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 16.04.3 LTS
Matrix products: default
BLAS: /usr/lib/openblas-base/libblas.so.3
LAPACK: /usr/lib/libopenblasp-r0.2.18.so
locale:
[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C LC_TIME=en_US.UTF-8
[4] LC_COLLATE=en_US.UTF-8 LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
[7] LC_PAPER=en_US.UTF-8 LC_NAME=C LC_ADDRESS=C
[10] LC_TELEPHONE=C LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] shiny_1.0.5 jsonlite_1.5 fiery_1.1.0
loaded via a namespace (and not attached):
[1] Rcpp_0.12.15 rstudioapi_0.7 xml2_1.2.0 reqres_0.2.1 webutils_0.6
[6] uuid_0.1-2 xtable_1.8-2 R6_2.2.2 globals_0.11.0 tools_3.4.3
[11] parallel_3.4.3 brotli_1.1 htmltools_0.3.6 yaml_2.1.16 assertthat_0.2.0
[16] digest_0.6.14 crayon_1.3.4 later_0.6 codetools_0.2-14 triebeard_0.3.0
[21] glue_1.2.0 mime_0.5 stringi_1.1.6 compiler_3.4.3 urltools_1.7.0
[26] future_1.6.2 httpuv_1.3.5 listenv_0.7.0
`

Test failure with upcoming version of later

With the version of later that will be released soon, the following test fails:

expect_message({ # Need to force some cycles to happen
Sys.sleep(.1)
later::run_now()
Sys.sleep(.1)
later::run_now()
}, 'Cannot stop server from within a non-blocking event cycle')

This is because run_now() has changed: previously, if any errors/warnings/messages happened in a callback, they would pass all the way through to the caller of run_now(). In the new version of later, run_now uses R_TopLevelExec to invoke the callbacks, and errors/warnings/messages are not passed through R_TopLevelExec. (They will, however, still display at the console.)

You can install the upcoming version of later with:

devtools::install_github('r-lib/[email protected]')

I currently don't know of any way to capture the message generated by a callback invoked by run_now(), so I think your only recourse at this point may be to remove the test. If you can somehow move the expect_message() into the later callback, that would work. But your code may not be structured for that.

Request Body is Empty

Sent a POST request to the server with body:

curl -d 'Hello' -X POST http://127.0.0.1:8080

On the fiery side the body is always null. Do I have to call a certain function to get the body property filled?

Release fiery 1.1.2

Prepare for release:

  • devtools::check()
  • devtools::check_win_devel()
  • rhub::check_for_cran()
  • revdepcheck::revdep_check(num_workers = 4)
  • Polish NEWS
  • Polish pkgdown reference index

Submit to CRAN:

  • usethis::use_version('patch')
  • Update cran-comments.md
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted 🎉
  • usethis::use_github_release()
  • usethis::use_dev_version()
  • Tweet

Access-Control-Allow-Origin CORS problem

fiery is awesome! It makes some project much more easy.

I'm using fiery with node: write a app and use node to fetch this.
While, there is a CORS problem:

Chorme console:

XMLHttpRequest cannot load http://localhost:9123/XXXXXXXXXXX. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access.

I think it could be handled in the fiery, wondering if there is any method to set Access-Control-Allow-Origin in the header.

infrastructure for event logging

Need to come up with a flexible API for handling of logging.

Current idea:
Fire gets a log(event, message) method and all internals will call it. The log method will dispatch the logging to a user defined logger which can write to a db, output to console, etc. If none exists it will be a zero-operation...

Question: Nuxt or Angular + Fiery and concurrent requests?

After a few years of using Shiny and we have had lots of issues with it, e.g. it is too opinionated and it does not support concurrent requests on the open source version.

I wonder Fiery support concurrent requests?

We would like to use fiery as an API only and use Nuxt or Angular to send the requests, so we need CORS, does Fiery support CORS then?

Force HTTPS with Dash for R using Fiery on Heroku

Hi @thomasp85 - I recently created a post on the Dash for R forum, am seeking advice regarding HTTPS routing for a Dash for R app, which (according to the documentation here) "uses the Fiery web framework under the hood." Reproducing my post below in case this is a more appropriate forum for it:

I am hosting a Dash for R web app on a Heroku Hobby Dyno using a custom domain with automatic SSL/TLS enabled (i.e., when you explicitly navigate to https://examplewebsite.org, the connection is secure. However, I would like to redirect from http://examplewebsite.org to HTTPS (i.e., force the site to use SSL/TLS). I have read the Heroku documentation which:

  1. Indicates that redirects must be performed at the application level - I must code the redirect logic into my Dash app.

  2. Recommends the use of GoogleCloudPlatform/flask-talisman for Flask apps in Python. However, I am using Dash for R, regarding which the documentation provides no recommendations. Even if I had the R-equivalent of Talisman installed, I am not sure how to explicitly access the Flask app object in Dash for R to attempt to apply it.

I've looked through the fiery::Fire and Dash::dash documentation, but can't seem to find anything that would force HTTPS. I also looked at your heroku-fiery-demo repo, but don't see anything there either.

I haven't been able to find a solution to this issue and kindly request any guidance. Thanks so much!

Combined log format doesn't seem to work

library(fiery)

# Create a New App
app <- Fire$new()

# Handle requests
app$on('request', function(server, request, ...) {
  response <- request$respond()
  response$status <- 200L
  response$body <- paste0('<h1>This is indeed a test. You are number ', server$get_data('visits'), '</h1>')
  response$type <- 'html'
})

# works as expected
app$set_logger(logger_console())

# doesn't work
# app$set_logger(logger_console(combined_log_format))

app$ignite(showcase = TRUE)

More generally, it seems like errors/warnings from the logger itself should be reported in some way to help debug these sort of issues

Error in attributes(.Data)... on linux ubuntu

Hi, @thomasp85
I've run your example code on both Windows 10(my own PC) and Linux Ubuntu(on the server). When on Windows, it runs OK, but when on Ubuntu, it comes with "Error in attributes(.Data)... "

My Ubuntu environment is below,

> sessionInfo()
R version 3.4.0 (2017-04-21)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 14.04.5 LTS

Matrix products: default
BLAS: /usr/lib/libblas/libblas.so.3.0
LAPACK: /usr/lib/lapack/liblapack.so.3.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C
 [9] LC_ADDRESS=C               LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base

other attached packages:
[1] fiery_1.0.0

loaded via a namespace (and not attached):
 [1] Rcpp_0.12.12     codetools_0.2-15 brotli_1.0       listenv_0.6.0
 [5] future_1.6.0     digest_0.6.12    later_0.3        assertthat_0.2.0
 [9] R6_2.2.2         jsonlite_1.5     stringi_1.1.5    uuid_0.1-2
[13] xml2_1.1.1       webutils_0.6     urltools_1.6.0   reqres_0.2.0
[17] tools_3.4.0      triebeard_0.3.0  httpuv_1.3.3     parallel_3.4.0
[21] compiler_3.4.0   globals_0.10.2

And my code is just yours, (some rows deleted)

> library(fiery)
> 
> app <- Fire$new()
> app$host <- '192.168.1.22'  #This is the host of server
> app$port <- 19177
> 
> # Setup the data every time it starts
> app$on('start', function(server, ...) {
+   server$set_data('visits', 0)
+   server$set_data('cycles', 0)
+ })
> 
> # Count the number of cycles (internal loops)
> app$on('cycle-start', function(server, ...) {
+   server$set_data('cycles', server$get_data('cycles') + 1)
+ })
> 
> # Count the number of requests
> app$on('before-request', function(server, ...) {
+   server$set_data('visits', server$get_data('visits') + 1)
+ })
> 
> # Handle requests
> app$on('request', function(server, request, ...) {
+   response <- request$respond()
+   response$status <- 200L
+   response$body <- paste0('<h1>This is indeed a test. You are number ', server$get_data('visits'), '</h1>')
+   response$type <- 'html'
+ })
> 
> # Show number of requests in the console
> app$on('after-request', function(server, ...) {
+   message(server$get_data('visits'))
+   flush.console()
+ })
> 
> 
> # Be polite
> app$on('end', function(server) {
+   message('Goodbye')
+   flush.console()
+ })
> 
> app$ignite(showcase = TRUE)
Fire started at 192.168.1.22:19177
Error in attributes(.Data) <- c(attributes(.Data), attrib) : 
  'names' attribute [8] must be the same length as the vector [7]
ERROR: [on_request_read] parse error
Error in attributes(.Data) <- c(attributes(.Data), attrib) : 
  'names' attribute [8] must be the same length as the vector [7]
ERROR: [on_request_read] parse error

The same code I run on Windows do not come with any error (the host I set is '127.0.0.1'). However, when on Windows, I couldn't let other people in the local area network(LAN) visit my app whatever host I set(e.g. app$host <- '0.0.0.0' or app$host <- '127.0.0.1' or app$host <- '192.168.1.144'(my IPv4 address on Windows))

So, there are 2 questions I want to consult,
1, how to solve the Ubuntu 'Error in attributes(.Data)...'?
2, how to set the host address to let others visit my app if I use Windows 10?

Waiting for your reply~
Thank you~

Better doc organisation

Currently everything is documented under the Fire object which is suboptimal - this needs to be split up somehow

Release fiery 1.2.0

Prepare for release:

  • git pull
  • Check current CRAN check results
  • Polish NEWS
  • urlchecker::url_check()
  • devtools::build_readme()
  • devtools::check(remote = TRUE, manual = TRUE)
  • devtools::check_win_devel()
  • revdepcheck::revdep_check(num_workers = 4)
  • Update cran-comments.md
  • git push
  • Draft blog post

Submit to CRAN:

  • usethis::use_version('minor')
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted 🎉
  • usethis::use_github_release()
  • usethis::use_dev_version(push = TRUE)
  • Finish blog post
  • Tweet
  • Add link to blog post in pkgdown news menu

Writing to the log in a loop, results in only the last 'i' being written N times.

Writing to the log within a loop, results in the last value to be written 'N' times. I'm guessing this is because of the future evaluation, but I'm not sure how to get around it.

test_data <- data.frame(id = 1:5, data = LETTERS[1:5], stringsAsFactors = FALSE)
for (i in seq_len(nrow(test_data))) {
  message('Writing to log: ', jsonlite::toJSON(test_data[i, ]))
  server$log('test', jsonlite::toJSON(list(input = test_data[i, ])))
}

outputs:

Fire started at 127.0.0.1:8080
2019-02-26 13:53:01 - start: 127.0.0.1:8080
2019-02-26 13:53:01 - message: Writing to log: [{"id":1,"data":"A"}]
2019-02-26 13:53:01 - test: {"input":[{"id":5,"data":"E"}]}
2019-02-26 13:53:01 - message: Writing to log: [{"id":2,"data":"B"}]
2019-02-26 13:53:01 - test: {"input":[{"id":5,"data":"E"}]}
2019-02-26 13:53:01 - message: Writing to log: [{"id":3,"data":"C"}]
2019-02-26 13:53:01 - test: {"input":[{"id":5,"data":"E"}]}
2019-02-26 13:53:01 - message: Writing to log: [{"id":4,"data":"D"}]
2019-02-26 13:53:01 - test: {"input":[{"id":5,"data":"E"}]}
2019-02-26 13:53:01 - message: Writing to log: [{"id":5,"data":"E"}]
2019-02-26 13:53:01 - test: {"input":[{"id":5,"data":"E"}]}
2019-02-26 13:53:01 - message: 1
2019-02-26 13:53:01 - request: 127.0.0.1 - ID_127.0.0.1 [26/Feb/2019:13:53:01 +0100] "GET / HTTP/1.1" 200 48
2019-02-26 13:53:06 - message: Goodbye
2019-02-26 13:53:06 - stop: 127.0.0.1:8080

complete example based on the minimal fiery example:

library(fiery)

# Create a New App
app <- Fire$new()

app$set_logger(logger_console())

# Setup the data every time it starts
app$on('start', function(server, ...) {
  server$set_data('visits', 0)
  server$set_data('cycles', 0)
})

# Count the number of cycles (internal loops)
app$on('cycle-start', function(server, ...) {
  server$set_data('cycles', server$get_data('cycles') + 1)
})

# Count the number of requests
app$on('before-request', function(server, ...) {
  server$set_data('visits', server$get_data('visits') + 1)
})

# Handle requests
app$on('request', function(server, request, ...) {
  response <- request$respond()
  response$status <- 200L
  response$body <- paste0('<h1>This is indeed a test. You are number ', server$get_data('visits'), '</h1>')
  response$type <- 'html'
  test_data <- data.frame(id = 1:5, data = LETTERS[1:5], stringsAsFactors = FALSE)
  for (i in seq_len(nrow(test_data))) {
    message('Writing to log: ', jsonlite::toJSON(test_data[i, ]))
    server$log('test', jsonlite::toJSON(list(input = test_data[i, ])))
  }
  TRUE
})

# Show number of requests in the console
app$on('after-request', function(server, ...) {
  message(server$get_data('visits'))
  flush.console()
})

# Terminate the server after 50 cycles
app$on('cycle-end', function(server, ...) {
  if (server$get_data('cycles') > 50) {
    message('Ending...')
    flush.console()
    server$extinguish()
  }
})

# Be polite
app$on('end', function(server) {
  message('Goodbye')
  flush.console()
})
  
app$ignite(showcase = TRUE)

Can you give an example?

This package is great! Thanks very much for your excellent work.
I'm confused for passing value from outside into Fiery. The example may be like this:

curl http://127.0.0.1:8080/predict?val=10

The output depends on the input val=10, I want to get(10*2):

{
  "v": 20,
}

Thank you very much for your help.

Example for a prediction using C5.0 decision tree

Hi,

I was so impressed and amazed at this package, it seemed to have the right balance between complexity and customization, I found you through this post (https://www.r-bloggers.com/a-simple-prediction-web-service-using-the-new-fiery-package/).

I tired to modify your example to handle a JSON as input, specifically using C5.0 decision tree, however I could not manage to do so. I was hoping you could have a document/pdf on github which would use a complex model (random forest, or anything using Caret) where instead of single parameter we send in a JSON.

I know it may seem a rich of me to ask you to do so, but I see tremendous potential to your package, unlike openCPU this is so easier to setup. Thank you so much for this again!

future::multiprocess is deprecated since long and soon to become defunct

Hi. The 'multiprocess' backend has been deprecated since future 1.20.0 (Oct 2020). The fiery package uses it in the code and mentions it in the docs, e.g.

fiery/R/FutureStack.R

Lines 96 to 104 in da9c27f

#' @importFrom R6 R6Class
#' @importFrom future multiprocess
#'
AsyncStack <- R6Class('AsyncStack',
inherit = FutureStack,
private = list(
catcher = 'multiprocess'
)
)

Using it produces a deprecation warning:

Warning message:
Detected creation of a 'multiprocess' future. Strategy 'multiprocess' is deprecated in future (>= 1.20.0)
[2020-10-30]. Instead, explicitly specify either 'multisession' (recommended) or 'multicore'. Starting 
with future 1.31.0 [2023-01-31], 'multiprocess' is the same as 'sequential'.

But this soon become a defunct error. When that happens, R CMD check on the fiery package will break:

* checking tests ... ERROR
  Running ‘testthat.R’
Running the tests in ‘tests/testthat.R’ failed.
Complete output:
  > library(testthat)
  > library(fiery)
  > 
  > test_check("fiery")
  
  [ FAIL 1 | WARN 0 | SKIP 1 | PASS 253 ]
  
  ══ Skipped tests ═══════════════════════════════════════════════════════════════
  • On CRAN (1)
  
  ══ Failed tests ════════════════════════════════════════════════════════════════
  ── Error ('test-Fire.R:368'): futures can be added and called ──────────────────
  <defunctError/error/condition>
  Error: Detected creation of a 'multiprocess' future. Strategy 'multiprocess' is defunct in future (>= 1.20.0) [2020-10-30
]. Instead, explicitly specify either 'multisession' (recommended) or 'multicore'.
  Backtrace:
      ▆
   1. └─app$async(...) at test-Fire.R:368:4
   2.   └─private$ASYNC$add(substitute(expr), then, substituted = TRUE)
   3.     └─private$make_future(expr, then, ...)
   4.       ├─base::do.call(private$catcher, list(expr = expr, lazy = private$lazy))
   5.       └─future::multiprocess(...)
   6.         └─future (local) dfcn(msg = msg, package = .packageName)
   7.           └─base::.Defunct(...)
  
  [ FAIL 1 | WARN 0 | SKIP 1 | PASS 253 ]
  Error: Test failures
  Execution halted

Can you please fix?

PS. This one went under my revdepcheck radar, because it called multiprocess() directly. Please avoid that and use the recommended plan(...) approach instead. (It might be that we one day will replace all those backend functions with backend configuration objects with the same names)

how to return an error response from inside a plugin ?

Trying to write an authentication plugin to use with fiery and and routr but I cannot figure how to block the process if a condition is not met: e.g. return an error directly from the plugin.

A very minimal example would be something like this:

Basic <- R6Class('Basic', 
                 public = list(
                   initialize = function() {
                     
                   },
                   name = 'Basic',
                   on_attach = function(server, ...) {
                     server$on('before-request', function(request,...) {
                  
                       if (request$headers$Auth == 'foo') {
                          # continue
                         }
                       else {
                         # return error response
                         }
                     })
                   }
                 ))

basic = Basic$new()
route <- Route$new()

route$add_handler('post', '/', function(request, response, keys, ...) {
  response$status <- 200L
  response$body <- 'request handled'
  TRUE
})

router <- RouteStack$new()
router$add_route(route, 'app_router')

app <- Fire$new()

app$attach(basic)
app$attach(router)
app$ignite(block = TRUE)

How should I do that ?

Installation issue

Dear Thomas,

when I try to install the latest CRAN version, the installation stops at the testing step as shown below.
I'm using the latest fedora and I have try both "terminal" R 3.4.1 and rstudio. How can I help?

Erwan

R version 3.4.1 (2017-06-30) -- "Single Candle"
Copyright (C) 2017 The R Foundation for Statistical Computing
Platform: x86_64-redhat-linux-gnu (64-bit)

R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.

Natural language support but running in an English locale

R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.

Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.

> install.packages("fiery")
Installing package into ‘/home/lepennec/R/x86_64-redhat-linux-gnu-library/3.4’
(as ‘lib’ is unspecified)
--- Please select a CRAN mirror for use in this session ---
trying URL 'https://ftp.gwdg.de/pub/misc/cran/src/contrib/fiery_1.0.0.tar.gz'
Content type 'application/octet-stream' length 45562 bytes (44 KB)
==================================================
downloaded 44 KB

* installing *source* package ‘fiery’ ...
** package ‘fiery’ successfully unpacked and MD5 sums checked
** R
** preparing package for lazy loading
** help
*** installing help indices
  converting help for package ‘fiery’
    finding HTML links ... done
    Fire                                    html  
    event_doc                               html  
    fake_request                            html  
    fiery-package                           html  
    plugin_doc                              html  
Rd warning: /tmp/Rtmp5rAFdd/R.INSTALL3a4d65823c9a/fiery/man/plugin_doc.Rd:25: missing file link ‘setRefClass’
*** copying figures
** building package indices
** testing if installed package can be loaded

Release fiery 1.1.4

Prepare for release:

  • git pull
  • Check current CRAN check results
  • Polish NEWS
  • devtools::build_readme()
  • urlchecker::url_check()
  • devtools::check(remote = TRUE, manual = TRUE)
  • devtools::check_win_devel()
  • rhub::check_for_cran()
  • revdepcheck::revdep_check(num_workers = 4)
  • Update cran-comments.md
  • git push

Submit to CRAN:

  • usethis::use_version('patch')
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted 🎉
  • git push
  • usethis::use_github_release()
  • usethis::use_dev_version()
  • git push

Get overview of potential performance bottlenecks

At the moment the event cycle should be a zero-operation unless handlers have been attached. Still, I need to look into the efficiency of the cycle, as well as the event flushing mechanism to make sure it is as performant as possible

TLS 1.2 support?

I'm looking into this package as an option for serving data science models at my company. We have a security compliance policy that dictates we can only use http server frameworks that support/ are compatible with TLS 1.2 or higher. Does fiery meet this requirement?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.