1 unstable release
new 0.1.0 | Jan 21, 2025 |
---|
#4 in #caryatid
83 downloads per month
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