Skip to content

Commit

Permalink
Merge branch 'dev' of https://github.com/rexyai/RestRserve into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
artemklevtsov committed Feb 29, 2020
2 parents ec51b6f + 9d7071c commit 1572567
Show file tree
Hide file tree
Showing 53 changed files with 597 additions and 374 deletions.
3 changes: 3 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ CONTRIBUTING.md
^\.travis\.yml$
^\.openapi\.yaml$
^pkgdown$
^\.dockerignore$
^\.appveyor.yml$
cran-comments.md
39 changes: 39 additions & 0 deletions .appveyor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
init:
ps: |
$ErrorActionPreference = "Stop"
Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1"
Import-Module '..\appveyor-tool.ps1'
install:
- ps: Bootstrap
cache:
- C:\RLibrary
environment:
global:
CRAN: http://cloud.r-project.org
WARNINGS_ARE_ERRORS: 0
matrix:
- R_VERSION: devel
GCC_PATH: mingw_32
- R_VERSION: devel
R_ARCH: x64
GCC_PATH: mingw_64
build_script:
- travis-tool.sh install_deps
test_script:
- travis-tool.sh run_tests
on_failure:
- 7z a failure.zip *.Rcheck\*
- appveyor PushArtifact failure.zip
artifacts:
- path: '*.Rcheck\**\*.log'
name: Logs
- path: '*.Rcheck\**\*.out'
name: Logs
- path: '*.Rcheck\**\*.fail'
name: Logs
- path: '*.Rcheck\**\*.Rout'
name: Logs
- path: '\*_*.tar.gz'
name: Bits
- path: '\*_*.zip'
name: Bits
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.git
.Rproj.user
73 changes: 73 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
name: Bug report
about: Create a report to help us improve RestRserve
title: "[BUG] short bug description "
labels: bug, not-confirmed
assignees: ''

---

### Reporting an Issue
Make use of the *Preview* tab just above!

