From 7d3624680c7431add420427e0b322238b281bc5a Mon Sep 17 00:00:00 2001 From: Alberto Ruiz <17555470+Az107@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:34:36 +0200 Subject: [PATCH 1/6] first QoS --- Cargo.toml | 5 +++++ src/hteapot.rs | 31 ++++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bda8168..0a21ce9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,8 @@ authors = ["Alb Ruiz G. "] [lib] name = "hteapot" path = "src/hteapot.rs" + +[[bin]] +name = "hteapot" + + diff --git a/src/hteapot.rs b/src/hteapot.rs index 7df8603..5030605 100644 --- a/src/hteapot.rs +++ b/src/hteapot.rs @@ -212,12 +212,24 @@ impl Hteapot { } // Parse the request - pub fn request_parser(request: &str) -> HttpRequest { + pub fn request_parser(request: &str) -> Result { let mut lines = request.lines(); - let first_line = lines.next().unwrap(); + let first_line = lines.next(); + if first_line.is_none() { + return Err("Error parsng request"); + } + let first_line = first_line.unwrap(); let mut words = first_line.split_whitespace(); - let method = words.next().unwrap(); - let mut path = words.next().unwrap().to_string(); + let method = words.next(); + if method.is_none() { + return Err("Error parsng request"); + } + let method = method.unwrap(); + let path = words.next(); + if path.is_none() { + return Err("Error parsng request"); + } + let mut path = path.unwrap().to_string(); let mut headers: HashMap = HashMap::new(); loop { let line = lines.next().unwrap(); @@ -265,13 +277,13 @@ impl Hteapot { } } - HttpRequest { + Ok(HttpRequest { method: HttpMethod::from_str(method), path: path.to_string(), args: args, headers: headers, body: body.trim_end().to_string(), - } + }) } // Handle the client when a request is received @@ -287,6 +299,11 @@ impl Hteapot { } let request = Self::request_parser(&request_buffer); + if request.is_err() { + eprintln!("{}", request.err().unwrap()); + return; + } + let request = request.unwrap(); //let response = Self::response_maker(HttpStatus::IAmATeapot, "Hello, World!"); let response = action(request); let r = stream.write(response.as_bytes()); @@ -306,7 +323,7 @@ impl Hteapot { #[test] fn test_http_parser() { let request = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nUser-Agent: curl/7.68.0\r\nAccept: */*\r\n\r\n"; - let parsed_request = Hteapot::request_parser(request); + let parsed_request = Hteapot::request_parser(request).unwrap(); assert_eq!(parsed_request.method, HttpMethod::GET); assert_eq!(parsed_request.path, "/"); assert_eq!(parsed_request.args.len(), 0); From 06b7da2e178140b26f0328062f2546b0bc7bcab5 Mon Sep 17 00:00:00 2001 From: Alberto Ruiz <17555470+Az107@users.noreply.github.com> Date: Wed, 19 Jun 2024 12:50:03 +0200 Subject: [PATCH 2/6] Optminization pt 1 --- src/hteapot.rs | 86 ++++++++++++++++++++++++++++---------------------- src/main.rs | 4 +-- 2 files changed, 50 insertions(+), 40 deletions(-) diff --git a/src/hteapot.rs b/src/hteapot.rs index 5030605..8f283e5 100644 --- a/src/hteapot.rs +++ b/src/hteapot.rs @@ -3,10 +3,10 @@ // Also provide utilities to parse the requests and build the responses use std::collections::HashMap; -use std::io::{Read, Write}; +use std::hash::Hash; +use std::io::{BufReader, BufWriter, Read, Write}; use std::net::{TcpListener, TcpStream}; -use std::sync::Arc; -use std::thread; +use std::{clone, str}; #[derive(Debug)] @@ -148,6 +148,7 @@ pub struct HttpRequest { pub struct Hteapot { port: u16, address: String, + cache: HashMap, // this will store a map from path to their actions // path_table: HashMap String>>>, } @@ -159,12 +160,13 @@ impl Hteapot { Hteapot { port: port, address: address.to_string(), + cache: HashMap::new(), // path_table: HashMap::new(), } } // Start the server - pub fn listen(&self, action: impl Fn(HttpRequest) -> String + Send + Sync + 'static ){ + pub fn listen(&mut self, action: impl Fn(HttpRequest) -> String ){ let addr = format!("{}:{}", self.address, self.port); let listener = TcpListener::bind(addr); let listener = match listener { @@ -174,17 +176,13 @@ impl Hteapot { return; } }; - let action_clone = Arc::new(action); + //let action_clone = Arc::new(action); for stream in listener.incoming() { match stream { Ok(stream) => { - let action_clone = action_clone.clone(); - thread::spawn(move || { - Hteapot::handle_client(stream, |req| { - action_clone(req) - }); - }); - + self.handle_client(stream, |req| { + action(req) + }); } Err(e) => { println!("Error: {}", e); @@ -241,11 +239,7 @@ impl Hteapot { let value = parts.next().unwrap(); headers.insert(key, value.to_string()); } - let remaining_lines: Vec<&str> = lines.collect(); - let body = remaining_lines.join(""); - let body = body.trim().trim_end(); - //remove all traling zero bytes - let body = body.trim_matches(char::from(0)); + let body = lines.collect::>().join("").trim().trim_end_matches(char::from(0)).to_string(); let mut args: HashMap = HashMap::new(); //remove http or https from the path if path.starts_with("http://") { @@ -287,30 +281,46 @@ impl Hteapot { } // Handle the client when a request is received - fn handle_client(mut stream: TcpStream , action: impl Fn(HttpRequest) -> String ) { - let mut request_buffer: String = String::new(); - loop { - let mut buffer = [0; 1024]; - stream.read(&mut buffer).unwrap_or_default(); - if buffer[0] == 0 {break}; - let partial_request_buffer = String::from_utf8_lossy(&buffer).to_string(); - request_buffer.push_str(&partial_request_buffer); - if *buffer.last().unwrap() == 0 {break;} - } - - let request = Self::request_parser(&request_buffer); - if request.is_err() { - eprintln!("{}", request.err().unwrap()); - return; - } - let request = request.unwrap(); - //let response = Self::response_maker(HttpStatus::IAmATeapot, "Hello, World!"); - let response = action(request); - let r = stream.write(response.as_bytes()); + fn handle_client(&mut self, mut stream: TcpStream , action: impl Fn(HttpRequest) -> String ) { + //let mut request_buffer: String = String::new(); + let mut buffer = [0; 1024]; + let mut reader = BufReader::new(&stream); + let mut writer = BufWriter::new(&stream); + let r = reader.read(&mut buffer); + let request_buffer = String::from_utf8_lossy(&buffer).to_string(); + // loop { + // let mut buffer = [0; 1024]; + // stream.read(&mut buffer).unwrap_or_default(); + // if buffer[0] == 0 {break}; + // let partial_request_buffer = String::from_utf8_lossy(&buffer).to_string(); + // request_buffer.push_str(&partial_request_buffer); + // if *buffer.last().unwrap() == 0 {break;} + // } + //let request_buffer = "GET /index.html HTTP/1.1\n\n\r".to_string(); + let response = match self.cache.get(&request_buffer) { + Some(r) => { + r.clone() + }, + None => { + let request = Self::request_parser(&request_buffer); + if request.is_err() { + eprintln!("{}", request.err().unwrap()); + return; + } + let request = request.unwrap(); + + let response = action(request); + if response.starts_with("HTTP/1.1 2") { + self.cache.insert(request_buffer, response.clone()); + } + response + }, + }; + let r = writer.write_all(response.as_bytes()); if r.is_err() { eprintln!("Error: {}", r.err().unwrap()); } - let r = stream.flush(); + let r = writer.flush(); if r.is_err() { eprintln!("Error: {}", r.err().unwrap()); } diff --git a/src/main.rs b/src/main.rs index b78e72a..4821398 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,9 +20,9 @@ fn main() { } else { config::Config::new_default() }; - let server = Hteapot::new(config.host.as_str(), config.port); + let mut server = Hteapot::new(config.host.as_str(), config.port); println!("Server started at http://{}:{}", config.host, config.port); - server.listen(move |req| { + server.listen( |req| { println!("Request: {:?}", req.path); let path = if req.path.ends_with("/") { let mut path = req.path.clone(); From 84470cc98e66413c94d954d3e854bb7f434cda43 Mon Sep 17 00:00:00 2001 From: Alberto Ruiz <17555470+Az107@users.noreply.github.com> Date: Fri, 21 Jun 2024 12:05:56 +0200 Subject: [PATCH 3/6] request improvements --- src/hteapot.rs | 104 ++++++++++++++++++++++--------------------------- src/main.rs | 2 +- 2 files changed, 47 insertions(+), 59 deletions(-) diff --git a/src/hteapot.rs b/src/hteapot.rs index 8f283e5..8d946a0 100644 --- a/src/hteapot.rs +++ b/src/hteapot.rs @@ -4,9 +4,10 @@ use std::collections::HashMap; use std::hash::Hash; -use std::io::{BufReader, BufWriter, Read, Write}; +use std::io::{BufRead, BufReader, BufWriter, Write}; use std::net::{TcpListener, TcpStream}; -use std::{clone, str}; +use std::{str, thread}; +use std::sync::Arc; #[derive(Debug)] @@ -166,7 +167,7 @@ impl Hteapot { } // Start the server - pub fn listen(&mut self, action: impl Fn(HttpRequest) -> String ){ + pub fn listen(&mut self, action: impl Fn(HttpRequest) -> String + Send + Sync + 'static ){ let addr = format!("{}:{}", self.address, self.port); let listener = TcpListener::bind(addr); let listener = match listener { @@ -174,15 +175,17 @@ impl Hteapot { Err(e) => { eprintln!("Error: {}", e); return; - } - }; - //let action_clone = Arc::new(action); - for stream in listener.incoming() { - match stream { - Ok(stream) => { - self.handle_client(stream, |req| { - action(req) - }); + } + }; + //let action_clone = Arc::new(action); + for stream in listener.incoming() { + match stream { + Ok(stream) => { + //let action_clone = action_clone.clone(); + Hteapot::handle_client(stream, |req| { + action(req) + }); + } Err(e) => { println!("Error: {}", e); @@ -210,27 +213,29 @@ impl Hteapot { } // Parse the request - pub fn request_parser(request: &str) -> Result { + pub fn request_parser(request: String) -> Result { let mut lines = request.lines(); let first_line = lines.next(); if first_line.is_none() { - return Err("Error parsng request"); + return Err("Error parsng request".to_string()); } let first_line = first_line.unwrap(); let mut words = first_line.split_whitespace(); let method = words.next(); if method.is_none() { - return Err("Error parsng request"); + return Err("Error parsng request".to_string()); } let method = method.unwrap(); let path = words.next(); if path.is_none() { - return Err("Error parsng request"); + return Err("Error parsng request".to_string()); } let mut path = path.unwrap().to_string(); let mut headers: HashMap = HashMap::new(); loop { - let line = lines.next().unwrap(); + let line = lines.next(); + if line.is_none() {break;} + let line = line.unwrap(); if line.is_empty() { break; } @@ -281,49 +286,32 @@ impl Hteapot { } // Handle the client when a request is received - fn handle_client(&mut self, mut stream: TcpStream , action: impl Fn(HttpRequest) -> String ) { - //let mut request_buffer: String = String::new(); - let mut buffer = [0; 1024]; - let mut reader = BufReader::new(&stream); + fn handle_client(stream: TcpStream , action: impl Fn(HttpRequest) -> String ) { + let reader = BufReader::new(&stream); let mut writer = BufWriter::new(&stream); - let r = reader.read(&mut buffer); - let request_buffer = String::from_utf8_lossy(&buffer).to_string(); - // loop { - // let mut buffer = [0; 1024]; - // stream.read(&mut buffer).unwrap_or_default(); - // if buffer[0] == 0 {break}; - // let partial_request_buffer = String::from_utf8_lossy(&buffer).to_string(); - // request_buffer.push_str(&partial_request_buffer); - // if *buffer.last().unwrap() == 0 {break;} - // } - //let request_buffer = "GET /index.html HTTP/1.1\n\n\r".to_string(); - let response = match self.cache.get(&request_buffer) { - Some(r) => { - r.clone() - }, - None => { - let request = Self::request_parser(&request_buffer); - if request.is_err() { - eprintln!("{}", request.err().unwrap()); - return; - } - let request = request.unwrap(); + let lines: Vec = reader.lines() + .map(|result| result.unwrap()) + .take_while(|line| !line.is_empty()) + .collect(); - let response = action(request); - if response.starts_with("HTTP/1.1 2") { - self.cache.insert(request_buffer, response.clone()); + let request_string = lines.join("\n"); + + let request = Self::request_parser(request_string); + if request.is_err() { + eprintln!("{}", request.err().unwrap()); + return; + } + let request = request.unwrap(); + + let response = action(request); + let r = writer.write_all(response.as_bytes()); + if r.is_err() { + eprintln!("Error: {}", r.err().unwrap()); } - response - }, - }; - let r = writer.write_all(response.as_bytes()); - if r.is_err() { - eprintln!("Error: {}", r.err().unwrap()); - } - let r = writer.flush(); - if r.is_err() { - eprintln!("Error: {}", r.err().unwrap()); - } + let r = writer.flush(); + if r.is_err() { + eprintln!("Error: {}", r.err().unwrap()); + } } } @@ -333,7 +321,7 @@ impl Hteapot { #[test] fn test_http_parser() { let request = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nUser-Agent: curl/7.68.0\r\nAccept: */*\r\n\r\n"; - let parsed_request = Hteapot::request_parser(request).unwrap(); + let parsed_request = Hteapot::request_parser(request.to_string()).unwrap(); assert_eq!(parsed_request.method, HttpMethod::GET); assert_eq!(parsed_request.path, "/"); assert_eq!(parsed_request.args.len(), 0); diff --git a/src/main.rs b/src/main.rs index 4821398..d47e3e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ fn main() { }; let mut server = Hteapot::new(config.host.as_str(), config.port); println!("Server started at http://{}:{}", config.host, config.port); - server.listen( |req| { + server.listen( move |req| { println!("Request: {:?}", req.path); let path = if req.path.ends_with("/") { let mut path = req.path.clone(); From 1a40fde1e00abe72d5356dbd6abe4d475bb843ef Mon Sep 17 00:00:00 2001 From: Alberto Ruiz <17555470+Az107@users.noreply.github.com> Date: Sun, 23 Jun 2024 13:31:22 +0200 Subject: [PATCH 4/6] prototype 1 --- src/hteapot.rs | 115 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 76 insertions(+), 39 deletions(-) diff --git a/src/hteapot.rs b/src/hteapot.rs index 8d946a0..25a2ba9 100644 --- a/src/hteapot.rs +++ b/src/hteapot.rs @@ -4,10 +4,12 @@ use std::collections::HashMap; use std::hash::Hash; -use std::io::{BufRead, BufReader, BufWriter, Write}; +use std::io::{self, BufRead, BufReader, BufWriter, Read, Write}; use std::net::{TcpListener, TcpStream}; +use std::thread::sleep; +use std::time::Duration; use std::{str, thread}; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; #[derive(Debug)] @@ -150,6 +152,7 @@ pub struct Hteapot { port: u16, address: String, cache: HashMap, + pool: Arc>> // this will store a map from path to their actions // path_table: HashMap String>>>, } @@ -162,6 +165,7 @@ impl Hteapot { port: port, address: address.to_string(), cache: HashMap::new(), + pool: Arc::new(Mutex::new(Vec::new())), // path_table: HashMap::new(), } } @@ -175,23 +179,36 @@ impl Hteapot { Err(e) => { eprintln!("Error: {}", e); return; - } - }; - //let action_clone = Arc::new(action); + } + }; + let arc_action = Arc::new(action); + listener.set_nonblocking(true).expect("set_nonblocking call failed"); + let pool_clone = self.pool.clone(); + let greeter_loop = thread::spawn(move || { + loop { for stream in listener.incoming() { - match stream { - Ok(stream) => { - //let action_clone = action_clone.clone(); - Hteapot::handle_client(stream, |req| { - action(req) - }); - - } - Err(e) => { - println!("Error: {}", e); + if stream.is_err() {break;} + let stream = stream.unwrap(); + stream.set_nonblocking(true).expect("set_nonblocking call failed"); + let mut pool = pool_clone.lock().expect("Error locking pool"); + pool.push(stream); } + sleep(Duration::from_millis(10)); } - } + }); + let pool_clone = self.pool.clone(); + thread::spawn(move || { + loop { + let mut pool = pool_clone.lock().expect("Error locking pool"); + pool.retain(|stream| { + let action_clone = arc_action.clone(); + Hteapot::handle_client(stream, move |request| { + action_clone(request) + }) + }); + } + }); + greeter_loop.join(); } @@ -286,32 +303,52 @@ impl Hteapot { } // Handle the client when a request is received - fn handle_client(stream: TcpStream , action: impl Fn(HttpRequest) -> String ) { - let reader = BufReader::new(&stream); - let mut writer = BufWriter::new(&stream); - let lines: Vec = reader.lines() - .map(|result| result.unwrap()) - .take_while(|line| !line.is_empty()) - .collect(); - - let request_string = lines.join("\n"); - + fn handle_client(stream: &TcpStream , action: impl Fn(HttpRequest) -> String + Send + Sync + 'static ) -> bool{ + let mut reader = BufReader::new(stream); + let mut writer = BufWriter::new(stream); + let mut request_buffer = Vec::new(); + loop { + let mut buffer = [0; 1024]; + match reader.read(&mut buffer) { + Err(e) => { + match e.kind() { + io::ErrorKind::WouldBlock => { + println!("would have blocked"); + return false; + }, + _ => panic!("Got an error: {}", e), + } + }, + Ok(m) => { + if m == 0 { + break; + } + }, + }; + request_buffer.append(&mut buffer.to_vec()); + if buffer[0] == 0 {break}; + if *buffer.last().unwrap() == 0 {break;} + } + + + let request_string = String::from_utf8(request_buffer).unwrap(); let request = Self::request_parser(request_string); if request.is_err() { eprintln!("{}", request.err().unwrap()); - return; - } - let request = request.unwrap(); - - let response = action(request); - let r = writer.write_all(response.as_bytes()); - if r.is_err() { - eprintln!("Error: {}", r.err().unwrap()); - } - let r = writer.flush(); - if r.is_err() { - eprintln!("Error: {}", r.err().unwrap()); - } + return false; + } + let request = request.unwrap(); + + let response = action(request); + let r = writer.write_all(response.as_bytes()); + if r.is_err() { + eprintln!("Error: {}", r.err().unwrap()); + } + let r = writer.flush(); + if r.is_err() { + eprintln!("Error: {}", r.err().unwrap()); + } + true } } From 597ddfb9213d584659c0711d5397ee08e6e6cb74 Mon Sep 17 00:00:00 2001 From: Alberto Ruiz <17555470+Az107@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:03:48 +0200 Subject: [PATCH 5/6] improvements --- src/hteapot.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hteapot.rs b/src/hteapot.rs index 25a2ba9..3580cc1 100644 --- a/src/hteapot.rs +++ b/src/hteapot.rs @@ -190,6 +190,7 @@ impl Hteapot { if stream.is_err() {break;} let stream = stream.unwrap(); stream.set_nonblocking(true).expect("set_nonblocking call failed"); + stream.set_nodelay(true).expect("set_nodelay call failed"); let mut pool = pool_clone.lock().expect("Error locking pool"); pool.push(stream); } @@ -314,9 +315,9 @@ impl Hteapot { match e.kind() { io::ErrorKind::WouldBlock => { println!("would have blocked"); - return false; + return true; }, - _ => panic!("Got an error: {}", e), + _ => return false, } }, Ok(m) => { From 6b1a67f403ff15283c888bf5b844310497176839 Mon Sep 17 00:00:00 2001 From: Alberto Ruiz <17555470+Az107@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:27:59 +0200 Subject: [PATCH 6/6] CPU usage optimization --- src/hteapot.rs | 75 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/src/hteapot.rs b/src/hteapot.rs index 3580cc1..1def0c7 100644 --- a/src/hteapot.rs +++ b/src/hteapot.rs @@ -9,7 +9,7 @@ use std::net::{TcpListener, TcpStream}; use std::thread::sleep; use std::time::Duration; use std::{str, thread}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, Condvar}; #[derive(Debug)] @@ -152,7 +152,9 @@ pub struct Hteapot { port: u16, address: String, cache: HashMap, - pool: Arc>> + pool: Arc<(Mutex>, Condvar)>, + //pool2: Arc<(Mutex>, Condvar)> + // this will store a map from path to their actions // path_table: HashMap String>>>, } @@ -165,7 +167,9 @@ impl Hteapot { port: port, address: address.to_string(), cache: HashMap::new(), - pool: Arc::new(Mutex::new(Vec::new())), + pool: Arc::new((Mutex::new(Vec::new()), Condvar::new())), + //pool2: Arc::new((Mutex::new(Vec::new()), Condvar::new())), + // path_table: HashMap::new(), } } @@ -182,33 +186,56 @@ impl Hteapot { } }; let arc_action = Arc::new(action); - listener.set_nonblocking(true).expect("set_nonblocking call failed"); + listener.set_nonblocking(false).expect("set_nonblocking call failed"); let pool_clone = self.pool.clone(); let greeter_loop = thread::spawn(move || { - loop { - for stream in listener.incoming() { - if stream.is_err() {break;} - let stream = stream.unwrap(); - stream.set_nonblocking(true).expect("set_nonblocking call failed"); - stream.set_nodelay(true).expect("set_nodelay call failed"); - let mut pool = pool_clone.lock().expect("Error locking pool"); - pool.push(stream); - } - sleep(Duration::from_millis(10)); + for stream in listener.incoming() { + + if stream.is_err() {continue;} + let stream = stream.unwrap(); + let (lock, cvar) = &*pool_clone; + let mut pool = lock.lock().expect("Error locking pool"); + pool.push(stream); + cvar.notify_one(); // Notify one waiting thread } }); let pool_clone = self.pool.clone(); + //let pool2_clone = self.pool2.clone(); thread::spawn(move || { loop { - let mut pool = pool_clone.lock().expect("Error locking pool"); - pool.retain(|stream| { - let action_clone = arc_action.clone(); - Hteapot::handle_client(stream, move |request| { - action_clone(request) - }) - }); + let (lock, cvar) = &*pool_clone; + let mut pool = lock.lock().expect("Error locking pool"); + + while pool.is_empty() { + pool = cvar.wait(pool).expect("Error waiting on cvar"); + } + pool.retain(|stream| { + let action_clone = arc_action.clone(); + Hteapot::handle_client(stream, move |request| { + action_clone(request) + }) + }); } }); + + // let pool2_clone = self.pool2.clone(); + // thread::spawn(move || { + // loop { + // let (lock, cvar) = &*pool2_clone; + // let mut pool = lock.lock().expect("Error locking pool"); + + // while pool.is_empty() { + // pool = cvar.wait(pool).expect("Error waiting on cvar"); + // } + // for stream in pool.iter() { + // let action_clone = arc_action.clone(); + // Hteapot::handle_client(stream, move |request| { + // action_clone(request) + // }); + // } + // pool.clear(); + // } + // }); greeter_loop.join(); } @@ -305,6 +332,7 @@ impl Hteapot { // Handle the client when a request is received fn handle_client(stream: &TcpStream , action: impl Fn(HttpRequest) -> String + Send + Sync + 'static ) -> bool{ + stream.set_nodelay(true); let mut reader = BufReader::new(stream); let mut writer = BufWriter::new(stream); let mut request_buffer = Vec::new(); @@ -314,10 +342,11 @@ impl Hteapot { Err(e) => { match e.kind() { io::ErrorKind::WouldBlock => { - println!("would have blocked"); return true; }, - _ => return false, + _ => { + return false; + }, } }, Ok(m) => {