From 3bc22865863d8e3b8a89cd92bedc81e1280999a3 Mon Sep 17 00:00:00 2001 From: aleidk Date: Tue, 23 Apr 2024 20:12:18 -0400 Subject: [PATCH 1/4] feat(grpc): Add basic grpc example --- Cargo.toml | 6 +++++ build.rs | 5 +++++ proto/helloworld.proto | 15 +++++++++++++ src/grpc.rs | 37 +++++++++++++++++++++++++++++++ src/grpc/client.rs | 33 ++++++++++++++++++++++++++++ src/grpc/server.rs | 50 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 13 +++++++++-- 7 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 build.rs create mode 100644 proto/helloworld.proto create mode 100644 src/grpc.rs create mode 100644 src/grpc/client.rs create mode 100644 src/grpc/server.rs diff --git a/Cargo.toml b/Cargo.toml index be46c38..ad55fd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,9 @@ edition = "2021" [dependencies] clap = { version = "4.5.4", features = ["derive"] } ignore = "0.4.22" +prost = "0.12.4" +tokio = { version = "1", features = ["full"] } +tonic = "0.11.0" + +[build-dependencies] +tonic-build = "0.11.0" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..4976509 --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +fn main() -> Result<(), Box> { + tonic_build::compile_protos("proto/helloworld.proto")?; + + Ok(()) +} diff --git a/proto/helloworld.proto b/proto/helloworld.proto new file mode 100644 index 0000000..519a530 --- /dev/null +++ b/proto/helloworld.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package helloworld; + +service Greater { + rpc SayHello (HelloRequest) returns (HelloResponse); +} + +message HelloRequest { + string name = 1; +} + +message HelloResponse { + string message = 1; +} diff --git a/src/grpc.rs b/src/grpc.rs new file mode 100644 index 0000000..f73f423 --- /dev/null +++ b/src/grpc.rs @@ -0,0 +1,37 @@ +use std::error::Error; +use std::net::{SocketAddr, TcpListener}; + +use tonic::async_trait; + +use self::client::GRPCClient; +use self::server::GRPCServer; + +mod client; +mod server; + +pub mod hello_world { + tonic::include_proto!("helloworld"); +} + +#[async_trait] +pub trait Connection { + async fn connect(&self) -> Result<(), Box>; +} + +fn is_socket_in_use(addr: String) -> bool { + let socket: SocketAddr = addr.parse().expect("Failed to create socket"); + match TcpListener::bind(socket) { + Ok(_) => true, + Err(_) => false, + } +} + +pub fn run() -> Result, Box> { + let addr = "[::1]:50051"; + + if is_socket_in_use(addr.to_string()) { + Ok(Box::new(GRPCServer::new(addr.to_string()))) + } else { + Ok(Box::new(GRPCClient::new(addr.to_string()))) + } +} diff --git a/src/grpc/client.rs b/src/grpc/client.rs new file mode 100644 index 0000000..d652274 --- /dev/null +++ b/src/grpc/client.rs @@ -0,0 +1,33 @@ +use super::hello_world; + +use hello_world::greater_client::GreaterClient; +use hello_world::HelloRequest; +use tonic::async_trait; + +#[derive(Debug, Default)] +pub struct GRPCClient { + address: String, +} + +impl GRPCClient { + pub fn new(address: String) -> Self { + Self { address } + } +} + +#[async_trait] +impl super::Connection for GRPCClient { + async fn connect(&self) -> Result<(), Box> { + let mut client = GreaterClient::connect(format!("http://{}", self.address)).await?; + + let request = tonic::Request::new(HelloRequest { + name: "Self".into(), + }); + + let response = client.say_hello(request).await?; + + println!("RESPONSE={:?}", response); + + Ok(()) + } +} diff --git a/src/grpc/server.rs b/src/grpc/server.rs new file mode 100644 index 0000000..1590ef6 --- /dev/null +++ b/src/grpc/server.rs @@ -0,0 +1,50 @@ +use super::hello_world; +use hello_world::greater_server::{Greater, GreaterServer}; +use hello_world::{HelloRequest, HelloResponse}; +use std::error::Error; +use std::net::SocketAddr; +use tonic::transport::Server; +use tonic::{async_trait, Request, Response, Result, Status}; + +#[derive(Debug, Default)] +pub struct GRPCServer { + address: String, +} + +impl GRPCServer { + pub fn new(address: String) -> Self { + Self { address } + } +} + +#[tonic::async_trait] +impl Greater for GRPCServer { + async fn say_hello( + &self, + request: Request, + ) -> Result, Status> { + println!("Got a request {:?}", request); + + let reply = hello_world::HelloResponse { + message: format!("Hello {}!", request.into_inner().name), + }; + + Ok(Response::new(reply)) + } +} + +#[async_trait] +impl super::Connection for GRPCServer { + async fn connect(&self) -> Result<(), Box> { + println!("Starting server on: \"{}\"", self.address); + + let socket: SocketAddr = self.address.parse()?; + + Server::builder() + .add_service(GreaterServer::new(GRPCServer::default())) + .serve(socket) + .await?; + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 749161a..167b791 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ use std::{env, path::PathBuf}; use clap::Parser; +use std::error::Error; mod file_explorer; +mod grpc; #[derive(Parser)] #[command(version, about, long_about = None)] @@ -11,7 +13,8 @@ struct Args { path: Option, } -fn main() { +#[tokio::main()] +async fn main() -> Result<(), Box> { let cli = Args::parse(); let path = cli .path @@ -19,5 +22,11 @@ fn main() { let files = file_explorer::walk_dir(&path).expect("error"); - eprintln!("DEBUGPRINT[4]: main.rs:20: files={:#?}", files); + eprintln!("DEBUGPRINT[4]: main.rs:20: files={:#?}", files.len()); + + let server = grpc::run()?; + + server.connect().await?; + + Ok(()) } From f803aa92f7847023649770f4bfa111fc9b543b2f Mon Sep 17 00:00:00 2001 From: aleidk Date: Wed, 24 Apr 2024 10:26:14 -0400 Subject: [PATCH 2/4] feat(refactor): update basic grpc method --- build.rs | 2 +- proto/helloworld.proto | 15 --------------- proto/juno.proto | 14 ++++++++++++++ src/grpc.rs | 4 ++-- src/grpc/client.rs | 15 +++++++-------- src/grpc/server.rs | 22 ++++++++++------------ 6 files changed, 34 insertions(+), 38 deletions(-) delete mode 100644 proto/helloworld.proto create mode 100644 proto/juno.proto diff --git a/build.rs b/build.rs index 4976509..db9dbf8 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,5 @@ fn main() -> Result<(), Box> { - tonic_build::compile_protos("proto/helloworld.proto")?; + tonic_build::compile_protos("proto/juno.proto")?; Ok(()) } diff --git a/proto/helloworld.proto b/proto/helloworld.proto deleted file mode 100644 index 519a530..0000000 --- a/proto/helloworld.proto +++ /dev/null @@ -1,15 +0,0 @@ -syntax = "proto3"; - -package helloworld; - -service Greater { - rpc SayHello (HelloRequest) returns (HelloResponse); -} - -message HelloRequest { - string name = 1; -} - -message HelloResponse { - string message = 1; -} diff --git a/proto/juno.proto b/proto/juno.proto new file mode 100644 index 0000000..c4cc39e --- /dev/null +++ b/proto/juno.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package juno; + +service JunoRequest { + rpc Ping (PingRequestMessage) returns (PingResponseMessage); +} + +message PingRequestMessage { +} + +message PingResponseMessage { + string message = 1; +} diff --git a/src/grpc.rs b/src/grpc.rs index f73f423..77e1b60 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -9,8 +9,8 @@ use self::server::GRPCServer; mod client; mod server; -pub mod hello_world { - tonic::include_proto!("helloworld"); +pub mod grpc_juno { + tonic::include_proto!("juno"); } #[async_trait] diff --git a/src/grpc/client.rs b/src/grpc/client.rs index d652274..a4488f6 100644 --- a/src/grpc/client.rs +++ b/src/grpc/client.rs @@ -1,8 +1,9 @@ -use super::hello_world; +use super::grpc_juno; -use hello_world::greater_client::GreaterClient; -use hello_world::HelloRequest; +use grpc_juno::juno_request_client::JunoRequestClient; +use grpc_juno::PingRequestMessage; use tonic::async_trait; +use tonic::Request; #[derive(Debug, Default)] pub struct GRPCClient { @@ -18,13 +19,11 @@ impl GRPCClient { #[async_trait] impl super::Connection for GRPCClient { async fn connect(&self) -> Result<(), Box> { - let mut client = GreaterClient::connect(format!("http://{}", self.address)).await?; + let mut client = JunoRequestClient::connect(format!("http://{}", self.address)).await?; - let request = tonic::Request::new(HelloRequest { - name: "Self".into(), - }); + let request = Request::new(PingRequestMessage {}); - let response = client.say_hello(request).await?; + let response = client.ping(request).await?; println!("RESPONSE={:?}", response); diff --git a/src/grpc/server.rs b/src/grpc/server.rs index 1590ef6..28afafb 100644 --- a/src/grpc/server.rs +++ b/src/grpc/server.rs @@ -1,6 +1,6 @@ -use super::hello_world; -use hello_world::greater_server::{Greater, GreaterServer}; -use hello_world::{HelloRequest, HelloResponse}; +use super::grpc_juno; +use grpc_juno::juno_request_server::{JunoRequest, JunoRequestServer}; +use grpc_juno::{PingRequestMessage, PingResponseMessage}; use std::error::Error; use std::net::SocketAddr; use tonic::transport::Server; @@ -18,15 +18,13 @@ impl GRPCServer { } #[tonic::async_trait] -impl Greater for GRPCServer { - async fn say_hello( +impl JunoRequest for GRPCServer { + async fn ping( &self, - request: Request, - ) -> Result, Status> { - println!("Got a request {:?}", request); - - let reply = hello_world::HelloResponse { - message: format!("Hello {}!", request.into_inner().name), + _request: Request, + ) -> Result, Status> { + let reply = PingResponseMessage { + message: "pong!".to_string(), }; Ok(Response::new(reply)) @@ -41,7 +39,7 @@ impl super::Connection for GRPCServer { let socket: SocketAddr = self.address.parse()?; Server::builder() - .add_service(GreaterServer::new(GRPCServer::default())) + .add_service(JunoRequestServer::new(GRPCServer::default())) .serve(socket) .await?; From 8cd4b4b10f67d3860bd63676a48329a966409a2a Mon Sep 17 00:00:00 2001 From: aleidk Date: Wed, 24 Apr 2024 11:23:36 -0400 Subject: [PATCH 3/4] feat(grpc): call walk_dir from grpc service --- proto/juno.proto | 9 +++++++++ src/grpc/client.rs | 10 ++++++---- src/grpc/server.rs | 26 +++++++++++++++++++++++++- src/main.rs | 5 +---- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/proto/juno.proto b/proto/juno.proto index c4cc39e..aad85d6 100644 --- a/proto/juno.proto +++ b/proto/juno.proto @@ -4,6 +4,7 @@ package juno; service JunoRequest { rpc Ping (PingRequestMessage) returns (PingResponseMessage); + rpc GetFiles (GetFilesRequest) returns (GetFilesResponse); } message PingRequestMessage { @@ -12,3 +13,11 @@ message PingRequestMessage { message PingResponseMessage { string message = 1; } + +message GetFilesRequest { + string path = 1; +} + +message GetFilesResponse { + repeated string files = 1; +} diff --git a/src/grpc/client.rs b/src/grpc/client.rs index a4488f6..68f3d65 100644 --- a/src/grpc/client.rs +++ b/src/grpc/client.rs @@ -1,7 +1,7 @@ use super::grpc_juno; use grpc_juno::juno_request_client::JunoRequestClient; -use grpc_juno::PingRequestMessage; +use grpc_juno::GetFilesRequest; use tonic::async_trait; use tonic::Request; @@ -21,11 +21,13 @@ impl super::Connection for GRPCClient { async fn connect(&self) -> Result<(), Box> { let mut client = JunoRequestClient::connect(format!("http://{}", self.address)).await?; - let request = Request::new(PingRequestMessage {}); + let request = Request::new(GetFilesRequest { + path: "/home/aleidk/Music/".to_string(), + }); - let response = client.ping(request).await?; + let response = client.get_files(request).await?.into_inner(); - println!("RESPONSE={:?}", response); + println!("RESPONSE={:?}", response.files); Ok(()) } diff --git a/src/grpc/server.rs b/src/grpc/server.rs index 28afafb..b7cb6d4 100644 --- a/src/grpc/server.rs +++ b/src/grpc/server.rs @@ -1,8 +1,12 @@ +use crate::file_explorer; + use super::grpc_juno; use grpc_juno::juno_request_server::{JunoRequest, JunoRequestServer}; -use grpc_juno::{PingRequestMessage, PingResponseMessage}; +use grpc_juno::{GetFilesRequest, GetFilesResponse, PingRequestMessage, PingResponseMessage}; use std::error::Error; use std::net::SocketAddr; +use std::path::PathBuf; +use std::str::FromStr; use tonic::transport::Server; use tonic::{async_trait, Request, Response, Result, Status}; @@ -29,6 +33,26 @@ impl JunoRequest for GRPCServer { Ok(Response::new(reply)) } + + async fn get_files( + &self, + request: Request, + ) -> Result, Status> { + let path = PathBuf::from_str(request.into_inner().path.as_str()) + .expect("Failed to create pathbuf"); + + let files = match file_explorer::walk_dir(&path) { + Ok(files) => files, + Err(_err) => panic!("Error reading path: {:?}", path), + }; + eprintln!("DEBUGPRINT[2]: server.rs:44: files={:#?}", files); + + let reply = GetFilesResponse { + files: files.iter().map(|x| x.display().to_string()).collect(), + }; + + Ok(Response::new(reply)) + } } #[async_trait] diff --git a/src/main.rs b/src/main.rs index 167b791..0717a75 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use core::panic; use std::{env, path::PathBuf}; use clap::Parser; @@ -20,10 +21,6 @@ async fn main() -> Result<(), Box> { .path .unwrap_or(env::current_dir().expect("Current directory is not available.")); - let files = file_explorer::walk_dir(&path).expect("error"); - - eprintln!("DEBUGPRINT[4]: main.rs:20: files={:#?}", files.len()); - let server = grpc::run()?; server.connect().await?; From f0485abfbba12f1cc21b4032fce8af13d0215bd7 Mon Sep 17 00:00:00 2001 From: aleidk Date: Mon, 29 Apr 2024 19:22:45 -0400 Subject: [PATCH 4/4] feat(configuration): Add global configuration also update some error messages --- Cargo.toml | 1 + src/configuration.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++ src/file_explorer.rs | 20 ++++++++++++++++--- src/grpc/client.rs | 2 +- src/grpc/server.rs | 3 +-- src/main.rs | 17 +--------------- 6 files changed, 67 insertions(+), 22 deletions(-) create mode 100644 src/configuration.rs diff --git a/Cargo.toml b/Cargo.toml index ad55fd3..b8a5deb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] clap = { version = "4.5.4", features = ["derive"] } ignore = "0.4.22" +lazy_static = "1.4.0" prost = "0.12.4" tokio = { version = "1", features = ["full"] } tonic = "0.11.0" diff --git a/src/configuration.rs b/src/configuration.rs new file mode 100644 index 0000000..73bcf2b --- /dev/null +++ b/src/configuration.rs @@ -0,0 +1,46 @@ +use clap::Parser; +use lazy_static::lazy_static; +use std::env; +use std::path::PathBuf; + +lazy_static! { + pub static ref CONFIG: Config = Config::new(); +} + +#[derive(Parser)] +#[command(version, about, long_about = None)] +struct Args { + #[arg(help = "Directory to scan for files")] + path: Option, +} + +#[derive(Debug)] +pub struct Config { + pub base_path: PathBuf, +} + +impl Default for Config { + fn default() -> Self { + Config { + base_path: env::current_dir().expect("Current directory is not available."), + } + } +} + +impl Config { + pub fn new() -> Self { + let mut config = Self::default(); + + let cli = Self::get_cli_args(); + + if let Some(path) = cli.path { + config.base_path = path; + } + + config + } + + fn get_cli_args() -> Args { + Args::parse() + } +} diff --git a/src/file_explorer.rs b/src/file_explorer.rs index f394d7c..e932592 100644 --- a/src/file_explorer.rs +++ b/src/file_explorer.rs @@ -1,8 +1,10 @@ use ignore::types::TypesBuilder; use ignore::WalkBuilder; -use std::{io, path::PathBuf}; +use std::path::PathBuf; -pub fn walk_dir(path: &PathBuf) -> io::Result> { +use crate::configuration::CONFIG; + +pub fn walk_dir(path: &PathBuf) -> Result, &str> { let mut types_builder = TypesBuilder::new(); types_builder.add_defaults(); @@ -14,7 +16,19 @@ pub fn walk_dir(path: &PathBuf) -> io::Result> { types_builder.select("sound"); - let entries: Vec = WalkBuilder::new(path) + let search_path = CONFIG.base_path.join(path); + eprintln!( + "DEBUGPRINT[1]: file_explorer.rs:19: search_path={:#?}", + search_path + ); + + // PathBuf.join() can override the hole path, this ensure we're not accessing files outside + // base_dir + if !search_path.starts_with(&CONFIG.base_path) { + return Err("Tried to access file or directory outside of server `base_dir` config."); + } + + let entries: Vec = WalkBuilder::new(search_path) .types(types_builder.build().unwrap()) .build() .filter_map(|entry| entry.ok()) diff --git a/src/grpc/client.rs b/src/grpc/client.rs index 68f3d65..b5026b1 100644 --- a/src/grpc/client.rs +++ b/src/grpc/client.rs @@ -22,7 +22,7 @@ impl super::Connection for GRPCClient { let mut client = JunoRequestClient::connect(format!("http://{}", self.address)).await?; let request = Request::new(GetFilesRequest { - path: "/home/aleidk/Music/".to_string(), + path: "/home/aleidk/Documents/".to_string(), }); let response = client.get_files(request).await?.into_inner(); diff --git a/src/grpc/server.rs b/src/grpc/server.rs index b7cb6d4..9b1363b 100644 --- a/src/grpc/server.rs +++ b/src/grpc/server.rs @@ -43,9 +43,8 @@ impl JunoRequest for GRPCServer { let files = match file_explorer::walk_dir(&path) { Ok(files) => files, - Err(_err) => panic!("Error reading path: {:?}", path), + Err(err) => return Err(Status::invalid_argument(err)), }; - eprintln!("DEBUGPRINT[2]: server.rs:44: files={:#?}", files); let reply = GetFilesResponse { files: files.iter().map(|x| x.display().to_string()).collect(), diff --git a/src/main.rs b/src/main.rs index 0717a75..dee7a89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,26 +1,11 @@ -use core::panic; -use std::{env, path::PathBuf}; - -use clap::Parser; use std::error::Error; +mod configuration; mod file_explorer; mod grpc; -#[derive(Parser)] -#[command(version, about, long_about = None)] -struct Args { - #[arg(help = "Directory to scan for files")] - path: Option, -} - #[tokio::main()] async fn main() -> Result<(), Box> { - let cli = Args::parse(); - let path = cli - .path - .unwrap_or(env::current_dir().expect("Current directory is not available.")); - let server = grpc::run()?; server.connect().await?;