Tutorial - Use Streaming in a C++ Serverless Function
This tutorial shows how to create an EDJX serverless function in C++ 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 C++ Sample Repository.
-
The tutorial starts with a simple Welcome to EDJX example C++ function code and extends it to use streams for HTTP request and HTTP response.
-
EdjCLI is used for building and deploying the function. Alternatively, you can build and deploy the function using EdjConsole or VS Code.
The instructions in this tutorial are for Linux. Alternatively, you can build this function using macOS or Windows (install the Windows Subsystem for Linux (WSL) and follow the Linux instructions). |
See the Using C++ to Create an EDJX Serverless Function topic for detailed installation options.
The following are the steps involved in building the serverless function:
Prerequisites
-
Install EdjCLI.
-
Install VS Code Extension.
SDKs
Install the EDJX C++ SDK and the WASI C++ SDK in the user’s home directory.
-
Ensure that the following tools are installed on your system:
make
(GNU Make
),git
,wget
, andtar
. -
Create an
edjx
directory in the user’s home directory:mkdir -p ~/edjx
See the release page for all EDJX C++ SDK downloads. Each build of the EDJX C++ SDK targets a specific WASI SDK. For example, wasi-12 releases of the EDJX C++ SDK should be used with WASI SDK 12.
|
Install the EDJX C++ SDK
Install the EDJX C++ SDK using one of the following options:
-
Option 1:
-
Set the EDJX SDK and WASI SDK variables with the specified version.
EDJX_SDK_VERSION=v22.12.1 WASI_SDK_VERSION=12
-
Set the
$INSTALLATION_DIR
variable to user’s home directory and change the directory.INSTALLATION_DIR="${HOME}/edjx" cd "$INSTALLATION_DIR"
-
Download the EDJX C++ SDK from the edjx-cpp-sdk GitHub repository.
wget "https://github.com/edjx/edjx-cpp-sdk/releases/download/${EDJX_SDK_VERSION}/edjx-cpp-sdk-${EDJX_SDK_VERSION}-wasi-${WASI_SDK_VERSION}.tar.gz"
-
Extract the EDJX C++ SDK from the archive.
tar -xvf "edjx-cpp-sdk-${EDJX_SDK_VERSION}-wasi-${WASI_SDK_VERSION}.tar.gz"
-
-
Option 2:
-
Set the
INSTALLATION_DIR
variable to the user’s home directory.INSTALLATION_DIR="${HOME}/edjx"
-
Get the latest EDJX C++ SDK by cloning the edjx-cpp-sdk repository.
git clone --depth 1 "https://github.com/edjx/edjx-cpp-sdk.git" "${INSTALLATION_DIR}/edjx-cpp-sdk"
With the --depth 1
argument, only the latest EDJX C++ SDK build for the specific WASI SDK version is downloaded. See theWASI_SDK_VERSION
file for the WASI SDK version against which the EDJX C++ SDK in the repository was compiled.The path to the EDJX C++ SDK is expected to be ~/edjx/edjx-cpp-sdk-v22.12.1-wasi-12.0/include
and~/edjx/edjx-cpp-sdk-v22.12.1-wasi-12.0/lib
.
-
Install the WASI C++ SDK
-
Set the WASI SDK variables with the specified version.
WASI_SDK_VERSION_FULL=12.0 WASI_SDK_VERSION="${WASI_SDK_VERSION_FULL%%.*}"
-
Set the variable to
linux
on Linux ormacos
on macOS.WASI_SDK_OS=linux
-
Set the
$INSTALLATION_DIR
variable to the user’s home directory and change the directory.INSTALLATION_DIR="${HOME}/edjx" cd "$INSTALLATION_DIR"
-
Download the WASI C++ SDK for Linux or MacOS from the WASI SDK Releases GitHub repository. This repository provides the necessary C and C++ standard library functions required to build C++ serverless functions.
wget "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/wasi-sdk-${WASI_SDK_VERSION_FULL}-${WASI_SDK_OS}.tar.gz"
The EDJX C++ SDK is compiled against a specific WASI SDK version. For example, edjx-cpp-sdk-vX.Y.Z-wasi-12.tar.gz
must be used with WASI SDK version 12. -
Extract the WASI SDK from the archive.
tar -xf "wasi-sdk-${WASI_SDK_VERSION_FULL}-${WASI_SDK_OS}.tar.gz"
(optional) You can delete the
.tar
file.Ensure that the bundled compiler is in the ${HOME}/edjx/wasi-sdk-12.0/bin/clang++
directory.
Create the cpp-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 app.
edjx config organization -i
If no organizations display, create one 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
cpp-function
inside the application and configure the parameters.The following example creates a new local
cpp-function-code
directory and initializes it with a simple C++ example function template.david@EDJX-MBP ~ % edjx function init cpp-function-code Function Name: cpp-function ✔ WASM ✔ C++ ✔ HTTP ✔ 60 ✔ 32 Setting up project with starter files... Project successfully initialized at cpp-function-code
(Optional) You can view the details of the functions created using the following command:
edjx function list Total functions: 2 Name Function ID Trigger Language Compute (GiB-Sec) Network (B) Requests Organization Created By Created On Last Updated cpp-function 76e9ec4b-bf65-45f5-9340-82be003436c4 HTTP C++ 0 0 0 CompletelyDifferentOrganization David Sebek 2022-11-09 17:22:02.167 -0500 EST 2022-11-09 17:22:02.772 -0500 EST 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 cpp-function-code
-
Open the directory in VS Code.
code .
Alternatively, open VS Code and navigate to File > Open Folder, and select the
cpp-function-code
directory. The directory and files of your function display. -
Open the serverless_function.cpp file.
The function contains the following code.
#include <edjx/logger.hpp> #include <edjx/request.hpp> #include <edjx/response.hpp> #include <edjx/http.hpp> using edjx::logger::info; using edjx::request::HttpRequest; using edjx::response::HttpResponse; using edjx::http::HttpStatusCode; static const HttpStatusCode HTTP_STATUS_OK = 200; HttpResponse serverless(const HttpRequest & req) { info("Inside example function"); return HttpResponse("Welcome to EDJX") .set_status(HTTP_STATUS_OK) .set_header("Server", "EDJX"); }
When this function receives an HTTP request, it responds with an HTTP response that contains a
Welcome to EDJX
string in the response body. -
Modify the function to send this message instead:
Welcome to EDJX! Streamed data will be echoed back.
return HttpResponse("Welcome to EDJX! Streamed data will be echoed back.") .set_status(HTTP_STATUS_OK) .set_header("Server", "EDJX");
-
Modify the
lib.cpp
file to allow a streamed HTTP response from the serverless function.If your function is sending HTTP response using streams, you cannot use standard lib.cpp
. Use the template lib.cpp instead, and you can optionally customize the template.#include <cstdlib> #include <cstdint> #include <edjx/logger.hpp> #include <edjx/request.hpp> #include <edjx/response.hpp> #include <edjx/error.hpp> #include <edjx/http.hpp> using edjx::logger::error; using edjx::request::HttpRequest; using edjx::response::HttpResponse; using edjx::error::HttpError; using edjx::http::HttpStatusCode; static const HttpStatusCode HTTP_STATUS_BAD_REQUEST = 400; extern bool serverless_streaming(HttpRequest & req); int main(void) { HttpRequest req; HttpError err = HttpRequest::from_client(req); if (err != HttpError::Success) { error(edjx::error::to_string(err)); HttpResponse().set_status(HTTP_STATUS_BAD_REQUEST).send(); return EXIT_FAILURE; } if (!serverless_streaming(req)) { error("Serverless streaming function returned an error"); return EXIT_FAILURE; } return EXIT_SUCCESS; } // // edjExecutor calls init() instead of _start() // (constructors of global objects are not called if _start() is not executed) // extern "C" void _start(void); __attribute__((export_name("init"))) extern "C" void init(void) { _start(); }
-
Update the serverless function signature in
serverless_function.cpp
using the following code:#include <edjx/stream.hpp> using edjx::logger::error; using edjx::error::HttpError; using edjx::stream::ReadStream;
bool serverless_streaming(HttpRequest & req) {
-
In the serverless function, open a read stream from the incoming HTTP request using
req.open_read_stream()
.#include <edjx/logger.hpp> #include <edjx/request.hpp> #include <edjx/response.hpp> #include <edjx/http.hpp> #include <edjx/stream.hpp> using edjx::logger::info; using edjx::request::HttpRequest; using edjx::response::HttpResponse; using edjx::http::HttpStatusCode; using edjx::logger::error; using edjx::error::HttpError; using edjx::stream::ReadStream; static const HttpStatusCode HTTP_STATUS_OK = 200; bool serverless_streaming(HttpRequest & req) { info("Inside example function"); // Open a read stream from the request ReadStream read_stream; HttpError http_err = req.open_read_stream(read_stream); if (http_err != HttpError::Success) { error("Could not open read stream: " + to_string(http_err)); return false; } return HttpResponse("Welcome to EDJX! Streamed data will be echoed back.") .set_status(HTTP_STATUS_OK) .set_header("Server", "EDJX"); }
-
Open a write stream for the response, and write the message for the client to the stream. Add the following to use error log messages:
using edjx::error::StreamError; using edjx::stream::WriteStream;
#include <edjx/logger.hpp> #include <edjx/request.hpp> #include <edjx/response.hpp> #include <edjx/http.hpp> #include <edjx/stream.hpp> using edjx::logger::info; using edjx::request::HttpRequest; using edjx::response::HttpResponse; using edjx::http::HttpStatusCode; using edjx::logger::error; using edjx::error::HttpError; using edjx::stream::ReadStream; using edjx::error::StreamError; using edjx::stream::WriteStream; static const HttpStatusCode HTTP_STATUS_OK = 200; bool serverless_streaming(HttpRequest & req) { info("Inside example function"); // Open a read stream from the request ReadStream read_stream; HttpError http_err = req.open_read_stream(read_stream); if (http_err != HttpError::Success) { error("Could not open read stream: " + to_string(http_err)); return false; } // Prepare a response HttpResponse res; res.set_status(HTTP_STATUS_OK); res.set_header("Serverless", "EDJX"); // Open a write stream for the response WriteStream write_stream; http_err = res.send_streaming(write_stream); if (http_err != HttpError::Success) { error("Could not open write stream: " + to_string(http_err)); return false; } // Stream the message StreamError write_err = write_stream.write_chunk( "Welcome to EDJX! Streamed data will be echoed back.\r\n" ); if (write_err != StreamError::Success) { error("Error when writing a chunk: " + to_string(write_err)); return false; } // Return true if the serverless_streaming function finished successfully return true; }
-
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 int count = 0; std::vector<uint8_t> chunk; StreamError read_err; while ((read_err = read_stream.read_chunk(chunk)) == StreamError::Success) { count++; write_err = write_stream.write_chunk(chunk); if (write_err != StreamError::Success) { error("Error when writing chunk: " + to_string(write_err)); return false; } } if (read_err != StreamError::EndOfStream) { error("Error when reading chunk: " + to_string(read_err)); return false; } // Return true if the serverless_streaming function finished successfully return true; }
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 StreamError stream_err = read_stream.pipe_to(write_stream); if (stream_err != StreamError::Success) { error("Error when piping streams: " + to_string(stream_err)); return false; } // Return true if the serverless_streaming function finished successfully return true; }
-
After receiving all data, send a text summary with the number of chunks that were received.
} // Write some statistics at the end write_err = write_stream.write_chunk( "\r\nTransmitted " + std::to_string(count) + " chunks.\r\n" ); if (write_err != StreamError::Success) { error("Error when writing the summary info: " + to_string(write_err)); return false; } // Return true if the serverless_streaming function finished successfully return true;
-
Close both the read and write streams.
} bool close_success = true; // Close the write stream StreamError close_err = write_stream.close(); if (close_err != StreamError::Success) { error("Error when closing the write stream: " + to_string(close_err)); close_success = false; } // Close the read stream close_err = read_stream.close(); if (close_err != StreamError::Success) { error("Error when closing the read stream: " + to_string(close_err)); close_success = false; } // Return true if the serverless_streaming function finished successfully return true; }
-
Save the file.
Build the cpp-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. ~/cpp-function-code$ edjx function build
The build process executes.
To build the function into a WASM file manually, you can use make all
.~/cpp-function-code$ make all 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>/bin
directory.
Deploy the cpp-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.
~/cpp-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
cpp-function
function that is inside the application.This information can also be accessed in the EdjConsole web interface. david@EDJX-MBP cpp-function-code % edjx function read cpp-function Name cpp-function Function ID 76e9ec4b-bf65-45f5-9340-82be003436c4 Trigger HTTP Language C++ 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:22:02.167 -0500 EST Last Updated 2022-11-09 17:22:02.772 -0500 EST Execution URL https://34e40e08-2e7e-47fb-aed5-b77123115c88.fn.edjx.net/cpp-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/cpp-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, and curl 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/cpp-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.