Skip to content

Commit

Permalink
docs
Browse files Browse the repository at this point in the history
levkk committed Oct 22, 2024
1 parent 9641853 commit 27c0c8a
Showing 6 changed files with 260 additions and 11 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ Rwf is a comprehensive framework for building web applications in Rust. Written

## Documentation

:blue_book: The documentation (work in progress) **[is available here](https://levkk.github.io/rwf/)**.
:blue_book: The documentation **[is available here](https://levkk.github.io/rwf/)**.

## Features overview

@@ -22,6 +22,7 @@ Rwf is a comprehensive framework for building web applications in Rust. Written
- :heavy_check_mark: Environment-specific configuration
- :heavy_check_mark: Logging and metrics
- :heavy_check_mark: [CLI](rwf-cli)
- :heavy_check_mark: WSGI server for [migrating](examples/django) from Django/Flask apps

## Quick start

71 changes: 71 additions & 0 deletions docs/docs/migrating-from-python.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Migrating from Python

Rwf is written in Rust, so if you have an existing application you want to migrate to Rust, you have options. Rwf comes with its own [WSGI](https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface) server, so you can run your existing Django or Flask apps without modifications, side by side with Rwf [controllers](../controllers/).

## Using WSGI

!!! note
Rwf WSGI server is still experimental and is not as advanced as the popular [uWSGI](https://uwsgi-docs.readthedocs.io/en/latest/) project.

Adding a WSGI application to your Rwf server is pretty straight forward. First, make sure that the Python project is in your `PYTHONPATH`, for example:

```bash
export PYTHONPATH=/path/to/python/project
```

Rwf will load your Python app directly into its own memory (using [pyo3](https://docs.rs/pyo3)), so it needs to be able to find it when importing your app modules.

### Django

Django applications come with a WSGI interface, which Rwf can use directly. Usually, the interface is located in its own file, e.g. `project/wsgi.py`. The `WsgiController` accepts the Python module as an argument on initialization, in this case, `project.wsgi`.

Once initialized, the controller can be added into the server, and mounted on the `/*` (root, wildcard) path. This ensures that all requests are handled by Django:

```rust
use rwf::prelude::*;
use rwf::http::Server;
use rwf::controller::WsgiController;

#[tokio::main]
async fn main() {
Server::vec![
WsgiController::new("project.wsgi")
.wildcard("/"),
]
.launch("0.0.0.0:8000")
.await
.unwrap();
}
```

### Python dependencies

Your application most likely has other dependencies, e.g. `django`, or `Flask` packages, and many more. To make sure they work when loaded into Rwf, either create and activate a virtual environment before launching the server, or install those packages globally (e.g., when deploying with Docker).

## Moving traffic to Rust

As you rewrite your endpoints to use Rwf and Rust, you can move traffic one route at time without disrupting your users. For example, if you are ready to move the route `/users` to Rust, add the controller for it into the server:

```rust
/// Your new "/users" controller
/// written in Rust.
use crate::controllers::Users;

#[tokio::main]
async fn main() {
Server::new(vec![
WsgiController::new("project.wsgi")
.wildcard("/"),
route!("/users" => Users),
])
.launch("0.0.0.0:8000")
.await
.unwrap()
}
```

Rwf routing algorithm will match requests to `/users` to the `Users` controller instead of sending it to WSGI, because the `Users` controller path is more specific and has higher priority than wildcard routes.

## Learn more

- [examples/django](https://github.com/levkk/rwf/tree/main/examples/django)
163 changes: 162 additions & 1 deletion examples/django/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,162 @@
venv
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
3 changes: 3 additions & 0 deletions rwf/src/controller/error.rs
Original file line number Diff line number Diff line change
@@ -35,6 +35,9 @@ pub enum Error {

#[error("session is not set")]
SessionMissingError,

#[error("timeout exceeded")]
TimeoutError(#[from] tokio::time::error::Elapsed),
}

impl Error {
28 changes: 19 additions & 9 deletions rwf/src/controller/wsgi.rs
Original file line number Diff line number Diff line change
@@ -3,16 +3,24 @@ use crate::http::{wsgi::WsgiRequest, Request, Response};

use async_trait::async_trait;
use pyo3::prelude::*;

use tokio::time::{timeout, Duration};

pub struct WsgiController {
path: &'static str,
timeout: Duration,
}

impl WsgiController {
pub fn new(path: &'static str) -> Self {
WsgiController { path }
WsgiController {
path,
timeout: Duration::from_secs(60),
}
}

pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
}

@@ -22,8 +30,10 @@ impl Controller for WsgiController {
let request = WsgiRequest::from_request(request)?;
let path = self.path;

let response = timeout(
Duration::from_secs(5),
// TODO: spawn blocking tasks cannot be aborted.
// This only aborts waiting for the result to be returned.
match timeout(
self.timeout,
tokio::task::spawn_blocking(move || {
let response = Python::with_gil(|py| {
// Import is cached.
@@ -36,10 +46,10 @@ impl Controller for WsgiController {
response
}),
)
.await
.unwrap()
.unwrap();

Ok(response.to_response()?)
.await?
{
Ok(response) => Ok(response.to_response()?),
Err(e) => Ok(Response::internal_error(e)),
}
}
}
3 changes: 3 additions & 0 deletions rwf/src/http/error.rs
Original file line number Diff line number Diff line change
@@ -28,6 +28,9 @@ pub enum Error {

#[error("parameter is missing")]
MissingParameter,

#[error("timeout exceeded")]
Timeout(#[from] tokio::time::error::Elapsed),
}

impl Error {

0 comments on commit 27c0c8a

Please sign in to comment.