dxr_server/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! # `dxr_server`
4//!
5//! This crate provides generic XML-RPC server functionality based on [`dxr`].
6
7use std::collections::HashMap;
8use std::sync::Arc;
9
10use http::header::{CONTENT_LENGTH, CONTENT_TYPE};
11use http::{HeaderMap, HeaderValue, StatusCode};
12
13use dxr::{Error, Fault, FaultResponse, MethodCall, MethodResponse, Value};
14
15#[cfg(feature = "multicall")]
16use dxr::multicall;
17
18mod handler;
19pub use handler::*;
20
21#[cfg(feature = "axum")]
22mod axum_support;
23#[cfg(feature = "axum")]
24pub use self::axum_support::*;
25
26// re-export axum, as it is exposed in the the public API
27#[cfg(feature = "axum")]
28pub use axum;
29
30// re-export the async_trait macro, as it is exposed as part of the public API
31pub use async_trait::async_trait;
32
33/// default server route / path for XML-RPC endpoints
34pub const DEFAULT_SERVER_ROUTE: &str = "/";
35
36/// type alias for atomically reference-counted map of XML-RPC method names and handlers
37pub type HandlerMap = Arc<HashMap<&'static str, Box<dyn Handler>>>;
38
39/// This function can be used in custom XML-RPC endpoints (BYOS - bring your own server).
40///
41/// It takes a map of method handlers ([`HandlerMap`]), the request body, and the request headers
42/// as arguments, and returns a tuple of HTTP status code [`http::StatusCode`], request
43/// response headers, and response body.
44pub async fn server(handlers: HandlerMap, body: &str, headers: HeaderMap) -> (StatusCode, HeaderMap, String) {
45    if headers.get(CONTENT_LENGTH).is_none() {
46        return fault_to_response(411, "Content-Length header missing.");
47    }
48
49    let call: MethodCall = match MethodCall::from_xml(body) {
50        Ok(call) => call,
51        Err(error) => {
52            let e = Error::invalid_data(error.to_string());
53            let f = Fault::from(e);
54            return fault_to_response(f.code(), f.string());
55        },
56    };
57
58    #[cfg(feature = "multicall")]
59    if call.name == "system.multicall" {
60        let calls = match multicall::from_multicall_params(call.params) {
61            Ok(calls) => calls,
62            Err(error) => {
63                let f = Fault::from(error);
64                return fault_to_response(f.code(), f.string());
65            },
66        };
67
68        let mut results = Vec::new();
69
70        for multi in calls {
71            match multi {
72                Ok((name, params)) => {
73                    let Some(handler) = handlers.get(name.as_str()) else {
74                        log_no_handler(&name);
75                        results.push(Err(Fault::new(404, String::from("Unknown method."))));
76                        continue;
77                    };
78
79                    let result = handler.handle(&params, headers.clone()).await;
80                    results.push(result);
81                },
82                Err(error) => {
83                    results.push(Err(Fault::from(error)));
84                },
85            }
86        }
87
88        let value = multicall::into_multicall_response(results);
89
90        return success_to_response(value);
91    }
92
93    let Some(handler) = handlers.get(call.name.as_ref()) else {
94        log_no_handler(&call.name);
95        return fault_to_response(404, "Unknown method.");
96    };
97
98    match handler.handle(&call.params, headers).await {
99        Ok(value) => success_to_response(value),
100        Err(fault) => fault_to_response(fault.code(), fault.string()),
101    }
102}
103
104fn response_headers() -> HeaderMap {
105    let mut headers = HeaderMap::new();
106    headers.insert(CONTENT_TYPE, HeaderValue::from_static("text/xml"));
107    headers
108}
109
110fn success_to_response(value: Value) -> (StatusCode, HeaderMap, String) {
111    let response = MethodResponse { value };
112
113    match response.to_xml() {
114        Ok(success) => (StatusCode::OK, response_headers(), success),
115        Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, response_headers(), error.to_string()),
116    }
117}
118
119fn fault_to_response(code: i32, string: &str) -> (StatusCode, HeaderMap, String) {
120    let fault = Fault::new(code, string.to_owned());
121    let response: FaultResponse = FaultResponse { fault };
122
123    match response.to_xml() {
124        Ok(fault) => (StatusCode::OK, response_headers(), fault),
125        Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, response_headers(), error.to_string()),
126    }
127}
128
129/// Write a debug log on missing rpc handler.
130fn log_no_handler(method: &str) {
131    log::debug!("No handler registered for method: {method}");
132}