Using Drain framework and Rust to build web applications
Have you ever forgotten to handle a specific thing, resulting in your app being vulnerable or incomplete? Or maybe you're tired of recompiling your whole backend to change one thing on a page, without using scripted programming languages? For both of those cases, Rust and Drain are an excellent solution. From now on, I assume, that you've previously worked on a web app in some other environment and learned Rust. Let's begin, shall we? Table of contents Initial preparation Installing Drain stack Configuration Hello, World! Functionalities 1. Initial preparation Begin with choosing your preferred IDE and installing Rust. Personally, I'd go with VSCode (or VSCodium) + rust-analyzer or RustRover. For Rust installation, refer to https://www.rust-lang.org/tools/install (RustRover might make this fully automatic). 2. Installing Drain stack Once you have your environment set up properly, make sure you have OpenSSL installed on your machine. On Windows, OpenSSL can be troublesome, so I really encourage you to use something Unix-based and POSIX compliant, to some extent (if you can't bare to run anything other than Windows, you probably need to use this). If you do, go ahead and install Drain. You can either clone its repository (up to the most recent commit) and use cargo run inside its root, or run cargo install drain_server (source release from crates.io). When you encounter compile-time errors related to OpenSSL, try setting the OPENSSL_DIR environment variable to where it should be installed on your system. 3. Configuration Drain can be configured via a config file in JSON format fetched in the runtime. Full list of fields available in the config is inside the README on GitHub and crates.io along with the description of each of them. Keep in mind, that some of the fields are optional, ergo they can be omitted. At this point, you can skip endpoints and endpoints_library fields, as we will discuss them later. Example config can be found here; if you want, you can copy-paste it and change according to your preferences. Once you're done with config.json, set the path to it in DRAIN_CONFIG environment variable and run the server to test if it's working. If it isn't, error messages will likely tell you, what's wrong. From this point forward, I'll assume your config is all set up and working except for endpoints and endpoints_library fields. 4. Hello, World! Now, we can proceed with running a "Hello, World!"-ish example to test this dynamic page functionality. Start with git clone https://github.com/fooooter/drain_page_template. This template is a working example of a dynamic webpage, so it can be already built and used. We will use it to make things simple for the sake of the tutorial + it also has two error pages predefined, so it will be easier to change them later on. use drain_common::RequestData::*; use drain_macros::{drain_endpoint, set_header}; #[drain_endpoint("index")] pub fn index() { let content: Vec = Vec::from(format!(r#" Index Hello, world! {} request was sent. "#, match request_data { Get(_) => "GET", Post{..} => "POST", Head(_) => "HEAD" })); set_header!("Content-Type", "text/html; charset=utf-8"); Some(content) } This is our index page! It displays the "Hello, World!" and tells, what kind of HTTP request was sent to the server. Go ahead and run cargo build! It will produce a shared object file (.so on Linux and .dll on Windows) in target/debug. Copy it to the directory specified in config's server_root field and set endpoints_library to its path relative to server_root ("libdynamic_pages.so" or "dynamic_pages.dll" most likely). Make sure "index" is specified inside the endpoints field. Restart the server if it's already running, open your favorite web browser and type "localhost" or whatever address and port you've bound the server to. You should see the following: 5. Functionalities Let's now talk in-depth about how to use Drain to create endpoints and use its functionalities. drain_macros and drain_common crates drain_macros and drain_common are two out of three main components, that make for Drain's environment. Those crates are providing tools for an efficient endpoint creation. Make sure they're included in Cargo.toml as dependencies. Also, you can shorten the path to each of them or to the content inside them. #[drain_endpoint()] (drain_macros) To mark a function in the library as an endpoint, define it using this macro. It takes URL path as an argument, so if you want it to execute each time the client requests "https://example.com/api/get_data", specify #[drain_endpoint("api/get_data")] (#[drain_endpoint(api/get_data)] should also work). Once again, don't forget to put "api/get_data" inside endpoints in config.json. DISCLAIMER: Don't put

Have you ever forgotten to handle a specific thing, resulting in your app being vulnerable or incomplete? Or maybe you're tired of recompiling your whole backend to change one thing on a page, without using scripted programming languages? For both of those cases, Rust and Drain are an excellent solution. From now on, I assume, that you've previously worked on a web app in some other environment and learned Rust.
Let's begin, shall we?
Table of contents
- Initial preparation
- Installing Drain stack
- Configuration
- Hello, World!
- Functionalities
1. Initial preparation
Begin with choosing your preferred IDE and installing Rust. Personally, I'd go with VSCode (or VSCodium) + rust-analyzer or RustRover.
For Rust installation, refer to https://www.rust-lang.org/tools/install (RustRover might make this fully automatic).
2. Installing Drain stack
Once you have your environment set up properly, make sure you have OpenSSL installed on your machine.
On Windows, OpenSSL can be troublesome, so I really encourage you to use something Unix-based and POSIX compliant, to some extent (if you can't bare to run anything other than Windows, you probably need to use this).
If you do, go ahead and install Drain. You can either clone its repository (up to the most recent commit) and use cargo run
inside its root, or run cargo install drain_server
(source release from crates.io).
When you encounter compile-time errors related to OpenSSL, try setting the OPENSSL_DIR
environment variable to where it should be installed on your system.
3. Configuration
Drain can be configured via a config file in JSON format fetched in the runtime. Full list of fields available in the config is inside the README on GitHub and crates.io along with the description of each of them.
Keep in mind, that some of the fields are optional, ergo they can be omitted.
At this point, you can skip endpoints
and endpoints_library
fields, as we will discuss them later.
Example config can be found here; if you want, you can copy-paste it and change according to your preferences.
Once you're done with config.json, set the path to it in DRAIN_CONFIG
environment variable and run the server to test if it's working. If it isn't, error messages will likely tell you, what's wrong.
From this point forward, I'll assume your config is all set up and working except for endpoints
and endpoints_library
fields.
4. Hello, World!
Now, we can proceed with running a "Hello, World!"-ish example to test this dynamic page functionality.
Start with git clone https://github.com/fooooter/drain_page_template
. This template is a working example of a dynamic webpage, so it can be already built and used.
We will use it to make things simple for the sake of the tutorial + it also has two error pages predefined, so it will be easier to change them later on.
use drain_common::RequestData::*;
use drain_macros::{drain_endpoint, set_header};
#[drain_endpoint("index")]
pub fn index() {
let content: Vec<u8> = Vec::from(format!(r#"
Index
Hello, world! {} request was sent.