Tutorial - Use Streaming in a Rust Serverless Function
This tutorial shows how to create an EDJX serverless function in Rust using the streaming feature. This function receives a streamed HTTP request from the client, and simultaneously sends a streamed HTTP response back to the client.
-
The tutorial uses a Code Sample Template. You can use code templates from the Sample Rust Repository.
-
The tutorial starts with an example Rust function code that prints Welcome to EDJX, and extends to use streams for HTTP request and HTTP response.
-
The instructions in this tutorial are for Linux and macOS.
-
The tutorial uses the EdjCLI and the EDJX VS Code extension to build and deploy the serverless function. It is also possible to build the function manually using
cargo
and then upload the produced WASM file to EDJX through EdjConsole.
The following are the steps involved in building the serverless function:
Prerequisites
-
Install the latest version of Rust.
Rust requires a C linker to be present on the system. On most Linux distributions, you need to have the gcc package installed. -
Open a terminal instance on your system.
-
Navigate to the
$HOME/
or~/
directory. -
Install the Rust WASM target.
rustup target add wasm32-unknown-unknown
-
Install EdjCLI.
-
Install VS Code Extension.
Create the rust-function Function
-
Open a terminal instance on your system.
-
If you are running EdjCLI for the first time on your system, initialize its configuration using the following command:
edjx config init
-
Use the EdjCLI to log into the EDJX Platform.
root@edjx:~ # edjx login Username: <email> Password: ********** Logged in successfully root@edjx:~ #
-
Select the organization to associate with the new serverless application.
edjx config organization -i
If no organizations display, create an organization using the EdjConsole.
-
Create an application
streaming-sample-app
.edjx application create -n streaming-sample-app
-
Set the
streaming-sample-app
application to be associated with the new function.edjx config application -i
-
Create a new serverless function named
rust-function
inside the application and configure the parameters.The following example creates a new local
rust-function-code
directory and initializes it with a simple Rust example function template.edjx function init rust-function-code Function Name: rust-function ✔ WASM ✔ Rust ✔ HTTP ✔ 30 ✔ 64 Setting up project with starter files...... Project successfully initialized in rust-function-code
(Optional) You can view the details of the functions created using the following command:
edjx function list Total functions: 1 Name Function ID Trigger Language Compute (GiB-Sec) Network (B) Requests Organization Created By Created On Last Updated rust-function f4998cb6-3011-465f-a25b-59b29c07d9d5 HTTP Rust 0 0 0 CompletelyDifferentOrganization David Sebek 2022-11-09 17:03:28.279 -0500 EST 2022-11-09 17:03:29.323 -0500 EST
-
Change to the function directory.
cd rust-function-code
-
Open the directory in VS Code.
code .
Or
Open VS Code and navigate to File > Open Folder and select the
rust-function-code
directory. The directory and files of your function display. -
Open the serverless_function.rs file.
The function contains the following code:
use edjx::{info, HttpRequest, HttpResponse, StatusCode}; pub fn serverless(_req: HttpRequest) -> HttpResponse { info!("Inside example function"); HttpResponse::from("Welcome to EDJX".to_string()) .set_status(StatusCode::OK) .set_header("Server".parse().unwrap(), "EDJX".parse().unwrap()) }
This function receives an HTTP request and sends an HTTP response that contains a
Welcome to EDJX
string in the response body. -
Modify the function to send the response as
Welcome to EDJX! Streamed data will be echoed back.
HttpResponse::from("Welcome to EDJX! Streamed data will be echoed back.".to_string()) .set_status(StatusCode::OK) .set_header("Server".parse().unwrap(), "EDJX".parse().unwrap())
-
Modify the
lib.rs
file to allow a streamed HTTP response from the serverless function.mod serverless_function; use edjx::{error, HttpRequest, HttpResponse, StatusCode}; // Response stream version #[no_mangle] pub fn init() { let req = match HttpRequest::from_client() { Ok(val) => val, Err(e) => { error!("{}", e.to_string().as_str()); HttpResponse::new() .set_status(StatusCode::BAD_REQUEST) .send() .unwrap(); return; } }; crate::serverless_function::serverless_streaming(req); }
If the function is sending HTTP response using streams, you cannot use the standard lib.rs
. Use template lib.rs instead, and optionally customize the template. -
Update the serverless function signature in
serverless_function.rs
.pub fn serverless_streaming(mut req: HttpRequest) {
-
In the serverless function, open a read stream from the incoming HTTP request using
req.get_read_stream()
.use edjx::{info, HttpRequest, HttpResponse, StatusCode}; pub fn serverless_streaming(mut req: HttpRequest) { info!("Inside example function"); // Open a read stream from the request let read_stream = match req.get_read_stream() { Ok(stream) => stream, Err(err) => { error!("Could not open read stream: {}", err.to_string()); return; } }; HttpResponse::from("Welcome to EDJX! Streamed data will be echoed back.".to_string()) .set_status(StatusCode::OK) .set_header("Server".parse().unwrap(), "EDJX".parse().unwrap())
-
Open a write stream for the response, and write the message for the client to the stream. Add
use::edjx::error
to use error log messages.use edjx::{error, info, HttpRequest, HttpResponse, StatusCode}; pub fn serverless_streaming(mut req: HttpRequest) { info!("Inside example function"); // Open a read stream from the request let read_stream = match req.get_read_stream() { Ok(stream) => stream, Err(err) => { error!("Could not open read stream: {}", err.to_string()); return; } }; // Prepare a response let mut res = HttpResponse::new() .set_status(StatusCode::OK) .set_header("Serverless".parse().unwrap(), "EDJX".parse().unwrap()); // Open a write stream for the response let mut write_stream = match res.send_streaming() { Ok(stream) => stream, Err(err) => { error!("Could not open write stream: {}", err); return; } }; // Stream the message if let Err(err) = write_stream.write_chunk_text( "Welcome to EDJX! Streamed data will be echoed back.\r\n" ) { error!("Error when writing a chunk: {}", err); return; } }
-
Add a loop that reads incoming chunks of data from the read stream and immediately sends any received chunk to the write stream. Add the code at the end of the function.
} // Read all chunks from the read stream and send them to the write stream let mut count = 0; while let Some(read_result) = read_stream.read_chunk() { match read_result { Ok(bytes) => { // A chunk of data was received. Send it back to the client. if let Err(err) = write_stream.write_chunk_binary(bytes) { error!("Error when writing a chunk: {}", err); return; } count += 1; } Err(err) => { error!("Error when reading a chunk: {}", err); return; } } } }
Alternatively, you can use the
pipe_to()
function, but the chunk count number will not be available.} // Read all chunks from the read stream and send them to the write stream if let Err(err) = read_stream.pipe_to(&mut write_stream) { error!("Error when piping streams: {}", err); return; } }
-
After receiving all data, send a text summary with the number of chunks that were received.
} // Write some statistics at the end if let Err(err) = write_stream.write_chunk_text( format!("\r\nTransmitted {} chunks.\r\n", count).as_str() ) { error!("Error when writing the summary info: {}", err); return; } }
-
Close both the read and write streams.Add
use::edjx::BaseStream
at the beginning of the file to be able to use theclose()
methods on streams.} // Close the write stream if let Err(err) = write_stream.close() { error!("Error when closing the write stream: {}", err); } // Close the read stream if let Err(err) = read_stream.close() { error!("Error when closing the read stream: {}", err); } }
Build the rust-function Function
-
With VS Code open, select the
edjconfig.yaml
file. -
Click Build.
To build using the EdjCLI, issue the edjx function build command while in the function directory. ~/rust-function-code$ edjx function build
The build process executes.
To build the function into a WASM file manually, use cargo
.~/rust-function-code$ cargo build --release --target=wasm32-unknown-unknown
-
When the process is successfully completed, a WASM file with the serverless function is created. The path to the WASM file is added to the
edjconfig.yaml
file as anartifact_path
.By default, the WASM file is built and saved under the
<function_directory>/target/wasm32-unknown-unknown/release
directory.
Deploy the rust-function Function
-
With VS Code open, click Deploy.
To deploy the function using the EdjCLI, issue the edjx function deploy command while in the function directory.
~/rust-function-code$ edjx function deploy
To deploy the function using EdjConsole, take the WASM file and deploy it via the EdjConsole web interface. -
Using the EdjCLI, select the
streaming-sample-app
application you created.edjx config application -i
-
Get information about the
rust-function
function that is inside the application.This information can also be accessed in the EdjConsole web interface. edjx function read rust-function Name rust-function Function ID f4998cb6-3011-465f-a25b-59b29c07d9d5 Trigger HTTP Language Rust Runtime WASM Timeout 60s Memory Allocated 32.0 MiB Compute (GiB-Sec) 0 GiB-SEC Network (B) 0 B Requests 0 Organization CompletelyDifferentOrganization Created By David Sebek Created At 2022-11-09 17:03:28.279 -0500 EST Last Updated 2022-11-09 17:03:29.323 -0500 EST Execution URL https://34e40e08-2e7e-47fb-aed5-b77123115c88.fn.edjx.net/rust-function
-
Copy the Execution URL. The running function can be accessed via this URL.
-
Test the function with the following
curl
command.curl -L 'https://c532061b-da57-424d-863b-b741a9f4cc58.fn.edjx.net/rust-function' -s -N -T.
In this case,
curl
accepts data from user input and sends lines of user input as individual data chunks. The serverless function receives these chunks on its read stream. The function then sends the chunk to its write stream, andcurl
displays the chunk.The example run below shows both the user input to
curl
and the function response at the same time. One line of user input is immediately followed by an identical line of function response. It demonstrates that the data is being streamed both ways between a client and the serverless function.david@EDJX-MBP ~ % curl -L 'https://c532061b-da57-424d-863b-b741a9f4cc58.fn.edjx.net/rust-function' -s -N -T. Welcome to EDJX! Streamed data will be echoed back. This function This function receives receives chunks of data chunks of data and sends them back and sends them back immediately. immediately. ^D Transmitted 5 chunks.
You can also upload data to the EDJX storage using streams. Refer to the code sample.