### Before filing an issue
Please ensure that you:
- searched existing [GitHub issues](https://github.com/rexyai/RestRserve/issues) which can be searched among open and closed ones;
- read the [Contributing](https://github.com/rexyai/RestRserve/blob/master/CONTRIBUTING.md) page for details on preferred reporting and style;

### Describe the bug
A clear and concise description of what the bug is.

### To Reproduce
Please provide a **minimal** example. 'minimal' generally means that example should not use any packages except `RestRserve`. If you are not sure what 'minimal reproducible example' is, please consult [here](https://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example). **We appreciate your time to make a bug report.**

For example:
```r
library("RestRserve")
app = Application$new()
app$add_post(path = "/bug", function(req, res) {
res$set_body(req$body)
})
app$logger$set_log_level("debug")
request = Request$new(
path = "/bug",
method = "POST",
body = '{"key":invalid_json}',
content_type = "application/json"
)

response = app$process_request(request)
cat(response$body)
```

### Expected behavior
A clear and concise description of what you expected to happen.

### Environment information
Please provide output of the `sessionInfo()` command.

For example:
```
R version 3.6.0 (2019-04-26)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS Mojave 10.14.6
Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRlapack.dylib
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] RestRserve_0.2.0
loaded via a namespace (and not attached):
[1] compiler_3.6.0 backports_1.1.5 R6_2.4.1 tools_3.6.0 Rcpp_1.0.3 uuid_0.1-2 checkmate_1.9.4 jsonlite_1.6 mime_0.7
```

### Additional context
Add any other context about the problem here.
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ deploy:
provider: pages
skip_cleanup: true
keep_history: true
committer_from_gh: true
github_token: $GITHUB_TOKEN
local_dir: docs
verbose: true
on:
branch: dev
branch: master
repo: rexyai/RestRserve
76 changes: 43 additions & 33 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,54 @@
# Project goals
## Contributing to RestRserve

- develop high-performance **http interface** which will allow to use R code as a **backend** for web-services
- make creation and deployment of applications simple and robust
- work nice with docker
Interested in contributing? Great! We really welcome bug reports and pull requests that expand and improve the functionality of `RestRserve` from all contributors.

# Development
### Asking Questions

### Design
- [chat on gitter](https://gitter.im/RestRserve/community)
- post a question on Stack Overflow using the [restrserve] tag

- minimal number of dependencies
- `Rserve`
- `R6` (which has 0 zero dependencies)
- `swagger` (swagger-ui assets)
- `yaml` (used for OpenAPI and actually not absolutely necessary)
### Bug reports

### Programming style
- make sure the issue is a bug report and **not** a question or feature request
- one issue for one purpose. Don't report more than one bug or request several features in the same issue
- feel free to add reactions to existing issues that are important to you. We monitor this and it helps us prioritize where to devote our efforts

- we use ` = ` for assignement
- for `R6` classes we use CamelCase
- for the rest we use snake_case
- we don't use `.` inside names
- stick to base R style (except point above)
- fail fast - catch errors at the early stages
### Feedback

Please share your feedback in [this github ticket](https://github.com/rexyai/RestRserve/issues/109)


### Pull Requests (PRs)

- If you are not fixing an open issue please consider to file a new issue before submitting the PR. So, It may save you time to create an issue first and start a discussion to see if your idea would be accepted in principle. If you are going to spend more than a day on the PR, creating an issue first lets other people know you are working on it to save duplicating effort.
- The PR's status must be passing tests before we will start to look at it
- every new feature or bug fix must have one or more new tests in `inst/tests/`
- please create the PR against the `dev` branch
- just one feature/bugfix per PR please. Small changes are easier to review and accept than big sweeping changes. Sometimes big sweeping changes are needed and we just have to discuss those case by case
- github enables us to squash commits together when merging, so you don't have to squash yourself

# Rserve resources
## Feature requests

[Rserve](https://github.com/s-u/Rserve/) is amazing software highly undervalued by the community. Here we keep links to useful resources about Rserve:
Please don't put "feature request" items into GitHub Issues. If there's a new feature that you want to see added to RestRserve, you'll need to write the code yourself - or convince someone else to partner with you to write the code (we love feature submissions!). If you enter a wish list item in GitHub Issues with no code, you can expect it to be marked "invalid" as soon as it's reviewed.

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/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)
1. [Rserve tag](https://stackoverflow.com/questions/tagged/rserve) on StackOverflow
Sometimes, the line between 'bug' and 'feature' is a hard one to draw. Generally, a feature is anything that adds new behavior, while a bug is anything that causes incorrect behavior.

# Drafting a release
If you need some feature but don't have capacity/experience to work on it you can ask fot support at https://rexy.ai/ or [[email protected]](mailto:[email protected]).

```sh
git tag -a v0.1.5 -m "version 0.1.5"
git push origin master --tags
```
## Programming Style

We use [lint](https://github.com/rexyai/RestRserve/blob/master/.lintr) in order to have consistent code style.

In a nutshell:

- do as other files do and do not invent a new style
- use `=` for assignment (not `<-`); see [here](https://github.com/Rdatatable/data.table/pull/3582#discussion_r287075480), [here](https://github.com/Rdatatable/data.table/issues/3590#issuecomment-495776200), [here](https://stackoverflow.com/questions/1741820/what-are-the-differences-between-and-in-r#comment14293879_1742591) and [here](https://twitter.com/kdpsinghlab/status/1044568690346393606)
- for `R6` classes we use CamelCase
- for the rest we use snake_case
- we don't use `.` inside names
- other then that stick to base R style
- Spacing
- 2-space indentation
- No trailing white space
- Add a whitespace between `if` and opening bracket, also before opening curly bracket: `if (condition) {`
- Use `L` suffix for integer; i.e. `x[1L]` not `x[1]` (to save coercion)
4 changes: 1 addition & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ Package: RestRserve
Type: Package
Title: High-Performance Web Server for Building Microservices and App Backends
Description:
Allows to easily create high-performance HTTP API from R functions.
Provides many small convenient tools like flexible logging, JSON encoding,
utils for app deployment and management.
Allows to easily create high-performance full featured HTTP API from R functions.
Version: 0.2.0
Authors@R: c(
person(given = "Dmitriy",
Expand Down
2 changes: 1 addition & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export(AuthBackendBearer)
export(AuthMiddleware)
export(BackendRserve)
export(CORSMiddleware)
export(ContentHandlers)
export(EncodeDecodeMiddleware)
export(HTTPError)
export(Logger)
export(Middleware)
Expand Down
41 changes: 20 additions & 21 deletions R/Application.R
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,11 @@
#' * **`content_type`** :: `character(1)`\cr
#' Default response body content type.
#'
#' * **`HTTPError`** :: `HTTPErrorFactory`\cr
#' * **`HTTPError`** :: `HTTPError`\cr
#' Class which raises HTTP errors. Global [HTTPError] is used by default. In theory
#' user can replace it with his own class (see `RestRserve:::HTTPErrorFactory`). However we believe
#' in the majority of the cases using [HTTPError] will be enough.
#'
#' * **`ContentHandlers`** :: `ContentHandler`\cr
#' Helper to decode request body and encode response body. Global [ContentHandlers] is used by default.
#'
#' * **`endpoints`** :: `named list()`\cr
#' Prints all the registered routes with allowed methods.
#'
Expand Down Expand Up @@ -112,7 +109,7 @@
#'
#' @export
#'
#' @seealso [HTTPError] [ContentHandlers] [Middleware]
#' @seealso [HTTPError] [Middleware]
#' [Request] [Response]
#'
#' @examples
Expand Down Expand Up @@ -190,9 +187,8 @@ Application = R6::R6Class(
logger = NULL,
content_type = NULL,
HTTPError = NULL,
ContentHandlers = NULL,
#------------------------------------------------------------------------
initialize = function(middleware = list(), content_type = "text/plain", ...) {
initialize = function(middleware = list(EncodeDecodeMiddleware$new()), content_type = "text/plain", ...) {
private$backend = BackendRserve$new()
private$routes = new.env(parent = emptyenv())
private$handlers = new.env(parent = emptyenv())
Expand All @@ -204,7 +200,6 @@ Application = R6::R6Class(
private$response = Response$new(content_type = self$content_type)
private$request = Request$new()

self$ContentHandlers = ContentHandlers

checkmate::assert_list(middleware, types = "Middleware", unique = TRUE)
private$middleware = list()
Expand Down Expand Up @@ -299,15 +294,20 @@ Application = R6::R6Class(
private$middleware = append(private$middleware, mw)
return(invisible(self))
},
process_request = function(request = private$request) {
process_request = function(request = NULL) {
# if we use fork-mode then on.exit wlll be called in a fork and
# request_id will never be updated. Hence if the input request is `private$request`
# we need to do it manually
if (is.null(request)) {
request = private$request
request$set_id()
}
on.exit(private$request$reset())

response = private$response
private$eval_with_error_handling({
response$reset()
response$set_content_type(self$content_type)
if (is.null(request$decode))
request$decode = self$ContentHandlers$get_decode(content_type = request$content_type)

# log request
self$logger$debug(
Expand Down Expand Up @@ -382,14 +382,6 @@ Application = R6::R6Class(
mw_status = private$eval_with_error_handling(FUN(request, response))
}

# this means that response wants RestRerveApplication to select
# how to encode automatically
if (!is.function(response$encode)) {
private$eval_with_error_handling({
response$encode = self$ContentHandlers$get_encode(response$content_type)
})
}

# log response
self$logger$debug(
"",
Expand Down Expand Up @@ -536,7 +528,13 @@ Application = R6::R6Class(
},
#------------------------------------------------------------------------
eval_with_error_handling = function(expr) {
x = try_capture_stack(expr)
expanded_traceback = isTRUE(getOption("RestRserve.runtime.traceback", TRUE))
if (expanded_traceback) {
x = try_capture_stack(expr)
} else {
x = try(expr, silent = TRUE)
}

success = TRUE
if (inherits(x, "HTTPErrorRaise")) {
# HTTPError response
Expand All @@ -558,7 +556,8 @@ 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)
private$response$body = self$HTTPError$encode(x$body)
private$response$encode = identity
success = FALSE
}
return(success)
Expand Down
7 changes: 2 additions & 5 deletions R/BackendRserve.R
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ BackendRserve = R6::R6Class(

set_request = function(request, path = "/", parameters_query = NULL, headers = NULL, body = NULL) {
# actually we can skip runtime check as inputs from Rserve are guaranteed
if (isTRUE(getOption('RestRserve_RuntimeAsserts', TRUE))) {
if (isTRUE(getOption('RestRserve.runtime.asserts', TRUE))) {
checkmate::assert_string(path)
checkmate::assert_character(parameters_query, null.ok = TRUE)
checkmate::assert(
Expand Down Expand Up @@ -182,11 +182,8 @@ BackendRserve = R6::R6Class(
}
if (is.null(body)) {
body = raw()
} else {
if (is.function(response$encode)) {
body = response$encode(body)
}
}

if (isTRUE(is.raw(body) || is.character(body))) {
return(list(body, response$content_type, headers, response$status_code))
} else {
Expand Down
8 changes: 6 additions & 2 deletions R/CORSMiddleware.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#' by the browser to allow ross site resource sharing. You can change this easy
#' just by providing `CORSMiddleware` as middleware to the [Application].
#'
#' This class inherits [Middleware].
#' This class inherits from [Middleware].
#'
#' @section Construction:
#'
Expand Down Expand Up @@ -48,7 +48,11 @@
#' app$add_route("/hello", method = "OPTIONS", FUN = function(req, res) {
#' res$set_header("Allow", "POST, OPTIONS")
#' })
#' req = Request$new(path = "/hello", headers = list("Access-Control-Request-Method" = "POST"), method = "OPTIONS")
#' req = Request$new(
#' path = "/hello",
#' headers = list("Access-Control-Request-Method" = "POST"),
#' method = "OPTIONS"
#' )
#' app$process_request(req)
#'
CORSMiddleware = R6::R6Class(
Expand Down
Loading

0 comments on commit 1572567

Please sign in to comment.