Skip to content

Commit

Permalink
Merge upstream
Browse files Browse the repository at this point in the history
Merge branch 'dev' of https://github.com/rexyai/RestRserve into dev

# Conflicts:
#	DESCRIPTION
  • Loading branch information
artemklevtsov committed Dec 18, 2019
2 parents d67ee17 + cd5266b commit 09e93b1
Show file tree
Hide file tree
Showing 33 changed files with 995 additions and 86 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ inst/examples/ssl/cert/*
RestRserve*.tar.gz
docs/
inst/doc
pkgdown/
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ addons:
- g++-5
- gfortran-5

r_github_packages:
- rstudio/rmarkdown

r_packages:
- covr
- lintr
Expand Down Expand Up @@ -58,4 +61,4 @@ deploy:
local_dir: docs
on:
branch: dev
repo: dselivanov/RestRserve
repo: rexyai/RestRserve
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
1. Official Rserve page on Rforge - [news section](http://rforge.net/Rserve/news.html)
1. Description of the **configuration** on [github wiki](https://github.com/s-u/Rserve/wiki/rserve.conf) - **not complete**. For most detailed information it worths to check source code - [setConfig function](https://github.com/s-u/Rserve/blob/05ff32d3c4512954a99162d392d0465d432d591e/src/Rserv.c#L1094) in `Rserve.c`
1. **http** interface:
* **request** object format. Rserve passes 4 values (`url`, `query`, `body`, `headers`) to the R code. Parsing logic is described in [FastRWeb::.http.request](https://github.com/s-u/FastRWeb/blob/aaf8847f11903675b1ec7eb9c0e1cc98b92512e5/R/run.R#L58) and [RestRserve:::parse_request](https://github.com/dselivanov/RestRserve/blob/4aecbfb18b8403908c727fa478d161247d591764/R/request.R#L4) functions
* **request** object format. Rserve passes 4 values (`url`, `query`, `body`, `headers`) to the R code. Parsing logic is described in [FastRWeb::.http.request](https://github.com/s-u/FastRWeb/blob/aaf8847f11903675b1ec7eb9c0e1cc98b92512e5/R/run.R#L58) and [RestRserve:::parse_request](https://github.com/rexyai/RestRserve/blob/4aecbfb18b8403908c727fa478d161247d591764/R/request.R#L4) functions
* **response** object from R code defined in [http.c](https://github.com/s-u/Rserve/blob/05ff32d3c4512954a99162d392d0465d432d591e/src/http.c#L353-L372) function inside `Rserve.c`
1. Some discissions at issue tracker
* [RServe for real-time](https://github.com/s-u/Rserve/issues/64)
Expand Down
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ Authors@R: c(
comment = c(ORCID = "0000-0003-0492-6647")),
person(given = "rexy.ai",
role = c("cph", "fnd")))
URL: http://restrserve.org, https://github.com/dselivanov/RestRserve
BugReports: https://github.com/dselivanov/RestRserve/issues
URL: http://restrserve.org, https://github.com/rexyai/RestRserve
BugReports: https://github.com/rexyai/RestRserve/issues
License: GPL (>= 2)
Depends:
R (>= 3.6.0)
Expand Down
33 changes: 26 additions & 7 deletions R/Application.R
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ Application = R6::R6Class(
content_type = switch(
tools::file_ext(file_path),
json = "application/json",
"application/x-yaml" # https://www.quora.com/What-is-the-correct-MIME-type-for-YAML-documents
"text/plain"
)

self$add_static(path = path, file_path = file_path, content_type = content_type)
Expand Down Expand Up @@ -309,14 +309,18 @@ Application = R6::R6Class(
if (is.null(request$decode))
request$decode = self$ContentHandlers$get_decode(content_type = request$content_type)

self$logger$trace(
# log request
self$logger$debug(
"",
context = list(
request_id = request$id,
method = request$method,
path = request$path,
parameters_query = request$parameters_query,
headers = request$headers
request = list(
method = request$method,
path = request$path,
parameters_query = request$parameters_query,
parameters_path = request$parameters_path,
headers = request$headers
)
)
)

Expand Down Expand Up @@ -381,8 +385,22 @@ Application = R6::R6Class(
# this means that response wants RestRerveApplication to select
# how to encode automatically
if (!is.function(response$encode)) {
response$encode = self$ContentHandlers$get_encode(response$content_type)
private$eval_with_error_handling({
response$encode = self$ContentHandlers$get_encode(response$content_type)
})
}

# log response
self$logger$debug(
"",
context = list(
request_id = request$id,
response = list(
status_code = response$status_code,
headers = response$headers
)
)
)
})
return(response)
},
Expand Down Expand Up @@ -540,6 +558,7 @@ Application = R6::R6Class(
for (field in c("body", "content_type", "headers", "status_code")) {
private$response[[field]] = x[[field]]
}
private$response$encode = self$ContentHandlers$get_encode(private$response$content_type)
success = FALSE
}
return(success)
Expand Down
4 changes: 3 additions & 1 deletion R/AuthBackendBasic.R
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
#' ````
#'
#' * `FUN` :: `function`\cr
#' Function to perform authentication.
#' `character(1)`, `character(1)` -> `logical(1)` \cr
#' Function to perform authentication which takes two arguments - `user` and `password`.
#' Returns boolean - whether access is allowed for a requested `user` or not.
#'
#' @section Methods:
#'
Expand Down
4 changes: 3 additions & 1 deletion R/AuthBackendBearer.R
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
#' ````
#'
#' * `FUN` :: `function`\cr
#' Function to perform authentication
#' `character(1)` -> `logical(1)` \cr
#' Function to perform authentication which takes one arguments - `token`.
#' Returns boolean - whether access is allowed for a requested `token` or not.
#'
#' @section Methods:
#'
Expand Down
2 changes: 1 addition & 1 deletion R/AuthMiddleware.R
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
#' Routes paths to protect.
#'
#' * `match` :: `character()`\cr
#' How routes will be matched: exact or partial (as prefix).
#' How routes will be matched: `"exact"` or `"partial"` (as prefix).
#'
#' * `id` :: `character(1)`\cr
#' Middleware id
Expand Down
33 changes: 19 additions & 14 deletions R/BackendRserve.R
Original file line number Diff line number Diff line change
Expand Up @@ -113,29 +113,18 @@ BackendRserve = R6::R6Class(
}

# print endpoints summary
if (length(self$endpoints) == 0) {
if (length(app$endpoints) == 0) {
app$logger$warn("", context = "'Application' doesn't have any endpoints")
}
app$logger$info("", context = list(http_port = http_port, endpoints = app$endpoints))

pid = Sys.getpid()
if (run_mode == 'BACKGROUND') {
pid = parallel::mcparallel(do.call(Rserve::run.Rserve, ARGS), detached = TRUE)[["pid"]]
}

# print msg now because after `do.call` process will be blocked
if (interactive()) {
message(sprintf("Started RestRserve in a %s process pid = %d", run_mode, pid))
msg = sprintf("You can kill process GROUP with 'kill -TERM -%d'", pid)
msg = paste(msg, '(current master process also will be killed)')
message(msg)
}

if (run_mode == 'FOREGROUND') {
return(ApplicationProcess$new(pid))
} else {
do.call(Rserve::run.Rserve, ARGS)
}

return(pid)
},

set_request = function(request, path = "/", parameters_query = NULL, headers = NULL, body = NULL) {
Expand Down Expand Up @@ -308,3 +297,19 @@ BackendRserve = R6::R6Class(
}
),
)

ApplicationProcess = R6::R6Class(
classname = "ApplicationProcess",
public = list(
pid = NULL,
initialize = function(pid) {
self$pid = pid
},
kill = function(signal = 15L) {
# kill service
tools::pskill(self$pid, signal)
# kill childs
system(sprintf("pkill -%s -P %s", signal, self$pid), wait = FALSE)
}
)
)
12 changes: 10 additions & 2 deletions R/ContentHandlers.R
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ ContentHandlersFactory = R6::R6Class(
},
get_encode = function(content_type) {
if (!is_string(content_type)) {
raise(HTTPError$internal_server_error(body = list(error = "Can't encode the body - invalid 'content_type'")))
raise(HTTPError$internal_server_error(
body = list(error = "500 Internal Server Error: can't encode the body - invalid 'content_type'"))
)
}
content_type = tolower(content_type)
encode = self$handlers[[content_type]][["encode"]]
Expand All @@ -74,7 +76,10 @@ ContentHandlersFactory = R6::R6Class(
content_type = strsplit(content_type, ';', TRUE)[[1]][[1]]
encode = self$handlers[[content_type]][["encode"]]
if (!is.function(encode)) {
encode = as.character
err = sprintf("500 Internal Server Error: can't encode body with content_type = '%s'", content_type)
raise(HTTPError$internal_server_error(
body = list(error = err)
))
}
}
return(encode)
Expand Down Expand Up @@ -124,6 +129,9 @@ ContentHandlersFactory = R6::R6Class(
# set default encoders
self$set_encode("application/json", to_json)
self$set_encode("text/plain", to_string)
self$set_encode("text/html", to_string)
self$set_encode("text/css", to_string)


# set default decoders
self$set_decode(
Expand Down
4 changes: 2 additions & 2 deletions R/logging.R
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ Logger = R6::R6Class(
#----------------------------------------
initialize = function(
level = c("info", "fatal", "error", "warn", "debug", "trace", "off", "all"),
name = "ROOT", FUN = NULL) {
name = "ROOT", printer = NULL) {
self$set_log_level(level)
self$set_name(name)
self$set_printer(FUN)
self$set_printer(printer)
},
#----------------------------------------
trace = function(msg, ...) {
Expand Down
3 changes: 0 additions & 3 deletions R/zzz.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
recent_rserve = as.numeric_version("1.8.6")
current_rserve = utils::packageVersion("Rserve")
if (interactive()) {
msg = paste("RestRserve is still work in progress",
"- while we try hard to have stable API expect some breaking changes.")
packageStartupMessage(msg)
if (current_rserve < recent_rserve) {
packageStartupMessage(
sprintf("Rserve version %s detected", current_rserve), "\n",
Expand Down
35 changes: 21 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
# RestRserve
# RestRserve <a href='http://restrserve.org'><img src='man/figures/logo.png' align="right" height="128" /></a>

[![Travis-CI Build Status](https://travis-ci.org/dselivanov/RestRserve.svg?branch=dev)](https://travis-ci.org/dselivanov/RestRserve)
[![codecov](https://codecov.io/gh/dselivanov/RestRserve/branch/dev/graph/badge.svg)](https://codecov.io/gh/dselivanov/RestRserve/branch/dev)
[![Travis-CI Build Status](https://travis-ci.org/rexyai/RestRserve.svg?branch=dev)](https://travis-ci.org/rexyai/RestRserve)
[![codecov](https://codecov.io/gh/rexyai/RestRserve/branch/dev/graph/badge.svg)](https://codecov.io/gh/rexyai/RestRserve/branch/dev)
[![License](https://eddelbuettel.github.io/badges/GPL2+.svg)](http://www.gnu.org/licenses/gpl-2.0.html)
[![Lifecycle: maturing](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://www.tidyverse.org/lifecycle/#maturing)

**RestRserve is still work in progress - while we try hard to have stable API expect some breaking changes.**

[RestRserve](https://github.com/dselivanov/RestRserve) is an R web API framework for building **high-performance microservices and app backends**. Thanks to [Rserve](https://github.com/s-u/Rserve) it is **parallel by design**. It will handle incoming requests in parallel - each request in a separate fork (all the credits for that should go to [Simon Urbanek](https://github.com/s-u)).
[RestRserve](https://github.com/rexyai/RestRserve) is an R web API framework for building **high-performance** AND **robust** microservices and app backends. With [Rserve](https://github.com/s-u/Rserve) backend on UNIX-like systems it is **parallel by design**. It will handle incoming requests in parallel - each request in a separate fork (all the credits should go to [Simon Urbanek](https://github.com/s-u)).

## Installation

### From source
```r
remotes::install_github("dselivanov/RestRserve")
remotes::install_github("rexyai/RestRserve@dev")
```

### Docker

Automated docker builds from docker-hub: [https://hub.docker.com/r/dselivanov/restrserve/](https://hub.docker.com/r/dselivanov/restrserve/)
Automated docker builds from docker-hub: [https://hub.docker.com/r/rexyai/restrserve/](https://hub.docker.com/r/rexyai/restrserve/)

## Quick start

Expand All @@ -31,27 +29,36 @@ app$add_get(
FUN = function(request, response) {
response$set_body("Hello from RestRserve")
})
app$run(http_port = 8080)
backend = BackendRserve$new()
backend$start(app, http_port = 8080)
```

Now you can type `http://localhost:8080/hello` in your favourite browser and see (surprisingly!) *Hello from RestRserve*.

Please follow [quick start article on http://restrserve.org/](./articles/quick-start.html) for more details.

## Learn RestRserve

- follow [quick start guide on http://restrserve.org/](http://restrserve.org/articles/RestRserve.html) for more details.
- check out "Articles" section on http://restrserve.org/
- browse [examples on https://github.com/rexyai/RestRserve](https://github.com/rexyai/RestRserve/tree/dev/inst/examples)


## Features

- Easy to install, small number of dependencies
- Fully featured http server with the **support for URL encoded and multipart forms**
- Allows to build **high performance REST API** ( > 2000 req/sec per CPU core)
- Build **safe and secure applications** - RestRserve supports *https*, provides building blocks for basic/token authentication
- Concise and intuitive syntax
- Allows to **raise meaningful http errors** and interrupt request handling from any place of your code
- **Raise meaningful http errors** and allows to interrupt request handling from any place of the user code
- Comes with **many examples** - see `inst/examples`
- Saves you from boilerplate code:
- automatically **lazily** decodes request body of common formats
- automatically encodes response body for common formats
- automatically **lazily** decodes request body from the common formats
- automatically encodes response body to the common formats
- automatically parses URI templates (such as `/get/{item_id}`)
- helps to expose OpenAPI and Swagger/Redoc/Rapidoc UI
- It is [fast](http://restrserve.org/articles/benchmarks/Benchmarks.html)!

![](vignettes/img/bench-rps.png)

## Acknowledgements

Expand Down
2 changes: 1 addition & 1 deletion RestRserve.Rproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ StripTrailingWhitespace: Yes
BuildType: Package
PackageUseDevtools: Yes
PackageInstallArgs: --no-multiarch --with-keep.source
PackageRoxygenize: rd,collate,namespace,vignette
PackageRoxygenize: rd,collate,namespace
2 changes: 1 addition & 1 deletion _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ url: http://restrserve.org
authors:
rexy.ai:
href: https://rexy.ai
html: <a href="https://www.rexy.ai"><img src="https://s3-eu-west-1.amazonaws.com/rexy.ai/images/rexy-logo.svg" height="48" alt="rexy.ai"></a>
html: <a href="https://www.rexy.ai"><img src="https://s3-eu-west-1.amazonaws.com/rexy.ai/images/rexy-logo.svg" height="32" alt="rexy.ai"></a>
26 changes: 19 additions & 7 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,28 @@ FROM r-base:3.6.1
ENV R_FORGE_PKGS Rserve
ENV R_CRAN_PKGS Rcpp R6 uuid checkmate mime jsonlite

RUN apt-get update && \
apt-get install -y --no-install-recommends libssl-dev libjemalloc-dev && \
install2.r -r http://www.rforge.net/ $R_FORGE_PKGS && \
install2.r $R_CRAN_PKGS

RUN cd /tmp && \
wget http://www.haproxy.org/download/2.0/src/haproxy-2.0.8.tar.gz && \
tar -xvzf haproxy-2.0.8.tar.gz && \
cd haproxy-2.0.8/ && \
make TARGET=linux-glibc && \
chmod +x haproxy && \
cp haproxy /bin/ && \
rm -rf /tmp/*

COPY . /tmp/RestRserve

ENV STRIP "-- --strip"
RUN R CMD build --no-manual --no-build-vignettes /tmp/RestRserve && \
R CMD INSTALL /RestRserve*.tar.gz && \
rm -rf /tmp/RestRserve* && \
rm /RestRserve*.tar.gz

RUN apt-get update && \
apt-get install -y --no-install-recommends libssl-dev libjemalloc-dev && \
install2.r -r http://www.rforge.net/ $STRIP $R_FORGE_PKGS && \
install2.r $STRIP $R_CRAN_PKGS && \
R CMD build --no-manual --no-build-vignettes /tmp/RestRserve && \
R CMD INSTALL $STRIP /RestRserve*.tar.gz && rm -rf /tmp/RestRserve* && rm /RestRserve*.tar.gz
WORKDIR /

ENV LD_PRELOAD /usr/lib/x86_64-linux-gnu/libjemalloc.so

Expand Down
1 change: 1 addition & 0 deletions inst/examples/plot-base/app.R
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ plot_handler = function(request, response) {
# on.exit(unlink(tmp))
# response$body = readBin(tmp, raw(), file.size(tmp))
response$body = c("tmpfile" = tmp)
response$encode = identity
}


Expand Down
2 changes: 1 addition & 1 deletion inst/tinytest/test-app-openapi.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ rs = app$process_request(rq)
expect_equal(names(rs$body), "file")
expect_true(file.exists(rs$body))
expect_equal(readLines(rs$body, n = 1L), "openapi: 3.0.1")
expect_equal(rs$content_type, "application/x-yaml")
expect_equal(rs$content_type, "text/plain")
expect_equal(rs$headers, list())
expect_equal(rs$status_code, 200L)

Expand Down
Loading

0 comments on commit 09e93b1

Please sign in to comment.