Skip to content

Question: Redis as a HTTP Server #411

@aminroosta

Description

@aminroosta

I'm planning to write the equivalent of webdis, but implemented in rust as a dynamic module.

Here is a tiny example that seems to work just fine. I'd love to hear your thoughts on the drawbacks of this approach.

Expand to see the Code:
use axum::{routing::get, Router};
use lazy_static::lazy_static;
use redis_module::{redis_module, Context, RedisString, Status, ThreadSafeContext};
use tokio::sync::oneshot;
use std::{
    sync::Mutex,
    thread::{self, JoinHandle},
};

lazy_static! {
    static ref SERVER_THREAD: Mutex<Option<ServerHandle>> = Mutex::new(None);
}

struct ServerHandle {
    handle: JoinHandle<()>,
    shutdown_tx: oneshot::Sender<()>,
}

fn init(_ctx: &Context, _args: &[RedisString]) -> Status {
    let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();

    let handle = thread::spawn(move || {
        let runtime = tokio::runtime::Runtime::new().unwrap();
        runtime.block_on(async {
            let app = Router::new().route("/", get(root));
            let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();

            axum::serve(listener, app)
                .with_graceful_shutdown(async {
                    shutdown_rx.await.unwrap();
                })
                .await
                .unwrap();
        });
    });

    let mut server_thread = SERVER_THREAD.lock().unwrap();
    *server_thread = Some(ServerHandle {
        handle,
        shutdown_tx,
    });

    Status::Ok
}

fn deinit(_ctx: &Context) -> Status {
    let mut server_thread = SERVER_THREAD.lock().unwrap();

    if let Some(ServerHandle {
        shutdown_tx,
        handle,
    }) = server_thread.take()
    {
        let _ = shutdown_tx.send(());
        handle.join().unwrap();
    }

    Status::Ok
}

async fn root() -> String {
    let ctx = ThreadSafeContext::new();
    let version = ctx.lock().get_redis_version().unwrap();

    format!("{}.{}.{}", version.major, version.minor, version.patch)
}

//////////////////////////////////////////////////////

redis_module! {
    name: "hello",
    version: 1,
    allocator: (redis_module::alloc::RedisAlloc, redis_module::alloc::RedisAlloc),
    data_types: [],
    init: init,
    deinit: deinit,
    commands: [
    ],
}

I'm able to LOAD and UNLOAD the module.

127.0.0.1:6379> MODULE LOAD /Users/user/axredis/target/release/libaxredis.dylib
OK
127.0.0.1:6379> MODULE UNLOAD hello
OK

While the module is loaded, I can get the redis/valkey version over HTTP.

$ curl 127.0.0.1:3000
8.0.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions