From b51a16b4aa8f8bda6eadc99107d1293b47450186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B4=BA=E5=87=AF?= <2401470435@qq.com> Date: Thu, 9 Jan 2025 14:51:50 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E9=97=A8=E6=88=B7=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E9=9B=86=E6=88=90,=20=E8=BF=90=E8=A1=8C=E6=A0=B7=E4=BE=8B?= =?UTF-8?q?=E5=8F=8A=E6=B5=8B=E8=AF=95=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/portal_route_example/Cargo.toml | 12 ++ examples/portal_route_example/build.rs | 4 + examples/portal_route_example/src/main.rs | 40 ++++ src/lib.rs | 1 + src/portal_route/mod.rs | 2 + src/portal_route/portal_api.rs | 62 ++++++ src/portal_route/route.rs | 227 ++++++++++++++++++++++ tests/portal_test.rs | 99 ++++++++++ 8 files changed, 447 insertions(+) create mode 100644 examples/portal_route_example/Cargo.toml create mode 100644 examples/portal_route_example/build.rs create mode 100644 examples/portal_route_example/src/main.rs create mode 100644 src/portal_route/mod.rs create mode 100644 src/portal_route/portal_api.rs create mode 100644 src/portal_route/route.rs create mode 100644 tests/portal_test.rs diff --git a/examples/portal_route_example/Cargo.toml b/examples/portal_route_example/Cargo.toml new file mode 100644 index 0000000..f64832c --- /dev/null +++ b/examples/portal_route_example/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "portal_route_example" +version = "0.1.0" +edition = "2021" + +[dependencies] +fleet_apiserver = { path = "../../../apiserver" } +serde_json = "1.0.127" +actix-web = "4.9.0" +env_logger = "0.11.6" +tokio = "1.42.0" +log = "0.4.22" \ No newline at end of file diff --git a/examples/portal_route_example/build.rs b/examples/portal_route_example/build.rs new file mode 100644 index 0000000..ae5b884 --- /dev/null +++ b/examples/portal_route_example/build.rs @@ -0,0 +1,4 @@ +fn main() { + // 插件启动器 fleet-datamgr.so 的位置 + println!("cargo:rustc-link-search=/path/to/fleet-datamgr/release/"); +} \ No newline at end of file diff --git a/examples/portal_route_example/src/main.rs b/examples/portal_route_example/src/main.rs new file mode 100644 index 0000000..d4e6ed4 --- /dev/null +++ b/examples/portal_route_example/src/main.rs @@ -0,0 +1,40 @@ +use fleet_apiserver::portal_route::portal_api; +use fleet_apiserver::portal_route::route; +use fleet_apiserver::cores::plugin::PluginManager; +use env_logger::{Builder, Target}; +use std::ffi::CString; +use std::sync::Arc; + +const DATABASE_URL: &str = "sqlite://./test-database.sqlite"; +const ADDRESS: &str = "0.0.0.0:8080"; + +#[link(name = "fleet-datamgr")] +extern "C" {} + +fn setup_logger() { + let mut builder = Builder::from_default_env(); + builder.target(Target::Stdout); + builder.init(); +} + + +#[tokio::main] +async fn main() { + unsafe { + setup_logger(); + + let plugin_manager = Arc::new(PluginManager::new()); + + let data_plugin_manager = portal_api::NewPluginManager(); + let plugin_file_path = CString::new("/home/trickster/Downloads/ripple-remastered/portal-plugin/portal/release/portal-plugin.so").unwrap(); + portal_api::LoadPluginFromFile(data_plugin_manager, plugin_file_path.as_ptr()); + + portal_route::route::init_plugin(plugin_manager.clone(), data_plugin_manager); + if let Err(e) = fleet_apiserver::start_server(DATABASE_URL, ADDRESS, plugin_manager).await { + eprintln!("Failed to start server: {}", e); + } + + portal_api::UnloadAllPlugins(data_plugin_manager); + portal_api::DeletePluginManager(data_plugin_manager); + } +} diff --git a/src/lib.rs b/src/lib.rs index 2f2daf7..9483d29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub mod cores; pub mod schema; pub mod db; pub mod custom_route; +pub mod portal_route; pub use cores::{prepare_app_state, start_server}; pub use cores::events::APIServerEventClient; diff --git a/src/portal_route/mod.rs b/src/portal_route/mod.rs new file mode 100644 index 0000000..1ead564 --- /dev/null +++ b/src/portal_route/mod.rs @@ -0,0 +1,2 @@ +pub mod portal_api; +pub mod route; \ No newline at end of file diff --git a/src/portal_route/portal_api.rs b/src/portal_route/portal_api.rs new file mode 100644 index 0000000..eafbe7e --- /dev/null +++ b/src/portal_route/portal_api.rs @@ -0,0 +1,62 @@ +unsafe extern "C" { + pub fn NewPluginManager() -> *mut ::std::os::raw::c_void; +} +unsafe extern "C" { + pub fn DeletePluginManager(pluginManager: *mut ::std::os::raw::c_void); +} +unsafe extern "C" { + pub fn LoadPluginFromFile( + pluginManager: *mut ::std::os::raw::c_void, + path: *const ::std::os::raw::c_char, + ); +} +unsafe extern "C" { + pub fn LoadPluginsFromDirectory( + pluginManager: *mut ::std::os::raw::c_void, + path: *const ::std::os::raw::c_char, + ); +} +unsafe extern "C" { + pub fn UnloadAllPlugins(pluginManager: *mut ::std::os::raw::c_void); +} +unsafe extern "C" { + pub fn UploadFile( + pluginManager: *mut ::std::os::raw::c_void, + fileName: *const ::std::os::raw::c_char, + fileContent: *const ::std::os::raw::c_char, + fileSize: ::std::os::raw::c_int, + fileDescription: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +unsafe extern "C" { + pub fn UploadStatus( + pluginManager: *mut ::std::os::raw::c_void, + status: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +unsafe extern "C" { + pub fn UploadInstruction( + pluginManager: *mut ::std::os::raw::c_void, + instruction: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +unsafe extern "C" { + pub fn DownloadFile( + pluginManager: *mut ::std::os::raw::c_void, + fileName: *const ::std::os::raw::c_char, + fileSize: *mut ::std::os::raw::c_int, + ) -> *const ::std::os::raw::c_char; +} +unsafe extern "C" { + pub fn ListFiles( + pluginManager: *mut ::std::os::raw::c_void, + indexCount: *mut ::std::os::raw::c_int, + ) -> *mut *const ::std::os::raw::c_char; +} +unsafe extern "C" { + pub fn QueryFile( + pluginManager: *mut ::std::os::raw::c_void, + fileName: *const ::std::os::raw::c_char, + indexCount: *mut ::std::os::raw::c_int, + ) -> *mut *const ::std::os::raw::c_char; +} diff --git a/src/portal_route/route.rs b/src/portal_route/route.rs new file mode 100644 index 0000000..ea882aa --- /dev/null +++ b/src/portal_route/route.rs @@ -0,0 +1,227 @@ +use crate::portal_route::portal_api; +use std::ffi::CString; +use std::ffi::CStr; +use std::os::raw::c_char; +use std::sync::Arc; +use crate::cores::apiserver::{CustomRouteProvider, APIServerRoute, AppState}; +use crate::cores::plugin::PluginManager; +use actix_web::{web, HttpRequest, HttpResponse, Route}; +use actix_web::http::Method; +use actix_web::web::{Bytes}; +use serde_json::json; + + + +pub struct SimpleRoute { + pub method: Method, + pub path: String, + pub handler: fn(request: HttpRequest, body: Bytes) -> HttpResponse, +} + +impl APIServerRoute for SimpleRoute { + fn get_method(&self) -> Method { + self.method.clone() + } + + fn get_path(&self) -> String { + self.path.clone() + } + + fn get_web_route(&self) -> Route { + let handler = self.handler; + web::route() + .method(self.method.clone()) + .to(move |request: HttpRequest, body: Bytes| async move { + handler(request, body) + }) + } +} + +struct DataApiRouteProvider; + +static mut DATA_PLUGIN_MANAGER: *mut ::std::os::raw::c_void = ::std::ptr::null_mut(); + +impl DataApiRouteProvider { + // /** + // * upload file + // * req-header: x-data-file-name: File name + // * req-body: file content + // * req-header: x-data-file-description: File description + // * res: Ok = success + // */ + fn upload_file_handler(request: HttpRequest, body: Bytes) -> HttpResponse { + unsafe { + let header_file_name = request.headers().get("x-data-file-name").unwrap().to_str().unwrap_or_default().to_string(); + let file_name = CString::new(header_file_name).unwrap(); + let file_content = body.to_vec(); + let header_file_description = request.headers().get("x-data-file-description").unwrap().to_str().unwrap_or_default().to_string(); + let file_description = CString::new(header_file_description).unwrap(); + println!("file_size: {:?}", file_content.len()); + let ret = portal_api::UploadFile(DATA_PLUGIN_MANAGER, file_name.as_ptr(), file_content.as_ptr() as *const i8, file_content.len() as i32, file_description.as_ptr()); + if ret == 1 { + HttpResponse::Ok().finish() + } else { + HttpResponse::InternalServerError().finish() + } + } + } + + // /** + // * upload status + // * req-header: x-data-status: Status + // * res: Ok = success + // */ + fn upload_status_handler(request: HttpRequest, body: Bytes) -> HttpResponse { + unsafe { + let header_status = request.headers().get("x-data-status").unwrap().to_str().unwrap_or_default().to_string(); + let status = CString::new(header_status).unwrap(); + let ret = portal_api::UploadStatus(DATA_PLUGIN_MANAGER, status.as_ptr()); + if ret == 1 { + HttpResponse::Ok().finish() + } else { + HttpResponse::InternalServerError().finish() + } + } + } + + // /** + // * upload instruction + // * req-header: x-data-instruction: Instruction + // * res: Ok = success + // */ + fn upload_instruction_handler(request: HttpRequest, body: Bytes) -> HttpResponse { + unsafe { + let header_instruction = request.headers().get("x-data-instruction").unwrap().to_str().unwrap_or_default().to_string(); + let instruction = CString::new(header_instruction).unwrap(); + let ret = portal_api::UploadInstruction(DATA_PLUGIN_MANAGER, instruction.as_ptr()); + println!("ret: {:?}", ret); + if ret == 1 { + HttpResponse::Ok().finish() + } else { + HttpResponse::InternalServerError().finish() + } + } + } + + // /** + // * download file + // * req-header: x-data-file-name: File name + // * res-body: File content + // */ + fn download_file_handler(request: HttpRequest, body: Bytes) -> HttpResponse { + unsafe { + let header_file_name = request.headers().get("x-data-file-name").unwrap().to_str().unwrap_or_default().to_string(); + let file_name = CString::new(header_file_name.clone()).unwrap(); + let mut file_length: i32 = 0; + let ret = portal_api::DownloadFile(DATA_PLUGIN_MANAGER, file_name.as_ptr(), &mut file_length); + let res_content = Vec::from_raw_parts(ret as *mut u8, file_length as usize, file_length as usize); + HttpResponse::Ok() + .body(res_content) + } + } + + // /** + // * list file + // * res-body: File list + // */ + fn list_file_handler(request: HttpRequest, body: Bytes) -> HttpResponse { + unsafe { + let mut index_count: i32 = 0; + let ret = portal_api::ListFiles(DATA_PLUGIN_MANAGER, &mut index_count); + let c_array = ret as *const *const c_char; + let mut vec = Vec::new(); + for i in 0..index_count { + let c_str = *c_array.offset(i as isize); + let bytes = CStr::from_ptr(c_str).to_bytes(); + let str_slice = std::str::from_utf8(bytes).unwrap(); + vec.push(str_slice.to_string()); + } + let json_response = match serde_json::to_string(&vec) { + Ok(json) => json, + Err(e) => { + eprintln!("Failed to convert Vec to JSON: {}", e); + return HttpResponse::InternalServerError().finish(); + } + }; + HttpResponse::Ok() + .content_type("application/json") + .body(json_response) + } + } + + // /** + // * query file + // * req-header: x-data-file-name: File name + // * res-body: File information + // */ + fn query_file_handler(request: HttpRequest, body: Bytes) -> HttpResponse { + unsafe { + let header_file_name = request.headers().get("x-data-file-name").unwrap().to_str().unwrap_or_default().to_string(); + let file_name = CString::new(header_file_name).unwrap(); + let mut index_count: i32 = 0; + let ret = portal_api::QueryFile(DATA_PLUGIN_MANAGER, file_name.as_ptr(), &mut index_count); + let c_array = ret as *const *const c_char; + let mut vec = Vec::new(); + for i in 0..index_count { + let c_str = *c_array.offset(i as isize); + let bytes = CStr::from_ptr(c_str).to_bytes(); + let str_slice = std::str::from_utf8(bytes).unwrap(); + vec.push(str_slice.to_string()); + } + let json_response = match serde_json::to_string(&vec) { + Ok(json) => json, + Err(e) => { + eprintln!("Failed to convert Vec to JSON: {}", e); + return HttpResponse::InternalServerError().finish(); + } + }; + HttpResponse::Ok() + .content_type("application/json") + .body(json_response) + } + } +} + +impl CustomRouteProvider for DataApiRouteProvider { + fn get_routes(&self) -> Vec> { + vec![ + Box::new(SimpleRoute { + method: Method::POST, + path: "/data/self/file".to_string(), + handler: Self::upload_file_handler, + }), + Box::new(SimpleRoute { + method: Method::POST, + path: "/data/other/status".to_string(), + handler: Self::upload_status_handler, + }), + Box::new(SimpleRoute { + method: Method::POST, + path: "/data/user/instruction".to_string(), + handler: Self::upload_instruction_handler, + }), + Box::new(SimpleRoute { + method: Method::GET, + path: "/data/self/file".to_string(), + handler: Self::download_file_handler, + }), + Box::new(SimpleRoute { + method: Method::GET, + path: "/data/self/files".to_string(), + handler: Self::list_file_handler, + }), + Box::new(SimpleRoute { + method: Method::GET, + path: "/data/self/fileinfo".to_string(), + handler: Self::query_file_handler, + }), + ] + } +} + +pub fn init_plugin(plugin_manager: Arc, data_plugin_manager: *mut ::std::os::raw::c_void) { + unsafe { + DATA_PLUGIN_MANAGER = data_plugin_manager; + } + plugin_manager.register(Arc::new(DataApiRouteProvider {})); +} diff --git a/tests/portal_test.rs b/tests/portal_test.rs new file mode 100644 index 0000000..d9fb95c --- /dev/null +++ b/tests/portal_test.rs @@ -0,0 +1,99 @@ +use reqwest::Client; +use reqwest::header::{HeaderMap, HeaderValue}; +use std::error::Error; +use serde_json::Value; + +#[tokio::test] +async fn test_portal() -> Result<(), Box> { + let client = Client::new(); + let mut headers = HeaderMap::new(); + + // uplaod file test + // url: http://localhost:8080/data/self/file + // method: POST + // req-header: x-data-file-name: test.txt + // req-header: x-data-file-description: this is a test file + // req-body: file content: "hello world" + // res: 200 OK + + // headers.insert("x-data-file-name", HeaderValue::from_str("test2.txt")?); + // headers.insert("x-data-file-description", HeaderValue::from_str("this is a test file")?); + let res = client + .post("http://localhost:8080/data/self/file") + .headers(headers.clone()) + .body("hello world") + .send() + .await?; + let status = res.status(); + println!("{:?}", status); + + + // upload status + // url: http://localhost:8080/data/other/status + // method: POST + // req-header: x-data-status: Status = "success" + // res: Ok = success + + headers.clear(); + headers.insert("x-data-status", HeaderValue::from_str("success")?); + let res = client + .post("http://192.168.38.130:8080/data/other/status") + .headers(headers.clone()) + .send() + .await?; + println!("{:?}", res.status()); + + + // upload instruction + // url: http://localhost:8080/data/user/instruction + // req-header: x-data-instruction: Instruction = "Install" + // res: Ok = success + + headers.clear(); + headers.insert("x-data-instruction", HeaderValue::from_str("Install")?); + let res = client + .post("http://localhost:8080/data/user/instruction") + .headers(headers.clone()) + .send() + .await?; + println!("{:?}", res.status()); + + + // download file + // url: http://localhost:8080/data/self/file + // method: GET + // req-header: x-data-file-name: File name = "test.txt" + // res-header: x-data-file-name: File name = "test.txt" + // res-body: File content + + headers.clear(); + headers.insert("x-data-file-name", HeaderValue::from_str("test.txt")?); + let res = client + .get("http://localhost:8080/data/self/file") + .headers(headers.clone()) + .send() + .await?; + let body = res.bytes().await?; + if let Ok(text) = std::str::from_utf8(&body) { + println!("{}", text); + } + + + // query file + // url: http://localhost:8080/data/self/fileinfo + // method: GET + // req-header: x-data-file-name: File name = "test.txt" + // res-body: File info + + headers.clear(); + headers.insert("x-data-file-name", HeaderValue::from_str("test")?); + let res = client + .get("http://localhost:8080/data/self/fileinfo") + .headers(headers.clone()) + .send() + .await?; + let json: Value = res.json().await?; + println!("{:?}", json); + + Ok(()) +} \ No newline at end of file -- Gitee From d1bde523cbb4db669540fc18cf0b4d412901d457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B4=BA=E5=87=AF?= <2401470435@qq.com> Date: Fri, 10 Jan 2025 17:27:07 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=AE=8C=E5=96=84route=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 4 + examples/portal_route_example/src/main.rs | 6 +- src/portal_route/route.rs | 144 ++++++++++++++++++---- tests/portal_test.rs | 53 ++++++++ 4 files changed, 183 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3ecf91a..875b4d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,10 @@ path = "examples/reflector_example/src/main.rs" name = "custom-route-example" path = "examples/custom_route_example/src/main.rs" +[[bin]] +name = "portal_route_example" +path = "examples/portal_route_example/src/main.rs" + [dependencies] #feventbus = "0.3.0" feventbus = { git = "https://gitee.com/iscas-system/eventbus.git" } diff --git a/examples/portal_route_example/src/main.rs b/examples/portal_route_example/src/main.rs index d4e6ed4..a5fd7a7 100644 --- a/examples/portal_route_example/src/main.rs +++ b/examples/portal_route_example/src/main.rs @@ -26,10 +26,10 @@ async fn main() { let plugin_manager = Arc::new(PluginManager::new()); let data_plugin_manager = portal_api::NewPluginManager(); - let plugin_file_path = CString::new("/home/trickster/Downloads/ripple-remastered/portal-plugin/portal/release/portal-plugin.so").unwrap(); - portal_api::LoadPluginFromFile(data_plugin_manager, plugin_file_path.as_ptr()); + let plugin_file_path = CString::new("/home/trickster/Downloads/ripple-remastered/plugins/release").unwrap(); + portal_api::LoadPluginsFromDirectory(data_plugin_manager, plugin_file_path.as_ptr()); - portal_route::route::init_plugin(plugin_manager.clone(), data_plugin_manager); + route::init_plugin(plugin_manager.clone(), data_plugin_manager); if let Err(e) = fleet_apiserver::start_server(DATABASE_URL, ADDRESS, plugin_manager).await { eprintln!("Failed to start server: {}", e); } diff --git a/src/portal_route/route.rs b/src/portal_route/route.rs index ea882aa..2da85cc 100644 --- a/src/portal_route/route.rs +++ b/src/portal_route/route.rs @@ -50,14 +50,42 @@ impl DataApiRouteProvider { // * res: Ok = success // */ fn upload_file_handler(request: HttpRequest, body: Bytes) -> HttpResponse { + + let header_file_name = match request.headers().get("x-data-file-name") { + Some(value) => match value.to_str() { + Ok(s) => s.to_string(), + Err(_) => return HttpResponse::BadRequest().body("Invalid x-data-file-name header"), + }, + None => return HttpResponse::BadRequest().body("Missing x-data-file-name header"), + }; + + let file_name = match CString::new(header_file_name) { + Ok(cstr) => cstr, + Err(_) => return HttpResponse::BadRequest().body("Invalid file name"), + }; + + let file_content = body.to_vec(); + + let header_file_description = match request.headers().get("x-data-file-description") { + Some(value) => match value.to_str() { + Ok(s) => s.to_string(), + Err(_) => return HttpResponse::BadRequest().body("Invalid x-data-file-description header"), + }, + None => return HttpResponse::BadRequest().body("Missing x-data-file-description header"), + }; + + let file_description = match CString::new(header_file_description) { + Ok(cstr) => cstr, + Err(_) => return HttpResponse::BadRequest().body("Invalid file description"), + }; unsafe { - let header_file_name = request.headers().get("x-data-file-name").unwrap().to_str().unwrap_or_default().to_string(); - let file_name = CString::new(header_file_name).unwrap(); - let file_content = body.to_vec(); - let header_file_description = request.headers().get("x-data-file-description").unwrap().to_str().unwrap_or_default().to_string(); - let file_description = CString::new(header_file_description).unwrap(); - println!("file_size: {:?}", file_content.len()); - let ret = portal_api::UploadFile(DATA_PLUGIN_MANAGER, file_name.as_ptr(), file_content.as_ptr() as *const i8, file_content.len() as i32, file_description.as_ptr()); + let ret = portal_api::UploadFile( + DATA_PLUGIN_MANAGER, + file_name.as_ptr(), + file_content.as_ptr() as *const i8, + file_content.len() as i32, + file_description.as_ptr() + ); if ret == 1 { HttpResponse::Ok().finish() } else { @@ -72,10 +100,25 @@ impl DataApiRouteProvider { // * res: Ok = success // */ fn upload_status_handler(request: HttpRequest, body: Bytes) -> HttpResponse { + + let header_status = match request.headers().get("x-data-status") { + Some(value) => match value.to_str() { + Ok(s) => s.to_string(), + Err(_) => return HttpResponse::BadRequest().body("Invalid x-data-status header"), + }, + None => return HttpResponse::BadRequest().body("Missing x-data-status header"), + }; + + let status = match CString::new(header_status) { + Ok(cstr) => cstr, + Err(_) => return HttpResponse::BadRequest().body("Invalid status"), + }; + unsafe { - let header_status = request.headers().get("x-data-status").unwrap().to_str().unwrap_or_default().to_string(); - let status = CString::new(header_status).unwrap(); - let ret = portal_api::UploadStatus(DATA_PLUGIN_MANAGER, status.as_ptr()); + let ret = portal_api::UploadStatus( + DATA_PLUGIN_MANAGER, + status.as_ptr() + ); if ret == 1 { HttpResponse::Ok().finish() } else { @@ -90,10 +133,25 @@ impl DataApiRouteProvider { // * res: Ok = success // */ fn upload_instruction_handler(request: HttpRequest, body: Bytes) -> HttpResponse { + + let header_instruction = match request.headers().get("x-data-instruction") { + Some(value) => match value.to_str() { + Ok(s) => s.to_string(), + Err(_) => return HttpResponse::BadRequest().body("Invalid x-data-instruction header"), + }, + None => return HttpResponse::BadRequest().body("Missing x-data-instruction header"), + }; + + let instruction = match CString::new(header_instruction) { + Ok(cstr) => cstr, + Err(_) => return HttpResponse::BadRequest().body("Invalid instruction"), + }; + unsafe { - let header_instruction = request.headers().get("x-data-instruction").unwrap().to_str().unwrap_or_default().to_string(); - let instruction = CString::new(header_instruction).unwrap(); - let ret = portal_api::UploadInstruction(DATA_PLUGIN_MANAGER, instruction.as_ptr()); + let ret = portal_api::UploadInstruction( + DATA_PLUGIN_MANAGER, + instruction.as_ptr() + ); println!("ret: {:?}", ret); if ret == 1 { HttpResponse::Ok().finish() @@ -109,11 +167,27 @@ impl DataApiRouteProvider { // * res-body: File content // */ fn download_file_handler(request: HttpRequest, body: Bytes) -> HttpResponse { + + let header_file_name = match request.headers().get("x-data-file-name") { + Some(value) => match value.to_str() { + Ok(s) => s.to_string(), + Err(_) => return HttpResponse::BadRequest().body("Invalid x-data-file-name header"), + }, + None => return HttpResponse::BadRequest().body("Missing x-data-file-name header"), + }; + + let file_name = match CString::new(header_file_name) { + Ok(cstr) => cstr, + Err(_) => return HttpResponse::BadRequest().body("Invalid file name"), + }; + unsafe { - let header_file_name = request.headers().get("x-data-file-name").unwrap().to_str().unwrap_or_default().to_string(); - let file_name = CString::new(header_file_name.clone()).unwrap(); let mut file_length: i32 = 0; - let ret = portal_api::DownloadFile(DATA_PLUGIN_MANAGER, file_name.as_ptr(), &mut file_length); + let ret = portal_api::DownloadFile( + DATA_PLUGIN_MANAGER, + file_name.as_ptr(), + &mut file_length + ); let res_content = Vec::from_raw_parts(ret as *mut u8, file_length as usize, file_length as usize); HttpResponse::Ok() .body(res_content) @@ -155,11 +229,27 @@ impl DataApiRouteProvider { // * res-body: File information // */ fn query_file_handler(request: HttpRequest, body: Bytes) -> HttpResponse { + + let header_file_name = match request.headers().get("x-data-file-name") { + Some(value) => match value.to_str() { + Ok(s) => s.to_string(), + Err(_) => return HttpResponse::BadRequest().body("Invalid x-data-file-name header"), + }, + None => return HttpResponse::BadRequest().body("Missing x-data-file-name header"), + }; + + let file_name = match CString::new(header_file_name) { + Ok(cstr) => cstr, + Err(_) => return HttpResponse::BadRequest().body("Invalid file name"), + }; + unsafe { - let header_file_name = request.headers().get("x-data-file-name").unwrap().to_str().unwrap_or_default().to_string(); - let file_name = CString::new(header_file_name).unwrap(); let mut index_count: i32 = 0; - let ret = portal_api::QueryFile(DATA_PLUGIN_MANAGER, file_name.as_ptr(), &mut index_count); + let ret = portal_api::QueryFile( + DATA_PLUGIN_MANAGER, + file_name.as_ptr(), + &mut index_count + ); let c_array = ret as *const *const c_char; let mut vec = Vec::new(); for i in 0..index_count { @@ -168,7 +258,19 @@ impl DataApiRouteProvider { let str_slice = std::str::from_utf8(bytes).unwrap(); vec.push(str_slice.to_string()); } - let json_response = match serde_json::to_string(&vec) { + let json_response: Vec<_> = vec.iter().map(|s| { + let parts: Vec<&str> = s.split('|').collect(); + json!({ + "file_name": parts.get(0).unwrap_or(&""), + "owner": parts.get(1).unwrap_or(&""), + "description": parts.get(2).unwrap_or(&""), + "avaliability": parts.get(3).unwrap_or(&""), + "operation_type": parts.get(4).unwrap_or(&""), + "operation_detail": parts.get(5).unwrap_or(&""), + "operation_time": parts.get(6).unwrap_or(&"") + }) + }).collect(); + let json_res = match serde_json::to_string(&json_response) { Ok(json) => json, Err(e) => { eprintln!("Failed to convert Vec to JSON: {}", e); @@ -177,7 +279,7 @@ impl DataApiRouteProvider { }; HttpResponse::Ok() .content_type("application/json") - .body(json_response) + .body(json_res) } } } diff --git a/tests/portal_test.rs b/tests/portal_test.rs index d9fb95c..8fe189c 100644 --- a/tests/portal_test.rs +++ b/tests/portal_test.rs @@ -1,10 +1,63 @@ +use actix_web::rt::Runtime; use reqwest::Client; use reqwest::header::{HeaderMap, HeaderValue}; use std::error::Error; +use std::sync::Arc; +use std::sync::Mutex; +use std::thread; use serde_json::Value; +use fleet_apiserver::portal_route::portal_api; +use fleet_apiserver::portal_route::route; +use fleet_apiserver::cores::plugin::PluginManager; + +const DATABASE_URL: &str = "sqlite://./test-database.sqlite"; +const ADDRESS: &str = "0.0.0.0:8080"; + +struct SafePtr(*mut ::std::os::raw::c_void); + +// 使用 `unsafe` 实现 `Send` +unsafe impl Send for SafePtr {} + +#[link(name = "fleet-datamgr")] +extern "C" {} + #[tokio::test] async fn test_portal() -> Result<(), Box> { + + unsafe { + let plugin_manager = Arc::new(PluginManager::new()); + let data_plugin_manager = Arc::new(Mutex::new(SafePtr(portal_api::NewPluginManager()))); + + // portal-plugin.so 及 storage-plugin.so 的位置 + let plugin_file_path = std::ffi::CString::new("/home/trickster/Downloads/ripple-remastered/plugins/release").unwrap(); + let plugin_manager = plugin_manager.clone(); + let data_plugin_manager = Arc::clone(&data_plugin_manager); + + let server_handle = thread::spawn(move || { + let rt = Runtime::new().unwrap(); + rt.block_on(async { + portal_api::LoadPluginsFromDirectory(data_plugin_manager.lock().unwrap().0, plugin_file_path.as_ptr()); + route::init_plugin(plugin_manager.clone(), data_plugin_manager.lock().unwrap().0); + if let Err(e) = fleet_apiserver::start_server(DATABASE_URL, ADDRESS, plugin_manager).await { + eprintln!("Failed to start server: {}", e); + } + portal_api::UnloadAllPlugins(data_plugin_manager.lock().unwrap().0); + portal_api::DeletePluginManager(data_plugin_manager.lock().unwrap().0); + }); + }); + + server_handle.join().unwrap(); + + } + + + // 等待服务器启动 + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + + + + let client = Client::new(); let mut headers = HeaderMap::new(); -- Gitee From eb3bd4b7dfa5b0ee97c2c535671c7037c074ed7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B4=BA=E5=87=AF?= <2401470435@qq.com> Date: Sat, 11 Jan 2025 14:55:24 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0HttpResponse=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/portal_route/route.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/portal_route/route.rs b/src/portal_route/route.rs index 2da85cc..093383f 100644 --- a/src/portal_route/route.rs +++ b/src/portal_route/route.rs @@ -47,7 +47,7 @@ impl DataApiRouteProvider { // * req-header: x-data-file-name: File name // * req-body: file content // * req-header: x-data-file-description: File description - // * res: Ok = success + // * res: Upload success // */ fn upload_file_handler(request: HttpRequest, body: Bytes) -> HttpResponse { @@ -87,7 +87,7 @@ impl DataApiRouteProvider { file_description.as_ptr() ); if ret == 1 { - HttpResponse::Ok().finish() + HttpResponse::Ok().body("Upload file success") } else { HttpResponse::InternalServerError().finish() } @@ -120,7 +120,7 @@ impl DataApiRouteProvider { status.as_ptr() ); if ret == 1 { - HttpResponse::Ok().finish() + HttpResponse::Ok().bodu("Upload status success") } else { HttpResponse::InternalServerError().finish() } @@ -154,7 +154,7 @@ impl DataApiRouteProvider { ); println!("ret: {:?}", ret); if ret == 1 { - HttpResponse::Ok().finish() + HttpResponse::Ok().body("Upload instruction success") } else { HttpResponse::InternalServerError().finish() } -- Gitee