#rest #module #message #server #message-bus #request #caryatid

caryatid_module_rest_server

REST server module for Caryatid

1 unstable release

new 0.1.0 Jan 21, 2025

#4 in #caryatid

Download history 83/week @ 2025-01-19

83 downloads per month

Apache-2.0

19KB
246 lines

Standard REST server module for Caryatid

The RESTServer module provides a REST endpoint which turns HTTP requests into message bus requests.

For example, an HTTP request GET /foo/bar is turned into a message bus request on topic rest.get.foo.bar. Notice how the method (GET, POST etc.) is lower-cased, and the path is turned from slashes into dots so that the topic wildcards work. You could for example subscribe to rest.get.foo.# to get all GET requests on foo.

Configuration

The REST server just needs configuration for what address and port to listen on:

[module.rest_server]
address = "0.0.0.0"
port = 4340
topic = "rest"

For safety, by default the server only listens on 127.0.0.1 (localhost). Address 0.0.0.0 as above listens on all interfaces.

The prefix added to the topic can be changed by setting topic. It defaults to rest and can therefore usually be left out.

Messages

The RESTServer module sends a RESTRequest message, and expects a RESTResponse both of which are defined in the common messages in the SDK:

/// REST request message
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct RESTRequest {

    // HTTP method: GET, POST etc.
    pub method: String,

    // URL path: /foo
    pub path: String,

    // Request body (if any)
    pub body: String
}

/// REST response message
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct RESTResponse {

    // HTTP response code
    pub code: u16,

    // Response body (if any)
    pub body: String
}

pub trait GetRESTResponse {
    fn get_rest_response(&self) -> Option<RESTResponse>;
}

It is up to the receiver to handle errors by returning a suitable code (e.g. 40x, 500). The body should be set to "" if no response body is required.

The GetRESTResponse trait is used to check if a message is a RESTResponse and return it if so.

Registration

The RESTServer module needs to be parameterised with the type of an outer message enum which contains RESTRequest and RESTResponse variants, provides a From implementation to promote them, as well as a GetRESTResponse implementation to extract a response from the outer message. For example, your system-wide message enum might be:

use caryatid_sdk::messages::{RESTRequest, RESTResponse, GetRESTResponse};

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum Message {
    None(()),
    // ... other messages ...
    RESTRequest(RESTRequest),   // REST request
    RESTResponse(RESTResponse), // REST response
}

// Casts to platform-wide messages
impl From<RESTRequest> for Message {
    fn from(msg: RESTRequest) -> Self {
        Message::RESTRequest(msg)
    }
}

impl From<RESTResponse> for Message {
    fn from(msg: RESTResponse) -> Self {
        Message::RESTResponse(msg)
    }
}

// Casts from platform-wide messages
impl GetRESTResponse for Message {
    fn get_rest_response(&self) -> Option<RESTResponse> {
        if let Message::RESTResponse(result) = self {
            Some(result.clone())
        } else {
            None
        }
    }
}

Then within your main.rs you would register the RESTServer module into the process like this:

    RESTServer::<Message>::register(&mut process);

See the REST example to see this in action.

A simple REST handler

You can see a simple 'Hello, world!' handler in rest_hello_world.rs. The core of it is this:

       context.message_bus.handle(&topic, |message: Arc<Message>| {
            let response = match message.as_ref() {
                Message::RESTRequest(request) => {
                    info!("REST hello world received {} {}", request.method, request.path);
                    RESTResponse {
                        code: 200,
                        body: "Hello, world!".to_string()
                    }
                },
                _ => {
                    error!("Unexpected message type {:?}", message);
                    RESTResponse {
                        code: 500,
                        body: "Unexpected message in REST request".to_string() }
                }
            };

            future::ready(Arc::new(Message::RESTResponse(response)))
        })?;

Dependencies

~10–17MB
~231K SLoC