From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <pdm-devel-bounces@lists.proxmox.com>
Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68])
	by lore.proxmox.com (Postfix) with ESMTPS id CCBDC1FF168
	for <inbox@lore.proxmox.com>; Tue,  4 Mar 2025 13:05:50 +0100 (CET)
Received: from firstgate.proxmox.com (localhost [127.0.0.1])
	by firstgate.proxmox.com (Proxmox) with ESMTP id 787D81DAC1;
	Tue,  4 Mar 2025 13:05:47 +0100 (CET)
From: Shannon Sterz <s.sterz@proxmox.com>
To: pdm-devel@lists.proxmox.com
Date: Tue,  4 Mar 2025 13:04:48 +0100
Message-Id: <20250304120506.135617-4-s.sterz@proxmox.com>
X-Mailer: git-send-email 2.39.5
In-Reply-To: <20250304120506.135617-1-s.sterz@proxmox.com>
References: <20250304120506.135617-1-s.sterz@proxmox.com>
MIME-Version: 1.0
X-SPAM-LEVEL: Spam detection results:  0
 AWL -1.323 Adjusted score from AWL reputation of From: address
 BAYES_00                 -1.9 Bayes spam probability is 0 to 1%
 DMARC_MISSING             0.1 Missing DMARC policy
 ENA_SUBJ_ODD_CASE         2.6 Subject has odd case
 KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment
 SPF_HELO_NONE           0.001 SPF: HELO does not publish an SPF Record
 SPF_PASS               -0.001 SPF: sender matches SPF record
Subject: [pdm-devel] [PATCH proxmox v4 03/21] router/rest-server: add new
 `AsyncHttpBodyParameters` api handler type
X-BeenThere: pdm-devel@lists.proxmox.com
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: Proxmox Datacenter Manager development discussion
 <pdm-devel.lists.proxmox.com>
List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pdm-devel>, 
 <mailto:pdm-devel-request@lists.proxmox.com?subject=unsubscribe>
List-Archive: <http://lists.proxmox.com/pipermail/pdm-devel/>
List-Post: <mailto:pdm-devel@lists.proxmox.com>
List-Help: <mailto:pdm-devel-request@lists.proxmox.com?subject=help>
List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel>, 
 <mailto:pdm-devel-request@lists.proxmox.com?subject=subscribe>
Reply-To: Proxmox Datacenter Manager development discussion
 <pdm-devel@lists.proxmox.com>
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Errors-To: pdm-devel-bounces@lists.proxmox.com
Sender: "pdm-devel" <pdm-devel-bounces@lists.proxmox.com>

this allows us to write api handlers that have access to a request's
headers and to create a low level response while being able to also
specify the parameter in the request's body. this is useful for
endpoints that should not use url parameters, but still need to
access/set specific headers.

previously, `AsyncHttp` did not allow for parameters that were marked
as non-optional to be passed in the body of a request.

as a side-effect, the body is parsed by the rest server to check for
parameters and consumed. so it cannot be passed on by the handler.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
 proxmox-rest-server/src/rest.rs   |  5 ++++
 proxmox-router/src/cli/command.rs | 12 +++++++++
 proxmox-router/src/format.rs      |  6 +++++
 proxmox-router/src/router.rs      | 45 +++++++++++++++++++++++++++++++
 4 files changed, 68 insertions(+)

diff --git a/proxmox-rest-server/src/rest.rs b/proxmox-rest-server/src/rest.rs
index 78339b75..350ce957 100644
--- a/proxmox-rest-server/src/rest.rs
+++ b/proxmox-rest-server/src/rest.rs
@@ -546,6 +546,11 @@ pub(crate) async fn handle_api_request<Env: RpcEnvironment, S: 'static + BuildHa
             let params = parse_query_parameters(info.parameters, "", &parts, &uri_param)?;
             (handler)(parts, req_body, params, info, Box::new(rpcenv)).await
         }
+        ApiHandler::AsyncHttpBodyParameters(handler) => {
+            let params =
+                get_request_parameters(info.parameters, &parts, req_body, uri_param).await?;
+            (handler)(parts, params, info, Box::new(rpcenv)).await
+        }
         ApiHandler::StreamSync(handler) => {
             let params =
                 get_request_parameters(info.parameters, &parts, req_body, uri_param).await?;
diff --git a/proxmox-router/src/cli/command.rs b/proxmox-router/src/cli/command.rs
index 01f64d19..2ca2356a 100644
--- a/proxmox-router/src/cli/command.rs
+++ b/proxmox-router/src/cli/command.rs
@@ -107,6 +107,12 @@ async fn handle_simple_command_future(
         ApiHandler::AsyncHttp(_) => {
             bail!("CliHandler does not support ApiHandler::AsyncHttp - internal error")
         }
+        #[cfg(feature = "server")]
+        ApiHandler::AsyncHttpBodyParameters(_) => {
+            bail!(
+                "CliHandler does not support ApiHandler::AsyncHttpBodyParameters - internal error"
+            )
+        }
     };
 
     match result {
@@ -159,6 +165,12 @@ pub(crate) fn handle_simple_command<'cli>(
         ApiHandler::AsyncHttp(_) => {
             bail!("CliHandler does not support ApiHandler::AsyncHttp - internal error");
         }
+        #[cfg(feature = "server")]
+        ApiHandler::AsyncHttpBodyParameters(_) => {
+            bail!(
+                "CliHandler does not support ApiHandler::AsyncHttpBodyParameters - internal error"
+            );
+        }
     };
 
     match result {
diff --git a/proxmox-router/src/format.rs b/proxmox-router/src/format.rs
index 67568af0..be40895a 100644
--- a/proxmox-router/src/format.rs
+++ b/proxmox-router/src/format.rs
@@ -32,6 +32,12 @@ fn dump_method_definition(method: &str, path: &str, def: Option<&ApiMethod>) ->
                 method = if method == "GET" { "DOWNLOAD" } else { method };
             }
 
+            #[cfg(feature = "server")]
+            if let ApiHandler::AsyncHttpBodyParameters(_) = api_method.handler {
+                method = if method == "POST" { "UPLOAD" } else { method };
+                method = if method == "GET" { "DOWNLOAD" } else { method };
+            }
+
             let res = format!(
                 "**{} {}**\n\n{}{}\n\n{}",
                 method, path, description, param_descr, return_descr
diff --git a/proxmox-router/src/router.rs b/proxmox-router/src/router.rs
index 33b598da..0c4e78d4 100644
--- a/proxmox-router/src/router.rs
+++ b/proxmox-router/src/router.rs
@@ -435,6 +435,44 @@ pub type ApiAsyncHttpHandlerFn = &'static (dyn Fn(
 pub type ApiResponseFuture =
     Pin<Box<dyn Future<Output = Result<Response<Body>, anyhow::Error>> + Send>>;
 
+/// Asynchronous HTTP API handlers with parameters specified in their bodies
+///
+/// They get low level access to request and response data, but it is also possible to specify
+/// their parameters in the request body.
+///
+/// ```
+/// use serde_json::Value;
+///
+/// use hyper::{Body, Response, http::request::Parts};
+///
+/// use proxmox_router::{ApiHandler, ApiMethod, ApiResponseFuture, RpcEnvironment};
+/// use proxmox_schema::ObjectSchema;
+///
+/// fn low_level_hello(
+///    parts: Parts,
+///    param: Value,
+///    info: &ApiMethod,
+///    rpcenv: Box<dyn RpcEnvironment>,
+/// ) -> ApiResponseFuture {
+///    Box::pin(async move {
+///        let response = http::Response::builder()
+///            .status(200)
+///            .body(Body::from("Hello world!"))?;
+///        Ok(response)
+///    })
+/// }
+///
+/// const API_METHOD_LOW_LEVEL_HELLO_BODY_PARAMETER: ApiMethod = ApiMethod::new(
+///    &ApiHandler::AsyncHttpBodyParameters(&low_level_hello),
+///    &ObjectSchema::new("Hello World Example (low level)", &[])
+/// );
+/// ```
+#[cfg(feature = "server")]
+pub type ApiAsyncHttpHandlerBodyParametersFn = &'static (dyn Fn(Parts, Value, &'static ApiMethod, Box<dyn RpcEnvironment>) -> ApiResponseFuture
+              + Send
+              + Sync
+              + 'static);
+
 /// Enum for different types of API handler functions.
 #[non_exhaustive]
 pub enum ApiHandler {
@@ -446,6 +484,8 @@ pub enum ApiHandler {
     StreamAsync(StreamApiAsyncHandlerFn),
     #[cfg(feature = "server")]
     AsyncHttp(ApiAsyncHttpHandlerFn),
+    #[cfg(feature = "server")]
+    AsyncHttpBodyParameters(ApiAsyncHttpHandlerBodyParametersFn),
 }
 
 #[cfg(feature = "test-harness")]
@@ -478,6 +518,11 @@ impl PartialEq for ApiHandler {
                 (ApiHandler::AsyncHttp(l), ApiHandler::AsyncHttp(r)) => {
                     core::mem::transmute::<_, usize>(l) == core::mem::transmute::<_, usize>(r)
                 }
+                #[cfg(feature = "server")]
+                (
+                    ApiHandler::AsyncHttpBodyParameters(l),
+                    ApiHandler::AsyncHttpBodyParameters(r),
+                ) => core::mem::transmute::<_, usize>(l) == core::mem::transmute::<_, usize>(r),
                 _ => false,
             }
         }
-- 
2.39.5



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel